From f2e48fe339683dfc5f52ab72db0ca14e936e829e Mon Sep 17 00:00:00 2001 From: s3lph Date: Sat, 7 Jul 2018 15:20:54 +0200 Subject: [PATCH 1/4] Removed bcrypt dependency from README --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index f162e95..a6d078e 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,6 @@ This project intends to provide a well-tested and maintainable alternative to - Python 3 (>=3.6) - Python dependencies: - apsw - - bcrypt ## Usage From b453721821407fa9f6559b2f74c3d4b01d5a1742 Mon Sep 17 00:00:00 2001 From: s3lph Date: Sat, 7 Jul 2018 15:23:24 +0200 Subject: [PATCH 2/4] README: Added link to old matemat webapp --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a6d078e..d1eec08 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ It provides a touch-input-friendly user interface (as most input happens through soda machine's touch screen). This project intends to provide a well-tested and maintainable alternative to -[TODO][todo] (discontinued). +[ckruse/matemat][oldapp] (last commit 2013-07-09). ## Further Documentation @@ -36,6 +36,7 @@ python -m matemat [MIT License][mit-license] +[oldapp]: https://github.com/ckruse/matemat [mit-license]: https://gitlab.com/s3lph/matemat/blob/master/LICENSE [master]: https://gitlab.com/s3lph/matemat/commits/master [wiki]: https://gitlab.com/s3lph/matemat/wikis/home \ No newline at end of file From 00bcae9874d5f0bbc7b883bc2e360bce0a2ee123 Mon Sep 17 00:00:00 2001 From: s3lph Date: Sat, 7 Jul 2018 15:29:32 +0200 Subject: [PATCH 3/4] Fixed behavior for missing Content-Type in multipart parts --- matemat/webserver/test/test_parse_request.py | 45 +++++++++++++------- matemat/webserver/util.py | 7 ++- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/matemat/webserver/test/test_parse_request.py b/matemat/webserver/test/test_parse_request.py index 0a94065..acb225c 100644 --- a/matemat/webserver/test/test_parse_request.py +++ b/matemat/webserver/test/test_parse_request.py @@ -185,6 +185,35 @@ class TestParseRequest(unittest.TestCase): self.assertEqual('', args['foo'].get_str()) self.assertEqual('42', args['bar'].get_str()) + def test_parse_post_multipart_no_contenttype(self): + """ + Test that the Content-Type is set to 'application/octet-stream' if it is absent from the multipart header. + """ + path, args = parse_args('/', + postbody=b'--testBoundary1337\r\n' + b'Content-Disposition: form-data; name="foo"\r\n' + b'Content-Type: text/plain\r\n\r\n' + b'42\r\n' + b'--testBoundary1337\r\n' + b'Content-Disposition: form-data; name="bar"; filename="bar.bin"\r\n' + b'Content-Type: application/octet-stream\r\n\r\n' + b'1337\r\n' + b'--testBoundary1337\r\n' + b'Content-Disposition: form-data; name="baz"\r\n\r\n' + b'Hello, World!\r\n' + b'--testBoundary1337--\r\n', + enctype='multipart/form-data; boundary=testBoundary1337') + self.assertEqual('/', path) + self.assertEqual(3, len(args)) + self.assertIn('foo', args) + self.assertIn('bar', args) + self.assertIn('baz', args) + self.assertTrue(args['foo'].is_scalar) + self.assertTrue(args['bar'].is_scalar) + self.assertTrue(args['baz'].is_scalar) + self.assertEqual('application/octet-stream', args['baz'].get_content_type()) + self.assertEqual('Hello, World!', args['baz'].get_str()) + def test_parse_post_multipart_broken_boundaries(self): with self.assertRaises(ValueError): # Boundary not defined in Content-Type @@ -237,22 +266,6 @@ class TestParseRequest(unittest.TestCase): b'Hello, World!\r\n' b'--testBoundary1337\r\n', enctype='multipart/form-data; boundary=testBoundary1337') - with self.assertRaises(ValueError): - # Missing Content-Type header in one part - parse_args('/', - postbody=b'--testBoundary1337\r\n' - b'Content-Disposition: form-data; name="foo"\r\n' - b'Content-Type: text/plain\r\n\r\n' - b'42\r\n' - b'--testBoundary1337\r\n' - b'Content-Disposition: form-data; name="bar"; filename="bar.bin"\r\n' - b'Content-Type: application/octet-stream\r\n\r\n' - b'1337\r\n' - b'--testBoundary1337\r\n' - b'Content-Disposition: form-data; name="baz"\r\n\r\n' - b'Hello, World!\r\n' - b'--testBoundary1337--\r\n', - enctype='multipart/form-data; boundary=testBoundary1337') with self.assertRaises(ValueError): # Missing Content-Disposition header in one part parse_args('/', diff --git a/matemat/webserver/util.py b/matemat/webserver/util.py index 2bc2244..6e19d7b 100644 --- a/matemat/webserver/util.py +++ b/matemat/webserver/util.py @@ -46,8 +46,11 @@ def _parse_multipart(body: bytes, boundary: str) -> List[RequestArgument]: # Add header to hdr dict hk, hv = head.decode('utf-8').split(':') hdr[hk.strip()] = hv.strip() - # At least Content-Type and Content-Disposition must be present - if 'Content-Type' not in hdr or 'Content-Disposition' not in hdr: + # No content type set - set broadest possible type + if 'Content-Type' not in hdr: + hdr['Content-Type'] = 'application/octet-stream' + # At least Content-Disposition must be present + if 'Content-Disposition' not in hdr: raise ValueError('Missing Content-Type or Content-Disposition header') # Extract Content-Disposition header value and its arguments cd, *cdargs = hdr['Content-Disposition'].split(';') From 528f7322ac1731d4437d2158a07b1187f1c24351 Mon Sep 17 00:00:00 2001 From: s3lph Date: Sat, 7 Jul 2018 15:37:57 +0200 Subject: [PATCH 4/4] Docstrings for the request parser tests. --- matemat/webserver/test/test_parse_request.py | 51 ++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/matemat/webserver/test/test_parse_request.py b/matemat/webserver/test/test_parse_request.py index acb225c..d144ac1 100644 --- a/matemat/webserver/test/test_parse_request.py +++ b/matemat/webserver/test/test_parse_request.py @@ -7,16 +7,25 @@ from matemat.webserver.util import parse_args class TestParseRequest(unittest.TestCase): def test_parse_get_root(self): + """ + Test that the simple root path is parsed correctly ('/' path, no args). + """ path, args = parse_args('/') self.assertEqual('/', path) self.assertEqual(0, len(args)) def test_parse_get_no_args(self): + """ + Test that a GET request without arguments is parsed correctly. + """ path, args = parse_args('/index.html') self.assertEqual('/index.html', path) self.assertEqual(0, len(args)) def test_parse_get_root_getargs(self): + """ + Test that a GET request for '/' with scalar arguments is parsed correctly. + """ path, args = parse_args('/?foo=42&bar=1337&baz=Hello,%20World!') self.assertEqual('/', path) self.assertEqual(3, len(args)) @@ -34,6 +43,9 @@ class TestParseRequest(unittest.TestCase): self.assertEqual('Hello, World!', args['baz'].get_str()) def test_parse_get_getargs(self): + """ + Test that a GET request for an arbitrary path with scalar arguments is parsed correctly. + """ path, args = parse_args('/abc/def?foo=42&bar=1337&baz=Hello,%20World!') self.assertEqual('/abc/def', path) self.assertEqual(3, len(args)) @@ -51,6 +63,9 @@ class TestParseRequest(unittest.TestCase): self.assertEqual('Hello, World!', args['baz'].get_str()) def test_parse_get_getarray(self): + """ + Test that a GET request with mixed scalar and array arguments is parsed correctly. + """ path, args = parse_args('/abc/def?foo=42&foo=1337&baz=Hello,%20World!') self.assertEqual('/abc/def', path) self.assertEqual(2, len(args)) @@ -63,6 +78,9 @@ class TestParseRequest(unittest.TestCase): self.assertEqual('1337', args['foo'].get_str(1)) def test_parse_get_zero_arg(self): + """ + Test that a GET request with an empty argument is parsed correctly. + """ path, args = parse_args('/abc/def?foo=&bar=42') self.assertEqual(2, len(args)) self.assertIn('foo', args) @@ -74,10 +92,16 @@ class TestParseRequest(unittest.TestCase): self.assertEqual('42', args['bar'].get_str()) def test_parse_get_urlencoded_encoding_fail(self): + """ + Test that a GET request with non-decodable escape sequences fails. + """ with self.assertRaises(ValueError): parse_args('/?foo=42&bar=%80&baz=Hello,%20World!') def test_parse_post_urlencoded(self): + """ + Test that a urlencoded POST request with scalar arguments is parsed correctly. + """ path, args = parse_args('/', postbody=b'foo=42&bar=1337&baz=Hello,%20World!', enctype='application/x-www-form-urlencoded') @@ -97,6 +121,9 @@ class TestParseRequest(unittest.TestCase): self.assertEqual('Hello, World!', args['baz'].get_str()) def test_parse_post_urlencoded_array(self): + """ + Test that a urlencoded POST request with mixed scalar and array arguments is parsed correctly. + """ path, args = parse_args('/', postbody=b'foo=42&foo=1337&baz=Hello,%20World!', enctype='application/x-www-form-urlencoded') @@ -111,6 +138,9 @@ class TestParseRequest(unittest.TestCase): self.assertEqual('1337', args['foo'].get_str(1)) def test_parse_post_urlencoded_zero_arg(self): + """ + Test that a urlencoded POST request with an empty argument is parsed correctly. + """ path, args = parse_args('/abc/def', postbody=b'foo=&bar=42', enctype='application/x-www-form-urlencoded') self.assertEqual(2, len(args)) self.assertIn('foo', args) @@ -122,12 +152,18 @@ class TestParseRequest(unittest.TestCase): self.assertEqual('42', args['bar'].get_str()) def test_parse_post_urlencoded_encoding_fail(self): + """ + Test that a urlencoded POST request with a non-decodable escape sequence fails. + """ with self.assertRaises(ValueError): parse_args('/', postbody=b'foo=42&bar=%80&baz=Hello,%20World!', enctype='application/x-www-form-urlencoded') def test_parse_post_multipart_no_args(self): + """ + Test that a multipart POST request with no arguments is parsed correctly. + """ path, args = parse_args('/', postbody=b'--testBoundary1337--\r\n', enctype='multipart/form-data; boundary=testBoundary1337') @@ -135,6 +171,9 @@ class TestParseRequest(unittest.TestCase): self.assertEqual(0, len(args)) def test_parse_post_multipart(self): + """ + Test that a multipart POST request with scalar arguments is parsed correctly. + """ path, args = parse_args('/', postbody=b'--testBoundary1337\r\n' b'Content-Disposition: form-data; name="foo"\r\n' @@ -166,6 +205,9 @@ class TestParseRequest(unittest.TestCase): self.assertEqual('Hello, World!', args['baz'].get_str()) def test_parse_post_multipart_zero_arg(self): + """ + Test that a multipart POST request with an empty argument is parsed correctly. + """ path, args = parse_args('/abc/def', postbody=b'--testBoundary1337\r\n' b'Content-Disposition: form-data; name="foo"\r\n' @@ -215,6 +257,9 @@ class TestParseRequest(unittest.TestCase): self.assertEqual('Hello, World!', args['baz'].get_str()) def test_parse_post_multipart_broken_boundaries(self): + """ + Test that multiple cases with broken multipart boundaries fail. + """ with self.assertRaises(ValueError): # Boundary not defined in Content-Type parse_args('/', @@ -318,6 +363,9 @@ class TestParseRequest(unittest.TestCase): enctype='multipart/form-data; boundary=testBoundary1337') def test_get_post_precedence_urlencoded(self): + """ + Test the precedence of urlencoded POST arguments over GET arguments. + """ path, args = parse_args('/foo?foo=thisshouldnotbethere&bar=isurvived', postbody=b'foo=42&foo=1337&baz=Hello,%20World!', enctype='application/x-www-form-urlencoded') @@ -333,6 +381,9 @@ class TestParseRequest(unittest.TestCase): self.assertEqual('Hello, World!', args['baz'].get_str()) def test_get_post_precedence_multipart(self): + """ + Test the precedence of multipart POST arguments over GET arguments. + """ path, args = parse_args('/foo?foo=thisshouldnotbethere&bar=isurvived', postbody=b'--testBoundary1337\r\n' b'Content-Disposition: form-data; name="foo"\r\n'