5 Commits

Author SHA1 Message Date
Miguel Grinberg
f23c78533e Release 0.7.2 2021-09-28 17:21:05 +01:00
Miguel Grinberg
d29ed6aaa1 Document a security risk in the send_file function 2021-09-28 17:15:07 +01:00
Miguel Grinberg
8e5fb92ff1 Validate redirect URLs 2021-09-28 17:12:15 +01:00
Miguel Grinberg
06015934b8 Return a 400 error when request object could not be created 2021-09-28 17:09:02 +01:00
Miguel Grinberg
568cd51fd2 Version 0.7.2.dev0 2021-09-27 23:01:20 +01:00
6 changed files with 47 additions and 18 deletions

View File

@@ -1,5 +1,11 @@
# Microdot change log # Microdot change log
**Release 0.7.2** - 2021-09-28
- Document a security risk in the send_file function ([commit](https://github.com/miguelgrinberg/microdot/commit/d29ed6aaa1f2080fcf471bf6ae0f480f95ff1716)) (thanks **Ky Tran**!)
- Validate redirect URLs ([commit](https://github.com/miguelgrinberg/microdot/commit/8e5fb92ff1ccd50972b0c1cb5a6c3bd5eb54d86b)) (thanks **Ky Tran**!)
- Return a 400 error when request object could not be created ([commit](https://github.com/miguelgrinberg/microdot/commit/06015934b834622d39f52b3e13d16bfee9dc8e5a))
**Release 0.7.1** - 2021-09-27 **Release 0.7.1** - 2021-09-27
- Breaking change: Limit the size of each request line to 2KB. A different maximum can be set in `Request.max_readline`. ([commit](https://github.com/miguelgrinberg/microdot/commit/de9c991a9ab836d57d5c08bf4282f99f073b502a)) (thanks **Ky Tran**!) - Breaking change: Limit the size of each request line to 2KB. A different maximum can be set in `Request.max_readline`. ([commit](https://github.com/miguelgrinberg/microdot/commit/de9c991a9ab836d57d5c08bf4282f99f073b502a)) (thanks **Ky Tran**!)

View File

@@ -1,6 +1,6 @@
[metadata] [metadata]
name = microdot name = microdot
version = 0.7.1 version = 0.7.2
author = Miguel Grinberg author = Miguel Grinberg
author_email = miguel.grinberg@gmail.com author_email = miguel.grinberg@gmail.com
description = The impossibly small web framework for MicroPython description = The impossibly small web framework for MicroPython

View File

@@ -310,7 +310,6 @@ class Request():
@staticmethod @staticmethod
def _safe_readline(stream): def _safe_readline(stream):
line = stream.readline(Request.max_readline + 1) line = stream.readline(Request.max_readline + 1)
print(line, Request.max_readline)
if len(line) > Request.max_readline: if len(line) > Request.max_readline:
raise ValueError('line too long') raise ValueError('line too long')
return line return line
@@ -431,6 +430,8 @@ class Response():
:param status_code: The 3xx status code to use for the redirect. The :param status_code: The 3xx status code to use for the redirect. The
default is 302. default is 302.
""" """
if '\x0d' in location or '\x0a' in location:
raise ValueError('invalid redirect URL')
return cls(status_code=status_code, headers={'Location': location}) return cls(status_code=status_code, headers={'Location': location})
@classmethod @classmethod
@@ -443,6 +444,10 @@ class Response():
:param content_type: The ``Content-Type`` header to use in the :param content_type: The ``Content-Type`` header to use in the
response. If omitted, it is generated response. If omitted, it is generated
automatically from the file extension. automatically from the file extension.
Security note: The filename is assumed to be trusted. Never pass
filenames provided by the user before validating and sanitizing them
first.
""" """
if content_type is None: if content_type is None:
ext = filename.split('.')[-1] ext = filename.split('.')[-1]
@@ -795,7 +800,11 @@ class Microdot():
else: else:
stream = sock stream = sock
req = None
try:
req = Request.create(self, stream, addr) req = Request.create(self, stream, addr)
except Exception as exc: # pragma: no cover
print_exception(exc)
if req: if req:
if req.content_length > req.max_content_length: if req.content_length > req.max_content_length:
if 413 in self.error_handlers: if 413 in self.error_handlers:
@@ -836,6 +845,8 @@ class Microdot():
res = self.error_handlers[500](req) res = self.error_handlers[500](req)
else: else:
res = 'Internal server error', 500 res = 'Internal server error', 500
else:
res = 'Bad request', 400
if isinstance(res, tuple): if isinstance(res, tuple):
res = Response(*res) res = Response(*res)
elif not isinstance(res, Response): elif not isinstance(res, Response):

View File

@@ -218,8 +218,12 @@ class Microdot(BaseMicrodot):
self.server.close() self.server.close()
async def dispatch_request(self, reader, writer): async def dispatch_request(self, reader, writer):
req = None
try:
req = await Request.create(self, reader, req = await Request.create(self, reader,
writer.get_extra_info('peername')) writer.get_extra_info('peername'))
except Exception as exc: # pragma: no cover
print_exception(exc)
if req: if req:
if req.content_length > req.max_content_length: if req.content_length > req.max_content_length:
if 413 in self.error_handlers: if 413 in self.error_handlers:
@@ -266,6 +270,8 @@ class Microdot(BaseMicrodot):
self.error_handlers[500], req) self.error_handlers[500], req)
else: else:
res = 'Internal server error', 500 res = 'Internal server error', 500
else:
res = 'Bad request', 400
if isinstance(res, tuple): if isinstance(res, tuple):
res = Response(*res) res = Response(*res)
elif not isinstance(res, Response): elif not isinstance(res, Response):

View File

@@ -73,7 +73,10 @@ class TestMicrodot(unittest.TestCase):
mock_socket._requests.append(fd) mock_socket._requests.append(fd)
self._add_shutdown(app) self._add_shutdown(app)
app.run() app.run()
assert fd.response == b'' self.assertTrue(fd.response.startswith(b'HTTP/1.0 400 N/A\r\n'))
self.assertIn(b'Content-Length: 11\r\n', fd.response)
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
self.assertTrue(fd.response.endswith(b'\r\n\r\nBad request'))
def test_method_decorators(self): def test_method_decorators(self):
app = Microdot() app = Microdot()

View File

@@ -84,7 +84,10 @@ class TestMicrodotAsync(unittest.TestCase):
mock_socket._requests.append(fd) mock_socket._requests.append(fd)
self._add_shutdown(app) self._add_shutdown(app)
app.run() app.run()
assert fd.response == b'' self.assertTrue(fd.response.startswith(b'HTTP/1.0 400 N/A\r\n'))
self.assertIn(b'Content-Length: 11\r\n', fd.response)
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
self.assertTrue(fd.response.endswith(b'\r\n\r\nBad request'))
def test_before_after_request(self): def test_before_after_request(self):
app = Microdot() app = Microdot()