Skip to content

Commit fdab133

Browse files
Normalize Content-Length header via de-duplication
Normalize Content-Length header via de-duplication according to RFC 7231 Section 3.3.2. Co-authored-by: Stephen Brown II <[email protected]>
1 parent e3b1347 commit fdab133

File tree

2 files changed

+26
-6
lines changed

2 files changed

+26
-6
lines changed

h11/_headers.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ def raw_items(self):
118118

119119
def normalize_and_validate(headers, _parsed=False):
120120
new_headers = []
121-
saw_content_length = False
121+
seen_content_length = None
122122
saw_transfer_encoding = False
123123
for name, value in headers:
124124
# For headers coming out of the parser, we can safely skip some steps,
@@ -132,11 +132,17 @@ def normalize_and_validate(headers, _parsed=False):
132132
raw_name = name
133133
name = name.lower()
134134
if name == b"content-length":
135-
if saw_content_length:
136-
raise LocalProtocolError("multiple Content-Length headers")
135+
lengths = set(length.strip() for length in value.split(b","))
136+
if len(lengths) != 1:
137+
raise LocalProtocolError("conflicting Content-Length headers")
138+
value = lengths.pop()
137139
validate(_content_length_re, value, "bad Content-Length")
138-
saw_content_length = True
139-
if name == b"transfer-encoding":
140+
if seen_content_length is None:
141+
seen_content_length = value
142+
new_headers.append((raw_name, name, value))
143+
elif seen_content_length != value:
144+
raise LocalProtocolError("conflicting Content-Length headers")
145+
elif name == b"transfer-encoding":
140146
# "A server that receives a request message with a transfer coding
141147
# it does not understand SHOULD respond with 501 (Not
142148
# Implemented)."
@@ -154,7 +160,9 @@ def normalize_and_validate(headers, _parsed=False):
154160
error_status_hint=501,
155161
)
156162
saw_transfer_encoding = True
157-
new_headers.append((raw_name, name, value))
163+
new_headers.append((raw_name, name, value))
164+
else:
165+
new_headers.append((raw_name, name, value))
158166
return Headers(new_headers)
159167

160168

h11/tests/test_headers.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ def test_normalize_and_validate():
5454
normalize_and_validate([("Content-Length", "1x")])
5555
with pytest.raises(LocalProtocolError):
5656
normalize_and_validate([("Content-Length", "1"), ("Content-Length", "2")])
57+
assert normalize_and_validate(
58+
[("Content-Length", "0"), ("Content-Length", "0")]
59+
) == [(b"content-length", b"0")]
60+
assert normalize_and_validate([("Content-Length", "0 , 0")]) == [
61+
(b"content-length", b"0")
62+
]
63+
with pytest.raises(LocalProtocolError):
64+
normalize_and_validate(
65+
[("Content-Length", "1"), ("Content-Length", "1"), ("Content-Length", "2")]
66+
)
67+
with pytest.raises(LocalProtocolError):
68+
normalize_and_validate([("Content-Length", "1 , 1,2")])
5769

5870
# transfer-encoding
5971
assert normalize_and_validate([("Transfer-Encoding", "chunked")]) == [

0 commit comments

Comments
 (0)