Skip to content

Commit 40de69a

Browse files
committed
Issue #25738: Merge HTTP server from 3.5
2 parents 58f0169 + e42e129 commit 40de69a

5 files changed

Lines changed: 73 additions & 14 deletions

File tree

Doc/library/http.server.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,9 @@ of which this module provides three different variants:
191191
a complete set of headers, as the response body. The :attr:`responses`
192192
attribute holds the default values for *message* and *explain* that
193193
will be used if no value is provided; for unknown codes the default value
194-
for both is the string ``???``.
194+
for both is the string ``???``. The body will be empty if the method is
195+
HEAD or the response code is one of the following: ``1xx``,
196+
``204 No Content``, ``205 Reset Content``, ``304 Not Modified``.
195197

196198
.. versionchanged:: 3.4
197199
The error response includes a Content-Length header.

Lib/http/server.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -446,23 +446,30 @@ def send_error(self, code, message=None, explain=None):
446446
if explain is None:
447447
explain = longmsg
448448
self.log_error("code %d, message %s", code, message)
449-
# HTML encode to prevent Cross Site Scripting attacks (see bug #1100201)
450-
content = (self.error_message_format % {
451-
'code': code,
452-
'message': html.escape(message, quote=False),
453-
'explain': html.escape(explain, quote=False)
454-
})
455-
body = content.encode('UTF-8', 'replace')
456449
self.send_response(code, message)
457-
self.send_header("Content-Type", self.error_content_type)
458450
self.send_header('Connection', 'close')
459-
self.send_header('Content-Length', int(len(body)))
451+
452+
# Message body is omitted for cases described in:
453+
# - RFC7230: 3.3. 1xx, 204(No Content), 304(Not Modified)
454+
# - RFC7231: 6.3.6. 205(Reset Content)
455+
body = None
456+
if (code >= 200 and
457+
code not in (HTTPStatus.NO_CONTENT,
458+
HTTPStatus.RESET_CONTENT,
459+
HTTPStatus.NOT_MODIFIED)):
460+
# HTML encode to prevent Cross Site Scripting attacks
461+
# (see bug #1100201)
462+
content = (self.error_message_format % {
463+
'code': code,
464+
'message': html.escape(message, quote=False),
465+
'explain': html.escape(explain, quote=False)
466+
})
467+
body = content.encode('UTF-8', 'replace')
468+
self.send_header("Content-Type", self.error_content_type)
469+
self.send_header('Content-Length', int(len(body)))
460470
self.end_headers()
461471

462-
if (self.command != 'HEAD' and
463-
code >= 200 and
464-
code not in (
465-
HTTPStatus.NO_CONTENT, HTTPStatus.NOT_MODIFIED)):
472+
if self.command != 'HEAD' and body:
466473
self.wfile.write(body)
467474

468475
def send_response(self, code, message=None):

Lib/test/test_httpservers.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ def do_LATINONEHEADER(self):
116116
body = self.headers['x-special-incoming'].encode('utf-8')
117117
self.wfile.write(body)
118118

119+
def do_SEND_ERROR(self):
120+
self.send_error(int(self.path[1:]))
121+
122+
def do_HEAD(self):
123+
self.send_error(int(self.path[1:]))
124+
119125
def setUp(self):
120126
BaseTestCase.setUp(self)
121127
self.con = http.client.HTTPConnection(self.HOST, self.PORT)
@@ -237,6 +243,44 @@ def test_error_content_length(self):
237243
data = res.read()
238244
self.assertEqual(int(res.getheader('Content-Length')), len(data))
239245

246+
def test_send_error(self):
247+
allow_transfer_encoding_codes = (HTTPStatus.NOT_MODIFIED,
248+
HTTPStatus.RESET_CONTENT)
249+
for code in (HTTPStatus.NO_CONTENT, HTTPStatus.NOT_MODIFIED,
250+
HTTPStatus.PROCESSING, HTTPStatus.RESET_CONTENT,
251+
HTTPStatus.SWITCHING_PROTOCOLS):
252+
self.con.request('SEND_ERROR', '/{}'.format(code))
253+
res = self.con.getresponse()
254+
self.assertEqual(code, res.status)
255+
self.assertEqual(None, res.getheader('Content-Length'))
256+
self.assertEqual(None, res.getheader('Content-Type'))
257+
if code not in allow_transfer_encoding_codes:
258+
self.assertEqual(None, res.getheader('Transfer-Encoding'))
259+
260+
data = res.read()
261+
self.assertEqual(b'', data)
262+
263+
def test_head_via_send_error(self):
264+
allow_transfer_encoding_codes = (HTTPStatus.NOT_MODIFIED,
265+
HTTPStatus.RESET_CONTENT)
266+
for code in (HTTPStatus.OK, HTTPStatus.NO_CONTENT,
267+
HTTPStatus.NOT_MODIFIED, HTTPStatus.RESET_CONTENT,
268+
HTTPStatus.SWITCHING_PROTOCOLS):
269+
self.con.request('HEAD', '/{}'.format(code))
270+
res = self.con.getresponse()
271+
self.assertEqual(code, res.status)
272+
if code == HTTPStatus.OK:
273+
self.assertTrue(int(res.getheader('Content-Length')) > 0)
274+
self.assertIn('text/html', res.getheader('Content-Type'))
275+
else:
276+
self.assertEqual(None, res.getheader('Content-Length'))
277+
self.assertEqual(None, res.getheader('Content-Type'))
278+
if code not in allow_transfer_encoding_codes:
279+
self.assertEqual(None, res.getheader('Transfer-Encoding'))
280+
281+
data = res.read()
282+
self.assertEqual(b'', data)
283+
240284

241285
class RequestHandlerLoggingTestCase(BaseTestCase):
242286
class request_handler(BaseHTTPRequestHandler):

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,7 @@ Arkady Koplyarov
787787
Peter A. Koren
788788
Марк Коренберг
789789
Vlad Korolev
790+
Susumu Koshiba
790791
Joseph Koshy
791792
Daniel Kozan
792793
Jerzy Kozera

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ Core and Builtins
2727
Library
2828
-------
2929

30+
- Issue #25738: Stop http.server.BaseHTTPRequestHandler.send_error() from
31+
sending a message body for 205 Reset Content. Also, don't send Content
32+
header fields in responses that don't have a body. Patch by Susumu
33+
Koshiba.
34+
3035
- Issue #21313: Fix the "platform" module to tolerate when sys.version
3136
contains truncated build information.
3237

0 commit comments

Comments
 (0)