88"""
99
1010try :
11- from typing import Dict , Tuple , Union , TYPE_CHECKING
11+ from typing import List , Dict , Tuple , Union , Any , TYPE_CHECKING
1212 from socket import socket
1313 from socketpool import SocketPool
1414
2222from .headers import Headers
2323
2424
25+ class _IFieldStorage :
26+ """Interface with shared methods for QueryParams and FormData."""
27+
28+ _storage : Dict [str , List [Union [str , bytes ]]]
29+
30+ def _add_field_value (self , field_name : str , value : Union [str , bytes ]) -> None :
31+ if field_name not in self ._storage :
32+ self ._storage [field_name ] = [value ]
33+ else :
34+ self ._storage [field_name ].append (value )
35+
36+ def get (self , field_name : str , default : Any = None ) -> Union [str , bytes , None ]:
37+ """Get the value of a field."""
38+ return self ._storage .get (field_name , [default ])[0 ]
39+
40+ def get_list (self , field_name : str ) -> List [Union [str , bytes ]]:
41+ """Get the list of values of a field."""
42+ return self ._storage .get (field_name , [])
43+
44+ @property
45+ def fields (self ):
46+ """Returns a list of field names."""
47+ return list (self ._storage .keys ())
48+
49+ def __getitem__ (self , field_name : str ):
50+ return self .get (field_name )
51+
52+ def __iter__ (self ):
53+ return iter (self ._storage )
54+
55+ def __len__ (self ):
56+ return len (self ._storage )
57+
58+ def __contains__ (self , key : str ):
59+ return key in self ._storage
60+
61+ def __repr__ (self ) -> str :
62+ return f"{ self .__class__ .__name__ } ({ repr (self ._storage )} )"
63+
64+
65+ class QueryParams (_IFieldStorage ):
66+ """
67+ Class for parsing and storing GET quer parameters requests.
68+
69+ Examples::
70+
71+ query_params = QueryParams(b"foo=bar&baz=qux&baz=quux")
72+ # QueryParams({"foo": "bar", "baz": ["qux", "quux"]})
73+
74+ query_params.get("foo") # "bar"
75+ query_params["foo"] # "bar"
76+ query_params.get("non-existent-key") # None
77+ query_params.get_list("baz") # ["qux", "quux"]
78+ "unknown-key" in query_params # False
79+ query_params.fields # ["foo", "baz"]
80+ """
81+
82+ _storage : Dict [str , List [Union [str , bytes ]]]
83+
84+ def __init__ (self , query_string : str ) -> None :
85+ self ._storage = {}
86+
87+ for query_param in query_string .split ("&" ):
88+ if "=" in query_param :
89+ key , value = query_param .split ("=" , 1 )
90+ self ._add_field_value (key , value )
91+ elif query_param :
92+ self ._add_field_value (query_param , "" )
93+
94+
95+ class FormData (_IFieldStorage ):
96+ """
97+ Class for parsing and storing form data from POST requests.
98+
99+ Supports ``application/x-www-form-urlencoded``, ``multipart/form-data`` and ``text/plain``
100+ content types.
101+
102+ Examples::
103+
104+ form_data = FormData(b"foo=bar&baz=qux&baz=quuz", "application/x-www-form-urlencoded")
105+ # or
106+ form_data = FormData(b"foo=bar\\ r\\ nbaz=qux\\ r\\ nbaz=quux", "text/plain")
107+ # FormData({"foo": "bar", "baz": "qux"})
108+
109+ form_data.get("foo") # "bar"
110+ form_data["foo"] # "bar"
111+ form_data.get("non-existent-key") # None
112+ form_data.get_list("baz") # ["qux", "quux"]
113+ "unknown-key" in form_data # False
114+ form_data.fields # ["foo", "baz"]
115+ """
116+
117+ _storage : Dict [str , List [Union [str , bytes ]]]
118+
119+ def __init__ (self , data : bytes , content_type : str ) -> None :
120+ self .content_type = content_type
121+ self ._storage = {}
122+
123+ if content_type .startswith ("application/x-www-form-urlencoded" ):
124+ self ._parse_x_www_form_urlencoded (data )
125+
126+ elif content_type .startswith ("multipart/form-data" ):
127+ boundary = content_type .split ("boundary=" )[1 ]
128+ self ._parse_multipart_form_data (data , boundary )
129+
130+ elif content_type .startswith ("text/plain" ):
131+ self ._parse_text_plain (data )
132+
133+ def _parse_x_www_form_urlencoded (self , data : bytes ) -> None :
134+ decoded_data = data .decode ()
135+
136+ for field_name , value in [
137+ key_value .split ("=" , 1 ) for key_value in decoded_data .split ("&" )
138+ ]:
139+ self ._add_field_value (field_name , value )
140+
141+ def _parse_multipart_form_data (self , data : bytes , boundary : str ) -> None :
142+ blocks = data .split (b"--" + boundary .encode ())[1 :- 1 ]
143+
144+ for block in blocks :
145+ disposition , content = block .split (b"\r \n \r \n " , 1 )
146+ field_name = disposition .split (b'"' , 2 )[1 ].decode ()
147+ value = content [:- 2 ]
148+
149+ self ._add_field_value (field_name , value )
150+
151+ def _parse_text_plain (self , data : bytes ) -> None :
152+ lines = data .split (b"\r \n " )[:- 1 ]
153+
154+ for line in lines :
155+ field_name , value = line .split (b"=" , 1 )
156+
157+ self ._add_field_value (field_name .decode (), value .decode ())
158+
159+
25160class Request :
26161 """
27162 Incoming request, constructed from raw incoming bytes.
@@ -44,8 +179,7 @@ class Request:
44179
45180 Example::
46181
47- request.client_address
48- # ('192.168.137.1', 40684)
182+ request.client_address # ('192.168.137.1', 40684)
49183 """
50184
51185 method : str
@@ -54,15 +188,17 @@ class Request:
54188 path : str
55189 """Path of the request, e.g. ``"/foo/bar"``."""
56190
57- query_params : Dict [ str , str ]
191+ query_params : QueryParams
58192 """
59193 Query/GET parameters in the request.
60194
61195 Example::
62196
63- request = Request(raw_request=b"GET /?foo=bar HTTP/1.1...")
64- request.query_params
65- # {"foo": "bar"}
197+ request = Request(..., raw_request=b"GET /?foo=bar&baz=qux HTTP/1.1...")
198+
199+ request.query_params # QueryParams({"foo": "bar"})
200+ request.query_params["foo"] # "bar"
201+ request.query_params.get_list("baz") # ["qux"]
66202 """
67203
68204 http_version : str
@@ -91,6 +227,7 @@ def __init__(
91227 self .connection = connection
92228 self .client_address = client_address
93229 self .raw_request = raw_request
230+ self ._form_data = None
94231
95232 if raw_request is None :
96233 raise ValueError ("raw_request cannot be None" )
@@ -117,6 +254,53 @@ def body(self) -> bytes:
117254 def body (self , body : bytes ) -> None :
118255 self .raw_request = self ._raw_header_bytes + b"\r \n \r \n " + body
119256
257+ @property
258+ def form_data (self ) -> Union [FormData , None ]:
259+ """
260+ POST data of the request.
261+
262+ Example::
263+
264+ # application/x-www-form-urlencoded
265+ request = Request(...,
266+ raw_request=b\" \" \" ...
267+ foo=bar&baz=qux\" \" \"
268+ )
269+
270+ # or
271+
272+ # multipart/form-data
273+ request = Request(...,
274+ raw_request=b\" \" \" ...
275+ --boundary
276+ Content-Disposition: form-data; name="foo"
277+
278+ bar
279+ --boundary
280+ Content-Disposition: form-data; name="baz"
281+
282+ qux
283+ --boundary--\" \" \"
284+ )
285+
286+ # or
287+
288+ # text/plain
289+ request = Request(...,
290+ raw_request=b\" \" \" ...
291+ foo=bar
292+ baz=qux
293+ \" \" \"
294+ )
295+
296+ request.form_data # FormData({'foo': ['bar'], 'baz': ['qux']})
297+ request.form_data["foo"] # "bar"
298+ request.form_data.get_list("baz") # ["qux"]
299+ """
300+ if self ._form_data is None and self .method == "POST" :
301+ self ._form_data = FormData (self .body , self .headers ["Content-Type" ])
302+ return self ._form_data
303+
120304 def json (self ) -> Union [dict , None ]:
121305 """Body of the request, as a JSON-decoded dictionary."""
122306 return json .loads (self .body ) if self .body else None
@@ -148,13 +332,7 @@ def _parse_start_line(header_bytes: bytes) -> Tuple[str, str, Dict[str, str], st
148332
149333 path , query_string = path .split ("?" , 1 )
150334
151- query_params = {}
152- for query_param in query_string .split ("&" ):
153- if "=" in query_param :
154- key , value = query_param .split ("=" , 1 )
155- query_params [key ] = value
156- elif query_param :
157- query_params [query_param ] = ""
335+ query_params = QueryParams (query_string )
158336
159337 return method , path , query_params , http_version
160338
0 commit comments