Skip to content

Commit c302fe5

Browse files
committed
proxy: Fix issue handling header with multiple values
Some headers support multiple values like 'Set-Cookie'. We were overriding values so that we always get the last header value. Fix this limitation to handle cases where the proxy-target sends multiple values for a header
1 parent e45c7fc commit c302fe5

File tree

2 files changed

+44
-1
lines changed

2 files changed

+44
-1
lines changed

configurable_http_proxy/handlers.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,9 +308,17 @@ async def call_proxy(self, path=None):
308308
return
309309

310310
self.set_status(response.code)
311+
# NOTE: Is there a better way to handle this part ? We are currently using the private _headers
312+
# In tornado 6 - This is Server, Content-Type, Date
313+
existing_headers = {h[0].lower() for h in self._headers.get_all()}
311314
for key, val in response.headers.get_all():
312-
if key.lower() not in ("content-length", "transfer-encoding", "content-encoding", "connection"):
315+
if key.lower() in ("content-length", "transfer-encoding", "content-encoding", "connection"):
316+
# Ignore these headers
317+
continue
318+
if key.lower() in existing_headers: # Replace existing values from the proxy server
313319
self.set_header(key, val)
320+
else:
321+
self.add_header(key, val)
314322
if response.body:
315323
self.write(response.body)
316324
self.set_header("Content-Length", len(response.body))

configurable_http_proxy_test/test_proxy.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,24 @@ async def get(self, path=None):
3232
}
3333
self.set_status(200)
3434
self.set_header("Content-Type", "application/json")
35+
if self.get_argument("with_set_cookie"):
36+
# Values that set-cookie can take:
37+
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
38+
values = {
39+
"Secure": "",
40+
"HttpOnly": "",
41+
"SameSite": "None",
42+
"Path": "/",
43+
"Domain": "example.com",
44+
"Max-Age": "999999",
45+
"Expires": "Fri, 01 Oct 2020 06:12:16 GMT", # .strftime('%a, %d %b %Y %H:%M:%S %Z')
46+
}
47+
self.add_header("Set-Cookie", "key=val")
48+
for name, val in values.items():
49+
self.add_header("Set-Cookie", f"{name}_key=val; {name}={val}")
50+
combined = "; ".join((f"{name}={val}" for name, val in values.items()))
51+
self.add_header("Set-Cookie", f"combined_key=val; {combined}")
52+
3553
self.write(json.dumps(reply))
3654
self.finish()
3755

@@ -448,3 +466,20 @@ def test_custom_headers_higher_priority(self):
448466
reply = json.loads(resp.body)
449467
assert reply["path"] == "/"
450468
assert reply["headers"].get("Testing") == "from_custom"
469+
470+
def test_receiving_headers_setcookie(self):
471+
# When the same header has multiple values - it needs to be handled correctly.
472+
resp = self.fetch("/?with_set_cookie=1")
473+
headers = list(resp.headers.get_all())
474+
cookies = {}
475+
for header_name, header in headers:
476+
if header_name.lower() !='set-cookie':
477+
continue
478+
key, val = header.split("=", 1)
479+
cookies[key] = val
480+
assert "key" in cookies
481+
assert cookies['key'] == 'val'
482+
assert "combined_key" in cookies
483+
assert cookies['combined_key'] == 'val; Secure=; HttpOnly=; SameSite=None; Path=/; Domain=example.com; Max-Age=999999; Expires=Fri, 01 Oct 2020 06:12:16 GMT'
484+
for prefix in ["Secure", "HttpOnly", "SameSite", "Path", "Domain", "Max-Age", "Expires"]:
485+
assert prefix + "_key" in cookies

0 commit comments

Comments
 (0)