50
50
51
51
ASCIISET : Final [Set [str ]] = set (string .printable )
52
52
53
- # See https://tools.ietf .org/html/rfc7230#section-3.1.1
54
- # and https://tools.ietf .org/html/rfc7230#appendix-B
53
+ # See https://www.rfc-editor .org/rfc/rfc9110.html#name-overview
54
+ # and https://www.rfc-editor .org/rfc/rfc9110.html#name-tokens
55
55
#
56
56
# method = token
57
57
# tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
58
58
# "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
59
59
# token = 1*tchar
60
60
METHRE : Final [Pattern [str ]] = re .compile (r"[!#$%&'*+\-.^_`|~0-9A-Za-z]+" )
61
- VERSRE : Final [Pattern [str ]] = re .compile (r"HTTP/(\d+ ).(\d+ )" )
62
- HDRRE : Final [Pattern [bytes ]] = re .compile (rb"[\x00-\x1F\x7F()<>@,;:\[\]={} \t\\\\\" ]" )
61
+ VERSRE : Final [Pattern [str ]] = re .compile (r"HTTP/(\d).(\d)" )
62
+ HDRRE : Final [Pattern [bytes ]] = re .compile (rb"[\x00-\x1F\x7F()<>@,;:\[\]={} \t\"\\ ]" )
63
63
64
64
65
65
class RawRequestMessage (NamedTuple ):
@@ -131,8 +131,11 @@ def parse_headers(
131
131
except ValueError :
132
132
raise InvalidHeader (line ) from None
133
133
134
- bname = bname .strip (b" \t " )
135
- bvalue = bvalue .lstrip ()
134
+ # https://www.rfc-editor.org/rfc/rfc9112.html#section-5.1-2
135
+ if {bname [0 ], bname [- 1 ]} & {32 , 9 }: # {" ", "\t"}
136
+ raise InvalidHeader (line )
137
+
138
+ bvalue = bvalue .lstrip (b" \t " )
136
139
if HDRRE .search (bname ):
137
140
raise InvalidHeader (bname )
138
141
if len (bname ) > self .max_field_size :
@@ -153,6 +156,7 @@ def parse_headers(
153
156
# consume continuation lines
154
157
continuation = line and line [0 ] in (32 , 9 ) # (' ', '\t')
155
158
159
+ # Deprecated: https://www.rfc-editor.org/rfc/rfc9112.html#name-obsolete-line-folding
156
160
if continuation :
157
161
bvalue_lst = [bvalue ]
158
162
while continuation :
@@ -187,10 +191,14 @@ def parse_headers(
187
191
str (header_length ),
188
192
)
189
193
190
- bvalue = bvalue .strip ()
194
+ bvalue = bvalue .strip (b" \t " )
191
195
name = bname .decode ("utf-8" , "surrogateescape" )
192
196
value = bvalue .decode ("utf-8" , "surrogateescape" )
193
197
198
+ # https://www.rfc-editor.org/rfc/rfc9110.html#section-5.5-5
199
+ if "\n " in value or "\r " in value or "\x00 " in value :
200
+ raise InvalidHeader (bvalue )
201
+
194
202
headers .add (name , value )
195
203
raw_headers .append ((bname , bvalue ))
196
204
@@ -301,15 +309,12 @@ def get_content_length() -> Optional[int]:
301
309
if length_hdr is None :
302
310
return None
303
311
304
- try :
305
- length = int ( length_hdr )
306
- except ValueError :
312
+ # Shouldn't allow +/- or other number formats.
313
+ # https://www.rfc-editor.org/rfc/rfc9110#section-8.6-2
314
+ if not length_hdr . strip ( " \t " ). isdigit () :
307
315
raise InvalidHeader (CONTENT_LENGTH )
308
316
309
- if length < 0 :
310
- raise InvalidHeader (CONTENT_LENGTH )
311
-
312
- return length
317
+ return int (length_hdr )
313
318
314
319
length = get_content_length ()
315
320
# do not support old websocket spec
@@ -449,6 +454,15 @@ def parse_headers(
449
454
upgrade = False
450
455
chunked = False
451
456
457
+ # https://www.rfc-editor.org/rfc/rfc9110.html#section-5.5-6
458
+ # https://www.rfc-editor.org/rfc/rfc9110.html#name-collected-abnf
459
+ singletons = (hdrs .CONTENT_LENGTH , hdrs .CONTENT_LOCATION , hdrs .CONTENT_RANGE ,
460
+ hdrs .CONTENT_TYPE , hdrs .ETAG , hdrs .HOST , hdrs .MAX_FORWARDS ,
461
+ hdrs .SERVER , hdrs .TRANSFER_ENCODING , hdrs .USER_AGENT )
462
+ bad_hdr = next ((h for h in singletons if len (headers .getall (h , ())) > 1 ), None )
463
+ if bad_hdr is not None :
464
+ raise BadHttpMessage ("Duplicate '{}' header found." .format (bad_hdr ))
465
+
452
466
# keep-alive
453
467
conn = headers .get (hdrs .CONNECTION )
454
468
if conn :
@@ -502,7 +516,7 @@ def parse_message(self, lines: List[bytes]) -> RawRequestMessage:
502
516
# request line
503
517
line = lines [0 ].decode ("utf-8" , "surrogateescape" )
504
518
try :
505
- method , path , version = line .split (None , 2 )
519
+ method , path , version = line .split (maxsplit = 2 )
506
520
except ValueError :
507
521
raise BadStatusLine (line ) from None
508
522
@@ -516,14 +530,10 @@ def parse_message(self, lines: List[bytes]) -> RawRequestMessage:
516
530
raise BadStatusLine (method )
517
531
518
532
# version
519
- try :
520
- if version .startswith ("HTTP/" ):
521
- n1 , n2 = version [5 :].split ("." , 1 )
522
- version_o = HttpVersion (int (n1 ), int (n2 ))
523
- else :
524
- raise BadStatusLine (version )
525
- except Exception :
526
- raise BadStatusLine (version )
533
+ match = VERSRE .match (version )
534
+ if match is None :
535
+ raise BadStatusLine (line )
536
+ version_o = HttpVersion (int (match .group (1 )), int (match .group (2 )))
527
537
528
538
if method == "CONNECT" :
529
539
# authority-form,
@@ -590,12 +600,12 @@ class HttpResponseParser(HttpParser[RawResponseMessage]):
590
600
def parse_message (self , lines : List [bytes ]) -> RawResponseMessage :
591
601
line = lines [0 ].decode ("utf-8" , "surrogateescape" )
592
602
try :
593
- version , status = line .split (None , 1 )
603
+ version , status = line .split (maxsplit = 1 )
594
604
except ValueError :
595
605
raise BadStatusLine (line ) from None
596
606
597
607
try :
598
- status , reason = status .split (None , 1 )
608
+ status , reason = status .split (maxsplit = 1 )
599
609
except ValueError :
600
610
reason = ""
601
611
@@ -611,13 +621,9 @@ def parse_message(self, lines: List[bytes]) -> RawResponseMessage:
611
621
version_o = HttpVersion (int (match .group (1 )), int (match .group (2 )))
612
622
613
623
# The status code is a three-digit number
614
- try :
615
- status_i = int (status )
616
- except ValueError :
617
- raise BadStatusLine (line ) from None
618
-
619
- if status_i > 999 :
624
+ if len (status ) != 3 or not status .isdigit ():
620
625
raise BadStatusLine (line )
626
+ status_i = int (status )
621
627
622
628
# read headers
623
629
(
@@ -751,14 +757,13 @@ def feed_data(
751
757
else :
752
758
size_b = chunk [:pos ]
753
759
754
- try :
755
- size = int (bytes (size_b ), 16 )
756
- except ValueError :
760
+ if not size_b .isdigit ():
757
761
exc = TransferEncodingError (
758
762
chunk [:pos ].decode ("ascii" , "surrogateescape" )
759
763
)
760
764
self .payload .set_exception (exc )
761
- raise exc from None
765
+ raise exc
766
+ size = int (bytes (size_b ), 16 )
762
767
763
768
chunk = chunk [pos + 2 :]
764
769
if size == 0 : # eof marker
0 commit comments