Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #18210 -- Escaped special characters in reverse prefixes.

Ensured that special characters passed in to reverse via the
prefix argument are properly escaped so that calls to
django.utils.regex_helpers.normalize and/or string formatting
operations don't result in exceptions.

Thanks to toofishes for the error report.
  • Loading branch information...
commit 90e530978d590a5bdcf75525aa03f844766018b8 1 parent 0546794
Gabriel Hurley authored November 03, 2012
10  django/core/urlresolvers.py
@@ -16,6 +16,7 @@
16 16
 from django.utils.datastructures import MultiValueDict
17 17
 from django.utils.encoding import force_str, force_text, iri_to_uri
18 18
 from django.utils.functional import memoize, lazy
  19
+from django.utils.http import urlquote
19 20
 from django.utils.importlib import import_module
20 21
 from django.utils.module_loading import module_has_submodule
21 22
 from django.utils.regex_helper import normalize
@@ -379,14 +380,15 @@ def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
379 380
         except (ImportError, AttributeError) as e:
380 381
             raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e))
381 382
         possibilities = self.reverse_dict.getlist(lookup_view)
382  
-        prefix_norm, prefix_args = normalize(_prefix)[0]
  383
+
  384
+        prefix_norm, prefix_args = normalize(urlquote(_prefix))[0]
383 385
         for possibility, pattern, defaults in possibilities:
384 386
             for result, params in possibility:
385 387
                 if args:
386 388
                     if len(args) != len(params) + len(prefix_args):
387 389
                         continue
388 390
                     unicode_args = [force_text(val) for val in args]
389  
-                    candidate =  (prefix_norm + result) % dict(zip(prefix_args + params, unicode_args))
  391
+                    candidate = (prefix_norm + result) % dict(zip(prefix_args + params, unicode_args))
390 392
                 else:
391 393
                     if set(kwargs.keys()) | set(defaults.keys()) != set(params) | set(defaults.keys()) | set(prefix_args):
392 394
                         continue
@@ -398,8 +400,8 @@ def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
398 400
                     if not matches:
399 401
                         continue
400 402
                     unicode_kwargs = dict([(k, force_text(v)) for (k, v) in kwargs.items()])
401  
-                    candidate = (prefix_norm + result) % unicode_kwargs
402  
-                if re.search('^%s%s' % (_prefix, pattern), candidate, re.UNICODE):
  403
+                    candidate = (prefix_norm.replace('%', '%%') + result) % unicode_kwargs
  404
+                if re.search('^%s%s' % (prefix_norm, pattern), candidate, re.UNICODE):
403 405
                     return candidate
404 406
         # lookup_view can be URL label, or dotted path, or callable, Any of
405 407
         # these can be passed in at the top, but callables are not friendly in
12  tests/regressiontests/urlpatterns_reverse/tests.py
@@ -171,6 +171,18 @@ def test_reverse_none(self):
171 171
         # Reversing None should raise an error, not return the last un-named view.
172 172
         self.assertRaises(NoReverseMatch, reverse, None)
173 173
 
  174
+    def test_prefix_braces(self):
  175
+        self.assertEqual('/%7B%7Binvalid%7D%7D/includes/non_path_include/',
  176
+               reverse('non_path_include', prefix='/{{invalid}}/'))
  177
+
  178
+    def test_prefix_parenthesis(self):
  179
+        self.assertEqual('/bogus%29/includes/non_path_include/',
  180
+               reverse('non_path_include', prefix='/bogus)/'))
  181
+
  182
+    def test_prefix_format_char(self):
  183
+        self.assertEqual('/bump%2520map/includes/non_path_include/',
  184
+               reverse('non_path_include', prefix='/bump%20map/'))
  185
+
174 186
 class ResolverTests(unittest.TestCase):
175 187
     def test_resolver_repr(self):
176 188
         """

0 notes on commit 90e5309

Please sign in to comment.
Something went wrong with that request. Please try again.