##// END OF EJS Templates
chore(regex): properly escape \d used in regexes
super-admin -
r5187:51a848c7 default
parent child Browse files
Show More
@@ -1,240 +1,240 b''
1
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import copy
20 import copy
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.lib import helpers
24 from rhodecode.lib import helpers
25 from rhodecode.lib.utils2 import AttributeDict
25 from rhodecode.lib.utils2 import AttributeDict
26 from rhodecode.model.settings import IssueTrackerSettingsModel
26 from rhodecode.model.settings import IssueTrackerSettingsModel
27 from rhodecode.tests import no_newline_id_generator
27 from rhodecode.tests import no_newline_id_generator
28
28
29
29
30 @pytest.mark.parametrize('url, expected_url', [
30 @pytest.mark.parametrize('url, expected_url', [
31 (r'https://rc.com', '<a href="https://rc.com">https://rc.com</a>'),
31 (r'https://rc.com', '<a href="https://rc.com">https://rc.com</a>'),
32 (r'https://rc.com/test', '<a href="https://rc.com/test">https://rc.com/test</a>'),
32 (r'https://rc.com/test', '<a href="https://rc.com/test">https://rc.com/test</a>'),
33 (r'https://rc.com/!foo', '<a href="https://rc.com/!foo">https://rc.com/!foo</a>'),
33 (r'https://rc.com/!foo', '<a href="https://rc.com/!foo">https://rc.com/!foo</a>'),
34 (r'https://rc.com/&foo', '<a href="https://rc.com/&amp;foo">https://rc.com/&amp;foo</a>'),
34 (r'https://rc.com/&foo', '<a href="https://rc.com/&amp;foo">https://rc.com/&amp;foo</a>'),
35 (r'https://rc.com/?foo-1&bar=1', '<a href="https://rc.com/?foo-1&amp;bar=1">https://rc.com/?foo-1&amp;bar=1</a>'),
35 (r'https://rc.com/?foo-1&bar=1', '<a href="https://rc.com/?foo-1&amp;bar=1">https://rc.com/?foo-1&amp;bar=1</a>'),
36 (r'https://rc.com?foo-1&bar=1', '<a href="https://rc.com?foo-1&amp;bar=1">https://rc.com?foo-1&amp;bar=1</a>'),
36 (r'https://rc.com?foo-1&bar=1', '<a href="https://rc.com?foo-1&amp;bar=1">https://rc.com?foo-1&amp;bar=1</a>'),
37 (r'https://rc.com/#foo', '<a href="https://rc.com/#foo">https://rc.com/#foo</a>'),
37 (r'https://rc.com/#foo', '<a href="https://rc.com/#foo">https://rc.com/#foo</a>'),
38 (r'https://rc.com/@foo', '<a href="https://rc.com/@foo">https://rc.com/@foo</a>'),
38 (r'https://rc.com/@foo', '<a href="https://rc.com/@foo">https://rc.com/@foo</a>'),
39 ])
39 ])
40 def test_urlify_text(url, expected_url):
40 def test_urlify_text(url, expected_url):
41 assert helpers.urlify_text(url) == expected_url
41 assert helpers.urlify_text(url) == expected_url
42
42
43
43
44 @pytest.mark.parametrize('repo_name, commit_id, path, expected_result', [
44 @pytest.mark.parametrize('repo_name, commit_id, path, expected_result', [
45 # Simple case 1
45 # Simple case 1
46 ('repo', 'commit', 'a/b',
46 ('repo', 'commit', 'a/b',
47 '<a href="/repo/files/commit/"><i class="icon-home"></i></a>'
47 '<a href="/repo/files/commit/"><i class="icon-home"></i></a>'
48 ' / '
48 ' / '
49 '<a href="/repo/files/commit/a">a</a>'
49 '<a href="/repo/files/commit/a">a</a>'
50 ' / '
50 ' / '
51 'b'),
51 'b'),
52
52
53 # Simple case
53 # Simple case
54 ('rX<X', 'cX<X', 'pX<X/aX<X/bX<X',
54 ('rX<X', 'cX<X', 'pX<X/aX<X/bX<X',
55 '<a href="/rX%3CX/files/cX%3CX/"><i class="icon-home"></i></a>'
55 '<a href="/rX%3CX/files/cX%3CX/"><i class="icon-home"></i></a>'
56 ' / '
56 ' / '
57 '<a href="/rX%3CX/files/cX%3CX/pX%3CX">pX&lt;X</a>'
57 '<a href="/rX%3CX/files/cX%3CX/pX%3CX">pX&lt;X</a>'
58 ' / '
58 ' / '
59 '<a href="/rX%3CX/files/cX%3CX/pX%3CX/aX%3CX">aX&lt;X</a>'
59 '<a href="/rX%3CX/files/cX%3CX/pX%3CX/aX%3CX">aX&lt;X</a>'
60 ' / '
60 ' / '
61 'bX&lt;X'),
61 'bX&lt;X'),
62
62
63 # Path with only one segment
63 # Path with only one segment
64 ('rX<X', 'cX<X', 'pX<X',
64 ('rX<X', 'cX<X', 'pX<X',
65 '<a href="/rX%3CX/files/cX%3CX/"><i class="icon-home"></i></a>'
65 '<a href="/rX%3CX/files/cX%3CX/"><i class="icon-home"></i></a>'
66 ' / '
66 ' / '
67 'pX&lt;X'),
67 'pX&lt;X'),
68
68
69 # Empty path
69 # Empty path
70 ('rX<X', 'cX<X', '',
70 ('rX<X', 'cX<X', '',
71 '<i class="icon-home"></i>'),
71 '<i class="icon-home"></i>'),
72
72
73 # simple quote
73 # simple quote
74 ('rX"X', 'cX"X', 'pX"X/aX"X/bX"X',
74 ('rX"X', 'cX"X', 'pX"X/aX"X/bX"X',
75 '<a href="/rX%22X/files/cX%22X/"><i class="icon-home"></i></a>'
75 '<a href="/rX%22X/files/cX%22X/"><i class="icon-home"></i></a>'
76 ' / '
76 ' / '
77 '<a href="/rX%22X/files/cX%22X/pX%22X">pX&#34;X</a>'
77 '<a href="/rX%22X/files/cX%22X/pX%22X">pX&#34;X</a>'
78 ' / '
78 ' / '
79 '<a href="/rX%22X/files/cX%22X/pX%22X/aX%22X">aX&#34;X</a>'
79 '<a href="/rX%22X/files/cX%22X/pX%22X/aX%22X">aX&#34;X</a>'
80 ' / '
80 ' / '
81 'bX&#34;X'),
81 'bX&#34;X'),
82
82
83 ], ids=['simple1', 'simple2', 'one_segment', 'empty_path', 'simple_quote'])
83 ], ids=['simple1', 'simple2', 'one_segment', 'empty_path', 'simple_quote'])
84 def test_files_breadcrumbs_xss(repo_name, commit_id, path, app, expected_result):
84 def test_files_breadcrumbs_xss(repo_name, commit_id, path, app, expected_result):
85 result = helpers.files_breadcrumbs(repo_name, 'hg', commit_id, path)
85 result = helpers.files_breadcrumbs(repo_name, 'hg', commit_id, path)
86 # Expect it to encode all path fragments properly. This is important
86 # Expect it to encode all path fragments properly. This is important
87 # because it returns an instance of `literal`.
87 # because it returns an instance of `literal`.
88 if path != '':
88 if path != '':
89 expected_result = expected_result + helpers.files_icon.format(helpers.escape(path))
89 expected_result = expected_result + helpers.files_icon.format(helpers.escape(path))
90 assert result == expected_result
90 assert result == expected_result
91
91
92
92
93 def test_format_binary():
93 def test_format_binary():
94 assert helpers.format_byte_size_binary(298489462784) == '278.0 GiB'
94 assert helpers.format_byte_size_binary(298489462784) == '278.0 GiB'
95
95
96
96
97 @pytest.mark.parametrize('text_string, pattern, expected', [
97 @pytest.mark.parametrize('text_string, pattern, expected', [
98 ('No issue here', r'(?:#)(?P<issue_id>\d+)', []),
98 ('No issue here', r'(?:#)(?P<issue_id>\d+)', []),
99 ('Fix #42', '(?:#)(?P<issue_id>\d+)',
99 ('Fix #42', r'(?:#)(?P<issue_id>\d+)',
100 [{'url': 'https://r.io/{repo}/i/42', 'id': '42'}]),
100 [{'url': 'https://r.io/{repo}/i/42', 'id': '42'}]),
101 ('Fix #42, #53', '(?:#)(?P<issue_id>\d+)', [
101 ('Fix #42, #53', r'(?:#)(?P<issue_id>\d+)', [
102 {'url': 'https://r.io/{repo}/i/42', 'id': '42'},
102 {'url': 'https://r.io/{repo}/i/42', 'id': '42'},
103 {'url': 'https://r.io/{repo}/i/53', 'id': '53'}]),
103 {'url': 'https://r.io/{repo}/i/53', 'id': '53'}]),
104 ('Fix #42', '(?:#)?<issue_id>\d+)', []), # Broken regex
104 ('Fix #42', r'(?:#)?<issue_id>\d+)', []), # Broken regex
105 ])
105 ])
106 def test_extract_issues(backend, text_string, pattern, expected):
106 def test_extract_issues(backend, text_string, pattern, expected):
107 repo = backend.create_repo()
107 repo = backend.create_repo()
108 config = {
108 config = {
109 '123': {
109 '123': {
110 'uid': '123',
110 'uid': '123',
111 'pat': pattern,
111 'pat': pattern,
112 'url': r'https://r.io/${repo}/i/${issue_id}',
112 'url': r'https://r.io/${repo}/i/${issue_id}',
113 'pref': '#',
113 'pref': '#',
114 'desc': 'Test Pattern'
114 'desc': 'Test Pattern'
115 }
115 }
116 }
116 }
117
117
118 def get_settings_mock(self, cache=True):
118 def get_settings_mock(self, cache=True):
119 return config
119 return config
120
120
121 with mock.patch.object(IssueTrackerSettingsModel,
121 with mock.patch.object(IssueTrackerSettingsModel,
122 'get_settings', get_settings_mock):
122 'get_settings', get_settings_mock):
123 text, issues, errors = helpers.process_patterns(text_string, repo.repo_name)
123 text, issues, errors = helpers.process_patterns(text_string, repo.repo_name)
124
124
125 expected = copy.deepcopy(expected)
125 expected = copy.deepcopy(expected)
126 for item in expected:
126 for item in expected:
127 item['url'] = item['url'].format(repo=repo.repo_name)
127 item['url'] = item['url'].format(repo=repo.repo_name)
128
128
129 assert issues == expected
129 assert issues == expected
130
130
131
131
132 @pytest.mark.parametrize('text_string, pattern, link_format, expected_text', [
132 @pytest.mark.parametrize('text_string, pattern, link_format, expected_text', [
133 ('Fix #42', '(?:#)(?P<issue_id>\d+)', 'html',
133 ('Fix #42', r'(?:#)(?P<issue_id>\d+)', 'html',
134 'Fix <a class="tooltip issue-tracker-link" href="https://r.io/{repo}/i/42" title="Test Pattern">#42</a>'),
134 'Fix <a class="tooltip issue-tracker-link" href="https://r.io/{repo}/i/42" title="Test Pattern">#42</a>'),
135
135
136 ('Fix #42', '(?:#)(?P<issue_id>\d+)', 'markdown',
136 ('Fix #42', r'(?:#)(?P<issue_id>\d+)', 'markdown',
137 'Fix [#42](https://r.io/{repo}/i/42)'),
137 'Fix [#42](https://r.io/{repo}/i/42)'),
138
138
139 ('Fix #42', '(?:#)(?P<issue_id>\d+)', 'rst',
139 ('Fix #42', r'(?:#)(?P<issue_id>\d+)', 'rst',
140 'Fix `#42 <https://r.io/{repo}/i/42>`_'),
140 'Fix `#42 <https://r.io/{repo}/i/42>`_'),
141
141
142 ('Fix #42', '(?:#)?<issue_id>\d+)', 'html',
142 ('Fix #42', r'(?:#)?<issue_id>\d+)', 'html',
143 'Fix #42'), # Broken regex
143 'Fix #42'), # Broken regex
144 ])
144 ])
145 def test_process_patterns_repo(backend, text_string, pattern, expected_text, link_format):
145 def test_process_patterns_repo(backend, text_string, pattern, expected_text, link_format):
146 repo = backend.create_repo()
146 repo = backend.create_repo()
147
147
148 def get_settings_mock(self, cache=True):
148 def get_settings_mock(self, cache=True):
149 return {
149 return {
150 '123': {
150 '123': {
151 'uid': '123',
151 'uid': '123',
152 'pat': pattern,
152 'pat': pattern,
153 'url': 'https://r.io/${repo}/i/${issue_id}',
153 'url': 'https://r.io/${repo}/i/${issue_id}',
154 'pref': '#',
154 'pref': '#',
155 'desc': 'Test Pattern'
155 'desc': 'Test Pattern'
156 }
156 }
157 }
157 }
158
158
159 with mock.patch.object(IssueTrackerSettingsModel,
159 with mock.patch.object(IssueTrackerSettingsModel,
160 'get_settings', get_settings_mock):
160 'get_settings', get_settings_mock):
161 processed_text, issues, error = helpers.process_patterns(
161 processed_text, issues, error = helpers.process_patterns(
162 text_string, repo.repo_name, link_format)
162 text_string, repo.repo_name, link_format)
163
163
164 assert processed_text == expected_text.format(repo=repo.repo_name)
164 assert processed_text == expected_text.format(repo=repo.repo_name)
165
165
166
166
167 @pytest.mark.parametrize('text_string, pattern, expected_text', [
167 @pytest.mark.parametrize('text_string, pattern, expected_text', [
168 ('Fix #42', '(?:#)(?P<issue_id>\d+)',
168 ('Fix #42', r'(?:#)(?P<issue_id>\d+)',
169 'Fix <a class="tooltip issue-tracker-link" href="https://r.io/i/42" title="Test Pattern">#42</a>'),
169 'Fix <a class="tooltip issue-tracker-link" href="https://r.io/i/42" title="Test Pattern">#42</a>'),
170 ('Fix #42', '(?:#)?<issue_id>\d+)',
170 ('Fix #42', r'(?:#)?<issue_id>\d+)',
171 'Fix #42'), # Broken regex
171 'Fix #42'), # Broken regex
172 ])
172 ])
173 def test_process_patterns_no_repo(text_string, pattern, expected_text):
173 def test_process_patterns_no_repo(text_string, pattern, expected_text):
174
174
175 def get_settings_mock(self, cache=True):
175 def get_settings_mock(self, cache=True):
176 return {
176 return {
177 '123': {
177 '123': {
178 'uid': '123',
178 'uid': '123',
179 'pat': pattern,
179 'pat': pattern,
180 'url': 'https://r.io/i/${issue_id}',
180 'url': 'https://r.io/i/${issue_id}',
181 'pref': '#',
181 'pref': '#',
182 'desc': 'Test Pattern'
182 'desc': 'Test Pattern'
183 }
183 }
184 }
184 }
185
185
186 with mock.patch.object(IssueTrackerSettingsModel,
186 with mock.patch.object(IssueTrackerSettingsModel,
187 'get_global_settings', get_settings_mock):
187 'get_global_settings', get_settings_mock):
188 processed_text, issues, errors = helpers.process_patterns(
188 processed_text, issues, errors = helpers.process_patterns(
189 text_string, '')
189 text_string, '')
190
190
191 assert processed_text == expected_text
191 assert processed_text == expected_text
192
192
193
193
194 def test_process_patterns_non_existent_repo_name(backend):
194 def test_process_patterns_non_existent_repo_name(backend):
195 text_string = 'Fix #42'
195 text_string = 'Fix #42'
196 pattern = r'(?:#)(?P<issue_id>\d+)'
196 pattern = r'(?:#)(?P<issue_id>\d+)'
197 expected_text = ('Fix <a class="tooltip issue-tracker-link" '
197 expected_text = ('Fix <a class="tooltip issue-tracker-link" '
198 'href="https://r.io/do-not-exist/i/42" title="Test Pattern">#42</a>')
198 'href="https://r.io/do-not-exist/i/42" title="Test Pattern">#42</a>')
199
199
200 def get_settings_mock(self, cache=True):
200 def get_settings_mock(self, cache=True):
201 return {
201 return {
202 '123': {
202 '123': {
203 'uid': '123',
203 'uid': '123',
204 'pat': pattern,
204 'pat': pattern,
205 'url': 'https://r.io/${repo}/i/${issue_id}',
205 'url': 'https://r.io/${repo}/i/${issue_id}',
206 'pref': '#',
206 'pref': '#',
207 'desc': 'Test Pattern'
207 'desc': 'Test Pattern'
208 }
208 }
209 }
209 }
210
210
211 with mock.patch.object(IssueTrackerSettingsModel,
211 with mock.patch.object(IssueTrackerSettingsModel,
212 'get_global_settings', get_settings_mock):
212 'get_global_settings', get_settings_mock):
213 processed_text, issues, errors = helpers.process_patterns(
213 processed_text, issues, errors = helpers.process_patterns(
214 text_string, 'do-not-exist')
214 text_string, 'do-not-exist')
215
215
216 assert processed_text == expected_text
216 assert processed_text == expected_text
217
217
218
218
219 def test_get_visual_attr(baseapp):
219 def test_get_visual_attr(baseapp):
220 from rhodecode.apps._base import TemplateArgs
220 from rhodecode.apps._base import TemplateArgs
221 c = TemplateArgs()
221 c = TemplateArgs()
222 assert None is helpers.get_visual_attr(c, 'fakse')
222 assert None is helpers.get_visual_attr(c, 'fakse')
223
223
224 # emulate the c.visual behaviour
224 # emulate the c.visual behaviour
225 c.visual = AttributeDict({})
225 c.visual = AttributeDict({})
226 assert None is helpers.get_visual_attr(c, 'some_var')
226 assert None is helpers.get_visual_attr(c, 'some_var')
227
227
228 c.visual.some_var = 'foobar'
228 c.visual.some_var = 'foobar'
229 assert 'foobar' == helpers.get_visual_attr(c, 'some_var')
229 assert 'foobar' == helpers.get_visual_attr(c, 'some_var')
230
230
231
231
232 @pytest.mark.parametrize('test_text, inclusive, expected_text', [
232 @pytest.mark.parametrize('test_text, inclusive, expected_text', [
233 ('just a string', False, 'just a string'),
233 ('just a string', False, 'just a string'),
234 ('just a string\n', False, 'just a string'),
234 ('just a string\n', False, 'just a string'),
235 ('just a string\n next line', False, 'just a string...'),
235 ('just a string\n next line', False, 'just a string...'),
236 ('just a string\n next line', True, 'just a string\n...'),
236 ('just a string\n next line', True, 'just a string\n...'),
237 ], ids=no_newline_id_generator)
237 ], ids=no_newline_id_generator)
238 def test_chop_at(test_text, inclusive, expected_text):
238 def test_chop_at(test_text, inclusive, expected_text):
239 assert helpers.chop_at_smart(
239 assert helpers.chop_at_smart(
240 test_text, '\n', inclusive, '...') == expected_text
240 test_text, '\n', inclusive, '...') == expected_text
General Comments 0
You need to be logged in to leave comments. Login now