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
This commit is contained in:
parent
c3697a091b
commit
e3a7bfccbb
|
@ -17,6 +17,7 @@ TRAILING_PUNCTUATION = ['.', ',', ')', '>', '\n', '>']
|
||||||
DOTS = [u'·', u'*', u'\u2022', u'•', u'•', u'•']
|
DOTS = [u'·', u'*', u'\u2022', u'•', u'•', u'•']
|
||||||
|
|
||||||
unencoded_ampersands_re = re.compile(r'&(?!(\w+|#\d+);)')
|
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+)')
|
word_split_re = re.compile(r'(\s+)')
|
||||||
punctuation_re = re.compile('^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % \
|
punctuation_re = re.compile('^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % \
|
||||||
('|'.join([re.escape(x) for x in LEADING_PUNCTUATION]),
|
('|'.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))
|
return unencoded_ampersands_re.sub('&', force_unicode(value))
|
||||||
fix_ampersands = allow_lazy(fix_ampersands, unicode)
|
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):
|
def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
|
||||||
"""
|
"""
|
||||||
Converts any URLs in text into clickable links.
|
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.
|
# Make URL we want to point to.
|
||||||
url = None
|
url = None
|
||||||
if middle.startswith('http://') or middle.startswith('https://'):
|
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 \
|
elif middle.startswith('www.') or ('@' not in middle and \
|
||||||
middle and middle[0] in string.ascii_letters + string.digits and \
|
middle and middle[0] in string.ascii_letters + string.digits and \
|
||||||
(middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))):
|
(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):
|
elif '@' in middle and not ':' in middle and simple_email_re.match(middle):
|
||||||
url = 'mailto:%s' % middle
|
url = 'mailto:%s' % middle
|
||||||
nofollow_attr = ''
|
nofollow_attr = ''
|
||||||
|
|
|
@ -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.
|
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
|
Session cookies now have the ``httponly`` flag by default
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -238,6 +238,19 @@ class DefaultFiltersTests(TestCase):
|
||||||
# Check urlize with https addresses
|
# Check urlize with https addresses
|
||||||
self.assertEqual(urlize('https://google.com'),
|
self.assertEqual(urlize('https://google.com'),
|
||||||
u'<a href="https://google.com" rel="nofollow">https://google.com</a>')
|
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):
|
def test_wordcount(self):
|
||||||
self.assertEqual(wordcount(''), 0)
|
self.assertEqual(wordcount(''), 0)
|
||||||
|
|
Loading…
Reference in New Issue