From e42e129ebec9918adcae6f93b18bb6652f29c3fe Mon Sep 17 00:00:00 2001 From: Martin Panter Date: Wed, 8 Jun 2016 08:29:13 +0000 Subject: =?UTF-8?q?Issue=20#25738:=20Don=E2=80=99t=20send=20message=20body?= =?UTF-8?q?=20for=20205=20Reset=20Content?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch by Susumu Koshiba. --- Doc/library/http.server.rst | 4 +++- Lib/http/server.py | 30 ++++++++++++++++++++---------- Lib/test/test_httpservers.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ Misc/ACKS | 1 + Misc/NEWS | 5 +++++ 5 files changed, 73 insertions(+), 11 deletions(-) diff --git a/Doc/library/http.server.rst b/Doc/library/http.server.rst index e0b2874..05ad2d7 100644 --- a/Doc/library/http.server.rst +++ b/Doc/library/http.server.rst @@ -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. diff --git a/Lib/http/server.py b/Lib/http/server.py index 3bd1f7a..00620d1 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -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): diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 1fffdb1..72e6e08 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -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): diff --git a/Misc/ACKS b/Misc/ACKS index 02e4821..262c647 100644 --- 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 diff --git a/Misc/NEWS b/Misc/NEWS index ff71902..4763374 100644 --- 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. -- cgit v0.12