Commits

Robert Brewer  committed 8088b54

Implemented WSGI 1.0.1's requirement to not send more than Content-Length bytes. Includes 2 tests.

  • Participants
  • Parent commits 330702e

Comments (0)

Files changed (2)

File cherrypy/test/test_conn.py

             return ["a" * 1024] * 1024
         one_megabyte_of_a.exposed = True
 
+        def custom_cl(self, body, cl):
+            cherrypy.response.headers['Content-Length'] = cl
+            if not isinstance(body, list):
+                body = [body]
+            newbody = []
+            for chunk in body:
+                if isinstance(chunk, unicode):
+                    chunk = chunk.encode('ISO-8859-1')
+                newbody.append(chunk)
+            return newbody
+        custom_cl.exposed = True
+        # Turn off the encoding tool so it doens't collapse
+        # our response body and reclaculate the Content-Length.
+        custom_cl._cp_config = {'tools.encode.on': False}
+
     cherrypy.tree.mount(Root())
     cherrypy.config.update({
         'server.max_request_body_size': 1001,
         self.assertStatus(413)
         conn.close()
 
-    def test_Content_Length(self):
+    def test_Content_Length_in(self):
         # Try a non-chunked request where Content-Length exceeds
         # server.max_request_body_size. Assert error before body send.
         self.persistent = True
                         "the maximum allowed bytes.")
         conn.close()
 
+    def test_Content_Length_out_preheaders(self):
+        # Try a non-chunked response where Content-Length is less than
+        # the actual bytes in the response body.
+        self.persistent = True
+        conn = self.HTTP_CONN
+        conn.putrequest("GET", "/custom_cl?body=I+have+too+many+bytes&cl=5",
+                        skip_host=True)
+        conn.putheader("Host", self.HOST)
+        conn.endheaders()
+        response = conn.getresponse()
+        self.status, self.headers, self.body = webtest.shb(response)
+        self.assertStatus(500)
+        self.assertBody(
+            "The requested resource returned more bytes than the "
+            "declared Content-Length.")
+        conn.close()
+
+    def test_Content_Length_out_postheaders(self):
+        # Try a non-chunked response where Content-Length is less than
+        # the actual bytes in the response body.
+        self.persistent = True
+        conn = self.HTTP_CONN
+        conn.putrequest("GET", "/custom_cl?body=I+too&body=+have+too+many&cl=5",
+                        skip_host=True)
+        conn.putheader("Host", self.HOST)
+        conn.endheaders()
+        response = conn.getresponse()
+        self.status, self.headers, self.body = webtest.shb(response)
+        self.assertStatus(200)
+        self.assertBody("I too")
+        conn.close()
+
     def test_598(self):
         remote_data_conn = urlopen('%s://%s:%s/one_megabyte_of_a/' %
                                           (self.scheme, self.HOST, self.PORT,))

File cherrypy/wsgiserver/__init__.py

         self.req = req
         self.started_response = False
         self.env = self.get_environ()
+        self.remaining_bytes_out = None
     
     def get_environ(self):
         """Return a new environ dict targeting the given wsgi.version"""
                 raise TypeError("WSGI response header key %r is not a byte string." % k)
             if not isinstance(v, str):
                 raise TypeError("WSGI response header value %r is not a byte string." % v)
+            if k.lower() == 'content-length':
+                self.remaining_bytes_out = int(v)
         self.req.outheaders.extend(headers)
         
         return self.write
         if not self.started_response:
             raise AssertionError("WSGI write called before start_response.")
         
+        chunklen = len(chunk)
+        rbo = self.remaining_bytes_out
+        if rbo is not None and chunklen > rbo:
+            if not self.req.sent_headers:
+                # Whew. We can send a 500 to the client.
+                self.req.simple_response("500 Internal Server Error",
+                    "The requested resource returned more bytes than the "
+                    "declared Content-Length.")
+            else:
+                # Dang. We have probably already sent data. Truncate the chunk
+                # to fit (so the client doesn't hang) and raise an error later.
+                chunk = chunk[:rbo]
+        
         if not self.req.sent_headers:
             self.req.sent_headers = True
             self.req.send_headers()
         
         self.req.write(chunk)
+        
+        if rbo is not None:
+            rbo -= chunklen
+            if rbo < 0:
+                raise ValueError(
+                    "Response body exceeds the declared Content-Length.")
 
 
 class WSGIGateway_10(WSGIGateway):