##// END OF EJS Templates
fix(app): Added proper encoding to avoid app crashes while downloading files. Fixes: RCCE-37
ilin.s -
r5269:bd1533d7 default
parent child Browse files
Show More
@@ -1,1584 +1,1586 b''
1 1 # Copyright (C) 2011-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 import itertools
20 20 import logging
21 21 import os
22 22 import collections
23 23 import urllib.request
24 24 import urllib.parse
25 25 import urllib.error
26 26 import pathlib
27 27
28 28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
29 29
30 30 from pyramid.renderers import render
31 31 from pyramid.response import Response
32 32
33 33 import rhodecode
34 34 from rhodecode.apps._base import RepoAppView
35 35
36 36
37 37 from rhodecode.lib import diffs, helpers as h, rc_cache
38 38 from rhodecode.lib import audit_logger
39 39 from rhodecode.lib.hash_utils import sha1_safe
40 40 from rhodecode.lib.rc_cache.archive_cache import get_archival_cache_store, get_archival_config, ReentrantLock
41 41 from rhodecode.lib.str_utils import safe_bytes, convert_special_chars
42 42 from rhodecode.lib.view_utils import parse_path_ref
43 43 from rhodecode.lib.exceptions import NonRelativePathError
44 44 from rhodecode.lib.codeblocks import (
45 45 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
46 46 from rhodecode.lib.utils2 import convert_line_endings, detect_mode
47 47 from rhodecode.lib.type_utils import str2bool
48 48 from rhodecode.lib.str_utils import safe_str, safe_int
49 49 from rhodecode.lib.auth import (
50 50 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
51 51 from rhodecode.lib.vcs import path as vcspath
52 52 from rhodecode.lib.vcs.backends.base import EmptyCommit
53 53 from rhodecode.lib.vcs.conf import settings
54 54 from rhodecode.lib.vcs.nodes import FileNode
55 55 from rhodecode.lib.vcs.exceptions import (
56 56 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
57 57 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
58 58 NodeDoesNotExistError, CommitError, NodeError)
59 59
60 60 from rhodecode.model.scm import ScmModel
61 61 from rhodecode.model.db import Repository
62 62
63 63 log = logging.getLogger(__name__)
64 64
65 65
66 66 def get_archive_name(db_repo_id, db_repo_name, commit_sha, ext, subrepos=False, path_sha='', with_hash=True):
67 67 # original backward compat name of archive
68 68 clean_name = safe_str(convert_special_chars(db_repo_name).replace('/', '_'))
69 69
70 70 # e.g vcsserver-id-abcd-sub-1-abcfdef-archive-all.zip
71 71 # vcsserver-id-abcd-sub-0-abcfdef-COMMIT_SHA-PATH_SHA.zip
72 72 id_sha = sha1_safe(str(db_repo_id))[:4]
73 73 sub_repo = 'sub-1' if subrepos else 'sub-0'
74 74 commit = commit_sha if with_hash else 'archive'
75 75 path_marker = (path_sha if with_hash else '') or 'all'
76 76 archive_name = f'{clean_name}-id-{id_sha}-{sub_repo}-{commit}-{path_marker}{ext}'
77 77
78 78 return archive_name
79 79
80 80
81 81 def get_path_sha(at_path):
82 82 return safe_str(sha1_safe(at_path)[:8])
83 83
84 84
85 85 def _get_archive_spec(fname):
86 86 log.debug('Detecting archive spec for: `%s`', fname)
87 87
88 88 fileformat = None
89 89 ext = None
90 90 content_type = None
91 91 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
92 92
93 93 if fname.endswith(extension):
94 94 fileformat = a_type
95 95 log.debug('archive is of type: %s', fileformat)
96 96 ext = extension
97 97 break
98 98
99 99 if not fileformat:
100 100 raise ValueError()
101 101
102 102 # left over part of whole fname is the commit
103 103 commit_id = fname[:-len(ext)]
104 104
105 105 return commit_id, ext, fileformat, content_type
106 106
107 107
108 108 class RepoFilesView(RepoAppView):
109 109
110 110 @staticmethod
111 111 def adjust_file_path_for_svn(f_path, repo):
112 112 """
113 113 Computes the relative path of `f_path`.
114 114
115 115 This is mainly based on prefix matching of the recognized tags and
116 116 branches in the underlying repository.
117 117 """
118 118 tags_and_branches = itertools.chain(
119 119 repo.branches.keys(),
120 120 repo.tags.keys())
121 121 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
122 122
123 123 for name in tags_and_branches:
124 124 if f_path.startswith(f'{name}/'):
125 125 f_path = vcspath.relpath(f_path, name)
126 126 break
127 127 return f_path
128 128
129 129 def load_default_context(self):
130 130 c = self._get_local_tmpl_context(include_app_defaults=True)
131 131 c.rhodecode_repo = self.rhodecode_vcs_repo
132 132 c.enable_downloads = self.db_repo.enable_downloads
133 133 return c
134 134
135 135 def _ensure_not_locked(self, commit_id='tip'):
136 136 _ = self.request.translate
137 137
138 138 repo = self.db_repo
139 139 if repo.enable_locking and repo.locked[0]:
140 140 h.flash(_('This repository has been locked by %s on %s')
141 141 % (h.person_by_id(repo.locked[0]),
142 142 h.format_date(h.time_to_datetime(repo.locked[1]))),
143 143 'warning')
144 144 files_url = h.route_path(
145 145 'repo_files:default_path',
146 146 repo_name=self.db_repo_name, commit_id=commit_id)
147 147 raise HTTPFound(files_url)
148 148
149 149 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
150 150 _ = self.request.translate
151 151
152 152 if not is_head:
153 153 message = _('Cannot modify file. '
154 154 'Given commit `{}` is not head of a branch.').format(commit_id)
155 155 h.flash(message, category='warning')
156 156
157 157 if json_mode:
158 158 return message
159 159
160 160 files_url = h.route_path(
161 161 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
162 162 f_path=f_path)
163 163 raise HTTPFound(files_url)
164 164
165 165 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
166 166 _ = self.request.translate
167 167
168 168 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
169 169 self.db_repo_name, branch_name)
170 170 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
171 171 message = _('Branch `{}` changes forbidden by rule {}.').format(
172 172 h.escape(branch_name), h.escape(rule))
173 173 h.flash(message, 'warning')
174 174
175 175 if json_mode:
176 176 return message
177 177
178 178 files_url = h.route_path(
179 179 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
180 180
181 181 raise HTTPFound(files_url)
182 182
183 183 def _get_commit_and_path(self):
184 184 default_commit_id = self.db_repo.landing_ref_name
185 185 default_f_path = '/'
186 186
187 187 commit_id = self.request.matchdict.get(
188 188 'commit_id', default_commit_id)
189 189 f_path = self._get_f_path(self.request.matchdict, default_f_path)
190 190 return commit_id, f_path
191 191
192 192 def _get_default_encoding(self, c):
193 193 enc_list = getattr(c, 'default_encodings', [])
194 194 return enc_list[0] if enc_list else 'UTF-8'
195 195
196 196 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
197 197 """
198 198 This is a safe way to get commit. If an error occurs it redirects to
199 199 tip with proper message
200 200
201 201 :param commit_id: id of commit to fetch
202 202 :param redirect_after: toggle redirection
203 203 """
204 204 _ = self.request.translate
205 205
206 206 try:
207 207 return self.rhodecode_vcs_repo.get_commit(commit_id)
208 208 except EmptyRepositoryError:
209 209 if not redirect_after:
210 210 return None
211 211
212 212 add_new = upload_new = ""
213 213 if h.HasRepoPermissionAny(
214 214 'repository.write', 'repository.admin')(self.db_repo_name):
215 215 _url = h.route_path(
216 216 'repo_files_add_file',
217 217 repo_name=self.db_repo_name, commit_id=0, f_path='')
218 218 add_new = h.link_to(
219 219 _('add a new file'), _url, class_="alert-link")
220 220
221 221 _url_upld = h.route_path(
222 222 'repo_files_upload_file',
223 223 repo_name=self.db_repo_name, commit_id=0, f_path='')
224 224 upload_new = h.link_to(
225 225 _('upload a new file'), _url_upld, class_="alert-link")
226 226
227 227 h.flash(h.literal(
228 228 _('There are no files yet. Click here to %s or %s.') % (add_new, upload_new)), category='warning')
229 229 raise HTTPFound(
230 230 h.route_path('repo_summary', repo_name=self.db_repo_name))
231 231
232 232 except (CommitDoesNotExistError, LookupError) as e:
233 233 msg = _('No such commit exists for this repository. Commit: {}').format(commit_id)
234 234 h.flash(msg, category='error')
235 235 raise HTTPNotFound()
236 236 except RepositoryError as e:
237 237 h.flash(h.escape(safe_str(e)), category='error')
238 238 raise HTTPNotFound()
239 239
240 240 def _get_filenode_or_redirect(self, commit_obj, path, pre_load=None):
241 241 """
242 242 Returns file_node, if error occurs or given path is directory,
243 243 it'll redirect to top level path
244 244 """
245 245 _ = self.request.translate
246 246
247 247 try:
248 248 file_node = commit_obj.get_node(path, pre_load=pre_load)
249 249 if file_node.is_dir():
250 250 raise RepositoryError('The given path is a directory')
251 251 except CommitDoesNotExistError:
252 252 log.exception('No such commit exists for this repository')
253 253 h.flash(_('No such commit exists for this repository'), category='error')
254 254 raise HTTPNotFound()
255 255 except RepositoryError as e:
256 256 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
257 257 h.flash(h.escape(safe_str(e)), category='error')
258 258 raise HTTPNotFound()
259 259
260 260 return file_node
261 261
262 262 def _is_valid_head(self, commit_id, repo, landing_ref):
263 263 branch_name = sha_commit_id = ''
264 264 is_head = False
265 265 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
266 266
267 267 for _branch_name, branch_commit_id in repo.branches.items():
268 268 # simple case we pass in branch name, it's a HEAD
269 269 if commit_id == _branch_name:
270 270 is_head = True
271 271 branch_name = _branch_name
272 272 sha_commit_id = branch_commit_id
273 273 break
274 274 # case when we pass in full sha commit_id, which is a head
275 275 elif commit_id == branch_commit_id:
276 276 is_head = True
277 277 branch_name = _branch_name
278 278 sha_commit_id = branch_commit_id
279 279 break
280 280
281 281 if h.is_svn(repo) and not repo.is_empty():
282 282 # Note: Subversion only has one head.
283 283 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
284 284 is_head = True
285 285 return branch_name, sha_commit_id, is_head
286 286
287 287 # checked branches, means we only need to try to get the branch/commit_sha
288 288 if repo.is_empty():
289 289 is_head = True
290 290 branch_name = landing_ref
291 291 sha_commit_id = EmptyCommit().raw_id
292 292 else:
293 293 commit = repo.get_commit(commit_id=commit_id)
294 294 if commit:
295 295 branch_name = commit.branch
296 296 sha_commit_id = commit.raw_id
297 297
298 298 return branch_name, sha_commit_id, is_head
299 299
300 300 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
301 301
302 302 repo_id = self.db_repo.repo_id
303 303 force_recache = self.get_recache_flag()
304 304
305 305 cache_seconds = safe_int(
306 306 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
307 307 cache_on = not force_recache and cache_seconds > 0
308 308 log.debug(
309 309 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
310 310 'with caching: %s[TTL: %ss]' % (
311 311 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
312 312
313 313 cache_namespace_uid = f'repo.{rc_cache.FILE_TREE_CACHE_VER}.{repo_id}'
314 314 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
315 315
316 316 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
317 317 def compute_file_tree(_name_hash, _repo_id, _commit_id, _f_path, _full_load, _at_rev):
318 318 log.debug('Generating cached file tree at for repo_id: %s, %s, %s',
319 319 _repo_id, _commit_id, _f_path)
320 320
321 321 c.full_load = _full_load
322 322 return render(
323 323 'rhodecode:templates/files/files_browser_tree.mako',
324 324 self._get_template_context(c), self.request, _at_rev)
325 325
326 326 return compute_file_tree(
327 327 self.db_repo.repo_name_hash, self.db_repo.repo_id, commit_id, f_path, full_load, at_rev)
328 328
329 329 def create_pure_path(self, *parts):
330 330 # Split paths and sanitize them, removing any ../ etc
331 331 sanitized_path = [
332 332 x for x in pathlib.PurePath(*parts).parts
333 333 if x not in ['.', '..']]
334 334
335 335 pure_path = pathlib.PurePath(*sanitized_path)
336 336 return pure_path
337 337
338 338 def _is_lf_enabled(self, target_repo):
339 339 lf_enabled = False
340 340
341 341 lf_key_for_vcs_map = {
342 342 'hg': 'extensions_largefiles',
343 343 'git': 'vcs_git_lfs_enabled'
344 344 }
345 345
346 346 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
347 347
348 348 if lf_key_for_vcs:
349 349 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
350 350
351 351 return lf_enabled
352 352
353 353 @LoginRequired()
354 354 @HasRepoPermissionAnyDecorator(
355 355 'repository.read', 'repository.write', 'repository.admin')
356 356 def repo_archivefile(self):
357 357 # archive cache config
358 358 from rhodecode import CONFIG
359 359 _ = self.request.translate
360 360 self.load_default_context()
361 361 default_at_path = '/'
362 362 fname = self.request.matchdict['fname']
363 363 subrepos = self.request.GET.get('subrepos') == 'true'
364 364 with_hash = str2bool(self.request.GET.get('with_hash', '1'))
365 365 at_path = self.request.GET.get('at_path') or default_at_path
366 366
367 367 if not self.db_repo.enable_downloads:
368 368 return Response(_('Downloads disabled'))
369 369
370 370 try:
371 371 commit_id, ext, fileformat, content_type = \
372 372 _get_archive_spec(fname)
373 373 except ValueError:
374 374 return Response(_('Unknown archive type for: `{}`').format(
375 375 h.escape(fname)))
376 376
377 377 try:
378 378 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
379 379 except CommitDoesNotExistError:
380 380 return Response(_('Unknown commit_id {}').format(
381 381 h.escape(commit_id)))
382 382 except EmptyRepositoryError:
383 383 return Response(_('Empty repository'))
384 384
385 385 # we used a ref, or a shorter version, lets redirect client ot use explicit hash
386 386 if commit_id != commit.raw_id:
387 387 fname=f'{commit.raw_id}{ext}'
388 388 raise HTTPFound(self.request.current_route_path(fname=fname))
389 389
390 390 try:
391 391 at_path = commit.get_node(at_path).path or default_at_path
392 392 except Exception:
393 393 return Response(_('No node at path {} for this repository').format(h.escape(at_path)))
394 394
395 395 path_sha = get_path_sha(at_path)
396 396
397 397 # used for cache etc, consistent unique archive name
398 398 archive_name_key = get_archive_name(
399 399 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
400 400 path_sha=path_sha, with_hash=True)
401 401
402 402 if not with_hash:
403 403 path_sha = ''
404 404
405 405 # what end client gets served
406 406 response_archive_name = get_archive_name(
407 407 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
408 408 path_sha=path_sha, with_hash=with_hash)
409 409
410 410 # remove extension from our archive directory name
411 411 archive_dir_name = response_archive_name[:-len(ext)]
412 412
413 413 archive_cache_disable = self.request.GET.get('no_cache')
414 414
415 415 d_cache = get_archival_cache_store(config=CONFIG)
416 416
417 417 # NOTE: we get the config to pass to a call to lazy-init the SAME type of cache on vcsserver
418 418 d_cache_conf = get_archival_config(config=CONFIG)
419 419
420 420 reentrant_lock_key = archive_name_key + '.lock'
421 421 with ReentrantLock(d_cache, reentrant_lock_key):
422 422 # This is also a cache key
423 423 use_cached_archive = False
424 424 if archive_name_key in d_cache and not archive_cache_disable:
425 425 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
426 426 use_cached_archive = True
427 427 log.debug('Found cached archive as key=%s tag=%s, serving archive from cache reader=%s',
428 428 archive_name_key, tag, reader.name)
429 429 else:
430 430 reader = None
431 431 log.debug('Archive with key=%s is not yet cached, creating one now...', archive_name_key)
432 432
433 433 # generate new archive, as previous was not found in the cache
434 434 if not reader:
435 435
436 436 try:
437 437 commit.archive_repo(archive_name_key, archive_dir_name=archive_dir_name,
438 438 kind=fileformat, subrepos=subrepos,
439 439 archive_at_path=at_path, cache_config=d_cache_conf)
440 440 except ImproperArchiveTypeError:
441 441 return _('Unknown archive type')
442 442
443 443 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
444 444
445 445 if not reader:
446 446 raise ValueError('archive cache reader is empty, failed to fetch file from distributed archive cache')
447 447
448 448 def archive_iterator(_reader, block_size: int = 4096*512):
449 449 # 4096 * 64 = 64KB
450 450 while 1:
451 451 data = _reader.read(block_size)
452 452 if not data:
453 453 break
454 454 yield data
455 455
456 456 response = Response(app_iter=archive_iterator(reader))
457 457 response.content_disposition = f'attachment; filename={response_archive_name}'
458 458 response.content_type = str(content_type)
459 459
460 460 try:
461 461 return response
462 462 finally:
463 463 # store download action
464 464 audit_logger.store_web(
465 465 'repo.archive.download', action_data={
466 466 'user_agent': self.request.user_agent,
467 467 'archive_name': archive_name_key,
468 468 'archive_spec': fname,
469 469 'archive_cached': use_cached_archive},
470 470 user=self._rhodecode_user,
471 471 repo=self.db_repo,
472 472 commit=True
473 473 )
474 474
475 475 def _get_file_node(self, commit_id, f_path):
476 476 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
477 477 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
478 478 try:
479 479 node = commit.get_node(f_path)
480 480 if node.is_dir():
481 481 raise NodeError(f'{node} path is a {type(node)} not a file')
482 482 except NodeDoesNotExistError:
483 483 commit = EmptyCommit(
484 484 commit_id=commit_id,
485 485 idx=commit.idx,
486 486 repo=commit.repository,
487 487 alias=commit.repository.alias,
488 488 message=commit.message,
489 489 author=commit.author,
490 490 date=commit.date)
491 491 node = FileNode(safe_bytes(f_path), b'', commit=commit)
492 492 else:
493 493 commit = EmptyCommit(
494 494 repo=self.rhodecode_vcs_repo,
495 495 alias=self.rhodecode_vcs_repo.alias)
496 496 node = FileNode(safe_bytes(f_path), b'', commit=commit)
497 497 return node
498 498
499 499 @LoginRequired()
500 500 @HasRepoPermissionAnyDecorator(
501 501 'repository.read', 'repository.write', 'repository.admin')
502 502 def repo_files_diff(self):
503 503 c = self.load_default_context()
504 504 f_path = self._get_f_path(self.request.matchdict)
505 505 diff1 = self.request.GET.get('diff1', '')
506 506 diff2 = self.request.GET.get('diff2', '')
507 507
508 508 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
509 509
510 510 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
511 511 line_context = self.request.GET.get('context', 3)
512 512
513 513 if not any((diff1, diff2)):
514 514 h.flash(
515 515 'Need query parameter "diff1" or "diff2" to generate a diff.',
516 516 category='error')
517 517 raise HTTPBadRequest()
518 518
519 519 c.action = self.request.GET.get('diff')
520 520 if c.action not in ['download', 'raw']:
521 521 compare_url = h.route_path(
522 522 'repo_compare',
523 523 repo_name=self.db_repo_name,
524 524 source_ref_type='rev',
525 525 source_ref=diff1,
526 526 target_repo=self.db_repo_name,
527 527 target_ref_type='rev',
528 528 target_ref=diff2,
529 529 _query=dict(f_path=f_path))
530 530 # redirect to new view if we render diff
531 531 raise HTTPFound(compare_url)
532 532
533 533 try:
534 534 node1 = self._get_file_node(diff1, path1)
535 535 node2 = self._get_file_node(diff2, f_path)
536 536 except (RepositoryError, NodeError):
537 537 log.exception("Exception while trying to get node from repository")
538 538 raise HTTPFound(
539 539 h.route_path('repo_files', repo_name=self.db_repo_name,
540 540 commit_id='tip', f_path=f_path))
541 541
542 542 if all(isinstance(node.commit, EmptyCommit)
543 543 for node in (node1, node2)):
544 544 raise HTTPNotFound()
545 545
546 546 c.commit_1 = node1.commit
547 547 c.commit_2 = node2.commit
548 548
549 549 if c.action == 'download':
550 550 _diff = diffs.get_gitdiff(node1, node2,
551 551 ignore_whitespace=ignore_whitespace,
552 552 context=line_context)
553 553 # NOTE: this was using diff_format='gitdiff'
554 554 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
555 555
556 556 response = Response(self.path_filter.get_raw_patch(diff))
557 557 response.content_type = 'text/plain'
558 558 response.content_disposition = (
559 559 f'attachment; filename={f_path}_{diff1}_vs_{diff2}.diff'
560 560 )
561 561 charset = self._get_default_encoding(c)
562 562 if charset:
563 563 response.charset = charset
564 564 return response
565 565
566 566 elif c.action == 'raw':
567 567 _diff = diffs.get_gitdiff(node1, node2,
568 568 ignore_whitespace=ignore_whitespace,
569 569 context=line_context)
570 570 # NOTE: this was using diff_format='gitdiff'
571 571 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
572 572
573 573 response = Response(self.path_filter.get_raw_patch(diff))
574 574 response.content_type = 'text/plain'
575 575 charset = self._get_default_encoding(c)
576 576 if charset:
577 577 response.charset = charset
578 578 return response
579 579
580 580 # in case we ever end up here
581 581 raise HTTPNotFound()
582 582
583 583 @LoginRequired()
584 584 @HasRepoPermissionAnyDecorator(
585 585 'repository.read', 'repository.write', 'repository.admin')
586 586 def repo_files_diff_2way_redirect(self):
587 587 """
588 588 Kept only to make OLD links work
589 589 """
590 590 f_path = self._get_f_path_unchecked(self.request.matchdict)
591 591 diff1 = self.request.GET.get('diff1', '')
592 592 diff2 = self.request.GET.get('diff2', '')
593 593
594 594 if not any((diff1, diff2)):
595 595 h.flash(
596 596 'Need query parameter "diff1" or "diff2" to generate a diff.',
597 597 category='error')
598 598 raise HTTPBadRequest()
599 599
600 600 compare_url = h.route_path(
601 601 'repo_compare',
602 602 repo_name=self.db_repo_name,
603 603 source_ref_type='rev',
604 604 source_ref=diff1,
605 605 target_ref_type='rev',
606 606 target_ref=diff2,
607 607 _query=dict(f_path=f_path, diffmode='sideside',
608 608 target_repo=self.db_repo_name,))
609 609 raise HTTPFound(compare_url)
610 610
611 611 @LoginRequired()
612 612 def repo_files_default_commit_redirect(self):
613 613 """
614 614 Special page that redirects to the landing page of files based on the default
615 615 commit for repository
616 616 """
617 617 c = self.load_default_context()
618 618 ref_name = c.rhodecode_db_repo.landing_ref_name
619 619 landing_url = h.repo_files_by_ref_url(
620 620 c.rhodecode_db_repo.repo_name,
621 621 c.rhodecode_db_repo.repo_type,
622 622 f_path='',
623 623 ref_name=ref_name,
624 624 commit_id='tip',
625 625 query=dict(at=ref_name)
626 626 )
627 627
628 628 raise HTTPFound(landing_url)
629 629
630 630 @LoginRequired()
631 631 @HasRepoPermissionAnyDecorator(
632 632 'repository.read', 'repository.write', 'repository.admin')
633 633 def repo_files(self):
634 634 c = self.load_default_context()
635 635
636 636 view_name = getattr(self.request.matched_route, 'name', None)
637 637
638 638 c.annotate = view_name == 'repo_files:annotated'
639 639 # default is false, but .rst/.md files later are auto rendered, we can
640 640 # overwrite auto rendering by setting this GET flag
641 641 c.renderer = view_name == 'repo_files:rendered' or not self.request.GET.get('no-render', False)
642 642
643 643 commit_id, f_path = self._get_commit_and_path()
644 644
645 645 c.commit = self._get_commit_or_redirect(commit_id)
646 646 c.branch = self.request.GET.get('branch', None)
647 647 c.f_path = f_path
648 648 at_rev = self.request.GET.get('at')
649 649
650 650 # files or dirs
651 651 try:
652 652 c.file = c.commit.get_node(f_path, pre_load=['is_binary', 'size', 'data'])
653 653
654 654 c.file_author = True
655 655 c.file_tree = ''
656 656
657 657 # prev link
658 658 try:
659 659 prev_commit = c.commit.prev(c.branch)
660 660 c.prev_commit = prev_commit
661 661 c.url_prev = h.route_path(
662 662 'repo_files', repo_name=self.db_repo_name,
663 663 commit_id=prev_commit.raw_id, f_path=f_path)
664 664 if c.branch:
665 665 c.url_prev += '?branch=%s' % c.branch
666 666 except (CommitDoesNotExistError, VCSError):
667 667 c.url_prev = '#'
668 668 c.prev_commit = EmptyCommit()
669 669
670 670 # next link
671 671 try:
672 672 next_commit = c.commit.next(c.branch)
673 673 c.next_commit = next_commit
674 674 c.url_next = h.route_path(
675 675 'repo_files', repo_name=self.db_repo_name,
676 676 commit_id=next_commit.raw_id, f_path=f_path)
677 677 if c.branch:
678 678 c.url_next += '?branch=%s' % c.branch
679 679 except (CommitDoesNotExistError, VCSError):
680 680 c.url_next = '#'
681 681 c.next_commit = EmptyCommit()
682 682
683 683 # load file content
684 684 if c.file.is_file():
685 685
686 686 c.lf_node = {}
687 687
688 688 has_lf_enabled = self._is_lf_enabled(self.db_repo)
689 689 if has_lf_enabled:
690 690 c.lf_node = c.file.get_largefile_node()
691 691
692 692 c.file_source_page = 'true'
693 693 c.file_last_commit = c.file.last_commit
694 694
695 695 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
696 696
697 697 if not (c.file_size_too_big or c.file.is_binary):
698 698 if c.annotate: # annotation has precedence over renderer
699 699 c.annotated_lines = filenode_as_annotated_lines_tokens(
700 700 c.file
701 701 )
702 702 else:
703 703 c.renderer = (
704 704 c.renderer and h.renderer_from_filename(c.file.path)
705 705 )
706 706 if not c.renderer:
707 707 c.lines = filenode_as_lines_tokens(c.file)
708 708
709 709 _branch_name, _sha_commit_id, is_head = \
710 710 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
711 711 landing_ref=self.db_repo.landing_ref_name)
712 712 c.on_branch_head = is_head
713 713
714 714 branch = c.commit.branch if (
715 715 c.commit.branch and '/' not in c.commit.branch) else None
716 716 c.branch_or_raw_id = branch or c.commit.raw_id
717 717 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
718 718
719 719 author = c.file_last_commit.author
720 720 c.authors = [[
721 721 h.email(author),
722 722 h.person(author, 'username_or_name_or_email'),
723 723 1
724 724 ]]
725 725
726 726 else: # load tree content at path
727 727 c.file_source_page = 'false'
728 728 c.authors = []
729 729 # this loads a simple tree without metadata to speed things up
730 730 # later via ajax we call repo_nodetree_full and fetch whole
731 731 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
732 732
733 733 c.readme_data, c.readme_file = \
734 734 self._get_readme_data(self.db_repo, c.visual.default_renderer,
735 735 c.commit.raw_id, f_path)
736 736
737 737 except RepositoryError as e:
738 738 h.flash(h.escape(safe_str(e)), category='error')
739 739 raise HTTPNotFound()
740 740
741 741 if self.request.environ.get('HTTP_X_PJAX'):
742 742 html = render('rhodecode:templates/files/files_pjax.mako',
743 743 self._get_template_context(c), self.request)
744 744 else:
745 745 html = render('rhodecode:templates/files/files.mako',
746 746 self._get_template_context(c), self.request)
747 747 return Response(html)
748 748
749 749 @HasRepoPermissionAnyDecorator(
750 750 'repository.read', 'repository.write', 'repository.admin')
751 751 def repo_files_annotated_previous(self):
752 752 self.load_default_context()
753 753
754 754 commit_id, f_path = self._get_commit_and_path()
755 755 commit = self._get_commit_or_redirect(commit_id)
756 756 prev_commit_id = commit.raw_id
757 757 line_anchor = self.request.GET.get('line_anchor')
758 758 is_file = False
759 759 try:
760 760 _file = commit.get_node(f_path)
761 761 is_file = _file.is_file()
762 762 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
763 763 pass
764 764
765 765 if is_file:
766 766 history = commit.get_path_history(f_path)
767 767 prev_commit_id = history[1].raw_id \
768 768 if len(history) > 1 else prev_commit_id
769 769 prev_url = h.route_path(
770 770 'repo_files:annotated', repo_name=self.db_repo_name,
771 771 commit_id=prev_commit_id, f_path=f_path,
772 772 _anchor=f'L{line_anchor}')
773 773
774 774 raise HTTPFound(prev_url)
775 775
776 776 @LoginRequired()
777 777 @HasRepoPermissionAnyDecorator(
778 778 'repository.read', 'repository.write', 'repository.admin')
779 779 def repo_nodetree_full(self):
780 780 """
781 781 Returns rendered html of file tree that contains commit date,
782 782 author, commit_id for the specified combination of
783 783 repo, commit_id and file path
784 784 """
785 785 c = self.load_default_context()
786 786
787 787 commit_id, f_path = self._get_commit_and_path()
788 788 commit = self._get_commit_or_redirect(commit_id)
789 789 try:
790 790 dir_node = commit.get_node(f_path)
791 791 except RepositoryError as e:
792 792 return Response(f'error: {h.escape(safe_str(e))}')
793 793
794 794 if dir_node.is_file():
795 795 return Response('')
796 796
797 797 c.file = dir_node
798 798 c.commit = commit
799 799 at_rev = self.request.GET.get('at')
800 800
801 801 html = self._get_tree_at_commit(
802 802 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
803 803
804 804 return Response(html)
805 805
806 806 def _get_attachement_headers(self, f_path):
807 807 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
808 808 safe_path = f_name.replace('"', '\\"')
809 809 encoded_path = urllib.parse.quote(f_name)
810 810
811 return "attachment; " \
812 "filename=\"{}\"; " \
813 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
811 headers = "attachment; " \
812 "filename=\"{}\"; " \
813 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
814
815 return headers.encode().decode('iso-8859-1', errors='replace')
814 816
815 817 @LoginRequired()
816 818 @HasRepoPermissionAnyDecorator(
817 819 'repository.read', 'repository.write', 'repository.admin')
818 820 def repo_file_raw(self):
819 821 """
820 822 Action for show as raw, some mimetypes are "rendered",
821 823 those include images, icons.
822 824 """
823 825 c = self.load_default_context()
824 826
825 827 commit_id, f_path = self._get_commit_and_path()
826 828 commit = self._get_commit_or_redirect(commit_id)
827 829 file_node = self._get_filenode_or_redirect(commit, f_path)
828 830
829 831 raw_mimetype_mapping = {
830 832 # map original mimetype to a mimetype used for "show as raw"
831 833 # you can also provide a content-disposition to override the
832 834 # default "attachment" disposition.
833 835 # orig_type: (new_type, new_dispo)
834 836
835 837 # show images inline:
836 838 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
837 839 # for example render an SVG with javascript inside or even render
838 840 # HTML.
839 841 'image/x-icon': ('image/x-icon', 'inline'),
840 842 'image/png': ('image/png', 'inline'),
841 843 'image/gif': ('image/gif', 'inline'),
842 844 'image/jpeg': ('image/jpeg', 'inline'),
843 845 'application/pdf': ('application/pdf', 'inline'),
844 846 }
845 847
846 848 mimetype = file_node.mimetype
847 849 try:
848 850 mimetype, disposition = raw_mimetype_mapping[mimetype]
849 851 except KeyError:
850 852 # we don't know anything special about this, handle it safely
851 853 if file_node.is_binary:
852 854 # do same as download raw for binary files
853 855 mimetype, disposition = 'application/octet-stream', 'attachment'
854 856 else:
855 857 # do not just use the original mimetype, but force text/plain,
856 858 # otherwise it would serve text/html and that might be unsafe.
857 859 # Note: underlying vcs library fakes text/plain mimetype if the
858 860 # mimetype can not be determined and it thinks it is not
859 861 # binary.This might lead to erroneous text display in some
860 862 # cases, but helps in other cases, like with text files
861 863 # without extension.
862 864 mimetype, disposition = 'text/plain', 'inline'
863 865
864 866 if disposition == 'attachment':
865 867 disposition = self._get_attachement_headers(f_path)
866 868
867 869 stream_content = file_node.stream_bytes()
868 870
869 871 response = Response(app_iter=stream_content)
870 872 response.content_disposition = disposition
871 873 response.content_type = mimetype
872 874
873 875 charset = self._get_default_encoding(c)
874 876 if charset:
875 877 response.charset = charset
876 878
877 879 return response
878 880
879 881 @LoginRequired()
880 882 @HasRepoPermissionAnyDecorator(
881 883 'repository.read', 'repository.write', 'repository.admin')
882 884 def repo_file_download(self):
883 885 c = self.load_default_context()
884 886
885 887 commit_id, f_path = self._get_commit_and_path()
886 888 commit = self._get_commit_or_redirect(commit_id)
887 889 file_node = self._get_filenode_or_redirect(commit, f_path)
888 890
889 891 if self.request.GET.get('lf'):
890 892 # only if lf get flag is passed, we download this file
891 893 # as LFS/Largefile
892 894 lf_node = file_node.get_largefile_node()
893 895 if lf_node:
894 896 # overwrite our pointer with the REAL large-file
895 897 file_node = lf_node
896 898
897 899 disposition = self._get_attachement_headers(f_path)
898 900
899 901 stream_content = file_node.stream_bytes()
900 902
901 903 response = Response(app_iter=stream_content)
902 904 response.content_disposition = disposition
903 905 response.content_type = file_node.mimetype
904 906
905 907 charset = self._get_default_encoding(c)
906 908 if charset:
907 909 response.charset = charset
908 910
909 911 return response
910 912
911 913 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
912 914
913 915 cache_seconds = safe_int(
914 916 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
915 917 cache_on = cache_seconds > 0
916 918 log.debug(
917 919 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
918 920 'with caching: %s[TTL: %ss]' % (
919 921 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
920 922
921 923 cache_namespace_uid = f'repo.{repo_id}'
922 924 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
923 925
924 926 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
925 927 def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path):
926 928 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
927 929 _repo_id, commit_id, f_path)
928 930 try:
929 931 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path)
930 932 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
931 933 log.exception(safe_str(e))
932 934 h.flash(h.escape(safe_str(e)), category='error')
933 935 raise HTTPFound(h.route_path(
934 936 'repo_files', repo_name=self.db_repo_name,
935 937 commit_id='tip', f_path='/'))
936 938
937 939 return _d + _f
938 940
939 941 result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id,
940 942 commit_id, f_path)
941 943 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
942 944
943 945 @LoginRequired()
944 946 @HasRepoPermissionAnyDecorator(
945 947 'repository.read', 'repository.write', 'repository.admin')
946 948 def repo_nodelist(self):
947 949 self.load_default_context()
948 950
949 951 commit_id, f_path = self._get_commit_and_path()
950 952 commit = self._get_commit_or_redirect(commit_id)
951 953
952 954 metadata = self._get_nodelist_at_commit(
953 955 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
954 956 return {'nodes': [x for x in metadata]}
955 957
956 958 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
957 959 items = []
958 960 for name, commit_id in branches_or_tags.items():
959 961 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
960 962 items.append((sym_ref, name, ref_type))
961 963 return items
962 964
963 965 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
964 966 return commit_id
965 967
966 968 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
967 969 return commit_id
968 970
969 971 # NOTE(dan): old code we used in "diff" mode compare
970 972 new_f_path = vcspath.join(name, f_path)
971 973 return f'{new_f_path}@{commit_id}'
972 974
973 975 def _get_node_history(self, commit_obj, f_path, commits=None):
974 976 """
975 977 get commit history for given node
976 978
977 979 :param commit_obj: commit to calculate history
978 980 :param f_path: path for node to calculate history for
979 981 :param commits: if passed don't calculate history and take
980 982 commits defined in this list
981 983 """
982 984 _ = self.request.translate
983 985
984 986 # calculate history based on tip
985 987 tip = self.rhodecode_vcs_repo.get_commit()
986 988 if commits is None:
987 989 pre_load = ["author", "branch"]
988 990 try:
989 991 commits = tip.get_path_history(f_path, pre_load=pre_load)
990 992 except (NodeDoesNotExistError, CommitError):
991 993 # this node is not present at tip!
992 994 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
993 995
994 996 history = []
995 997 commits_group = ([], _("Changesets"))
996 998 for commit in commits:
997 999 branch = ' (%s)' % commit.branch if commit.branch else ''
998 1000 n_desc = f'r{commit.idx}:{commit.short_id}{branch}'
999 1001 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
1000 1002 history.append(commits_group)
1001 1003
1002 1004 symbolic_reference = self._symbolic_reference
1003 1005
1004 1006 if self.rhodecode_vcs_repo.alias == 'svn':
1005 1007 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1006 1008 f_path, self.rhodecode_vcs_repo)
1007 1009 if adjusted_f_path != f_path:
1008 1010 log.debug(
1009 1011 'Recognized svn tag or branch in file "%s", using svn '
1010 1012 'specific symbolic references', f_path)
1011 1013 f_path = adjusted_f_path
1012 1014 symbolic_reference = self._symbolic_reference_svn
1013 1015
1014 1016 branches = self._create_references(
1015 1017 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1016 1018 branches_group = (branches, _("Branches"))
1017 1019
1018 1020 tags = self._create_references(
1019 1021 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1020 1022 tags_group = (tags, _("Tags"))
1021 1023
1022 1024 history.append(branches_group)
1023 1025 history.append(tags_group)
1024 1026
1025 1027 return history, commits
1026 1028
1027 1029 @LoginRequired()
1028 1030 @HasRepoPermissionAnyDecorator(
1029 1031 'repository.read', 'repository.write', 'repository.admin')
1030 1032 def repo_file_history(self):
1031 1033 self.load_default_context()
1032 1034
1033 1035 commit_id, f_path = self._get_commit_and_path()
1034 1036 commit = self._get_commit_or_redirect(commit_id)
1035 1037 file_node = self._get_filenode_or_redirect(commit, f_path)
1036 1038
1037 1039 if file_node.is_file():
1038 1040 file_history, _hist = self._get_node_history(commit, f_path)
1039 1041
1040 1042 res = []
1041 1043 for section_items, section in file_history:
1042 1044 items = []
1043 1045 for obj_id, obj_text, obj_type in section_items:
1044 1046 at_rev = ''
1045 1047 if obj_type in ['branch', 'bookmark', 'tag']:
1046 1048 at_rev = obj_text
1047 1049 entry = {
1048 1050 'id': obj_id,
1049 1051 'text': obj_text,
1050 1052 'type': obj_type,
1051 1053 'at_rev': at_rev
1052 1054 }
1053 1055
1054 1056 items.append(entry)
1055 1057
1056 1058 res.append({
1057 1059 'text': section,
1058 1060 'children': items
1059 1061 })
1060 1062
1061 1063 data = {
1062 1064 'more': False,
1063 1065 'results': res
1064 1066 }
1065 1067 return data
1066 1068
1067 1069 log.warning('Cannot fetch history for directory')
1068 1070 raise HTTPBadRequest()
1069 1071
1070 1072 @LoginRequired()
1071 1073 @HasRepoPermissionAnyDecorator(
1072 1074 'repository.read', 'repository.write', 'repository.admin')
1073 1075 def repo_file_authors(self):
1074 1076 c = self.load_default_context()
1075 1077
1076 1078 commit_id, f_path = self._get_commit_and_path()
1077 1079 commit = self._get_commit_or_redirect(commit_id)
1078 1080 file_node = self._get_filenode_or_redirect(commit, f_path)
1079 1081
1080 1082 if not file_node.is_file():
1081 1083 raise HTTPBadRequest()
1082 1084
1083 1085 c.file_last_commit = file_node.last_commit
1084 1086 if self.request.GET.get('annotate') == '1':
1085 1087 # use _hist from annotation if annotation mode is on
1086 1088 commit_ids = {x[1] for x in file_node.annotate}
1087 1089 _hist = (
1088 1090 self.rhodecode_vcs_repo.get_commit(commit_id)
1089 1091 for commit_id in commit_ids)
1090 1092 else:
1091 1093 _f_history, _hist = self._get_node_history(commit, f_path)
1092 1094 c.file_author = False
1093 1095
1094 1096 unique = collections.OrderedDict()
1095 1097 for commit in _hist:
1096 1098 author = commit.author
1097 1099 if author not in unique:
1098 1100 unique[commit.author] = [
1099 1101 h.email(author),
1100 1102 h.person(author, 'username_or_name_or_email'),
1101 1103 1 # counter
1102 1104 ]
1103 1105
1104 1106 else:
1105 1107 # increase counter
1106 1108 unique[commit.author][2] += 1
1107 1109
1108 1110 c.authors = [val for val in unique.values()]
1109 1111
1110 1112 return self._get_template_context(c)
1111 1113
1112 1114 @LoginRequired()
1113 1115 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1114 1116 def repo_files_check_head(self):
1115 1117 self.load_default_context()
1116 1118
1117 1119 commit_id, f_path = self._get_commit_and_path()
1118 1120 _branch_name, _sha_commit_id, is_head = \
1119 1121 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1120 1122 landing_ref=self.db_repo.landing_ref_name)
1121 1123
1122 1124 new_path = self.request.POST.get('path')
1123 1125 operation = self.request.POST.get('operation')
1124 1126 path_exist = ''
1125 1127
1126 1128 if new_path and operation in ['create', 'upload']:
1127 1129 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1128 1130 try:
1129 1131 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1130 1132 # NOTE(dan): construct whole path without leading /
1131 1133 file_node = commit_obj.get_node(new_f_path)
1132 1134 if file_node is not None:
1133 1135 path_exist = new_f_path
1134 1136 except EmptyRepositoryError:
1135 1137 pass
1136 1138 except Exception:
1137 1139 pass
1138 1140
1139 1141 return {
1140 1142 'branch': _branch_name,
1141 1143 'sha': _sha_commit_id,
1142 1144 'is_head': is_head,
1143 1145 'path_exists': path_exist
1144 1146 }
1145 1147
1146 1148 @LoginRequired()
1147 1149 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1148 1150 def repo_files_remove_file(self):
1149 1151 _ = self.request.translate
1150 1152 c = self.load_default_context()
1151 1153 commit_id, f_path = self._get_commit_and_path()
1152 1154
1153 1155 self._ensure_not_locked()
1154 1156 _branch_name, _sha_commit_id, is_head = \
1155 1157 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1156 1158 landing_ref=self.db_repo.landing_ref_name)
1157 1159
1158 1160 self.forbid_non_head(is_head, f_path)
1159 1161 self.check_branch_permission(_branch_name)
1160 1162
1161 1163 c.commit = self._get_commit_or_redirect(commit_id)
1162 1164 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1163 1165
1164 1166 c.default_message = _(
1165 1167 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1166 1168 c.f_path = f_path
1167 1169
1168 1170 return self._get_template_context(c)
1169 1171
1170 1172 @LoginRequired()
1171 1173 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1172 1174 @CSRFRequired()
1173 1175 def repo_files_delete_file(self):
1174 1176 _ = self.request.translate
1175 1177
1176 1178 c = self.load_default_context()
1177 1179 commit_id, f_path = self._get_commit_and_path()
1178 1180
1179 1181 self._ensure_not_locked()
1180 1182 _branch_name, _sha_commit_id, is_head = \
1181 1183 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1182 1184 landing_ref=self.db_repo.landing_ref_name)
1183 1185
1184 1186 self.forbid_non_head(is_head, f_path)
1185 1187 self.check_branch_permission(_branch_name)
1186 1188
1187 1189 c.commit = self._get_commit_or_redirect(commit_id)
1188 1190 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1189 1191
1190 1192 c.default_message = _(
1191 1193 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1192 1194 c.f_path = f_path
1193 1195 node_path = f_path
1194 1196 author = self._rhodecode_db_user.full_contact
1195 1197 message = self.request.POST.get('message') or c.default_message
1196 1198 try:
1197 1199 nodes = {
1198 1200 safe_bytes(node_path): {
1199 1201 'content': b''
1200 1202 }
1201 1203 }
1202 1204 ScmModel().delete_nodes(
1203 1205 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1204 1206 message=message,
1205 1207 nodes=nodes,
1206 1208 parent_commit=c.commit,
1207 1209 author=author,
1208 1210 )
1209 1211
1210 1212 h.flash(
1211 1213 _('Successfully deleted file `{}`').format(
1212 1214 h.escape(f_path)), category='success')
1213 1215 except Exception:
1214 1216 log.exception('Error during commit operation')
1215 1217 h.flash(_('Error occurred during commit'), category='error')
1216 1218 raise HTTPFound(
1217 1219 h.route_path('repo_commit', repo_name=self.db_repo_name,
1218 1220 commit_id='tip'))
1219 1221
1220 1222 @LoginRequired()
1221 1223 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1222 1224 def repo_files_edit_file(self):
1223 1225 _ = self.request.translate
1224 1226 c = self.load_default_context()
1225 1227 commit_id, f_path = self._get_commit_and_path()
1226 1228
1227 1229 self._ensure_not_locked()
1228 1230 _branch_name, _sha_commit_id, is_head = \
1229 1231 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1230 1232 landing_ref=self.db_repo.landing_ref_name)
1231 1233
1232 1234 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1233 1235 self.check_branch_permission(_branch_name, commit_id=commit_id)
1234 1236
1235 1237 c.commit = self._get_commit_or_redirect(commit_id)
1236 1238 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1237 1239
1238 1240 if c.file.is_binary:
1239 1241 files_url = h.route_path(
1240 1242 'repo_files',
1241 1243 repo_name=self.db_repo_name,
1242 1244 commit_id=c.commit.raw_id, f_path=f_path)
1243 1245 raise HTTPFound(files_url)
1244 1246
1245 1247 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1246 1248 c.f_path = f_path
1247 1249
1248 1250 return self._get_template_context(c)
1249 1251
1250 1252 @LoginRequired()
1251 1253 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1252 1254 @CSRFRequired()
1253 1255 def repo_files_update_file(self):
1254 1256 _ = self.request.translate
1255 1257 c = self.load_default_context()
1256 1258 commit_id, f_path = self._get_commit_and_path()
1257 1259
1258 1260 self._ensure_not_locked()
1259 1261
1260 1262 c.commit = self._get_commit_or_redirect(commit_id)
1261 1263 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1262 1264
1263 1265 if c.file.is_binary:
1264 1266 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1265 1267 commit_id=c.commit.raw_id, f_path=f_path))
1266 1268
1267 1269 _branch_name, _sha_commit_id, is_head = \
1268 1270 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1269 1271 landing_ref=self.db_repo.landing_ref_name)
1270 1272
1271 1273 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1272 1274 self.check_branch_permission(_branch_name, commit_id=commit_id)
1273 1275
1274 1276 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1275 1277 c.f_path = f_path
1276 1278
1277 1279 old_content = c.file.str_content
1278 1280 sl = old_content.splitlines(1)
1279 1281 first_line = sl[0] if sl else ''
1280 1282
1281 1283 r_post = self.request.POST
1282 1284 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1283 1285 line_ending_mode = detect_mode(first_line, 0)
1284 1286 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1285 1287
1286 1288 message = r_post.get('message') or c.default_message
1287 1289
1288 1290 org_node_path = c.file.str_path
1289 1291 filename = r_post['filename']
1290 1292
1291 1293 root_path = c.file.dir_path
1292 1294 pure_path = self.create_pure_path(root_path, filename)
1293 1295 node_path = pure_path.as_posix()
1294 1296
1295 1297 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1296 1298 commit_id=commit_id)
1297 1299 if content == old_content and node_path == org_node_path:
1298 1300 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1299 1301 category='warning')
1300 1302 raise HTTPFound(default_redirect_url)
1301 1303
1302 1304 try:
1303 1305 mapping = {
1304 1306 c.file.bytes_path: {
1305 1307 'org_filename': org_node_path,
1306 1308 'filename': safe_bytes(node_path),
1307 1309 'content': safe_bytes(content),
1308 1310 'lexer': '',
1309 1311 'op': 'mod',
1310 1312 'mode': c.file.mode
1311 1313 }
1312 1314 }
1313 1315
1314 1316 commit = ScmModel().update_nodes(
1315 1317 user=self._rhodecode_db_user.user_id,
1316 1318 repo=self.db_repo,
1317 1319 message=message,
1318 1320 nodes=mapping,
1319 1321 parent_commit=c.commit,
1320 1322 )
1321 1323
1322 1324 h.flash(_('Successfully committed changes to file `{}`').format(
1323 1325 h.escape(f_path)), category='success')
1324 1326 default_redirect_url = h.route_path(
1325 1327 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1326 1328
1327 1329 except Exception:
1328 1330 log.exception('Error occurred during commit')
1329 1331 h.flash(_('Error occurred during commit'), category='error')
1330 1332
1331 1333 raise HTTPFound(default_redirect_url)
1332 1334
1333 1335 @LoginRequired()
1334 1336 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1335 1337 def repo_files_add_file(self):
1336 1338 _ = self.request.translate
1337 1339 c = self.load_default_context()
1338 1340 commit_id, f_path = self._get_commit_and_path()
1339 1341
1340 1342 self._ensure_not_locked()
1341 1343
1342 1344 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1343 1345 if c.commit is None:
1344 1346 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1345 1347
1346 1348 if self.rhodecode_vcs_repo.is_empty():
1347 1349 # for empty repository we cannot check for current branch, we rely on
1348 1350 # c.commit.branch instead
1349 1351 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1350 1352 else:
1351 1353 _branch_name, _sha_commit_id, is_head = \
1352 1354 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1353 1355 landing_ref=self.db_repo.landing_ref_name)
1354 1356
1355 1357 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1356 1358 self.check_branch_permission(_branch_name, commit_id=commit_id)
1357 1359
1358 1360 c.default_message = (_('Added file via RhodeCode Enterprise'))
1359 1361 c.f_path = f_path.lstrip('/') # ensure not relative path
1360 1362
1361 1363 return self._get_template_context(c)
1362 1364
1363 1365 @LoginRequired()
1364 1366 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1365 1367 @CSRFRequired()
1366 1368 def repo_files_create_file(self):
1367 1369 _ = self.request.translate
1368 1370 c = self.load_default_context()
1369 1371 commit_id, f_path = self._get_commit_and_path()
1370 1372
1371 1373 self._ensure_not_locked()
1372 1374
1373 1375 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1374 1376 if c.commit is None:
1375 1377 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1376 1378
1377 1379 # calculate redirect URL
1378 1380 if self.rhodecode_vcs_repo.is_empty():
1379 1381 default_redirect_url = h.route_path(
1380 1382 'repo_summary', repo_name=self.db_repo_name)
1381 1383 else:
1382 1384 default_redirect_url = h.route_path(
1383 1385 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1384 1386
1385 1387 if self.rhodecode_vcs_repo.is_empty():
1386 1388 # for empty repository we cannot check for current branch, we rely on
1387 1389 # c.commit.branch instead
1388 1390 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1389 1391 else:
1390 1392 _branch_name, _sha_commit_id, is_head = \
1391 1393 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1392 1394 landing_ref=self.db_repo.landing_ref_name)
1393 1395
1394 1396 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1395 1397 self.check_branch_permission(_branch_name, commit_id=commit_id)
1396 1398
1397 1399 c.default_message = (_('Added file via RhodeCode Enterprise'))
1398 1400 c.f_path = f_path
1399 1401
1400 1402 r_post = self.request.POST
1401 1403 message = r_post.get('message') or c.default_message
1402 1404 filename = r_post.get('filename')
1403 1405 unix_mode = 0
1404 1406
1405 1407 if not filename:
1406 1408 # If there's no commit, redirect to repo summary
1407 1409 if type(c.commit) is EmptyCommit:
1408 1410 redirect_url = h.route_path(
1409 1411 'repo_summary', repo_name=self.db_repo_name)
1410 1412 else:
1411 1413 redirect_url = default_redirect_url
1412 1414 h.flash(_('No filename specified'), category='warning')
1413 1415 raise HTTPFound(redirect_url)
1414 1416
1415 1417 root_path = f_path
1416 1418 pure_path = self.create_pure_path(root_path, filename)
1417 1419 node_path = pure_path.as_posix().lstrip('/')
1418 1420
1419 1421 author = self._rhodecode_db_user.full_contact
1420 1422 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1421 1423 nodes = {
1422 1424 safe_bytes(node_path): {
1423 1425 'content': safe_bytes(content)
1424 1426 }
1425 1427 }
1426 1428
1427 1429 try:
1428 1430
1429 1431 commit = ScmModel().create_nodes(
1430 1432 user=self._rhodecode_db_user.user_id,
1431 1433 repo=self.db_repo,
1432 1434 message=message,
1433 1435 nodes=nodes,
1434 1436 parent_commit=c.commit,
1435 1437 author=author,
1436 1438 )
1437 1439
1438 1440 h.flash(_('Successfully committed new file `{}`').format(
1439 1441 h.escape(node_path)), category='success')
1440 1442
1441 1443 default_redirect_url = h.route_path(
1442 1444 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1443 1445
1444 1446 except NonRelativePathError:
1445 1447 log.exception('Non Relative path found')
1446 1448 h.flash(_('The location specified must be a relative path and must not '
1447 1449 'contain .. in the path'), category='warning')
1448 1450 raise HTTPFound(default_redirect_url)
1449 1451 except (NodeError, NodeAlreadyExistsError) as e:
1450 1452 h.flash(h.escape(safe_str(e)), category='error')
1451 1453 except Exception:
1452 1454 log.exception('Error occurred during commit')
1453 1455 h.flash(_('Error occurred during commit'), category='error')
1454 1456
1455 1457 raise HTTPFound(default_redirect_url)
1456 1458
1457 1459 @LoginRequired()
1458 1460 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1459 1461 @CSRFRequired()
1460 1462 def repo_files_upload_file(self):
1461 1463 _ = self.request.translate
1462 1464 c = self.load_default_context()
1463 1465 commit_id, f_path = self._get_commit_and_path()
1464 1466
1465 1467 self._ensure_not_locked()
1466 1468
1467 1469 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1468 1470 if c.commit is None:
1469 1471 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1470 1472
1471 1473 # calculate redirect URL
1472 1474 if self.rhodecode_vcs_repo.is_empty():
1473 1475 default_redirect_url = h.route_path(
1474 1476 'repo_summary', repo_name=self.db_repo_name)
1475 1477 else:
1476 1478 default_redirect_url = h.route_path(
1477 1479 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1478 1480
1479 1481 if self.rhodecode_vcs_repo.is_empty():
1480 1482 # for empty repository we cannot check for current branch, we rely on
1481 1483 # c.commit.branch instead
1482 1484 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1483 1485 else:
1484 1486 _branch_name, _sha_commit_id, is_head = \
1485 1487 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1486 1488 landing_ref=self.db_repo.landing_ref_name)
1487 1489
1488 1490 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1489 1491 if error:
1490 1492 return {
1491 1493 'error': error,
1492 1494 'redirect_url': default_redirect_url
1493 1495 }
1494 1496 error = self.check_branch_permission(_branch_name, json_mode=True)
1495 1497 if error:
1496 1498 return {
1497 1499 'error': error,
1498 1500 'redirect_url': default_redirect_url
1499 1501 }
1500 1502
1501 1503 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1502 1504 c.f_path = f_path
1503 1505
1504 1506 r_post = self.request.POST
1505 1507
1506 1508 message = c.default_message
1507 1509 user_message = r_post.getall('message')
1508 1510 if isinstance(user_message, list) and user_message:
1509 1511 # we take the first from duplicated results if it's not empty
1510 1512 message = user_message[0] if user_message[0] else message
1511 1513
1512 1514 nodes = {}
1513 1515
1514 1516 for file_obj in r_post.getall('files_upload') or []:
1515 1517 content = file_obj.file
1516 1518 filename = file_obj.filename
1517 1519
1518 1520 root_path = f_path
1519 1521 pure_path = self.create_pure_path(root_path, filename)
1520 1522 node_path = pure_path.as_posix().lstrip('/')
1521 1523
1522 1524 nodes[safe_bytes(node_path)] = {
1523 1525 'content': content
1524 1526 }
1525 1527
1526 1528 if not nodes:
1527 1529 error = 'missing files'
1528 1530 return {
1529 1531 'error': error,
1530 1532 'redirect_url': default_redirect_url
1531 1533 }
1532 1534
1533 1535 author = self._rhodecode_db_user.full_contact
1534 1536
1535 1537 try:
1536 1538 commit = ScmModel().create_nodes(
1537 1539 user=self._rhodecode_db_user.user_id,
1538 1540 repo=self.db_repo,
1539 1541 message=message,
1540 1542 nodes=nodes,
1541 1543 parent_commit=c.commit,
1542 1544 author=author,
1543 1545 )
1544 1546 if len(nodes) == 1:
1545 1547 flash_message = _('Successfully committed {} new files').format(len(nodes))
1546 1548 else:
1547 1549 flash_message = _('Successfully committed 1 new file')
1548 1550
1549 1551 h.flash(flash_message, category='success')
1550 1552
1551 1553 default_redirect_url = h.route_path(
1552 1554 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1553 1555
1554 1556 except NonRelativePathError:
1555 1557 log.exception('Non Relative path found')
1556 1558 error = _('The location specified must be a relative path and must not '
1557 1559 'contain .. in the path')
1558 1560 h.flash(error, category='warning')
1559 1561
1560 1562 return {
1561 1563 'error': error,
1562 1564 'redirect_url': default_redirect_url
1563 1565 }
1564 1566 except (NodeError, NodeAlreadyExistsError) as e:
1565 1567 error = h.escape(e)
1566 1568 h.flash(error, category='error')
1567 1569
1568 1570 return {
1569 1571 'error': error,
1570 1572 'redirect_url': default_redirect_url
1571 1573 }
1572 1574 except Exception:
1573 1575 log.exception('Error occurred during commit')
1574 1576 error = _('Error occurred during commit')
1575 1577 h.flash(error, category='error')
1576 1578 return {
1577 1579 'error': error,
1578 1580 'redirect_url': default_redirect_url
1579 1581 }
1580 1582
1581 1583 return {
1582 1584 'error': None,
1583 1585 'redirect_url': default_redirect_url
1584 1586 }
General Comments 0
You need to be logged in to leave comments. Login now