Commits

Gustavo Picon  committed 2c7e50c Merge

Merged in amcone/cherrypy (pull request #66)

Fixes for #1146 and #1139

  • Participants
  • Parent commits 02c0506, 0c2655b

Comments (0)

Files changed (5)

File cherrypy/_cperror.py

                 303: "This resource can be found at ",
                 307: "This resource has moved temporarily to ",
             }[status]
-            msg += "<a href='%s'>%s</a>."
-            msgs = [msg % (u, u) for u in self.urls]
+            msg += '<a href=%s>%s</a>.'
+            from xml.sax import saxutils
+            msgs = [msg % (saxutils.quoteattr(u), u) for u in self.urls]
             response.body = ntob("<br />\n".join(msgs), 'utf-8')
             # Previous code may have set C-L, so we have to reset it
             # (allow finalize to set it).

File cherrypy/lib/httputil.py

         return []
 
     result = []
-    for element in fieldvalue.split(","):
+
+    # There's probably a regex that I could write to parse this
+    # behaviour, but for the sake of getting this done sooner rather
+    # than later.
+    if '"' in fieldvalue:
+        elements = []
+        remaining = fieldvalue
+        in_quote = False
+        while remaining:
+            for i, ch in enumerate(remaining):
+                if ch == '"':
+                    # Check if this quote is escaped.
+                    if i > 0:
+                        if remaining[i-1] == '\\':
+                            continue
+                    in_quote = not in_quote
+
+                # We find a comma and we aren't in quotes, so split the
+                # string.
+                elif ch == ',' and not in_quote:
+                    elements.append(remaining[:i])
+                    remaining = remaining[i+1:]
+                    break
+
+            # End of string, place the remainder in elements and stop
+            # processing.
+            else:
+                elements.append(remaining)
+                remaining = None
+    else:
+        elements = fieldvalue.split(",")
+
+    for element in elements:
         if fieldname.startswith("Accept") or fieldname == 'TE':
             hv = AcceptElement.from_str(element)
         else:

File cherrypy/test/test_core.py

             def fragment(self, frag):
                 raise cherrypy.HTTPRedirect("/some/url#%s" % frag)
 
