##// END OF EJS Templates
release: merge back stable branch into default
marcink -
r3352:ac6efde5 merge default
parent child Browse files
Show More
@@ -0,0 +1,50 b''
1 |RCE| 4.15.1 |RNS|
2 ------------------
3
4 Release Date
5 ^^^^^^^^^^^^
6
7 - 2019-01-01
8
9
10 New Features
11 ^^^^^^^^^^^^
12
13
14
15 General
16 ^^^^^^^
17
18 - Downloads: properly encode " in the filenames, and add RFC 5987 header for non-ascii files.
19 - Documentation: updated configuration for Nginx and reverse proxy.
20 - VCS: streaming will use now 100kb chunks for faster network throughput.
21
22
23 Security
24 ^^^^^^^^
25
26 - Diffs: fixed xss in context diff menu.
27 - Downloads: properly encode " in the filenames, prevents from hiding executable
28 files disguised in another type of file using crafted file names.
29
30 Performance
31 ^^^^^^^^^^^
32
33
34
35 Fixes
36 ^^^^^
37
38 - VCS: handle excessive slashes in from of the repo name path, fixes #5522.
39 This prevents 500 errors when excessive slashes are used
40 - SVN: support proxy-prefix properly, fixes #5521.
41 - Pull requests: validate ref types on API calls for pull request so users cannot
42 provide wrongs ones.
43 - Scheduler: fix url generation with proxy prefix.
44 - Celery: add DB connection ping to validate DB connection is working at worker startup.
45
46
47 Upgrade notes
48 ^^^^^^^^^^^^^
49
50 - Scheduled release addressing reported problems in 4.15.X releases.
@@ -47,3 +47,4 b' 7dc62c090881fb5d03268141e71e0940d7c3295d'
47 47 9151328c1c46b72ba6f00d7640d9141e75aa1ca2 v4.14.0
48 48 a47eeac5dfa41fa6779d90452affba4091c3ade8 v4.14.1
49 49 4b34ce0d2c3c10510626b3b65044939bb7a2cddf v4.15.0
50 14502561d22e6b70613674cd675ae9a604b7989f v4.15.1
@@ -9,6 +9,7 b' Release Notes'
9 9 .. toctree::
10 10 :maxdepth: 1
11 11
12 release-notes-4.15.1.rst
12 13 release-notes-4.15.0.rst
13 14 release-notes-4.14.1.rst
14 15 release-notes-4.14.0.rst
@@ -56,6 +56,25 b' class TestCreatePullRequestApi(object):'
56 56 assert_error(id_, expected, given=response.body)
57 57
58 58 @pytest.mark.backends("git", "hg")
59 @pytest.mark.parametrize('source_ref', [
60 'bookmarg:default:initial'
61 ])
62 def test_create_with_wrong_refs_data(self, backend, source_ref):
63
64 data = self._prepare_data(backend)
65 data['source_ref'] = source_ref
66
67 id_, params = build_data(
68 self.apikey_regular, 'create_pull_request', **data)
69
70 response = api_call(self.app, params)
71
72 expected = "Ref `{}` type is not allowed. " \
73 "Only:['bookmark', 'book', 'tag', 'branch'] " \
74 "are possible.".format(source_ref)
75 assert_error(id_, expected, given=response.body)
76
77 @pytest.mark.backends("git", "hg")
59 78 def test_create_with_correct_data(self, backend):
60 79 data = self._prepare_data(backend)
61 80 RepoModel().revoke_user_permission(
@@ -84,11 +84,11 b' class TestResolveRefOrError(object):'
84 84
85 85 def test_non_supported_refs(self):
86 86 repo = Mock()
87 ref = 'ancestor:ref'
87 ref = 'bookmark:ref'
88 88 with pytest.raises(JSONRPCError) as excinfo:
89 89 utils.resolve_ref_or_error(ref, repo)
90 90 expected_message = (
91 'The specified value:ancestor:`ref` does not exist, or is not allowed.')
91 'The specified value:bookmark:`ref` does not exist, or is not allowed.')
92 92 assert excinfo.value.message == expected_message
93 93
94 94 def test_branch_is_not_found(self):
@@ -388,7 +388,19 b' def get_commit_or_error(ref, repo):'
388 388 raise JSONRPCError('Ref `{ref}` does not exist'.format(ref=ref))
389 389
390 390
391 def resolve_ref_or_error(ref, repo):
391 def _get_ref_hash(repo, type_, name):
392 vcs_repo = repo.scm_instance()
393 if type_ in ['branch'] and vcs_repo.alias in ('hg', 'git'):
394 return vcs_repo.branches[name]
395 elif type_ in ['bookmark', 'book'] and vcs_repo.alias == 'hg':
396 return vcs_repo.bookmarks[name]
397 else:
398 raise ValueError()
399
400
401 def resolve_ref_or_error(ref, repo, allowed_ref_types=None):
402 allowed_ref_types = allowed_ref_types or ['bookmark', 'book', 'tag', 'branch']
403
392 404 def _parse_ref(type_, name, hash_=None):
393 405 return type_, name, hash_
394 406
@@ -399,6 +411,12 b' def resolve_ref_or_error(ref, repo):'
399 411 'Ref `{ref}` given in a wrong format. Please check the API'
400 412 ' documentation for more details'.format(ref=ref))
401 413
414 if ref_type not in allowed_ref_types:
415 raise JSONRPCError(
416 'Ref `{ref}` type is not allowed. '
417 'Only:{allowed_refs} are possible.'.format(
418 ref=ref, allowed_refs=allowed_ref_types))
419
402 420 try:
403 421 ref_hash = ref_hash or _get_ref_hash(repo, ref_type, ref_name)
404 422 except (KeyError, ValueError):
@@ -429,13 +447,3 b' def _get_commit_dict('
429 447 "raw_diff": raw_diff,
430 448 "stats": stats
431 449 }
432
433
434 def _get_ref_hash(repo, type_, name):
435 vcs_repo = repo.scm_instance()
436 if type_ == 'branch' and vcs_repo.alias in ('hg', 'git'):
437 return vcs_repo.branches[name]
438 elif type_ == 'bookmark' and vcs_repo.alias == 'hg':
439 return vcs_repo.bookmarks[name]
440 else:
441 raise ValueError()
@@ -428,7 +428,7 b' class TestRawFileHandling(object):'
428 428 repo_name=backend.repo_name,
429 429 commit_id=commit.raw_id, f_path='vcs/nodes.py'),)
430 430
431 assert response.content_disposition == "attachment; filename=nodes.py"
431 assert response.content_disposition == 'attachment; filename="nodes.py"; filename*=UTF-8\'\'nodes.py'
432 432 assert response.content_type == "text/x-python"
433 433
434 434 def test_download_file_wrong_cs(self, backend):
@@ -24,6 +24,7 b' import os'
24 24 import shutil
25 25 import tempfile
26 26 import collections
27 import urllib
27 28
28 29 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
29 30 from pyramid.view import view_config
@@ -709,9 +710,14 b' class RepoFilesView(RepoAppView):'
709 710
710 711 return Response(html)
711 712
712 def _get_attachement_disposition(self, f_path):
713 return 'attachment; filename=%s' % \
714 safe_str(f_path.split(Repository.NAME_SEP)[-1])
713 def _get_attachement_headers(self, f_path):
714 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
715 safe_path = f_name.replace('"', '\\"')
716 encoded_path = urllib.quote(f_name)
717
718 return "attachment; " \
719 "filename=\"{}\"; " \
720 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
715 721
716 722 @LoginRequired()
717 723 @HasRepoPermissionAnyDecorator(
@@ -766,7 +772,7 b' class RepoFilesView(RepoAppView):'
766 772 mimetype, disposition = 'text/plain', 'inline'
767 773
768 774 if disposition == 'attachment':
769 disposition = self._get_attachement_disposition(f_path)
775 disposition = self._get_attachement_headers(f_path)
770 776
771 777 def stream_node():
772 778 yield file_node.raw_bytes
@@ -805,7 +811,7 b' class RepoFilesView(RepoAppView):'
805 811 # overwrite our pointer with the REAL large-file
806 812 file_node = lf_node
807 813
808 disposition = self._get_attachement_disposition(f_path)
814 disposition = self._get_attachement_headers(f_path)
809 815
810 816 def stream_node():
811 817 yield file_node.raw_bytes
@@ -51,7 +51,8 b' class SimpleSvnApp(object):'
51 51 data = environ['wsgi.input']
52 52 req_method = environ['REQUEST_METHOD']
53 53 has_content_length = 'CONTENT_LENGTH' in environ
54 path_info = self._get_url(environ['PATH_INFO'])
54 path_info = self._get_url(
55 self.config.get('subversion_http_server_url', ''), environ['PATH_INFO'])
55 56 transfer_encoding = environ.get('HTTP_TRANSFER_ENCODING', '')
56 57 log.debug('Handling: %s method via `%s`', req_method, path_info)
57 58
@@ -117,9 +118,9 b' class SimpleSvnApp(object):'
117 118 response_headers)
118 119 return response.iter_content(chunk_size=1024)
119 120
120 def _get_url(self, path):
121 url_path = urlparse.urljoin(
122 self.config.get('subversion_http_server_url', ''), path)
121 def _get_url(self, svn_http_server, path):
122 svn_http_server_url = (svn_http_server or '').rstrip('/')
123 url_path = urlparse.urljoin(svn_http_server_url + '/', (path or '').lstrip('/'))
123 124 url_path = urllib.quote(url_path, safe="/:=~+!$,;'")
124 125 return url_path
125 126
@@ -43,6 +43,7 b' import sqlalchemy.exc'
43 43 import sqlalchemy.sql
44 44 import webob
45 45 import pyramid.threadlocal
46 from pyramid.settings import asbool
46 47
47 48 import rhodecode
48 49 from rhodecode.translation import _, _pluralize
@@ -361,7 +362,8 b' def ping_connection(connection, branch):'
361 362 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
362 363 """Custom engine_from_config functions."""
363 364 log = logging.getLogger('sqlalchemy.engine')
364 _ping_connection = configuration.pop('sqlalchemy.db1.ping_connection', None)
365 use_ping_connection = asbool(configuration.pop('sqlalchemy.db1.ping_connection', None))
366 debug = asbool(configuration.get('debug'))
365 367
366 368 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
367 369
@@ -370,12 +372,12 b' def engine_from_config(configuration, pr'
370 372 normal = '\x1b[0m'
371 373 return ''.join([color_seq, sql, normal])
372 374
373 if configuration['debug'] or _ping_connection:
375 if use_ping_connection:
376 log.debug('Adding ping_connection on the engine config.')
374 377 sqlalchemy.event.listen(engine, "engine_connect", ping_connection)
375 378
376 if configuration['debug']:
379 if debug:
377 380 # attach events only for debug configuration
378
379 381 def before_cursor_execute(conn, cursor, statement,
380 382 parameters, context, executemany):
381 383 setattr(conn, 'query_start_time', time.time())
@@ -394,10 +396,8 b' def engine_from_config(configuration, pr'
394 396 parameters, context, executemany):
395 397 delattr(conn, 'query_start_time')
396 398
397 sqlalchemy.event.listen(engine, "before_cursor_execute",
398 before_cursor_execute)
399 sqlalchemy.event.listen(engine, "after_cursor_execute",
400 after_cursor_execute)
399 sqlalchemy.event.listen(engine, "before_cursor_execute", before_cursor_execute)
400 sqlalchemy.event.listen(engine, "after_cursor_execute", after_cursor_execute)
401 401
402 402 return engine
403 403
@@ -92,6 +92,8 b' class PullRequestModel(BaseModel):'
92 92 'This pull request cannot be updated because the source '
93 93 'reference is missing.'),
94 94 }
95 REF_TYPES = ['bookmark', 'book', 'tag', 'branch']
96 UPDATABLE_REF_TYPES = ['bookmark', 'book', 'branch']
95 97
96 98 def __get_pull_request(self, pull_request):
97 99 return self._get_instance((
@@ -633,7 +635,7 b' class PullRequestModel(BaseModel):'
633 635
634 636 def has_valid_update_type(self, pull_request):
635 637 source_ref_type = pull_request.source_ref_parts.type
636 return source_ref_type in ['book', 'branch', 'tag']
638 return source_ref_type in self.REF_TYPES
637 639
638 640 def update_commits(self, pull_request):
639 641 """
@@ -713,7 +715,7 b' class PullRequestModel(BaseModel):'
713 715 pull_request_version = pull_request
714 716
715 717 try:
716 if target_ref_type in ('tag', 'branch', 'book'):
718 if target_ref_type in self.REF_TYPES:
717 719 target_commit = target_repo.get_commit(target_ref_name)
718 720 else:
719 721 target_commit = target_repo.get_commit(target_ref_id)
@@ -1289,7 +1291,7 b' class PullRequestModel(BaseModel):'
1289 1291 return merge_state
1290 1292
1291 1293 def _refresh_reference(self, reference, vcs_repository):
1292 if reference.type in ('branch', 'book'):
1294 if reference.type in self.UPDATABLE_REF_TYPES:
1293 1295 name_or_id = reference.name
1294 1296 else:
1295 1297 name_or_id = reference.commit_id
@@ -909,6 +909,8 b' def get_comments_for(diff_type, comments'
909 909 };
910 910
911 911 var animateText = $.debounce(100, function(fPath, anchorId) {
912 fPath = Select2.util.escapeMarkup(fPath);
913
912 914 // animate setting the text
913 915 var callback = function () {
914 916 $('.fpath-placeholder-text').animate({'opacity': 1.00}, 200)
@@ -161,9 +161,17 b' class TestSimpleSvnApp(object):'
161 161 response_headers = self.app._get_response_headers(headers)
162 162 assert sorted(response_headers) == sorted(expected_headers)
163 163
164 def test_get_url(self):
165 url = self.app._get_url(self.path)
166 expected_url = '{}{}'.format(self.host.strip('/'), self.path)
164 @pytest.mark.parametrize('svn_http_url, path_info, expected_url', [
165 ('http://localhost:8200', '/repo_name', 'http://localhost:8200/repo_name'),
166 ('http://localhost:8200///', '/repo_name', 'http://localhost:8200/repo_name'),
167 ('http://localhost:8200', '/group/repo_name', 'http://localhost:8200/group/repo_name'),
168 ('http://localhost:8200/', '/group/repo_name', 'http://localhost:8200/group/repo_name'),
169 ('http://localhost:8200/prefix', '/repo_name', 'http://localhost:8200/prefix/repo_name'),
170 ('http://localhost:8200/prefix', 'repo_name', 'http://localhost:8200/prefix/repo_name'),
171 ('http://localhost:8200/prefix', '/group/repo_name', 'http://localhost:8200/prefix/group/repo_name')
172 ])
173 def test_get_url(self, svn_http_url, path_info, expected_url):
174 url = self.app._get_url(svn_http_url, path_info)
167 175 assert url == expected_url
168 176
169 177 def test_call(self):
General Comments 0
You need to be logged in to leave comments. Login now