Author: aaugustin
Date: 2012-01-07 10:15:28 -0800 (Sat, 07 Jan 2012)
New Revision: 17347

Modified:
   django/trunk/django/utils/html.py
   django/trunk/docs/releases/1.4.txt
   django/trunk/tests/regressiontests/defaultfilters/tests.py
Log:
Fixed #9655 -- Prevented the urlize template filter from double-quoting URLs. 
Thanks Claude Paroz for writing the tests.


Modified: django/trunk/django/utils/html.py
===================================================================
--- django/trunk/django/utils/html.py   2012-01-07 11:17:44 UTC (rev 17346)
+++ django/trunk/django/utils/html.py   2012-01-07 18:15:28 UTC (rev 17347)
@@ -17,6 +17,7 @@
 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 @@
     return unencoded_ampersands_re.sub('&amp;', 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 @@
             # 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 = ''

Modified: django/trunk/docs/releases/1.4.txt
===================================================================
--- django/trunk/docs/releases/1.4.txt  2012-01-07 11:17:44 UTC (rev 17346)
+++ django/trunk/docs/releases/1.4.txt  2012-01-07 18:15:28 UTC (rev 17347)
@@ -1044,6 +1044,15 @@
 
 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
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

Modified: django/trunk/tests/regressiontests/defaultfilters/tests.py
===================================================================
--- django/trunk/tests/regressiontests/defaultfilters/tests.py  2012-01-07 
11:17:44 UTC (rev 17346)
+++ django/trunk/tests/regressiontests/defaultfilters/tests.py  2012-01-07 
18:15:28 UTC (rev 17347)
@@ -238,6 +238,19 @@
         # 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)

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to