Fixed #24013 -- Fixed escaping of reverse() prefix.
Prefix was treated as a part of the url pattern, which it is not. Improved tests to conform with RFC 3986 which allows certain characters in path segments without being escaped.
This commit is contained in:
parent
e4a578e70e
commit
c9f1a12925
|
@ -453,16 +453,15 @@ class RegexURLResolver(LocaleRegexProvider):
|
|||
)
|
||||
possibilities = self.reverse_dict.getlist(lookup_view)
|
||||
|
||||
prefix_norm, prefix_args = normalize(urlquote(_prefix))[0]
|
||||
for possibility, pattern, defaults in possibilities:
|
||||
for result, params in possibility:
|
||||
if args:
|
||||
if len(args) != len(params) + len(prefix_args):
|
||||
if len(args) != len(params):
|
||||
continue
|
||||
candidate_subs = dict(zip(prefix_args + params, text_args))
|
||||
candidate_subs = dict(zip(params, text_args))
|
||||
else:
|
||||
if (set(kwargs.keys()) | set(defaults.keys()) != set(params) |
|
||||
set(defaults.keys()) | set(prefix_args)):
|
||||
set(defaults.keys())):
|
||||
continue
|
||||
matches = True
|
||||
for k, v in defaults.items():
|
||||
|
@ -477,12 +476,10 @@ class RegexURLResolver(LocaleRegexProvider):
|
|||
# without quoting to build a decoded URL and look for a match.
|
||||
# Then, if we have a match, redo the substitution with quoted
|
||||
# arguments in order to return a properly encoded URL.
|
||||
candidate_pat = prefix_norm.replace('%', '%%') + result
|
||||
if re.search('^%s%s' % (prefix_norm, pattern), candidate_pat % candidate_subs, re.UNICODE):
|
||||
candidate_pat = _prefix.replace('%', '%%') + result
|
||||
if re.search('^%s%s' % (re.escape(_prefix), pattern), candidate_pat % candidate_subs, re.UNICODE):
|
||||
# safe characters from `pchar` definition of RFC 3986
|
||||
candidate_subs = dict((k, urlquote(v, safe=RFC3986_SUBDELIMS + str('/~:@')))
|
||||
for (k, v) in candidate_subs.items())
|
||||
url = candidate_pat % candidate_subs
|
||||
url = urlquote(candidate_pat % candidate_subs, safe=RFC3986_SUBDELIMS + str('/~:@'))
|
||||
# Don't allow construction of scheme relative urls.
|
||||
if url.startswith('//'):
|
||||
url = '/%%2F%s' % url[2:]
|
||||
|
|
|
@ -202,17 +202,24 @@ class URLPatternReverse(TestCase):
|
|||
reverse('non_path_include', prefix='/{{invalid}}/'))
|
||||
|
||||
def test_prefix_parenthesis(self):
|
||||
self.assertEqual('/bogus%29/includes/non_path_include/',
|
||||
reverse('non_path_include', prefix='/bogus)/'))
|
||||
# Parentheses are allowed and should not cause errors or be escaped
|
||||
self.assertEqual(
|
||||
'/bogus)/includes/non_path_include/',
|
||||
reverse('non_path_include', prefix='/bogus)/')
|
||||
)
|
||||
self.assertEqual(
|
||||
'/(bogus)/includes/non_path_include/',
|
||||
reverse('non_path_include', prefix='/(bogus)/')
|
||||
)
|
||||
|
||||
def test_prefix_format_char(self):
|
||||
self.assertEqual('/bump%2520map/includes/non_path_include/',
|
||||
reverse('non_path_include', prefix='/bump%20map/'))
|
||||
|
||||
def test_non_urlsafe_prefix_with_args(self):
|
||||
# Regression for #20022
|
||||
self.assertEqual('/%7Eme/places/1/',
|
||||
reverse('places', args=[1], prefix='/~me/'))
|
||||
# Regression for #20022, adjusted for #24013 because ~ is an unreserved
|
||||
# character. Tests whether % is escaped.
|
||||
self.assertEqual('/%257Eme/places/1/', reverse('places', args=[1], prefix='/%7Eme/'))
|
||||
|
||||
def test_patterns_reported(self):
|
||||
# Regression for #17076
|
||||
|
@ -227,6 +234,12 @@ class URLPatternReverse(TestCase):
|
|||
# exception
|
||||
self.fail("Expected a NoReverseMatch, but none occurred.")
|
||||
|
||||
def test_script_name_escaping(self):
|
||||
self.assertEqual(
|
||||
reverse('optional', args=['foo:bar'], prefix='/script:name/'),
|
||||
'/script:name/optional/foo:bar/'
|
||||
)
|
||||
|
||||
def test_reverse_returns_unicode(self):
|
||||
name, expected, args, kwargs = test_data[0]
|
||||
self.assertIsInstance(
|
||||
|
|
Loading…
Reference in New Issue