diff options
| author | Aymeric Augustin <aymeric.augustin@m4x.org> | 2012-01-07 18:15:28 +0000 |
|---|---|---|
| committer | Aymeric Augustin <aymeric.augustin@m4x.org> | 2012-01-07 18:15:28 +0000 |
| commit | e3a7bfccbb83712caf0645e4e33f5c03d9dc462b (patch) | |
| tree | 586ed337ed7ead1568522a96bea303afa9845b32 | |
| parent | c3697a091b7ff0d19f13821b18516ec8eaa1b8f8 (diff) | |
Fixed #9655 -- Prevented the urlize template filter from double-quoting URLs. Thanks Claude Paroz for writing the tests.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@17347 bcc190cf-cafb-0310-a4f2-bffc1f526a37
| -rw-r--r-- | django/utils/html.py | 14 | ||||
| -rw-r--r-- | docs/releases/1.4.txt | 9 | ||||
| -rw-r--r-- | tests/regressiontests/defaultfilters/tests.py | 13 |
3 files changed, 34 insertions, 2 deletions
diff --git a/django/utils/html.py b/django/utils/html.py index 2687eb5232..4f74a7492c 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -17,6 +17,7 @@ TRAILING_PUNCTUATION = ['.', ',', ')', '>', '\n', '>'] DOTS = [u'·', u'*', u'\u2022', u'•', u'•', u'•'] unencoded_ampersands_re = re.compile(r'&(?!(\w+|#\d+);)') +unquoted_percents_re = re.compile(r'%(?![0-9A-Fa-f]{2})') word_split_re = re.compile(r'(\s+)') punctuation_re = re.compile('^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % \ ('|'.join([re.escape(x) for x in LEADING_PUNCTUATION]), @@ -100,6 +101,15 @@ def fix_ampersands(value): return unencoded_ampersands_re.sub('&', force_unicode(value)) fix_ampersands = allow_lazy(fix_ampersands, unicode) +def smart_urlquote(url): + """Quotes an URL if it isn't already quoted.""" + # An URL is considered unquoted if it contains no % character, or if it + # contains a % not followed by two hexadecimal digits. See #9655. + if '%' not in url or unquoted_percents_re.search(url): + # See http://bugs.python.org/issue2637 + return urlquote(url, safe='!*\'();:@&=+$,/?#[]~') + return url + def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False): """ Converts any URLs in text into clickable links. @@ -130,11 +140,11 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False): # Make URL we want to point to. url = None if middle.startswith('http://') or middle.startswith('https://'): - url = urlquote(middle, safe='/&=:;#?+*') + url = smart_urlquote(middle) elif middle.startswith('www.') or ('@' not in middle and \ middle and middle[0] in string.ascii_letters + string.digits and \ (middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))): - url = urlquote('http://%s' % middle, safe='/&=:;#?+*') + url = smart_urlquote('http://%s' % middle) elif '@' in middle and not ':' in middle and simple_email_re.match(middle): url = 'mailto:%s' % middle nofollow_attr = '' diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index de7afc83c3..9f9ce7fb59 100644 --- a/docs/releases/1.4.txt +++ b/docs/releases/1.4.txt @@ -1044,6 +1044,15 @@ Now, the flags are keyword arguments of :meth:`@register.filter See :ref:`filters and auto-escaping <filters-auto-escaping>` for more information. +The :tfilter:`urlize` filter no longer escapes every URL +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When an URL contains a ``%xx`` sequence, where ``xx`` are two hexadecimal +digits, :tfilter:`urlize` assumes that the URL is already escaped, and doesn't +apply URL escaping again. This is wrong for URLs whose unquoted form contains +a ``%xx`` sequence, but such URLs are very unlikely to happen in the wild, +since they would confuse browsers too. + Session cookies now have the ``httponly`` flag by default ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py index 5e8c8f1538..00518344a3 100644 --- a/tests/regressiontests/defaultfilters/tests.py +++ b/tests/regressiontests/defaultfilters/tests.py @@ -238,6 +238,19 @@ class DefaultFiltersTests(TestCase): # Check urlize with https addresses self.assertEqual(urlize('https://google.com'), u'<a href="https://google.com" rel="nofollow">https://google.com</a>') + # Check urlize doesn't overquote already quoted urls - see #9655 + self.assertEqual(urlize('http://hi.baidu.com/%D6%D8%D0%C2%BF'), + u'<a href="http://hi.baidu.com/%D6%D8%D0%C2%BF" rel="nofollow">' + u'http://hi.baidu.com/%D6%D8%D0%C2%BF</a>') + self.assertEqual(urlize('www.mystore.com/30%OffCoupons!'), + u'<a href="http://www.mystore.com/30%25OffCoupons!" rel="nofollow">' + u'www.mystore.com/30%OffCoupons!</a>') + self.assertEqual(urlize('http://en.wikipedia.org/wiki/Caf%C3%A9'), + u'<a href="http://en.wikipedia.org/wiki/Caf%C3%A9" rel="nofollow">' + u'http://en.wikipedia.org/wiki/Caf%C3%A9</a>') + self.assertEqual(urlize('http://en.wikipedia.org/wiki/Café'), + u'<a href="http://en.wikipedia.org/wiki/Caf%C3%A9" rel="nofollow">' + u'http://en.wikipedia.org/wiki/Café</a>') def test_wordcount(self): self.assertEqual(wordcount(''), 0) |
