Refactor forwarded header handling into opt-in kaya-forwarded package with trusted CIDRs
CI / Build Pip package (push) Successful in 3m59s

This commit is contained in:
2026-09-05 15:11:05 +08:00
parent 850d5dda35
commit aa35e2d30c
17 changed files with 643 additions and 222 deletions
-59
View File
@@ -62,11 +62,6 @@ class AsgiTest(unittest.TestCase):
async def handle_request(ctx: HttpContext, _: List[str]) -> None:
await ctx.stream_body(200, (chunk async for chunk in ctx.request_body))
@self.app.GET('/client-ip')
async def handle_request(ctx: HttpContext) -> None:
host, port = ctx.client if ctx.client is not None else (None, None)
await ctx.send_str(200, json.dumps({'host': host, 'port': port}))
@async_test
async def test_hello(self):
transport = httpx.ASGITransport(app=self.app)
@@ -195,60 +190,6 @@ class AsgiTest(unittest.TestCase):
'employee_id': 101325
}, response)
@async_test
async def test_client_ip_forwarded(self):
transport = httpx.ASGITransport(app=self.app)
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:80") as client:
# socket peer, no forwarded headers
r = await client.get("/client-ip")
socket_client = json.loads(r.text)
self.assertEqual('127.0.0.1', socket_client['host'])
# RFC 7239 Forwarded header, with port
r = await client.get("/client-ip", headers={'Forwarded': 'for=203.0.113.5:1234'})
self.assertEqual({'host': '203.0.113.5', 'port': 1234}, json.loads(r.text))
# RFC 7239 Forwarded header, bracketed IPv6 with port
r = await client.get("/client-ip", headers={'Forwarded': 'for="[2001:db8::1]:4711"'})
self.assertEqual({'host': '2001:db8::1', 'port': 4711}, json.loads(r.text))
# RFC 7239 Forwarded header without port keeps the socket port
r = await client.get("/client-ip", headers={'Forwarded': 'for=203.0.113.5'})
self.assertEqual({'host': '203.0.113.5', 'port': socket_client['port']}, json.loads(r.text))
# Forwarded with for=unknown falls through to X-Forwarded-For
r = await client.get("/client-ip", headers={
'Forwarded': 'for=unknown',
'X-Forwarded-For': '198.51.100.7',
})
self.assertEqual('198.51.100.7', json.loads(r.text)['host'])
# Forwarded takes precedence over X-Forwarded-For
r = await client.get("/client-ip", headers={
'Forwarded': 'for=203.0.113.5',
'X-Forwarded-For': '198.51.100.7',
})
self.assertEqual('203.0.113.5', json.loads(r.text)['host'])
# X-Forwarded-For: first entry of the chain, port from X-Forwarded-Port
r = await client.get("/client-ip", headers={
'X-Forwarded-For': '203.0.113.5, 70.41.3.18',
'X-Forwarded-Port': '8443',
})
self.assertEqual({'host': '203.0.113.5', 'port': 8443}, json.loads(r.text))
# X-Forwarded-Host fallback
r = await client.get("/client-ip", headers={'X-Forwarded-Host': '198.51.100.7'})
self.assertEqual('198.51.100.7', json.loads(r.text)['host'])
# invalid X-Forwarded-Port is ignored, socket port is kept
r = await client.get("/client-ip", headers={
'X-Forwarded-For': '203.0.113.5',
'X-Forwarded-Port': 'not-a-port',
})
self.assertEqual({'host': '203.0.113.5', 'port': socket_client['port']}, json.loads(r.text))
@async_test
async def test_nested_param_routes(self):
app = KayaApp()