Skip to content

Commit de9c991

Browse files
Limit the size of each request line
1 parent d75449e commit de9c991

File tree

4 files changed

+55
-6
lines changed

4 files changed

+55
-6
lines changed

src/microdot.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,15 @@ class Request():
198198
#: Request.max_content_length = 1 * 1024 * 1024 # 1MB requests allowed
199199
max_content_length = 16 * 1024
200200

201+
#: Specify the maximum length allowed for a line in the request. Requests
202+
#: with longer lines will not be correctly interpreted. Applications can
203+
#: change this maximum as necessary.
204+
#:
205+
#: Example::
206+
#:
207+
#: Request.max_readline = 16 * 1024 # 16KB lines allowed
208+
max_readline = 2 * 1024
209+
201210
class G:
202211
pass
203212

@@ -244,7 +253,7 @@ def create(app, client_stream, client_addr):
244253
This method returns a newly created ``Request`` object.
245254
"""
246255
# request line
247-
line = client_stream.readline().strip().decode()
256+
line = Request._safe_readline(client_stream).strip().decode()
248257
if not line:
249258
return None
250259
method, url, http_version = line.split()
@@ -254,7 +263,7 @@ def create(app, client_stream, client_addr):
254263
headers = {}
255264
content_length = 0
256265
while True:
257-
line = client_stream.readline().strip().decode()
266+
line = Request._safe_readline(client_stream).strip().decode()
258267
if line == '':
259268
break
260269
header, value = line.split(':', 1)
@@ -298,6 +307,14 @@ def form(self):
298307
self._form = self._parse_urlencoded(self.body.decode())
299308
return self._form
300309

310+
@staticmethod
311+
def _safe_readline(stream):
312+
line = stream.readline(Request.max_readline + 1)
313+
print(line, Request.max_readline)
314+
if len(line) > Request.max_readline:
315+
raise ValueError('line too long')
316+
return line
317+
301318

302319
class Response():
303320
"""An HTTP response class.

src/microdot_asyncio.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ async def create(app, client_stream, client_addr):
3434
object.
3535
"""
3636
# request line
37-
line = (await client_stream.readline()).strip().decode()
37+
line = (await Request._safe_readline(client_stream)).strip().decode()
3838
if not line: # pragma: no cover
3939
return None
4040
method, url, http_version = line.split()
@@ -44,7 +44,8 @@ async def create(app, client_stream, client_addr):
4444
headers = {}
4545
content_length = 0
4646
while True:
47-
line = (await client_stream.readline()).strip().decode()
47+
line = (await Request._safe_readline(
48+
client_stream)).strip().decode()
4849
if line == '':
4950
break
5051
header, value = line.split(':', 1)
@@ -60,6 +61,13 @@ async def create(app, client_stream, client_addr):
6061
return Request(app, client_addr, method, url, http_version, headers,
6162
body)
6263

64+
@staticmethod
65+
async def _safe_readline(stream):
66+
line = (await stream.readline())
67+
if len(line) > Request.max_readline:
68+
raise ValueError('line too long')
69+
return line
70+
6371

6472
class Response(BaseResponse):
6573
"""An HTTP response class.

tests/microdot/test_request.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,18 @@ def test_form(self):
7979
req = Request.create('app', fd, 'addr')
8080
self.assertIsNone(req.form)
8181

82+
def test_large_line(self):
83+
saved_max_readline = Request.max_readline
84+
Request.max_readline = 16
85+
86+
fd = get_request_fd('GET', '/foo', headers={
87+
'Content-Type': 'application/x-www-form-urlencoded'},
88+
body='foo=bar&abc=def&x=y')
89+
with self.assertRaises(ValueError):
90+
Request.create('app', fd, 'addr')
91+
92+
Request.max_readline = saved_max_readline
93+
8294
def test_large_payload(self):
8395
saved_max_content_length = Request.max_content_length
8496
Request.max_content_length = 16
@@ -87,6 +99,6 @@ def test_large_payload(self):
8799
'Content-Type': 'application/x-www-form-urlencoded'},
88100
body='foo=bar&abc=def&x=y')
89101
req = Request.create('app', fd, 'addr')
90-
assert req.body == b''
102+
self.assertEqual(req.body, b'')
91103

92104
Request.max_content_length = saved_max_content_length

tests/microdot_asyncio/test_request_asyncio.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,18 @@ def test_form(self):
8989
req = _run(Request.create('app', fd, 'addr'))
9090
self.assertIsNone(req.form)
9191

92+
def test_large_line(self):
93+
saved_max_readline = Request.max_readline
94+
Request.max_readline = 16
95+
96+
fd = get_async_request_fd('GET', '/foo', headers={
97+
'Content-Type': 'application/x-www-form-urlencoded'},
98+
body='foo=bar&abc=def&x=y')
99+
with self.assertRaises(ValueError):
100+
_run(Request.create('app', fd, 'addr'))
101+
102+
Request.max_readline = saved_max_readline
103+
92104
def test_large_payload(self):
93105
saved_max_content_length = Request.max_content_length
94106
Request.max_content_length = 16
@@ -97,6 +109,6 @@ def test_large_payload(self):
97109
'Content-Type': 'application/x-www-form-urlencoded'},
98110
body='foo=bar&abc=def&x=y')
99111
req = _run(Request.create('app', fd, 'addr'))
100-
assert req.body == b''
112+
self.assertEqual(req.body, b'')
101113

102114
Request.max_content_length = saved_max_content_length

0 commit comments

Comments
 (0)