Skip to content

Commit

Permalink
Merge pull request django#7 from atl-gmathews/bb-cache-normalize
Browse files Browse the repository at this point in the history
Additional normalize() fixes
  • Loading branch information
belak committed Nov 9, 2015
2 parents 7fbad0b + 94c67fd commit 6a59cbf
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 23 deletions.
18 changes: 7 additions & 11 deletions django/core/urlresolvers.py
Expand Up @@ -421,15 +421,15 @@ def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e))
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):
if (set(kwargs.keys()) | set(defaults.keys()) != set(params) |
set(defaults.keys())):
continue
matches = True
for k, v in defaults.items():
Expand All @@ -444,14 +444,10 @@ def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
# 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:]
Expand Down
11 changes: 4 additions & 7 deletions django/utils/regex_helper.py
Expand Up @@ -7,8 +7,6 @@
"""
from __future__ import unicode_literals

from threading import local

from django.utils import six
from django.utils.six.moves import zip

Expand Down Expand Up @@ -48,8 +46,7 @@ class NonCapture(list):
Used to represent a non-capturing group in the pattern string.
"""

_normalized = local()
_normalized.results = {}
_normalized = {}

def normalize(pattern):
"""
Expand All @@ -74,11 +71,11 @@ def normalize(pattern):
resolving can be done using positional args when keyword args are
specified, the two cannot be mixed in the same reverse() call.
"""
if pattern not in _normalized.results:
_normalized.results[pattern] = _normalize(pattern)
if pattern not in _normalized:
_normalized[pattern] = _normalize(pattern)

# Return a copy of the list to avoid corrupting the cache
return list(_normalized.results[pattern])
return list(_normalized[pattern])

def _normalize(pattern):
# Do a linear scan to work out the special features of this pattern. The
Expand Down
23 changes: 18 additions & 5 deletions tests/urlpatterns_reverse/tests.py
Expand Up @@ -194,17 +194,24 @@ def test_prefix_braces(self):
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
Expand All @@ -219,6 +226,12 @@ def test_patterns_reported(self):
# 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/'
)


class ResolverTests(unittest.TestCase):
def test_resolver_repr(self):
Expand Down

0 comments on commit 6a59cbf

Please sign in to comment.