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