Compare commits

...

4 Commits

Author SHA1 Message Date
Eric Eastwood
7e1a2f5a96 Better label lowercase to avoid confusion 2024-11-01 11:40:04 -05:00
Eric Eastwood
150ce230cb Harden test 2024-11-01 11:35:55 -05:00
Eric Eastwood
842304df57 Add changelog 2024-11-01 11:30:34 -05:00
Eric Eastwood
d29cb8abed Remove usage on internal header encoding API 2024-11-01 11:24:22 -05:00
4 changed files with 30 additions and 31 deletions

1
changelog.d/17894.misc Normal file
View File

@@ -0,0 +1 @@
Remove usage of internal header encoding API.

View File

@@ -51,25 +51,17 @@ logger = logging.getLogger(__name__)
# "Hop-by-hop" headers (as opposed to "end-to-end" headers) as defined by RFC2616 # "Hop-by-hop" headers (as opposed to "end-to-end" headers) as defined by RFC2616
# section 13.5.1 and referenced in RFC9110 section 7.6.1. These are meant to only be # section 13.5.1 and referenced in RFC9110 section 7.6.1. These are meant to only be
# consumed by the immediate recipient and not be forwarded on. # consumed by the immediate recipient and not be forwarded on.
HOP_BY_HOP_HEADERS = { HOP_BY_HOP_HEADERS_LOWERCASE = {
"Connection", "connection",
"Keep-Alive", "keep-alive",
"Proxy-Authenticate", "proxy-authenticate",
"Proxy-Authorization", "proxy-authorization",
"TE", "te",
"Trailers", "trailers",
"Transfer-Encoding", "transfer-encoding",
"Upgrade", "upgrade",
} }
assert all(header.lower() == header for header in HOP_BY_HOP_HEADERS_LOWERCASE)
if hasattr(Headers, "_canonicalNameCaps"):
# Twisted < 24.7.0rc1
_canonicalHeaderName = Headers()._canonicalNameCaps # type: ignore[attr-defined]
else:
# Twisted >= 24.7.0rc1
# But note that `_encodeName` still exists on prior versions,
# it just encodes differently
_canonicalHeaderName = Headers()._encodeName
def parse_connection_header_value( def parse_connection_header_value(
@@ -92,12 +84,12 @@ def parse_connection_header_value(
Returns: Returns:
The set of header names that should not be copied over from the remote response. The set of header names that should not be copied over from the remote response.
The keys are capitalized in canonical capitalization. The keys are lowercased.
""" """
extra_headers_to_remove: Set[str] = set() extra_headers_to_remove: Set[str] = set()
if connection_header_value: if connection_header_value:
extra_headers_to_remove = { extra_headers_to_remove = {
_canonicalHeaderName(connection_option.strip()).decode("ascii") connection_option.decode("ascii").strip().lower()
for connection_option in connection_header_value.split(b",") for connection_option in connection_header_value.split(b",")
} }
@@ -194,7 +186,7 @@ class ProxyResource(_AsyncResource):
# The `Connection` header also defines which headers should not be copied over. # The `Connection` header also defines which headers should not be copied over.
connection_header = response_headers.getRawHeaders(b"connection") connection_header = response_headers.getRawHeaders(b"connection")
extra_headers_to_remove = parse_connection_header_value( extra_headers_to_remove_lowercase = parse_connection_header_value(
connection_header[0] if connection_header else None connection_header[0] if connection_header else None
) )
@@ -202,10 +194,10 @@ class ProxyResource(_AsyncResource):
for k, v in response_headers.getAllRawHeaders(): for k, v in response_headers.getAllRawHeaders():
# Do not copy over any hop-by-hop headers. These are meant to only be # Do not copy over any hop-by-hop headers. These are meant to only be
# consumed by the immediate recipient and not be forwarded on. # consumed by the immediate recipient and not be forwarded on.
header_key = k.decode("ascii") header_key_lowercase = k.decode("ascii").lower()
if ( if (
header_key in HOP_BY_HOP_HEADERS header_key_lowercase in HOP_BY_HOP_HEADERS_LOWERCASE
or header_key in extra_headers_to_remove or header_key_lowercase in extra_headers_to_remove_lowercase
): ):
continue continue

View File

@@ -903,12 +903,18 @@ class FederationClientProxyTests(BaseMultiWorkerStreamTestCase):
headers=Headers( headers=Headers(
{ {
"Content-Type": ["application/json"], "Content-Type": ["application/json"],
"Connection": ["close, X-Foo, X-Bar"], # Define some hop-by-hop headers (try with varying casing to
# make sure we still match-up the headers)
"Connection": ["close, X-fOo, X-Bar", "X-baz"],
# Should be removed because it's defined in the `Connection` header # Should be removed because it's defined in the `Connection` header
"X-Foo": ["foo"], "X-Foo": ["foo"],
"X-Bar": ["bar"], "X-Bar": ["bar"],
# (not in canonical case)
"x-baZ": ["baz"],
# Should be removed because it's a hop-by-hop header # Should be removed because it's a hop-by-hop header
"Proxy-Authorization": "abcdef", "Proxy-Authorization": "abcdef",
# Should be removed because it's a hop-by-hop header (not in canonical case)
"transfer-EnCoDiNg": "abcdef",
} }
), ),
) )

View File

@@ -30,19 +30,19 @@ from tests.unittest import TestCase
class ProxyTests(TestCase): class ProxyTests(TestCase):
@parameterized.expand( @parameterized.expand(
[ [
[b"close, X-Foo, X-Bar", {"Close", "X-Foo", "X-Bar"}], [b"close, X-Foo, X-Bar", {"close", "x-foo", "x-bar"}],
# No whitespace # No whitespace
[b"close,X-Foo,X-Bar", {"Close", "X-Foo", "X-Bar"}], [b"close,X-Foo,X-Bar", {"close", "x-foo", "x-bar"}],
# More whitespace # More whitespace
[b"close, X-Foo, X-Bar", {"Close", "X-Foo", "X-Bar"}], [b"close, X-Foo, X-Bar", {"close", "x-foo", "x-bar"}],
# "close" directive in not the first position # "close" directive in not the first position
[b"X-Foo, X-Bar, close", {"X-Foo", "X-Bar", "Close"}], [b"X-Foo, X-Bar, close", {"x-foo", "x-bar", "close"}],
# Normalizes header capitalization # Normalizes header capitalization
[b"keep-alive, x-fOo, x-bAr", {"Keep-Alive", "X-Foo", "X-Bar"}], [b"keep-alive, x-fOo, x-bAr", {"keep-alive", "x-foo", "x-bar"}],
# Handles header names with whitespace # Handles header names with whitespace
[ [
b"keep-alive, x foo, x bar", b"keep-alive, x foo, x bar",
{"Keep-Alive", "X foo", "X bar"}, {"keep-alive", "x foo", "x bar"},
], ],
] ]
) )