+            def url_with_quote(self):
+                raise cherrypy.HTTPRedirect("/some\"url/that'we/want")
+
         def login_redir():
             if not getattr(cherrypy.request, "login", None):
                 raise cherrypy.InternalRedirect("/internalredirect/login")
 
         self.getPage("/redirect/by_code?code=300")
         self.assertMatchesBody(
-            r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
+            r"<a href=(['\"])(.*)somewhere%20else\1>\2somewhere%20else</a>")
         self.assertStatus(300)
 
         self.getPage("/redirect/by_code?code=301")
         self.assertMatchesBody(
-            r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
+            r"<a href=(['\"])(.*)somewhere%20else\1>\2somewhere%20else</a>")
         self.assertStatus(301)
 
         self.getPage("/redirect/by_code?code=302")
         self.assertMatchesBody(
-            r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
+            r"<a href=(['\"])(.*)somewhere%20else\1>\2somewhere%20else</a>")
         self.assertStatus(302)
 
         self.getPage("/redirect/by_code?code=303")
         self.assertMatchesBody(
-            r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
+            r"<a href=(['\"])(.*)somewhere%20else\1>\2somewhere%20else</a>")
         self.assertStatus(303)
 
         self.getPage("/redirect/by_code?code=307")
         self.assertMatchesBody(
-            r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
+            r"<a href=(['\"])(.*)somewhere%20else\1>\2somewhere%20else</a>")
         self.assertStatus(307)
 
         self.getPage("/redirect/nomodify")
         frag = "foo"
         self.getPage("/redirect/fragment/%s" % frag)
         self.assertMatchesBody(
-            r"<a href='(.*)\/some\/url\#%s'>\1\/some\/url\#%s</a>" % (
+            r"<a href=(['\"])(.*)\/some\/url\#%s\1>\2\/some\/url\#%s</a>" % (
                 frag, frag))
         loc = self.assertHeader('Location')
         assert loc.endswith("#%s" % frag)
         assert 'Set-Cookie' in loc
         self.assertNoHeader('Set-Cookie')
 
+        def assertValidXHTML():
+            from xml.etree import ElementTree
+            try:
+                ElementTree.fromstring('<html><body>%s</body></html>' % self.body)
+            except ElementTree.ParseError as e:
+                self._handlewebError('automatically generated redirect '
+                    'did not generate well-formed html')
+
+        # check redirects to URLs generated valid HTML - we check this
+        # by seeing if it appears as valid XHTML.
+        self.getPage("/redirect/by_code?code=303")
+        self.assertStatus(303)
+        assertValidXHTML()
+
+        # do the same with a url containing quote characters.
+        self.getPage("/redirect/url_with_quote")
+        self.assertStatus(303)
+        assertValidXHTML()
+
     def test_InternalRedirect(self):
         # InternalRedirect
         self.getPage("/internalredirect/")

File cherrypy/test/test_http.py

 
 
 class HTTPTests(helper.CPWebCase):
+    
+    def make_connection(self):
+        if self.scheme == "https":
+            return HTTPSConnection('%s:%s' % (self.interface(), self.PORT))
+        else:
+            return HTTPConnection('%s:%s' % (self.interface(), self.PORT))      
 
     def setup_server():
         class Root:
                     summary.append("%s * %d" % (curchar, count))
                 return ", ".join(summary)
             post_multipart.exposed = True
+            
+            @cherrypy.expose
+            def post_filename(self, myfile):
+                '''Return the name of the file which was uploaded.'''
+                return myfile.filename
 
         cherrypy.tree.mount(Root())
         cherrypy.config.update({'server.max_request_body_size': 30000000})
         # Send a message with neither header and no body. Even though
         # the request is of method POST, this should be OK because we set
         # request.process_request_body to False for our handler.
-        if self.scheme == "https":
-            c = HTTPSConnection('%s:%s' % (self.interface(), self.PORT))
-        else:
-            c = HTTPConnection('%s:%s' % (self.interface(), self.PORT))
+        c = self.make_connection()
         c.request("POST", "/no_body")
         response = c.getresponse()
         self.body = response.fp.read()
         body = body.encode('Latin-1')
 
         # post file
-        if self.scheme == 'https':
-            c = HTTPSConnection('%s:%s' % (self.interface(), self.PORT))
-        else:
-            c = HTTPConnection('%s:%s' % (self.interface(), self.PORT))
+        c = self.make_connection()
         c.putrequest('POST', '/post_multipart')
         c.putheader('Content-Type', content_type)
         c.putheader('Content-Length', str(len(body)))
         self.status = str(response.status)
         self.assertStatus(200)
         self.assertBody(", ".join(["%s * 65536" % c for c in alphabet]))
+        
+    def test_post_filename_with_commas(self):
+        '''Testing that we can handle filenames with commas. This was
+        reported as a bug in:
+           https://bitbucket.org/cherrypy/cherrypy/issue/1146/'''
+        # We'll upload a bunch of files with differing names.
+        for fname in ['boop.csv', 'foo, bar.csv', 'bar, xxxx.csv', 'file"name.csv']:
+            files = [('myfile', fname, 'yunyeenyunyue')]
+            content_type, body = encode_multipart_formdata(files)
+            body = body.encode('Latin-1')
+
+            # post file
+            c = self.make_connection()
+            c.putrequest('POST', '/post_filename')
+            c.putheader('Content-Type', content_type)
+            c.putheader('Content-Length', str(len(body)))
+            c.endheaders()
+            c.send(body)
+
+            response = c.getresponse()
+            self.body = response.fp.read()
+            self.status = str(response.status)
+            self.assertStatus(200)
+            self.assertBody(fname)
 
     def test_malformed_request_line(self):
         if getattr(cherrypy.server, "using_apache", False):
             return self.skip("skipped due to known Apache differences...")
 
         # Test missing version in Request-Line
-        if self.scheme == 'https':
-            c = HTTPSConnection('%s:%s' % (self.interface(), self.PORT))
-        else:
-            c = HTTPConnection('%s:%s' % (self.interface(), self.PORT))
+        c = self.make_connection()
         c._output(ntob('GET /'))
         c._send_output()
         if hasattr(c, 'strict'):
         self.assertBody("Hello world!")
 
     def test_malformed_header(self):
-        if self.scheme == 'https':
-            c = HTTPSConnection('%s:%s' % (self.interface(), self.PORT))
-        else:
-            c = HTTPConnection('%s:%s' % (self.interface(), self.PORT))
+        c = self.make_connection()
         c.putrequest('GET', '/')
         c.putheader('Content-Type', 'text/plain')
         # See https://bitbucket.org/cherrypy/cherrypy/issue/941

File cherrypy/test/test_static.py

         self.getPage("/docroot")
         self.assertStatus(301)
         self.assertHeader('Location', '%s/docroot/' % self.base())
-        self.assertMatchesBody("This resource .* <a href='%s/docroot/'>"
+        self.assertMatchesBody("This resource .* <a href=(['\"])%s/docroot/\\1>"
                                "%s/docroot/</a>." % (self.base(), self.base()))
 
     def test_config_errors(self):