Skip to content

Commit 32233d6

Browse files
[3.14] gh-70765: avoid waiting for HTTP headers when parsing HTTP/0.9 requests (GH-139514) (#139600)
(cherry picked from commit 13dc2fd) (cherry picked from commit 1fe89d3) Co-authored-by: Bénédikt Tran <[email protected]>
1 parent 297157d commit 32233d6

File tree

3 files changed

+51
-0
lines changed

3 files changed

+51
-0
lines changed

Lib/http/server.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ def parse_request(self):
324324
error response has already been sent back.
325325
326326
"""
327+
is_http_0_9 = False
327328
self.command = None # set in case of error on the first line
328329
self.request_version = version = self.default_request_version
329330
self.close_connection = True
@@ -381,6 +382,7 @@ def parse_request(self):
381382
HTTPStatus.BAD_REQUEST,
382383
"Bad HTTP/0.9 request type (%r)" % command)
383384
return False
385+
is_http_0_9 = True
384386
self.command, self.path = command, path
385387

386388
# gh-87389: The purpose of replacing '//' with '/' is to protect
@@ -390,6 +392,11 @@ def parse_request(self):
390392
if self.path.startswith('//'):
391393
self.path = '/' + self.path.lstrip('/') # Reduce to a single /
392394

395+
# For HTTP/0.9, headers are not expected at all.
396+
if is_http_0_9:
397+
self.headers = {}
398+
return True
399+
393400
# Examine the headers and look for a Connection directive.
394401
try:
395402
self.headers = http.client.parse_headers(self.rfile,

Lib/test/test_httpservers.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
SimpleHTTPRequestHandler, CGIHTTPRequestHandler
99
from http import server, HTTPStatus
1010

11+
import contextlib
1112
import os
1213
import socket
1314
import sys
@@ -359,6 +360,44 @@ def test_head_via_send_error(self):
359360
self.assertEqual(b'', data)
360361

361362

363+
class HTTP09ServerTestCase(BaseTestCase):
364+
365+
class request_handler(NoLogRequestHandler, BaseHTTPRequestHandler):
366+
"""Request handler for HTTP/0.9 server."""
367+
368+
def do_GET(self):
369+
self.wfile.write(f'OK: here is {self.path}\r\n'.encode())
370+
371+
def setUp(self):
372+
super().setUp()
373+
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
374+
self.sock = self.enterContext(self.sock)
375+
self.sock.connect((self.HOST, self.PORT))
376+
377+
def test_simple_get(self):
378+
self.sock.send(b'GET /index.html\r\n')
379+
res = self.sock.recv(1024)
380+
self.assertEqual(res, b"OK: here is /index.html\r\n")
381+
382+
def test_invalid_request(self):
383+
self.sock.send(b'POST /index.html\r\n')
384+
res = self.sock.recv(1024)
385+
self.assertIn(b"Bad HTTP/0.9 request type ('POST')", res)
386+
387+
def test_single_request(self):
388+
self.sock.send(b'GET /foo.html\r\n')
389+
res = self.sock.recv(1024)
390+
self.assertEqual(res, b"OK: here is /foo.html\r\n")
391+
392+
# Ignore errors if the connection is already closed,
393+
# as this is the expected behavior of HTTP/0.9.
394+
with contextlib.suppress(OSError):
395+
self.sock.send(b'GET /bar.html\r\n')
396+
res = self.sock.recv(1024)
397+
# The server should not process our request.
398+
self.assertEqual(res, b'')
399+
400+
362401
def certdata_file(*path):
363402
return os.path.join(os.path.dirname(__file__), "certdata", *path)
364403

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)