Skip to content

Commit 5fa5a33

Browse files
committed
[3.13] gh-70765: avoid waiting for HTTP headers when parsing HTTP/0.9 requests (GH-139514)
(cherry picked from commit 13dc2fd) Co-authored-by: Bénédikt Tran <[email protected]>
1 parent 7e5d47d commit 5fa5a33

File tree

3 files changed

+49
-0
lines changed

3 files changed

+49
-0
lines changed

Lib/http/server.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ def parse_request(self):
275275
error response has already been sent back.
276276
277277
"""
278+
is_http_0_9 = False
278279
self.command = None # set in case of error on the first line
279280
self.request_version = version = self.default_request_version
280281
self.close_connection = True
@@ -332,6 +333,7 @@ def parse_request(self):
332333
HTTPStatus.BAD_REQUEST,
333334
"Bad HTTP/0.9 request type (%r)" % command)
334335
return False
336+
is_http_0_9 = True
335337
self.command, self.path = command, path
336338

337339
# gh-87389: The purpose of replacing '//' with '/' is to protect
@@ -341,6 +343,11 @@ def parse_request(self):
341343
if self.path.startswith('//'):
342344
self.path = '/' + self.path.lstrip('/') # Reduce to a single /
343345

346+
# For HTTP/0.9, headers are not expected at all.
347+
if is_http_0_9:
348+
self.headers = {}
349+
return True
350+
344351
# Examine the headers and look for a Connection directive.
345352
try:
346353
self.headers = http.client.parse_headers(self.rfile,

Lib/test/test_httpservers.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,43 @@ def test_head_via_send_error(self):
316316
self.assertEqual(b'', data)
317317

318318

319+
class HTTP09ServerTestCase(BaseTestCase):
320+
321+
class request_handler(NoLogRequestHandler, BaseHTTPRequestHandler):
322+
"""Request handler for HTTP/0.9 server."""
323+
324+
def do_GET(self):
325+
self.wfile.write(f'OK: here is {self.path}\r\n'.encode())
326+
327+
def setUp(self):
328+
super().setUp()
329+
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
330+
self.sock = self.enterContext(self.sock)
331+
self.sock.connect((self.HOST, self.PORT))
332+
333+
def test_simple_get(self):
334+
self.sock.send(b'GET /index.html\r\n')
335+
res = self.sock.recv(1024)
336+
self.assertEqual(res, b"OK: here is /index.html\r\n")
337+
338+
def test_invalid_request(self):
339+
self.sock.send(b'POST /index.html\r\n')
340+
res = self.sock.recv(1024)
341+
self.assertIn(b"Bad HTTP/0.9 request type ('POST')", res)
342+
343+
def test_single_request(self):
344+
self.sock.send(b'GET /foo.html\r\n')
345+
res = self.sock.recv(1024)
346+
self.assertEqual(res, b"OK: here is /foo.html\r\n")
347+
348+
self.sock.send(b'GET /bar.html\r\n')
349+
res = self.sock.recv(1024)
350+
# The server will not parse more input as it closed the connection.
351+
# Note that the socket connection itself is still opened since the
352+
# client is responsible for also closing it on their side.
353+
self.assertEqual(res, b'')
354+
355+
319356
class RequestHandlerLoggingTestCase(BaseTestCase):
320357
class request_handler(BaseHTTPRequestHandler):
321358
protocol_version = 'HTTP/1.1'
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
:mod:`http.server`: fix default handling of HTTP/0.9 requests in
2+
:class:`~http.server.BaseHTTPRequestHandler`. Previously,
3+
:meth:`!BaseHTTPRequestHandler.parse_request`` incorrectly
4+
waited for headers in the request although those are not
5+
supported in HTTP/0.9. Patch by Bénédikt Tran.

0 commit comments

Comments
 (0)