##// END OF EJS Templates
security: fixed XSS in file editing.
marcink -
r3901:01c5c83d default
parent child Browse files
Show More
@@ -1,1548 +1,1548 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 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 from pyramid.view import view_config
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 branch_name, 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_rev[1]
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):
185 185 msg = _('No such commit exists for this repository')
186 186 h.flash(msg, category='error')
187 187 raise HTTPNotFound()
188 188 except RepositoryError as e:
189 189 h.flash(safe_str(h.escape(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(safe_str(h.escape(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):
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 not repo.is_empty():
241 241 commit = repo.get_commit(commit_id=commit_id)
242 242 if commit:
243 243 branch_name = commit.branch
244 244 sha_commit_id = commit.raw_id
245 245
246 246 return branch_name, sha_commit_id, is_head
247 247
248 248 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False):
249 249
250 250 repo_id = self.db_repo.repo_id
251 251 force_recache = self.get_recache_flag()
252 252
253 253 cache_seconds = safe_int(
254 254 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
255 255 cache_on = not force_recache and cache_seconds > 0
256 256 log.debug(
257 257 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
258 258 'with caching: %s[TTL: %ss]' % (
259 259 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
260 260
261 261 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
262 262 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
263 263
264 264 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
265 265 condition=cache_on)
266 266 def compute_file_tree(ver, repo_id, commit_id, f_path, full_load):
267 267 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
268 268 ver, repo_id, commit_id, f_path)
269 269
270 270 c.full_load = full_load
271 271 return render(
272 272 'rhodecode:templates/files/files_browser_tree.mako',
273 273 self._get_template_context(c), self.request)
274 274
275 275 return compute_file_tree('v1', self.db_repo.repo_id, commit_id, f_path, full_load)
276 276
277 277 def _get_archive_spec(self, fname):
278 278 log.debug('Detecting archive spec for: `%s`', fname)
279 279
280 280 fileformat = None
281 281 ext = None
282 282 content_type = None
283 283 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
284 284
285 285 if fname.endswith(extension):
286 286 fileformat = a_type
287 287 log.debug('archive is of type: %s', fileformat)
288 288 ext = extension
289 289 break
290 290
291 291 if not fileformat:
292 292 raise ValueError()
293 293
294 294 # left over part of whole fname is the commit
295 295 commit_id = fname[:-len(ext)]
296 296
297 297 return commit_id, ext, fileformat, content_type
298 298
299 299 def create_pure_path(self, *parts):
300 300 # Split paths and sanitize them, removing any ../ etc
301 301 sanitized_path = [
302 302 x for x in pathlib2.PurePath(*parts).parts
303 303 if x not in ['.', '..']]
304 304
305 305 pure_path = pathlib2.PurePath(*sanitized_path)
306 306 return pure_path
307 307
308 308 def _is_lf_enabled(self, target_repo):
309 309 lf_enabled = False
310 310
311 311 lf_key_for_vcs_map = {
312 312 'hg': 'extensions_largefiles',
313 313 'git': 'vcs_git_lfs_enabled'
314 314 }
315 315
316 316 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
317 317
318 318 if lf_key_for_vcs:
319 319 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
320 320
321 321 return lf_enabled
322 322
323 323 @LoginRequired()
324 324 @HasRepoPermissionAnyDecorator(
325 325 'repository.read', 'repository.write', 'repository.admin')
326 326 @view_config(
327 327 route_name='repo_archivefile', request_method='GET',
328 328 renderer=None)
329 329 def repo_archivefile(self):
330 330 # archive cache config
331 331 from rhodecode import CONFIG
332 332 _ = self.request.translate
333 333 self.load_default_context()
334 334 default_at_path = '/'
335 335 fname = self.request.matchdict['fname']
336 336 subrepos = self.request.GET.get('subrepos') == 'true'
337 337 at_path = self.request.GET.get('at_path') or default_at_path
338 338
339 339 if not self.db_repo.enable_downloads:
340 340 return Response(_('Downloads disabled'))
341 341
342 342 try:
343 343 commit_id, ext, fileformat, content_type = \
344 344 self._get_archive_spec(fname)
345 345 except ValueError:
346 346 return Response(_('Unknown archive type for: `{}`').format(
347 347 h.escape(fname)))
348 348
349 349 try:
350 350 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
351 351 except CommitDoesNotExistError:
352 352 return Response(_('Unknown commit_id {}').format(
353 353 h.escape(commit_id)))
354 354 except EmptyRepositoryError:
355 355 return Response(_('Empty repository'))
356 356
357 357 try:
358 358 at_path = commit.get_node(at_path).path or default_at_path
359 359 except Exception:
360 360 return Response(_('No node at path {} for this repository').format(at_path))
361 361
362 362 path_sha = sha1(at_path)[:8]
363 363
364 364 # original backward compat name of archive
365 365 clean_name = safe_str(self.db_repo_name.replace('/', '_'))
366 366 short_sha = safe_str(commit.short_id)
367 367
368 368 if at_path == default_at_path:
369 369 archive_name = '{}-{}{}{}'.format(
370 370 clean_name,
371 371 '-sub' if subrepos else '',
372 372 short_sha,
373 373 ext)
374 374 # custom path and new name
375 375 else:
376 376 archive_name = '{}-{}{}-{}{}'.format(
377 377 clean_name,
378 378 '-sub' if subrepos else '',
379 379 short_sha,
380 380 path_sha,
381 381 ext)
382 382
383 383 use_cached_archive = False
384 384 archive_cache_enabled = CONFIG.get(
385 385 'archive_cache_dir') and not self.request.GET.get('no_cache')
386 386 cached_archive_path = None
387 387
388 388 if archive_cache_enabled:
389 389 # check if we it's ok to write
390 390 if not os.path.isdir(CONFIG['archive_cache_dir']):
391 391 os.makedirs(CONFIG['archive_cache_dir'])
392 392 cached_archive_path = os.path.join(
393 393 CONFIG['archive_cache_dir'], archive_name)
394 394 if os.path.isfile(cached_archive_path):
395 395 log.debug('Found cached archive in %s', cached_archive_path)
396 396 fd, archive = None, cached_archive_path
397 397 use_cached_archive = True
398 398 else:
399 399 log.debug('Archive %s is not yet cached', archive_name)
400 400
401 401 if not use_cached_archive:
402 402 # generate new archive
403 403 fd, archive = tempfile.mkstemp()
404 404 log.debug('Creating new temp archive in %s', archive)
405 405 try:
406 406 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
407 407 archive_at_path=at_path)
408 408 except ImproperArchiveTypeError:
409 409 return _('Unknown archive type')
410 410 if archive_cache_enabled:
411 411 # if we generated the archive and we have cache enabled
412 412 # let's use this for future
413 413 log.debug('Storing new archive in %s', cached_archive_path)
414 414 shutil.move(archive, cached_archive_path)
415 415 archive = cached_archive_path
416 416
417 417 # store download action
418 418 audit_logger.store_web(
419 419 'repo.archive.download', action_data={
420 420 'user_agent': self.request.user_agent,
421 421 'archive_name': archive_name,
422 422 'archive_spec': fname,
423 423 'archive_cached': use_cached_archive},
424 424 user=self._rhodecode_user,
425 425 repo=self.db_repo,
426 426 commit=True
427 427 )
428 428
429 429 def get_chunked_archive(archive_path):
430 430 with open(archive_path, 'rb') as stream:
431 431 while True:
432 432 data = stream.read(16 * 1024)
433 433 if not data:
434 434 if fd: # fd means we used temporary file
435 435 os.close(fd)
436 436 if not archive_cache_enabled:
437 437 log.debug('Destroying temp archive %s', archive_path)
438 438 os.remove(archive_path)
439 439 break
440 440 yield data
441 441
442 442 response = Response(app_iter=get_chunked_archive(archive))
443 443 response.content_disposition = str(
444 444 'attachment; filename=%s' % archive_name)
445 445 response.content_type = str(content_type)
446 446
447 447 return response
448 448
449 449 def _get_file_node(self, commit_id, f_path):
450 450 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
451 451 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
452 452 try:
453 453 node = commit.get_node(f_path)
454 454 if node.is_dir():
455 455 raise NodeError('%s path is a %s not a file'
456 456 % (node, type(node)))
457 457 except NodeDoesNotExistError:
458 458 commit = EmptyCommit(
459 459 commit_id=commit_id,
460 460 idx=commit.idx,
461 461 repo=commit.repository,
462 462 alias=commit.repository.alias,
463 463 message=commit.message,
464 464 author=commit.author,
465 465 date=commit.date)
466 466 node = FileNode(f_path, '', commit=commit)
467 467 else:
468 468 commit = EmptyCommit(
469 469 repo=self.rhodecode_vcs_repo,
470 470 alias=self.rhodecode_vcs_repo.alias)
471 471 node = FileNode(f_path, '', commit=commit)
472 472 return node
473 473
474 474 @LoginRequired()
475 475 @HasRepoPermissionAnyDecorator(
476 476 'repository.read', 'repository.write', 'repository.admin')
477 477 @view_config(
478 478 route_name='repo_files_diff', request_method='GET',
479 479 renderer=None)
480 480 def repo_files_diff(self):
481 481 c = self.load_default_context()
482 482 f_path = self._get_f_path(self.request.matchdict)
483 483 diff1 = self.request.GET.get('diff1', '')
484 484 diff2 = self.request.GET.get('diff2', '')
485 485
486 486 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
487 487
488 488 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
489 489 line_context = self.request.GET.get('context', 3)
490 490
491 491 if not any((diff1, diff2)):
492 492 h.flash(
493 493 'Need query parameter "diff1" or "diff2" to generate a diff.',
494 494 category='error')
495 495 raise HTTPBadRequest()
496 496
497 497 c.action = self.request.GET.get('diff')
498 498 if c.action not in ['download', 'raw']:
499 499 compare_url = h.route_path(
500 500 'repo_compare',
501 501 repo_name=self.db_repo_name,
502 502 source_ref_type='rev',
503 503 source_ref=diff1,
504 504 target_repo=self.db_repo_name,
505 505 target_ref_type='rev',
506 506 target_ref=diff2,
507 507 _query=dict(f_path=f_path))
508 508 # redirect to new view if we render diff
509 509 raise HTTPFound(compare_url)
510 510
511 511 try:
512 512 node1 = self._get_file_node(diff1, path1)
513 513 node2 = self._get_file_node(diff2, f_path)
514 514 except (RepositoryError, NodeError):
515 515 log.exception("Exception while trying to get node from repository")
516 516 raise HTTPFound(
517 517 h.route_path('repo_files', repo_name=self.db_repo_name,
518 518 commit_id='tip', f_path=f_path))
519 519
520 520 if all(isinstance(node.commit, EmptyCommit)
521 521 for node in (node1, node2)):
522 522 raise HTTPNotFound()
523 523
524 524 c.commit_1 = node1.commit
525 525 c.commit_2 = node2.commit
526 526
527 527 if c.action == 'download':
528 528 _diff = diffs.get_gitdiff(node1, node2,
529 529 ignore_whitespace=ignore_whitespace,
530 530 context=line_context)
531 531 diff = diffs.DiffProcessor(_diff, format='gitdiff')
532 532
533 533 response = Response(self.path_filter.get_raw_patch(diff))
534 534 response.content_type = 'text/plain'
535 535 response.content_disposition = (
536 536 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
537 537 )
538 538 charset = self._get_default_encoding(c)
539 539 if charset:
540 540 response.charset = charset
541 541 return response
542 542
543 543 elif c.action == 'raw':
544 544 _diff = diffs.get_gitdiff(node1, node2,
545 545 ignore_whitespace=ignore_whitespace,
546 546 context=line_context)
547 547 diff = diffs.DiffProcessor(_diff, format='gitdiff')
548 548
549 549 response = Response(self.path_filter.get_raw_patch(diff))
550 550 response.content_type = 'text/plain'
551 551 charset = self._get_default_encoding(c)
552 552 if charset:
553 553 response.charset = charset
554 554 return response
555 555
556 556 # in case we ever end up here
557 557 raise HTTPNotFound()
558 558
559 559 @LoginRequired()
560 560 @HasRepoPermissionAnyDecorator(
561 561 'repository.read', 'repository.write', 'repository.admin')
562 562 @view_config(
563 563 route_name='repo_files_diff_2way_redirect', request_method='GET',
564 564 renderer=None)
565 565 def repo_files_diff_2way_redirect(self):
566 566 """
567 567 Kept only to make OLD links work
568 568 """
569 569 f_path = self._get_f_path_unchecked(self.request.matchdict)
570 570 diff1 = self.request.GET.get('diff1', '')
571 571 diff2 = self.request.GET.get('diff2', '')
572 572
573 573 if not any((diff1, diff2)):
574 574 h.flash(
575 575 'Need query parameter "diff1" or "diff2" to generate a diff.',
576 576 category='error')
577 577 raise HTTPBadRequest()
578 578
579 579 compare_url = h.route_path(
580 580 'repo_compare',
581 581 repo_name=self.db_repo_name,
582 582 source_ref_type='rev',
583 583 source_ref=diff1,
584 584 target_ref_type='rev',
585 585 target_ref=diff2,
586 586 _query=dict(f_path=f_path, diffmode='sideside',
587 587 target_repo=self.db_repo_name,))
588 588 raise HTTPFound(compare_url)
589 589
590 590 @LoginRequired()
591 591 @HasRepoPermissionAnyDecorator(
592 592 'repository.read', 'repository.write', 'repository.admin')
593 593 @view_config(
594 594 route_name='repo_files', request_method='GET',
595 595 renderer=None)
596 596 @view_config(
597 597 route_name='repo_files:default_path', request_method='GET',
598 598 renderer=None)
599 599 @view_config(
600 600 route_name='repo_files:default_commit', request_method='GET',
601 601 renderer=None)
602 602 @view_config(
603 603 route_name='repo_files:rendered', request_method='GET',
604 604 renderer=None)
605 605 @view_config(
606 606 route_name='repo_files:annotated', request_method='GET',
607 607 renderer=None)
608 608 def repo_files(self):
609 609 c = self.load_default_context()
610 610
611 611 view_name = getattr(self.request.matched_route, 'name', None)
612 612
613 613 c.annotate = view_name == 'repo_files:annotated'
614 614 # default is false, but .rst/.md files later are auto rendered, we can
615 615 # overwrite auto rendering by setting this GET flag
616 616 c.renderer = view_name == 'repo_files:rendered' or \
617 617 not self.request.GET.get('no-render', False)
618 618
619 619 # redirect to given commit_id from form if given
620 620 get_commit_id = self.request.GET.get('at_rev', None)
621 621 if get_commit_id:
622 622 self._get_commit_or_redirect(get_commit_id)
623 623
624 624 commit_id, f_path = self._get_commit_and_path()
625 625 c.commit = self._get_commit_or_redirect(commit_id)
626 626 c.branch = self.request.GET.get('branch', None)
627 627 c.f_path = f_path
628 628
629 629 # prev link
630 630 try:
631 631 prev_commit = c.commit.prev(c.branch)
632 632 c.prev_commit = prev_commit
633 633 c.url_prev = h.route_path(
634 634 'repo_files', repo_name=self.db_repo_name,
635 635 commit_id=prev_commit.raw_id, f_path=f_path)
636 636 if c.branch:
637 637 c.url_prev += '?branch=%s' % c.branch
638 638 except (CommitDoesNotExistError, VCSError):
639 639 c.url_prev = '#'
640 640 c.prev_commit = EmptyCommit()
641 641
642 642 # next link
643 643 try:
644 644 next_commit = c.commit.next(c.branch)
645 645 c.next_commit = next_commit
646 646 c.url_next = h.route_path(
647 647 'repo_files', repo_name=self.db_repo_name,
648 648 commit_id=next_commit.raw_id, f_path=f_path)
649 649 if c.branch:
650 650 c.url_next += '?branch=%s' % c.branch
651 651 except (CommitDoesNotExistError, VCSError):
652 652 c.url_next = '#'
653 653 c.next_commit = EmptyCommit()
654 654
655 655 # files or dirs
656 656 try:
657 657 c.file = c.commit.get_node(f_path)
658 658 c.file_author = True
659 659 c.file_tree = ''
660 660
661 661 # load file content
662 662 if c.file.is_file():
663 663 c.lf_node = {}
664 664
665 665 has_lf_enabled = self._is_lf_enabled(self.db_repo)
666 666 if has_lf_enabled:
667 667 c.lf_node = c.file.get_largefile_node()
668 668
669 669 c.file_source_page = 'true'
670 670 c.file_last_commit = c.file.last_commit
671 671
672 672 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
673 673
674 674 if not (c.file_size_too_big or c.file.is_binary):
675 675 if c.annotate: # annotation has precedence over renderer
676 676 c.annotated_lines = filenode_as_annotated_lines_tokens(
677 677 c.file
678 678 )
679 679 else:
680 680 c.renderer = (
681 681 c.renderer and h.renderer_from_filename(c.file.path)
682 682 )
683 683 if not c.renderer:
684 684 c.lines = filenode_as_lines_tokens(c.file)
685 685
686 686 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
687 687 commit_id, self.rhodecode_vcs_repo)
688 688 c.on_branch_head = is_head
689 689
690 690 branch = c.commit.branch if (
691 691 c.commit.branch and '/' not in c.commit.branch) else None
692 692 c.branch_or_raw_id = branch or c.commit.raw_id
693 693 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
694 694
695 695 author = c.file_last_commit.author
696 696 c.authors = [[
697 697 h.email(author),
698 698 h.person(author, 'username_or_name_or_email'),
699 699 1
700 700 ]]
701 701
702 702 else: # load tree content at path
703 703 c.file_source_page = 'false'
704 704 c.authors = []
705 705 # this loads a simple tree without metadata to speed things up
706 706 # later via ajax we call repo_nodetree_full and fetch whole
707 707 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path)
708 708
709 709 except RepositoryError as e:
710 710 h.flash(safe_str(h.escape(e)), category='error')
711 711 raise HTTPNotFound()
712 712
713 713 if self.request.environ.get('HTTP_X_PJAX'):
714 714 html = render('rhodecode:templates/files/files_pjax.mako',
715 715 self._get_template_context(c), self.request)
716 716 else:
717 717 html = render('rhodecode:templates/files/files.mako',
718 718 self._get_template_context(c), self.request)
719 719 return Response(html)
720 720
721 721 @HasRepoPermissionAnyDecorator(
722 722 'repository.read', 'repository.write', 'repository.admin')
723 723 @view_config(
724 724 route_name='repo_files:annotated_previous', request_method='GET',
725 725 renderer=None)
726 726 def repo_files_annotated_previous(self):
727 727 self.load_default_context()
728 728
729 729 commit_id, f_path = self._get_commit_and_path()
730 730 commit = self._get_commit_or_redirect(commit_id)
731 731 prev_commit_id = commit.raw_id
732 732 line_anchor = self.request.GET.get('line_anchor')
733 733 is_file = False
734 734 try:
735 735 _file = commit.get_node(f_path)
736 736 is_file = _file.is_file()
737 737 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
738 738 pass
739 739
740 740 if is_file:
741 741 history = commit.get_path_history(f_path)
742 742 prev_commit_id = history[1].raw_id \
743 743 if len(history) > 1 else prev_commit_id
744 744 prev_url = h.route_path(
745 745 'repo_files:annotated', repo_name=self.db_repo_name,
746 746 commit_id=prev_commit_id, f_path=f_path,
747 747 _anchor='L{}'.format(line_anchor))
748 748
749 749 raise HTTPFound(prev_url)
750 750
751 751 @LoginRequired()
752 752 @HasRepoPermissionAnyDecorator(
753 753 'repository.read', 'repository.write', 'repository.admin')
754 754 @view_config(
755 755 route_name='repo_nodetree_full', request_method='GET',
756 756 renderer=None, xhr=True)
757 757 @view_config(
758 758 route_name='repo_nodetree_full:default_path', request_method='GET',
759 759 renderer=None, xhr=True)
760 760 def repo_nodetree_full(self):
761 761 """
762 762 Returns rendered html of file tree that contains commit date,
763 763 author, commit_id for the specified combination of
764 764 repo, commit_id and file path
765 765 """
766 766 c = self.load_default_context()
767 767
768 768 commit_id, f_path = self._get_commit_and_path()
769 769 commit = self._get_commit_or_redirect(commit_id)
770 770 try:
771 771 dir_node = commit.get_node(f_path)
772 772 except RepositoryError as e:
773 773 return Response('error: {}'.format(h.escape(safe_str(e))))
774 774
775 775 if dir_node.is_file():
776 776 return Response('')
777 777
778 778 c.file = dir_node
779 779 c.commit = commit
780 780
781 781 html = self._get_tree_at_commit(
782 782 c, commit.raw_id, dir_node.path, full_load=True)
783 783
784 784 return Response(html)
785 785
786 786 def _get_attachement_headers(self, f_path):
787 787 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
788 788 safe_path = f_name.replace('"', '\\"')
789 789 encoded_path = urllib.quote(f_name)
790 790
791 791 return "attachment; " \
792 792 "filename=\"{}\"; " \
793 793 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
794 794
795 795 @LoginRequired()
796 796 @HasRepoPermissionAnyDecorator(
797 797 'repository.read', 'repository.write', 'repository.admin')
798 798 @view_config(
799 799 route_name='repo_file_raw', request_method='GET',
800 800 renderer=None)
801 801 def repo_file_raw(self):
802 802 """
803 803 Action for show as raw, some mimetypes are "rendered",
804 804 those include images, icons.
805 805 """
806 806 c = self.load_default_context()
807 807
808 808 commit_id, f_path = self._get_commit_and_path()
809 809 commit = self._get_commit_or_redirect(commit_id)
810 810 file_node = self._get_filenode_or_redirect(commit, f_path)
811 811
812 812 raw_mimetype_mapping = {
813 813 # map original mimetype to a mimetype used for "show as raw"
814 814 # you can also provide a content-disposition to override the
815 815 # default "attachment" disposition.
816 816 # orig_type: (new_type, new_dispo)
817 817
818 818 # show images inline:
819 819 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
820 820 # for example render an SVG with javascript inside or even render
821 821 # HTML.
822 822 'image/x-icon': ('image/x-icon', 'inline'),
823 823 'image/png': ('image/png', 'inline'),
824 824 'image/gif': ('image/gif', 'inline'),
825 825 'image/jpeg': ('image/jpeg', 'inline'),
826 826 'application/pdf': ('application/pdf', 'inline'),
827 827 }
828 828
829 829 mimetype = file_node.mimetype
830 830 try:
831 831 mimetype, disposition = raw_mimetype_mapping[mimetype]
832 832 except KeyError:
833 833 # we don't know anything special about this, handle it safely
834 834 if file_node.is_binary:
835 835 # do same as download raw for binary files
836 836 mimetype, disposition = 'application/octet-stream', 'attachment'
837 837 else:
838 838 # do not just use the original mimetype, but force text/plain,
839 839 # otherwise it would serve text/html and that might be unsafe.
840 840 # Note: underlying vcs library fakes text/plain mimetype if the
841 841 # mimetype can not be determined and it thinks it is not
842 842 # binary.This might lead to erroneous text display in some
843 843 # cases, but helps in other cases, like with text files
844 844 # without extension.
845 845 mimetype, disposition = 'text/plain', 'inline'
846 846
847 847 if disposition == 'attachment':
848 848 disposition = self._get_attachement_headers(f_path)
849 849
850 850 stream_content = file_node.stream_bytes()
851 851
852 852 response = Response(app_iter=stream_content)
853 853 response.content_disposition = disposition
854 854 response.content_type = mimetype
855 855
856 856 charset = self._get_default_encoding(c)
857 857 if charset:
858 858 response.charset = charset
859 859
860 860 return response
861 861
862 862 @LoginRequired()
863 863 @HasRepoPermissionAnyDecorator(
864 864 'repository.read', 'repository.write', 'repository.admin')
865 865 @view_config(
866 866 route_name='repo_file_download', request_method='GET',
867 867 renderer=None)
868 868 @view_config(
869 869 route_name='repo_file_download:legacy', request_method='GET',
870 870 renderer=None)
871 871 def repo_file_download(self):
872 872 c = self.load_default_context()
873 873
874 874 commit_id, f_path = self._get_commit_and_path()
875 875 commit = self._get_commit_or_redirect(commit_id)
876 876 file_node = self._get_filenode_or_redirect(commit, f_path)
877 877
878 878 if self.request.GET.get('lf'):
879 879 # only if lf get flag is passed, we download this file
880 880 # as LFS/Largefile
881 881 lf_node = file_node.get_largefile_node()
882 882 if lf_node:
883 883 # overwrite our pointer with the REAL large-file
884 884 file_node = lf_node
885 885
886 886 disposition = self._get_attachement_headers(f_path)
887 887
888 888 stream_content = file_node.stream_bytes()
889 889
890 890 response = Response(app_iter=stream_content)
891 891 response.content_disposition = disposition
892 892 response.content_type = file_node.mimetype
893 893
894 894 charset = self._get_default_encoding(c)
895 895 if charset:
896 896 response.charset = charset
897 897
898 898 return response
899 899
900 900 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
901 901
902 902 cache_seconds = safe_int(
903 903 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
904 904 cache_on = cache_seconds > 0
905 905 log.debug(
906 906 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
907 907 'with caching: %s[TTL: %ss]' % (
908 908 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
909 909
910 910 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
911 911 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
912 912
913 913 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
914 914 condition=cache_on)
915 915 def compute_file_search(repo_id, commit_id, f_path):
916 916 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
917 917 repo_id, commit_id, f_path)
918 918 try:
919 919 _d, _f = ScmModel().get_nodes(
920 920 repo_name, commit_id, f_path, flat=False)
921 921 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
922 922 log.exception(safe_str(e))
923 923 h.flash(safe_str(h.escape(e)), category='error')
924 924 raise HTTPFound(h.route_path(
925 925 'repo_files', repo_name=self.db_repo_name,
926 926 commit_id='tip', f_path='/'))
927 927
928 928 return _d + _f
929 929
930 930 result = compute_file_search(self.db_repo.repo_id, commit_id, f_path)
931 931 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
932 932
933 933 @LoginRequired()
934 934 @HasRepoPermissionAnyDecorator(
935 935 'repository.read', 'repository.write', 'repository.admin')
936 936 @view_config(
937 937 route_name='repo_files_nodelist', request_method='GET',
938 938 renderer='json_ext', xhr=True)
939 939 def repo_nodelist(self):
940 940 self.load_default_context()
941 941
942 942 commit_id, f_path = self._get_commit_and_path()
943 943 commit = self._get_commit_or_redirect(commit_id)
944 944
945 945 metadata = self._get_nodelist_at_commit(
946 946 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
947 947 return {'nodes': metadata}
948 948
949 949 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
950 950 items = []
951 951 for name, commit_id in branches_or_tags.items():
952 952 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
953 953 items.append((sym_ref, name, ref_type))
954 954 return items
955 955
956 956 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
957 957 return commit_id
958 958
959 959 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
960 960 new_f_path = vcspath.join(name, f_path)
961 961 return u'%s@%s' % (new_f_path, commit_id)
962 962
963 963 def _get_node_history(self, commit_obj, f_path, commits=None):
964 964 """
965 965 get commit history for given node
966 966
967 967 :param commit_obj: commit to calculate history
968 968 :param f_path: path for node to calculate history for
969 969 :param commits: if passed don't calculate history and take
970 970 commits defined in this list
971 971 """
972 972 _ = self.request.translate
973 973
974 974 # calculate history based on tip
975 975 tip = self.rhodecode_vcs_repo.get_commit()
976 976 if commits is None:
977 977 pre_load = ["author", "branch"]
978 978 try:
979 979 commits = tip.get_path_history(f_path, pre_load=pre_load)
980 980 except (NodeDoesNotExistError, CommitError):
981 981 # this node is not present at tip!
982 982 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
983 983
984 984 history = []
985 985 commits_group = ([], _("Changesets"))
986 986 for commit in commits:
987 987 branch = ' (%s)' % commit.branch if commit.branch else ''
988 988 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
989 989 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
990 990 history.append(commits_group)
991 991
992 992 symbolic_reference = self._symbolic_reference
993 993
994 994 if self.rhodecode_vcs_repo.alias == 'svn':
995 995 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
996 996 f_path, self.rhodecode_vcs_repo)
997 997 if adjusted_f_path != f_path:
998 998 log.debug(
999 999 'Recognized svn tag or branch in file "%s", using svn '
1000 1000 'specific symbolic references', f_path)
1001 1001 f_path = adjusted_f_path
1002 1002 symbolic_reference = self._symbolic_reference_svn
1003 1003
1004 1004 branches = self._create_references(
1005 1005 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1006 1006 branches_group = (branches, _("Branches"))
1007 1007
1008 1008 tags = self._create_references(
1009 1009 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1010 1010 tags_group = (tags, _("Tags"))
1011 1011
1012 1012 history.append(branches_group)
1013 1013 history.append(tags_group)
1014 1014
1015 1015 return history, commits
1016 1016
1017 1017 @LoginRequired()
1018 1018 @HasRepoPermissionAnyDecorator(
1019 1019 'repository.read', 'repository.write', 'repository.admin')
1020 1020 @view_config(
1021 1021 route_name='repo_file_history', request_method='GET',
1022 1022 renderer='json_ext')
1023 1023 def repo_file_history(self):
1024 1024 self.load_default_context()
1025 1025
1026 1026 commit_id, f_path = self._get_commit_and_path()
1027 1027 commit = self._get_commit_or_redirect(commit_id)
1028 1028 file_node = self._get_filenode_or_redirect(commit, f_path)
1029 1029
1030 1030 if file_node.is_file():
1031 1031 file_history, _hist = self._get_node_history(commit, f_path)
1032 1032
1033 1033 res = []
1034 1034 for obj in file_history:
1035 1035 res.append({
1036 1036 'text': obj[1],
1037 1037 'children': [{'id': o[0], 'text': o[1], 'type': o[2]} for o in obj[0]]
1038 1038 })
1039 1039
1040 1040 data = {
1041 1041 'more': False,
1042 1042 'results': res
1043 1043 }
1044 1044 return data
1045 1045
1046 1046 log.warning('Cannot fetch history for directory')
1047 1047 raise HTTPBadRequest()
1048 1048
1049 1049 @LoginRequired()
1050 1050 @HasRepoPermissionAnyDecorator(
1051 1051 'repository.read', 'repository.write', 'repository.admin')
1052 1052 @view_config(
1053 1053 route_name='repo_file_authors', request_method='GET',
1054 1054 renderer='rhodecode:templates/files/file_authors_box.mako')
1055 1055 def repo_file_authors(self):
1056 1056 c = self.load_default_context()
1057 1057
1058 1058 commit_id, f_path = self._get_commit_and_path()
1059 1059 commit = self._get_commit_or_redirect(commit_id)
1060 1060 file_node = self._get_filenode_or_redirect(commit, f_path)
1061 1061
1062 1062 if not file_node.is_file():
1063 1063 raise HTTPBadRequest()
1064 1064
1065 1065 c.file_last_commit = file_node.last_commit
1066 1066 if self.request.GET.get('annotate') == '1':
1067 1067 # use _hist from annotation if annotation mode is on
1068 1068 commit_ids = set(x[1] for x in file_node.annotate)
1069 1069 _hist = (
1070 1070 self.rhodecode_vcs_repo.get_commit(commit_id)
1071 1071 for commit_id in commit_ids)
1072 1072 else:
1073 1073 _f_history, _hist = self._get_node_history(commit, f_path)
1074 1074 c.file_author = False
1075 1075
1076 1076 unique = collections.OrderedDict()
1077 1077 for commit in _hist:
1078 1078 author = commit.author
1079 1079 if author not in unique:
1080 1080 unique[commit.author] = [
1081 1081 h.email(author),
1082 1082 h.person(author, 'username_or_name_or_email'),
1083 1083 1 # counter
1084 1084 ]
1085 1085
1086 1086 else:
1087 1087 # increase counter
1088 1088 unique[commit.author][2] += 1
1089 1089
1090 1090 c.authors = [val for val in unique.values()]
1091 1091
1092 1092 return self._get_template_context(c)
1093 1093
1094 1094 @LoginRequired()
1095 1095 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1096 1096 @view_config(
1097 1097 route_name='repo_files_remove_file', request_method='GET',
1098 1098 renderer='rhodecode:templates/files/files_delete.mako')
1099 1099 def repo_files_remove_file(self):
1100 1100 _ = self.request.translate
1101 1101 c = self.load_default_context()
1102 1102 commit_id, f_path = self._get_commit_and_path()
1103 1103
1104 1104 self._ensure_not_locked()
1105 1105 _branch_name, _sha_commit_id, is_head = \
1106 1106 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1107 1107
1108 1108 self.forbid_non_head(is_head, f_path)
1109 1109 self.check_branch_permission(_branch_name)
1110 1110
1111 1111 c.commit = self._get_commit_or_redirect(commit_id)
1112 1112 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1113 1113
1114 1114 c.default_message = _(
1115 1115 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1116 1116 c.f_path = f_path
1117 1117
1118 1118 return self._get_template_context(c)
1119 1119
1120 1120 @LoginRequired()
1121 1121 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1122 1122 @CSRFRequired()
1123 1123 @view_config(
1124 1124 route_name='repo_files_delete_file', request_method='POST',
1125 1125 renderer=None)
1126 1126 def repo_files_delete_file(self):
1127 1127 _ = self.request.translate
1128 1128
1129 1129 c = self.load_default_context()
1130 1130 commit_id, f_path = self._get_commit_and_path()
1131 1131
1132 1132 self._ensure_not_locked()
1133 1133 _branch_name, _sha_commit_id, is_head = \
1134 1134 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1135 1135
1136 1136 self.forbid_non_head(is_head, f_path)
1137 1137 self.check_branch_permission(_branch_name)
1138 1138
1139 1139 c.commit = self._get_commit_or_redirect(commit_id)
1140 1140 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1141 1141
1142 1142 c.default_message = _(
1143 1143 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1144 1144 c.f_path = f_path
1145 1145 node_path = f_path
1146 1146 author = self._rhodecode_db_user.full_contact
1147 1147 message = self.request.POST.get('message') or c.default_message
1148 1148 try:
1149 1149 nodes = {
1150 1150 node_path: {
1151 1151 'content': ''
1152 1152 }
1153 1153 }
1154 1154 ScmModel().delete_nodes(
1155 1155 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1156 1156 message=message,
1157 1157 nodes=nodes,
1158 1158 parent_commit=c.commit,
1159 1159 author=author,
1160 1160 )
1161 1161
1162 1162 h.flash(
1163 1163 _('Successfully deleted file `{}`').format(
1164 1164 h.escape(f_path)), category='success')
1165 1165 except Exception:
1166 1166 log.exception('Error during commit operation')
1167 1167 h.flash(_('Error occurred during commit'), category='error')
1168 1168 raise HTTPFound(
1169 1169 h.route_path('repo_commit', repo_name=self.db_repo_name,
1170 1170 commit_id='tip'))
1171 1171
1172 1172 @LoginRequired()
1173 1173 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1174 1174 @view_config(
1175 1175 route_name='repo_files_edit_file', request_method='GET',
1176 1176 renderer='rhodecode:templates/files/files_edit.mako')
1177 1177 def repo_files_edit_file(self):
1178 1178 _ = self.request.translate
1179 1179 c = self.load_default_context()
1180 1180 commit_id, f_path = self._get_commit_and_path()
1181 1181
1182 1182 self._ensure_not_locked()
1183 1183 _branch_name, _sha_commit_id, is_head = \
1184 1184 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1185 1185
1186 1186 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1187 1187 self.check_branch_permission(_branch_name, commit_id=commit_id)
1188 1188
1189 1189 c.commit = self._get_commit_or_redirect(commit_id)
1190 1190 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1191 1191
1192 1192 if c.file.is_binary:
1193 1193 files_url = h.route_path(
1194 1194 'repo_files',
1195 1195 repo_name=self.db_repo_name,
1196 1196 commit_id=c.commit.raw_id, f_path=f_path)
1197 1197 raise HTTPFound(files_url)
1198 1198
1199 1199 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1200 1200 c.f_path = f_path
1201 1201
1202 1202 return self._get_template_context(c)
1203 1203
1204 1204 @LoginRequired()
1205 1205 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1206 1206 @CSRFRequired()
1207 1207 @view_config(
1208 1208 route_name='repo_files_update_file', request_method='POST',
1209 1209 renderer=None)
1210 1210 def repo_files_update_file(self):
1211 1211 _ = self.request.translate
1212 1212 c = self.load_default_context()
1213 1213 commit_id, f_path = self._get_commit_and_path()
1214 1214
1215 1215 self._ensure_not_locked()
1216 1216
1217 1217 c.commit = self._get_commit_or_redirect(commit_id)
1218 1218 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1219 1219
1220 1220 if c.file.is_binary:
1221 1221 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1222 1222 commit_id=c.commit.raw_id, f_path=f_path))
1223 1223
1224 1224 _branch_name, _sha_commit_id, is_head = \
1225 1225 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1226 1226
1227 1227 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1228 1228 self.check_branch_permission(_branch_name, commit_id=commit_id)
1229 1229
1230 1230 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1231 1231 c.f_path = f_path
1232 1232
1233 1233 old_content = c.file.content
1234 1234 sl = old_content.splitlines(1)
1235 1235 first_line = sl[0] if sl else ''
1236 1236
1237 1237 r_post = self.request.POST
1238 1238 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1239 1239 line_ending_mode = detect_mode(first_line, 0)
1240 1240 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1241 1241
1242 1242 message = r_post.get('message') or c.default_message
1243 1243 org_node_path = c.file.unicode_path
1244 1244 filename = r_post['filename']
1245 1245
1246 1246 root_path = c.file.dir_path
1247 1247 pure_path = self.create_pure_path(root_path, filename)
1248 1248 node_path = safe_unicode(bytes(pure_path))
1249 1249
1250 1250 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1251 1251 commit_id=commit_id)
1252 1252 if content == old_content and node_path == org_node_path:
1253 h.flash(_('No changes detected on {}').format(org_node_path),
1253 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1254 1254 category='warning')
1255 1255 raise HTTPFound(default_redirect_url)
1256 1256
1257 1257 try:
1258 1258 mapping = {
1259 1259 org_node_path: {
1260 1260 'org_filename': org_node_path,
1261 1261 'filename': node_path,
1262 1262 'content': content,
1263 1263 'lexer': '',
1264 1264 'op': 'mod',
1265 1265 'mode': c.file.mode
1266 1266 }
1267 1267 }
1268 1268
1269 1269 commit = ScmModel().update_nodes(
1270 1270 user=self._rhodecode_db_user.user_id,
1271 1271 repo=self.db_repo,
1272 1272 message=message,
1273 1273 nodes=mapping,
1274 1274 parent_commit=c.commit,
1275 1275 )
1276 1276
1277 1277 h.flash(_('Successfully committed changes to file `{}`').format(
1278 1278 h.escape(f_path)), category='success')
1279 1279 default_redirect_url = h.route_path(
1280 1280 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1281 1281
1282 1282 except Exception:
1283 1283 log.exception('Error occurred during commit')
1284 1284 h.flash(_('Error occurred during commit'), category='error')
1285 1285
1286 1286 raise HTTPFound(default_redirect_url)
1287 1287
1288 1288 @LoginRequired()
1289 1289 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1290 1290 @view_config(
1291 1291 route_name='repo_files_add_file', request_method='GET',
1292 1292 renderer='rhodecode:templates/files/files_add.mako')
1293 1293 @view_config(
1294 1294 route_name='repo_files_upload_file', request_method='GET',
1295 1295 renderer='rhodecode:templates/files/files_upload.mako')
1296 1296 def repo_files_add_file(self):
1297 1297 _ = self.request.translate
1298 1298 c = self.load_default_context()
1299 1299 commit_id, f_path = self._get_commit_and_path()
1300 1300
1301 1301 self._ensure_not_locked()
1302 1302
1303 1303 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1304 1304 if c.commit is None:
1305 1305 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1306 1306
1307 1307 if self.rhodecode_vcs_repo.is_empty():
1308 1308 # for empty repository we cannot check for current branch, we rely on
1309 1309 # c.commit.branch instead
1310 1310 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1311 1311 else:
1312 1312 _branch_name, _sha_commit_id, is_head = \
1313 1313 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1314 1314
1315 1315 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1316 1316 self.check_branch_permission(_branch_name, commit_id=commit_id)
1317 1317
1318 1318 c.default_message = (_('Added file via RhodeCode Enterprise'))
1319 1319 c.f_path = f_path.lstrip('/') # ensure not relative path
1320 1320
1321 1321 return self._get_template_context(c)
1322 1322
1323 1323 @LoginRequired()
1324 1324 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1325 1325 @CSRFRequired()
1326 1326 @view_config(
1327 1327 route_name='repo_files_create_file', request_method='POST',
1328 1328 renderer=None)
1329 1329 def repo_files_create_file(self):
1330 1330 _ = self.request.translate
1331 1331 c = self.load_default_context()
1332 1332 commit_id, f_path = self._get_commit_and_path()
1333 1333
1334 1334 self._ensure_not_locked()
1335 1335
1336 1336 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1337 1337 if c.commit is None:
1338 1338 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1339 1339
1340 1340 # calculate redirect URL
1341 1341 if self.rhodecode_vcs_repo.is_empty():
1342 1342 default_redirect_url = h.route_path(
1343 1343 'repo_summary', repo_name=self.db_repo_name)
1344 1344 else:
1345 1345 default_redirect_url = h.route_path(
1346 1346 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1347 1347
1348 1348 if self.rhodecode_vcs_repo.is_empty():
1349 1349 # for empty repository we cannot check for current branch, we rely on
1350 1350 # c.commit.branch instead
1351 1351 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1352 1352 else:
1353 1353 _branch_name, _sha_commit_id, is_head = \
1354 1354 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1355 1355
1356 1356 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1357 1357 self.check_branch_permission(_branch_name, commit_id=commit_id)
1358 1358
1359 1359 c.default_message = (_('Added file via RhodeCode Enterprise'))
1360 1360 c.f_path = f_path
1361 1361
1362 1362 r_post = self.request.POST
1363 1363 message = r_post.get('message') or c.default_message
1364 1364 filename = r_post.get('filename')
1365 1365 unix_mode = 0
1366 1366 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1367 1367
1368 1368 if not filename:
1369 1369 # If there's no commit, redirect to repo summary
1370 1370 if type(c.commit) is EmptyCommit:
1371 1371 redirect_url = h.route_path(
1372 1372 'repo_summary', repo_name=self.db_repo_name)
1373 1373 else:
1374 1374 redirect_url = default_redirect_url
1375 1375 h.flash(_('No filename specified'), category='warning')
1376 1376 raise HTTPFound(redirect_url)
1377 1377
1378 1378 root_path = f_path
1379 1379 pure_path = self.create_pure_path(root_path, filename)
1380 1380 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1381 1381
1382 1382 author = self._rhodecode_db_user.full_contact
1383 1383 nodes = {
1384 1384 node_path: {
1385 1385 'content': content
1386 1386 }
1387 1387 }
1388 1388
1389 1389 try:
1390 1390
1391 1391 commit = ScmModel().create_nodes(
1392 1392 user=self._rhodecode_db_user.user_id,
1393 1393 repo=self.db_repo,
1394 1394 message=message,
1395 1395 nodes=nodes,
1396 1396 parent_commit=c.commit,
1397 1397 author=author,
1398 1398 )
1399 1399
1400 1400 h.flash(_('Successfully committed new file `{}`').format(
1401 1401 h.escape(node_path)), category='success')
1402 1402
1403 1403 default_redirect_url = h.route_path(
1404 1404 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1405 1405
1406 1406 except NonRelativePathError:
1407 1407 log.exception('Non Relative path found')
1408 1408 h.flash(_('The location specified must be a relative path and must not '
1409 1409 'contain .. in the path'), category='warning')
1410 1410 raise HTTPFound(default_redirect_url)
1411 1411 except (NodeError, NodeAlreadyExistsError) as e:
1412 1412 h.flash(_(h.escape(e)), category='error')
1413 1413 except Exception:
1414 1414 log.exception('Error occurred during commit')
1415 1415 h.flash(_('Error occurred during commit'), category='error')
1416 1416
1417 1417 raise HTTPFound(default_redirect_url)
1418 1418
1419 1419 @LoginRequired()
1420 1420 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1421 1421 @CSRFRequired()
1422 1422 @view_config(
1423 1423 route_name='repo_files_upload_file', request_method='POST',
1424 1424 renderer='json_ext')
1425 1425 def repo_files_upload_file(self):
1426 1426 _ = self.request.translate
1427 1427 c = self.load_default_context()
1428 1428 commit_id, f_path = self._get_commit_and_path()
1429 1429
1430 1430 self._ensure_not_locked()
1431 1431
1432 1432 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1433 1433 if c.commit is None:
1434 1434 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1435 1435
1436 1436 # calculate redirect URL
1437 1437 if self.rhodecode_vcs_repo.is_empty():
1438 1438 default_redirect_url = h.route_path(
1439 1439 'repo_summary', repo_name=self.db_repo_name)
1440 1440 else:
1441 1441 default_redirect_url = h.route_path(
1442 1442 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1443 1443
1444 1444 if self.rhodecode_vcs_repo.is_empty():
1445 1445 # for empty repository we cannot check for current branch, we rely on
1446 1446 # c.commit.branch instead
1447 1447 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1448 1448 else:
1449 1449 _branch_name, _sha_commit_id, is_head = \
1450 1450 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1451 1451
1452 1452 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1453 1453 if error:
1454 1454 return {
1455 1455 'error': error,
1456 1456 'redirect_url': default_redirect_url
1457 1457 }
1458 1458 error = self.check_branch_permission(_branch_name, json_mode=True)
1459 1459 if error:
1460 1460 return {
1461 1461 'error': error,
1462 1462 'redirect_url': default_redirect_url
1463 1463 }
1464 1464
1465 1465 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1466 1466 c.f_path = f_path
1467 1467
1468 1468 r_post = self.request.POST
1469 1469
1470 1470 message = c.default_message
1471 1471 user_message = r_post.getall('message')
1472 1472 if isinstance(user_message, list) and user_message:
1473 1473 # we take the first from duplicated results if it's not empty
1474 1474 message = user_message[0] if user_message[0] else message
1475 1475
1476 1476 nodes = {}
1477 1477
1478 1478 for file_obj in r_post.getall('files_upload') or []:
1479 1479 content = file_obj.file
1480 1480 filename = file_obj.filename
1481 1481
1482 1482 root_path = f_path
1483 1483 pure_path = self.create_pure_path(root_path, filename)
1484 1484 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1485 1485
1486 1486 nodes[node_path] = {
1487 1487 'content': content
1488 1488 }
1489 1489
1490 1490 if not nodes:
1491 1491 error = 'missing files'
1492 1492 return {
1493 1493 'error': error,
1494 1494 'redirect_url': default_redirect_url
1495 1495 }
1496 1496
1497 1497 author = self._rhodecode_db_user.full_contact
1498 1498
1499 1499 try:
1500 1500 commit = ScmModel().create_nodes(
1501 1501 user=self._rhodecode_db_user.user_id,
1502 1502 repo=self.db_repo,
1503 1503 message=message,
1504 1504 nodes=nodes,
1505 1505 parent_commit=c.commit,
1506 1506 author=author,
1507 1507 )
1508 1508 if len(nodes) == 1:
1509 1509 flash_message = _('Successfully committed {} new files').format(len(nodes))
1510 1510 else:
1511 1511 flash_message = _('Successfully committed 1 new file')
1512 1512
1513 1513 h.flash(flash_message, category='success')
1514 1514
1515 1515 default_redirect_url = h.route_path(
1516 1516 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1517 1517
1518 1518 except NonRelativePathError:
1519 1519 log.exception('Non Relative path found')
1520 1520 error = _('The location specified must be a relative path and must not '
1521 1521 'contain .. in the path')
1522 1522 h.flash(error, category='warning')
1523 1523
1524 1524 return {
1525 1525 'error': error,
1526 1526 'redirect_url': default_redirect_url
1527 1527 }
1528 1528 except (NodeError, NodeAlreadyExistsError) as e:
1529 1529 error = h.escape(e)
1530 1530 h.flash(error, category='error')
1531 1531
1532 1532 return {
1533 1533 'error': error,
1534 1534 'redirect_url': default_redirect_url
1535 1535 }
1536 1536 except Exception:
1537 1537 log.exception('Error occurred during commit')
1538 1538 error = _('Error occurred during commit')
1539 1539 h.flash(error, category='error')
1540 1540 return {
1541 1541 'error': error,
1542 1542 'redirect_url': default_redirect_url
1543 1543 }
1544 1544
1545 1545 return {
1546 1546 'error': None,
1547 1547 'redirect_url': default_redirect_url
1548 1548 }
General Comments 0
You need to be logged in to leave comments. Login now