Commits

jomae  committed d2d0b13

0.12.3dev: Quotes non-ascii characters in a query string. IE7 cannot open such a URL.

Closes #10275.

  • Participants
  • Parent commits a62641d
  • Branches 0.12-stable

Comments (0)

Files changed (7)

File trac/search/web_ui.py

 from trac.util.datefmt import format_datetime
 from trac.util.html import find_element
 from trac.util.presentation import Paginator
+from trac.util.text import quote_query_string
 from trac.util.translation import _
 from trac.web import IRequestHandler
 from trac.web.chrome import add_link, add_stylesheet, add_warning, \
         if path:
             href = formatter.href.search(q=path)
             if query:
-                href += '&' + query[1:].replace(' ', '+')
+                href += '&' + quote_query_string(query[1:])
         else:
-            href = formatter.href.search() + query.replace(' ', '+')
+            href = formatter.href.search() + quote_query_string(query)
         href += fragment
         return tag.a(label, class_='search', href=href)
 

File trac/ticket/query.py

 from trac.util.datefmt import format_datetime, from_utimestamp, parse_date, \
                               to_timestamp, to_utimestamp, utc
 from trac.util.presentation import Paginator
-from trac.util.text import empty, shorten_line
+from trac.util.text import empty, shorten_line, quote_query_string
 from trac.util.translation import _, tag_
 from trac.web import arg_list_to_args, parse_arg_list, IRequestHandler
 from trac.web.href import Href
 
     def _format_link(self, formatter, ns, query, label):
         if query.startswith('?'):
+            query = quote_query_string(query)
             return tag.a(label, class_='query',
-                         href=formatter.href.query() + query.replace(' ', '+'))
+                         href=formatter.href.query() + query)
         else:
             try:
                 query = Query.from_string(self.env, query)

File trac/ticket/report.py

 from trac.util import as_int, content_disposition
 from trac.util.datefmt import format_datetime, format_time, from_utimestamp
 from trac.util.presentation import Paginator
-from trac.util.text import to_unicode
+from trac.util.text import to_unicode, quote_query_string
 from trac.util.translation import _, tag_
 from trac.web.api import IRequestHandler, RequestDone
 from trac.web.chrome import add_ctxtnav, add_link, add_notice, add_script, \
                 if query[-1] != '?':
                     query += '&'
                 query += report_id
-            req.redirect(req.href.query() + query)
+            req.redirect(req.href.query() + quote_query_string(query))
         elif query.startswith('query:'):
             try:
                 from trac.ticket.query import Query, QuerySyntaxError

File trac/ticket/tests/report.py

+# -*- coding: utf-8 -*-
+
 from trac.db.mysql_backend import MySQLConnection
 from trac.ticket.report import ReportModule
-from trac.test import EnvironmentStub
+from trac.test import EnvironmentStub, Mock
 from trac.web.api import Request, RequestDone
 
 import unittest
         self.env = EnvironmentStub()
         self.report_module = ReportModule(self.env)
 
+    def tearDown(self):
+        self.env.reset_db()
+
     def _make_environ(self, scheme='http', server_name='example.org',
                       server_port=80, method='GET', script_name='/trac',
                       **kwargs):
         self.assertEqual('TEST_COL,TEST_ZERO\r\n"value, needs escaped",0\r\n',
                          buf.getvalue())
 
+    def test_saved_custom_query_redirect(self):
+        query = u'query:?type=résumé'
+        db = self.env.get_db_cnx()
+        cursor = db.cursor()
+        cursor.execute("INSERT INTO report (title,query,description) "
+                       "VALUES (%s,%s,%s)", ('redirect', query, ''))
+        id = db.get_last_id(cursor, 'report')
+        db.commit()
+
+        headers_sent = {}
+        def start_response(status, headers):
+            headers_sent.update(dict(headers))
+        environ = self._make_environ()
+        req = Request(environ, start_response)
+        req.authname = 'anonymous'
+        req.session = Mock(save=lambda: None)
+        self.assertRaises(RequestDone,
+                          self.report_module._render_view, req, id)
+        self.assertEqual('http://example.org/trac/query?' + \
+                         'type=r%C3%A9sum%C3%A9&report=' + str(id),
+                         headers_sent['Location'])
+
 
 def suite():
     return unittest.makeSuite(ReportTestCase, 'test')

File trac/ticket/tests/wikisyntax.py

 
 query:?order=priority&owner=me
 
+query:?type=résumé
+
 query:status=new|reopened
 
 query:reporter!=
 query:group=owner
 
 query:verbose=1
+
+query:summary=résumé
 ------------------------------
 <p>
 <a class="query" href="/query?order=priority">query:?order=priority</a>
 <a class="query" href="/query?order=priority&amp;owner=me">query:?order=priority&amp;owner=me</a>
 </p>
 <p>
+<a class="query" href="/query?type=r%C3%A9sum%C3%A9">query:?type=résumé</a>
+</p>
+<p>
 <a class="query" href="/query?status=new&amp;status=reopened&amp;order=priority">query:status=new|reopened</a>
 </p>
 <p>
 <p>
 <a class="query" href="/query?order=priority&amp;row=description">query:verbose=1</a>
 </p>
+<p>
+<a class="query" href="/query?summary=r%C3%A9sum%C3%A9&amp;order=priority">query:summary=résumé</a>
+</p>
 ------------------------------
 ============================== TicketQuery macro: no results, list form
 Reopened tickets: [[TicketQuery(status=reopened)]]

File trac/util/tests/text.py

                            normalize_whitespace, to_unicode, \
                            text_width, print_table, unicode_quote, \
                            unicode_quote_plus, unicode_unquote, \
-                           unicode_urlencode, wrap
+                           unicode_urlencode, wrap, quote_query_string
 
 
 class ToUnicodeTestCase(unittest.TestCase):
                                             u'Üthing': empty}))
 
 
+class QuoteQueryStringTestCase(unittest.TestCase):
+    def test_quote(self):
+        text = u'type=the Ü thing&component=comp\x7fonent'
+        self.assertEqual('type=the+%C3%9C+thing&component=comp%7Fonent',
+                         quote_query_string(text))
+
+
 class WhitespaceTestCase(unittest.TestCase):
     def test_default(self):
         self.assertEqual(u'This is text ',
     suite.addTest(unittest.makeSuite(ExpandtabsTestCase, 'test'))
     suite.addTest(unittest.makeSuite(UnicodeQuoteTestCase, 'test'))
     suite.addTest(unittest.makeSuite(JavascriptQuoteTestCase, 'test'))
+    suite.addTest(unittest.makeSuite(QuoteQueryStringTestCase, 'test'))
     suite.addTest(unittest.makeSuite(WhitespaceTestCase, 'test'))
     suite.addTest(unittest.makeSuite(TextWidthTestCase, 'test'))
     suite.addTest(unittest.makeSuite(PrintTableTestCase, 'test'))

File trac/util/text.py

     return '&'.join(l)
 
 
+_qs_quote_safe = ''.join(chr(c) for c in xrange(0x21, 0x7f))
+
+def quote_query_string(text):
+    """Quote strings for query string
+    """
+    return unicode_quote_plus(text, _qs_quote_safe)
+
 def to_utf8(text, charset='iso-8859-15'):
     """Convert a string to UTF-8, assuming the encoding is either UTF-8, ISO
     Latin-1, or as specified by the optional `charset` parameter.