Limit the size of each request line

This commit is contained in:
Miguel Grinberg
2021-09-27 17:54:51 +01:00
parent d75449eb32
commit de9c991a9a
4 changed files with 55 additions and 6 deletions

View File

@@ -198,6 +198,15 @@ class Request():
#: Request.max_content_length = 1 * 1024 * 1024 # 1MB requests allowed #: Request.max_content_length = 1 * 1024 * 1024 # 1MB requests allowed
max_content_length = 16 * 1024 max_content_length = 16 * 1024
#: Specify the maximum length allowed for a line in the request. Requests
#: with longer lines will not be correctly interpreted. Applications can
#: change this maximum as necessary.
#:
#: Example::
#:
#: Request.max_readline = 16 * 1024 # 16KB lines allowed
max_readline = 2 * 1024
class G: class G:
pass pass
@@ -244,7 +253,7 @@ class Request():
This method returns a newly created ``Request`` object. This method returns a newly created ``Request`` object.
""" """
# request line # request line
line = client_stream.readline().strip().decode() line = Request._safe_readline(client_stream).strip().decode()
if not line: if not line:
return None return None
method, url, http_version = line.split() method, url, http_version = line.split()
@@ -254,7 +263,7 @@ class Request():
headers = {} headers = {}
content_length = 0 content_length = 0
while True: while True:
line = client_stream.readline().strip().decode() line = Request._safe_readline(client_stream).strip().decode()
if line == '': if line == '':
break break
header, value = line.split(':', 1) header, value = line.split(':', 1)
@@ -298,6 +307,14 @@ class Request():
self._form = self._parse_urlencoded(self.body.decode()) self._form = self._parse_urlencoded(self.body.decode())
return self._form return self._form
@staticmethod
def _safe_readline(stream):
line = stream.readline(Request.max_readline + 1)
print(line, Request.max_readline)
if len(line) > Request.max_readline:
raise ValueError('line too long')
return line
class Response(): class Response():
"""An HTTP response class. """An HTTP response class.

View File

@@ -34,7 +34,7 @@ class Request(BaseRequest):
object. object.
""" """
# request line # request line
line = (await client_stream.readline()).strip().decode() line = (await Request._safe_readline(client_stream)).strip().decode()
if not line: # pragma: no cover if not line: # pragma: no cover
return None return None
method, url, http_version = line.split() method, url, http_version = line.split()
@@ -44,7 +44,8 @@ class Request(BaseRequest):
headers = {} headers = {}
content_length = 0 content_length = 0
while True: while True:
line = (await client_stream.readline()).strip().decode() line = (await Request._safe_readline(
client_stream)).strip().decode()
if line == '': if line == '':
break break
header, value = line.split(':', 1) header, value = line.split(':', 1)
@@ -60,6 +61,13 @@ class Request(BaseRequest):
return Request(app, client_addr, method, url, http_version, headers, return Request(app, client_addr, method, url, http_version, headers,
body) body)
@staticmethod
async def _safe_readline(stream):
line = (await stream.readline())
if len(line) > Request.max_readline:
raise ValueError('line too long')
return line
class Response(BaseResponse): class Response(BaseResponse):
"""An HTTP response class. """An HTTP response class.

View File

@@ -79,6 +79,18 @@ class TestRequest(unittest.TestCase):
req = Request.create('app', fd, 'addr') req = Request.create('app', fd, 'addr')
self.assertIsNone(req.form) self.assertIsNone(req.form)
def test_large_line(self):
saved_max_readline = Request.max_readline
Request.max_readline = 16
fd = get_request_fd('GET', '/foo', headers={
'Content-Type': 'application/x-www-form-urlencoded'},
body='foo=bar&abc=def&x=y')
with self.assertRaises(ValueError):
Request.create('app', fd, 'addr')
Request.max_readline = saved_max_readline
def test_large_payload(self): def test_large_payload(self):
saved_max_content_length = Request.max_content_length saved_max_content_length = Request.max_content_length
Request.max_content_length = 16 Request.max_content_length = 16
@@ -87,6 +99,6 @@ class TestRequest(unittest.TestCase):
'Content-Type': 'application/x-www-form-urlencoded'}, 'Content-Type': 'application/x-www-form-urlencoded'},
body='foo=bar&abc=def&x=y') body='foo=bar&abc=def&x=y')
req = Request.create('app', fd, 'addr') req = Request.create('app', fd, 'addr')
assert req.body == b'' self.assertEqual(req.body, b'')
Request.max_content_length = saved_max_content_length Request.max_content_length = saved_max_content_length

View File

@@ -89,6 +89,18 @@ class TestRequestAsync(unittest.TestCase):
req = _run(Request.create('app', fd, 'addr')) req = _run(Request.create('app', fd, 'addr'))
self.assertIsNone(req.form) self.assertIsNone(req.form)
def test_large_line(self):
saved_max_readline = Request.max_readline
Request.max_readline = 16
fd = get_async_request_fd('GET', '/foo', headers={
'Content-Type': 'application/x-www-form-urlencoded'},
body='foo=bar&abc=def&x=y')
with self.assertRaises(ValueError):
_run(Request.create('app', fd, 'addr'))
Request.max_readline = saved_max_readline
def test_large_payload(self): def test_large_payload(self):
saved_max_content_length = Request.max_content_length saved_max_content_length = Request.max_content_length
Request.max_content_length = 16 Request.max_content_length = 16
@@ -97,6 +109,6 @@ class TestRequestAsync(unittest.TestCase):
'Content-Type': 'application/x-www-form-urlencoded'}, 'Content-Type': 'application/x-www-form-urlencoded'},
body='foo=bar&abc=def&x=y') body='foo=bar&abc=def&x=y')
req = _run(Request.create('app', fd, 'addr')) req = _run(Request.create('app', fd, 'addr'))
assert req.body == b'' self.assertEqual(req.body, b'')
Request.max_content_length = saved_max_content_length Request.max_content_length = saved_max_content_length