]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Issue #25738: Don’t send message body for 205 Reset Content
authorMartin Panter <vadmium+py@gmail.com>
Wed, 8 Jun 2016 08:29:13 +0000 (08:29 +0000)
committerMartin Panter <vadmium+py@gmail.com>
Wed, 8 Jun 2016 08:29:13 +0000 (08:29 +0000)
Patch by Susumu Koshiba.

Doc/library/http.server.rst
Lib/http/server.py
Lib/test/test_httpservers.py
Misc/ACKS
Misc/NEWS

index e0b2874cf9aedc25f40244d6922c229dbb3e77b7..05ad2d7bab0fb6a723023032a8014d42b619df2f 100644 (file)
@@ -191,7 +191,9 @@ of which this module provides three different variants:
       a complete set of headers, as the response body.  The :attr:`responses`
       attribute holds the default values for *message* and *explain* that
       will be used if no value is provided; for unknown codes the default value
-      for both is the string ``???``.
+      for both is the string ``???``. The body will be empty if the method is
+      HEAD or the response code is one of the following: ``1xx``,
+      ``204 No Content``, ``205 Reset Content``, ``304 Not Modified``.
 
       .. versionchanged:: 3.4
          The error response includes a Content-Length header.
index 3bd1f7afb363d81bf554e4f30a6c90dc83e7a008..00620d1f853fafa764631f22d000a59b3eb06ba7 100644 (file)
@@ -450,20 +450,30 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
         if explain is None:
             explain = longmsg
         self.log_error("code %d, message %s", code, message)
-        # using _quote_html to prevent Cross Site Scripting attacks (see bug #1100201)
-        content = (self.error_message_format %
-                   {'code': code, 'message': _quote_html(message), 'explain': _quote_html(explain)})
-        body = content.encode('UTF-8', 'replace')
         self.send_response(code, message)
-        self.send_header("Content-Type", self.error_content_type)
         self.send_header('Connection', 'close')
-        self.send_header('Content-Length', int(len(body)))
+
+        # Message body is omitted for cases described in:
+        #  - RFC7230: 3.3. 1xx, 204(No Content), 304(Not Modified)
+        #  - RFC7231: 6.3.6. 205(Reset Content)
+        body = None
+        if (code >= 200 and
+            code not in (HTTPStatus.NO_CONTENT,
+                         HTTPStatus.RESET_CONTENT,
+                         HTTPStatus.NOT_MODIFIED)):
+            # HTML encode to prevent Cross Site Scripting attacks
+            # (see bug #1100201)
+            content = (self.error_message_format % {
+                'code': code,
+                'message': _quote_html(message),
+                'explain': _quote_html(explain)
+            })
+            body = content.encode('UTF-8', 'replace')
+            self.send_header("Content-Type", self.error_content_type)
+            self.send_header('Content-Length', int(len(body)))
         self.end_headers()
 
-        if (self.command != 'HEAD' and
-                code >= 200 and
-                code not in (
-                    HTTPStatus.NO_CONTENT, HTTPStatus.NOT_MODIFIED)):
+        if self.command != 'HEAD' and body:
             self.wfile.write(body)
 
     def send_response(self, code, message=None):
index 1fffdb1a7bc37aed9f3274be738bb8a099680d4e..72e6e0888027febc78efb2b6ca01012e903ee374 100644 (file)
@@ -115,6 +115,12 @@ class BaseHTTPServerTestCase(BaseTestCase):
             body = self.headers['x-special-incoming'].encode('utf-8')
             self.wfile.write(body)
 
+        def do_SEND_ERROR(self):
+            self.send_error(int(self.path[1:]))
+
+        def do_HEAD(self):
+            self.send_error(int(self.path[1:]))
+
     def setUp(self):
         BaseTestCase.setUp(self)
         self.con = http.client.HTTPConnection(self.HOST, self.PORT)
@@ -236,6 +242,44 @@ class BaseHTTPServerTestCase(BaseTestCase):
         data = res.read()
         self.assertEqual(int(res.getheader('Content-Length')), len(data))
 
+    def test_send_error(self):
+        allow_transfer_encoding_codes = (HTTPStatus.NOT_MODIFIED,
+                                         HTTPStatus.RESET_CONTENT)
+        for code in (HTTPStatus.NO_CONTENT, HTTPStatus.NOT_MODIFIED,
+                     HTTPStatus.PROCESSING, HTTPStatus.RESET_CONTENT,
+                     HTTPStatus.SWITCHING_PROTOCOLS):
+            self.con.request('SEND_ERROR', '/{}'.format(code))
+            res = self.con.getresponse()
+            self.assertEqual(code, res.status)
+            self.assertEqual(None, res.getheader('Content-Length'))
+            self.assertEqual(None, res.getheader('Content-Type'))
+            if code not in allow_transfer_encoding_codes:
+                self.assertEqual(None, res.getheader('Transfer-Encoding'))
+
+            data = res.read()
+            self.assertEqual(b'', data)
+
+    def test_head_via_send_error(self):
+        allow_transfer_encoding_codes = (HTTPStatus.NOT_MODIFIED,
+                                         HTTPStatus.RESET_CONTENT)
+        for code in (HTTPStatus.OK, HTTPStatus.NO_CONTENT,
+                     HTTPStatus.NOT_MODIFIED, HTTPStatus.RESET_CONTENT,
+                     HTTPStatus.SWITCHING_PROTOCOLS):
+            self.con.request('HEAD', '/{}'.format(code))
+            res = self.con.getresponse()
+            self.assertEqual(code, res.status)
+            if code == HTTPStatus.OK:
+                self.assertTrue(int(res.getheader('Content-Length')) > 0)
+                self.assertIn('text/html', res.getheader('Content-Type'))
+            else:
+                self.assertEqual(None, res.getheader('Content-Length'))
+                self.assertEqual(None, res.getheader('Content-Type'))
+            if code not in allow_transfer_encoding_codes:
+                self.assertEqual(None, res.getheader('Transfer-Encoding'))
+
+            data = res.read()
+            self.assertEqual(b'', data)
+
 
 class RequestHandlerLoggingTestCase(BaseTestCase):
     class request_handler(BaseHTTPRequestHandler):
index 02e4821305d8da0185a6408a1449d778b301ce46..262c6473f154614c5caa0ea848343b54feb7ec88 100644 (file)
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -781,6 +781,7 @@ Arkady Koplyarov
 Peter A. Koren
 Марк Коренберг
 Vlad Korolev
+Susumu Koshiba
 Joseph Koshy
 Daniel Kozan
 Jerzy Kozera
index ff7190258b35dfee7e0356a96014e9bc61de5c02..47633743bdc453dd72eaf736ba879c04f508228e 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -131,6 +131,11 @@ Core and Builtins
 Library
 -------
 
+- Issue #25738: Stop http.server.BaseHTTPRequestHandler.send_error() from
+  sending a message body for 205 Reset Content.  Also, don't send Content
+  header fields in responses that don't have a body.  Patch by Susumu
+  Koshiba.
+
 - Issue #21313: Fix the "platform" module to tolerate when sys.version
   contains truncated build information.