diff --git a/matemat/webserver/httpd.py b/matemat/webserver/httpd.py index 79efb98..c59e3fc 100644 --- a/matemat/webserver/httpd.py +++ b/matemat/webserver/httpd.py @@ -13,7 +13,7 @@ from uuid import uuid4 from datetime import datetime, timedelta from matemat import __version__ as matemat_version -from matemat.webserver import RequestArgument +from matemat.webserver import RequestArguments from matemat.webserver.util import parse_args @@ -31,7 +31,7 @@ BaseHTTPRequestHandler.log_error = lambda self, fstring='', *args: None # Dictionary to hold registered pagelet paths and their handler functions _PAGELET_PATHS: Dict[str, Callable[[str, # HTTP method (GET, POST, ...) str, # Request path - Dict[str, RequestArgument], # args: (name, argument) + RequestArguments, # HTTP Request arguments Dict[str, Any], # Session vars Dict[str, str]], # Response headers Tuple[int, Union[bytes, str]]]] = dict() # Returns: (status code, response body) @@ -51,7 +51,7 @@ def pagelet(path: str): (method: str, path: str, - args: Dict[str, RequestArgument], + args: RequestArguments, session_vars: Dict[str, Any], headers: Dict[str, str]) -> (int, Optional[Union[str, bytes]]) @@ -69,7 +69,7 @@ def pagelet(path: str): def http_handler(fun: Callable[[str, str, - Dict[str, RequestArgument], + RequestArguments, Dict[str, Any], Dict[str, str]], Tuple[int, Union[bytes, str]]]): @@ -181,7 +181,7 @@ class HttpHandler(BaseHTTPRequestHandler): if session_id in self.server.session_vars: del self.server.session_vars[session_id] - def _handle(self, method: str, path: str, args: Dict[str, RequestArgument]) -> None: + def _handle(self, method: str, path: str, args: RequestArguments) -> None: """ Handle a HTTP request by either dispatching it to the appropriate pagelet or by serving a static resource. diff --git a/matemat/webserver/pagelets/login.py b/matemat/webserver/pagelets/login.py index f7813b4..7d0cc2d 100644 --- a/matemat/webserver/pagelets/login.py +++ b/matemat/webserver/pagelets/login.py @@ -2,7 +2,7 @@ from typing import Any, Dict, Optional, Tuple, Union from matemat.exceptions import AuthenticationError -from matemat.webserver import pagelet, RequestArgument +from matemat.webserver import pagelet, RequestArguments from matemat.primitives import User from matemat.db import MatematDatabase @@ -10,7 +10,7 @@ from matemat.db import MatematDatabase @pagelet('/login') def login_page(method: str, path: str, - args: Dict[str, RequestArgument], + args: RequestArguments, session_vars: Dict[str, Any], headers: Dict[str, str])\ -> Tuple[int, Optional[Union[str, bytes]]]: @@ -41,13 +41,11 @@ def login_page(method: str, ''' - return 200, data.format(msg=args['msg'] if 'msg' in args else '') + return 200, data.format(msg=str(args.msg) if 'msg' in args else '') elif method == 'POST': - username: RequestArgument = args['username'] - password: RequestArgument = args['password'] with MatematDatabase('test.db') as db: try: - user: User = db.login(username.get_str(), password.get_str()) + user: User = db.login(str(args.username), str(args.password)) except AuthenticationError: headers['Location'] = '/login?msg=Username%20or%20password%20wrong.%20Please%20try%20again.' return 301, bytes() diff --git a/matemat/webserver/pagelets/logout.py b/matemat/webserver/pagelets/logout.py index b70d7c1..beb86a3 100644 --- a/matemat/webserver/pagelets/logout.py +++ b/matemat/webserver/pagelets/logout.py @@ -1,13 +1,13 @@ from typing import Any, Dict, List, Optional, Tuple, Union -from matemat.webserver import pagelet, RequestArgument +from matemat.webserver import pagelet, RequestArguments @pagelet('/logout') def logout(method: str, path: str, - args: Dict[str, RequestArgument], + args: RequestArguments, session_vars: Dict[str, Any], headers: Dict[str, str])\ -> Tuple[int, Optional[Union[str, bytes]]]: diff --git a/matemat/webserver/pagelets/main.py b/matemat/webserver/pagelets/main.py index 2b9ce79..e22c872 100644 --- a/matemat/webserver/pagelets/main.py +++ b/matemat/webserver/pagelets/main.py @@ -1,7 +1,7 @@ -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Dict, Optional, Tuple, Union -from matemat.webserver import MatematWebserver, pagelet, RequestArgument +from matemat.webserver import pagelet, RequestArguments from matemat.primitives import User from matemat.db import MatematDatabase @@ -9,7 +9,7 @@ from matemat.db import MatematDatabase @pagelet('/') def main_page(method: str, path: str, - args: Dict[str, RequestArgument], + args: RequestArguments, session_vars: Dict[str, Any], headers: Dict[str, str])\ -> Tuple[int, Optional[Union[str, bytes]]]: diff --git a/matemat/webserver/pagelets/touchkey.py b/matemat/webserver/pagelets/touchkey.py index 22e3df4..4de8009 100644 --- a/matemat/webserver/pagelets/touchkey.py +++ b/matemat/webserver/pagelets/touchkey.py @@ -1,8 +1,8 @@ -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Dict, Optional, Tuple, Union from matemat.exceptions import AuthenticationError -from matemat.webserver import pagelet, RequestArgument +from matemat.webserver import pagelet, RequestArguments from matemat.primitives import User from matemat.db import MatematDatabase @@ -10,7 +10,7 @@ from matemat.db import MatematDatabase @pagelet('/touchkey') def touchkey_page(method: str, path: str, - args: Dict[str, RequestArgument], + args: RequestArguments, session_vars: Dict[str, Any], headers: Dict[str, str])\ -> Tuple[int, Optional[Union[str, bytes]]]: @@ -40,13 +40,11 @@ def touchkey_page(method: str, ''' - return 200, data.format(username=args['username'] if 'username' in args else '') + return 200, data.format(username=str(args.username) if 'username' in args else '') elif method == 'POST': - username: RequestArgument = args['username'] - touchkey: RequestArgument = args['touchkey'] with MatematDatabase('test.db') as db: try: - user: User = db.login(username.get_str(), touchkey=touchkey.get_str()) + user: User = db.login(str(args.username), touchkey=str(args.touchkey)) except AuthenticationError: headers['Location'] = f'/touchkey?username={args["username"]}&msg=Please%20try%20again.' return 301, bytes() diff --git a/matemat/webserver/test/abstract_httpd_test.py b/matemat/webserver/test/abstract_httpd_test.py index daa1126..103979b 100644 --- a/matemat/webserver/test/abstract_httpd_test.py +++ b/matemat/webserver/test/abstract_httpd_test.py @@ -1,5 +1,5 @@ -from typing import Any, Callable, Dict, List, Tuple, Union +from typing import Any, Callable, Dict, Tuple, Union import unittest.mock from io import BytesIO @@ -9,7 +9,7 @@ from abc import ABC from datetime import datetime from http.server import HTTPServer -from matemat.webserver import pagelet, RequestArgument +from matemat.webserver import pagelet, RequestArguments class HttpResponse: @@ -158,14 +158,14 @@ def test_pagelet(path: str): def with_testing_headers(fun: Callable[[str, str, - Dict[str, RequestArgument], + RequestArguments, Dict[str, Any], Dict[str, str]], Tuple[int, Union[bytes, str]]]): @pagelet(path) def testing_wrapper(method: str, path: str, - args: Dict[str, RequestArgument], + args: RequestArguments, session_vars: Dict[str, Any], headers: Dict[str, str]): status, body = fun(method, path, args, session_vars, headers) diff --git a/matemat/webserver/test/test_parse_request.py b/matemat/webserver/test/test_parse_request.py index a533936..0a94065 100644 --- a/matemat/webserver/test/test_parse_request.py +++ b/matemat/webserver/test/test_parse_request.py @@ -20,9 +20,9 @@ class TestParseRequest(unittest.TestCase): path, args = parse_args('/?foo=42&bar=1337&baz=Hello,%20World!') self.assertEqual('/', path) self.assertEqual(3, len(args)) - self.assertIn('foo', args.keys()) - self.assertIn('bar', args.keys()) - self.assertIn('baz', args.keys()) + 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) @@ -37,9 +37,9 @@ class TestParseRequest(unittest.TestCase): path, args = parse_args('/abc/def?foo=42&bar=1337&baz=Hello,%20World!') self.assertEqual('/abc/def', path) self.assertEqual(3, len(args)) - self.assertIn('foo', args.keys()) - self.assertIn('bar', args.keys()) - self.assertIn('baz', args.keys()) + 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) @@ -54,8 +54,8 @@ class TestParseRequest(unittest.TestCase): path, args = parse_args('/abc/def?foo=42&foo=1337&baz=Hello,%20World!') self.assertEqual('/abc/def', path) self.assertEqual(2, len(args)) - self.assertIn('foo', args.keys()) - self.assertIn('baz', args.keys()) + self.assertIn('foo', args) + self.assertIn('baz', args) self.assertTrue(args['foo'].is_array) self.assertTrue(args['baz'].is_scalar) self.assertEqual(2, len(args['foo'])) @@ -65,8 +65,8 @@ class TestParseRequest(unittest.TestCase): def test_parse_get_zero_arg(self): path, args = parse_args('/abc/def?foo=&bar=42') self.assertEqual(2, len(args)) - self.assertIn('foo', args.keys()) - self.assertIn('bar', args.keys()) + self.assertIn('foo', args) + self.assertIn('bar', args) self.assertTrue(args['foo'].is_scalar) self.assertTrue(args['bar'].is_scalar) self.assertEqual(1, len(args['foo'])) @@ -83,9 +83,9 @@ class TestParseRequest(unittest.TestCase): enctype='application/x-www-form-urlencoded') self.assertEqual('/', path) self.assertEqual(3, len(args)) - self.assertIn('foo', args.keys()) - self.assertIn('bar', args.keys()) - self.assertIn('baz', args.keys()) + 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) @@ -102,8 +102,8 @@ class TestParseRequest(unittest.TestCase): enctype='application/x-www-form-urlencoded') self.assertEqual('/', path) self.assertEqual(2, len(args)) - self.assertIn('foo', args.keys()) - self.assertIn('baz', args.keys()) + self.assertIn('foo', args) + self.assertIn('baz', args) self.assertTrue(args['foo'].is_array) self.assertTrue(args['baz'].is_scalar) self.assertEqual(2, len(args['foo'])) @@ -113,8 +113,8 @@ class TestParseRequest(unittest.TestCase): def test_parse_post_urlencoded_zero_arg(self): 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.keys()) - self.assertIn('bar', args.keys()) + self.assertIn('foo', args) + self.assertIn('bar', args) self.assertTrue(args['foo'].is_scalar) self.assertTrue(args['bar'].is_scalar) self.assertEqual(1, len(args['foo'])) @@ -152,9 +152,9 @@ class TestParseRequest(unittest.TestCase): enctype='multipart/form-data; boundary=testBoundary1337') self.assertEqual('/', path) self.assertEqual(3, len(args)) - self.assertIn('foo', args.keys()) - self.assertIn('bar', args.keys()) - self.assertIn('baz', args.keys()) + 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) @@ -177,8 +177,8 @@ class TestParseRequest(unittest.TestCase): b'--testBoundary1337--\r\n', enctype='multipart/form-data; boundary=testBoundary1337') self.assertEqual(2, len(args)) - self.assertIn('foo', args.keys()) - self.assertIn('bar', args.keys()) + self.assertIn('foo', args) + self.assertIn('bar', args) self.assertTrue(args['foo'].is_scalar) self.assertTrue(args['bar'].is_scalar) self.assertEqual(1, len(args['foo'])) diff --git a/matemat/webserver/test/test_post.py b/matemat/webserver/test/test_post.py index 9b2fe22..0bc5d16 100644 --- a/matemat/webserver/test/test_post.py +++ b/matemat/webserver/test/test_post.py @@ -1,7 +1,7 @@ -from typing import Any, Dict, List, Tuple, Union +from typing import Any, Dict, List -from matemat.webserver import HttpHandler, RequestArgument +from matemat.webserver import HttpHandler, RequestArguments from matemat.webserver.test.abstract_httpd_test import AbstractHttpdTest, test_pagelet import codecs @@ -10,7 +10,7 @@ import codecs @test_pagelet('/just/testing/post') def post_test_pagelet(method: str, path: str, - args: Dict[str, RequestArgument], + args: RequestArguments, session_vars: Dict[str, Any], headers: Dict[str, str]): """ @@ -18,12 +18,12 @@ def post_test_pagelet(method: str, """ headers['Content-Type'] = 'text/plain' dump: str = '' - for k, ra in args.items(): + for ra in args: for a in ra: if a.get_content_type().startswith('text/'): - dump += f'{k}: {a.get_str()}\n' + dump += f'{a.name}: {a.get_str()}\n' else: - dump += f'{k}: {codecs.encode(a.get_bytes(), "hex").decode("utf-8")}\n' + dump += f'{a.name}: {codecs.encode(a.get_bytes(), "hex").decode("utf-8")}\n' return 200, dump diff --git a/matemat/webserver/test/test_serve.py b/matemat/webserver/test/test_serve.py index 7e159e3..722870b 100644 --- a/matemat/webserver/test/test_serve.py +++ b/matemat/webserver/test/test_serve.py @@ -1,16 +1,16 @@ -from typing import Any, Dict, Union +from typing import Any, Dict import os import os.path -from matemat.webserver import HttpHandler, RequestArgument +from matemat.webserver import HttpHandler, RequestArguments from matemat.webserver.test.abstract_httpd_test import AbstractHttpdTest, test_pagelet @test_pagelet('/just/testing/serve_pagelet_ok') def serve_test_pagelet_ok(method: str, path: str, - args: Dict[str, RequestArgument], + args: RequestArguments, session_vars: Dict[str, Any], headers: Dict[str, str]): headers['Content-Type'] = 'text/plain' @@ -20,7 +20,7 @@ def serve_test_pagelet_ok(method: str, @test_pagelet('/just/testing/serve_pagelet_fail') def serve_test_pagelet_fail(method: str, path: str, - args: Dict[str, RequestArgument], + args: RequestArguments, session_vars: Dict[str, Any], headers: Dict[str, str]): session_vars['test'] = 'hello, world!' diff --git a/matemat/webserver/test/test_session.py b/matemat/webserver/test/test_session.py index fe30529..5cf408e 100644 --- a/matemat/webserver/test/test_session.py +++ b/matemat/webserver/test/test_session.py @@ -1,17 +1,17 @@ -from typing import Any, Dict, Union +from typing import Any, Dict from datetime import datetime, timedelta from time import sleep -from matemat.webserver import HttpHandler, RequestArgument +from matemat.webserver import HttpHandler, RequestArguments from matemat.webserver.test.abstract_httpd_test import AbstractHttpdTest, test_pagelet @test_pagelet('/just/testing/sessions') def session_test_pagelet(method: str, path: str, - args: Dict[str, RequestArgument], + args: RequestArguments, session_vars: Dict[str, Any], headers: Dict[str, str]): session_vars['test'] = 'hello, world!' diff --git a/matemat/webserver/util.py b/matemat/webserver/util.py index 61cf430..2bc2244 100644 --- a/matemat/webserver/util.py +++ b/matemat/webserver/util.py @@ -1,9 +1,9 @@ -from typing import Dict, List, Tuple, Optional, Union +from typing import Dict, List, Tuple, Optional import urllib.parse -from matemat.webserver import RequestArgument +from matemat.webserver import RequestArguments, RequestArgument def _parse_multipart(body: bytes, boundary: str) -> List[RequestArgument]: @@ -76,7 +76,7 @@ def _parse_multipart(body: bytes, boundary: str) -> List[RequestArgument]: def parse_args(request: str, postbody: Optional[bytes] = None, enctype: str = 'text/plain') \ - -> Tuple[str, Dict[str, RequestArgument]]: + -> Tuple[str, RequestArguments]: """ Given a HTTP request path, and optionally a HTTP POST body in application/x-www-form-urlencoded or multipart/form-data form, parse the arguments and return them as a dictionary. @@ -98,11 +98,11 @@ def parse_args(request: str, postbody: Optional[bytes] = None, enctype: str = 't else: getargs = urllib.parse.parse_qs(tokens.query, strict_parsing=True, keep_blank_values=True, errors='strict') - args: Dict[str, RequestArgument] = dict() - for k, v in getargs.items(): - args[k] = RequestArgument(k) - for _v in v: - args[k].append('text/plain', _v) + args = RequestArguments() + for k, vs in getargs.items(): + args[k].clear() + for v in vs: + args[k].append('text/plain', v) if postbody is not None: if enctype == 'application/x-www-form-urlencoded': @@ -113,10 +113,10 @@ def parse_args(request: str, postbody: Optional[bytes] = None, enctype: str = 't else: postargs = urllib.parse.parse_qs(pb, strict_parsing=True, keep_blank_values=True, errors='strict') # Write all POST values into the dict, overriding potential duplicates from GET - for k, v in postargs.items(): - args[k] = RequestArgument(k) - for _v in v: - args[k].append('text/plain', _v) + for k, vs in postargs.items(): + args[k].clear() + for v in vs: + args[k].append('text/plain', v) elif enctype.startswith('multipart/form-data'): # Parse the multipart boundary from the Content-Type header try: @@ -126,7 +126,9 @@ def parse_args(request: str, postbody: Optional[bytes] = None, enctype: str = 't # Parse the multipart body mpargs = _parse_multipart(postbody, boundary) for ra in mpargs: - args[ra.name] = ra + args[ra.name].clear() + for a in ra: + args[ra.name].append(a.get_content_type(), bytes(a)) else: raise ValueError(f'Unsupported Content-Type: {enctype}') # Return the path and the parsed arguments