"""Tests for HTTPServerTask — TDD: tests before implementation.""" import pytest from i2p_apps.i2ptunnel.config import TunnelDefinition, TunnelType from i2p_apps.i2ptunnel.http_server import HTTPServerTask def _make_server_def(**kw): defaults = dict( name="http-server", type=TunnelType.HTTPSERVER, target_host="127.0.0.1", target_port=7658, spoofed_host="mysite.i2p", ) defaults.update(kw) return TunnelDefinition(**defaults) class MockServerSession: def __init__(self): self.destination = "SERV" * 129 async def accept(self): raise NotImplementedError async def close(self): pass class TestHostSpoofing: def test_replace_host_header(self): task = HTTPServerTask(_make_server_def(spoofed_host="mysite.i2p"), MockServerSession()) headers = {"Host": "original.example.com", "Content-Type": "text/html"} spoofed = task._spoof_host(headers) assert spoofed["Host"] == "mysite.i2p" assert spoofed["Content-Type"] == "text/html" def test_no_spoofed_host_preserves(self): task = HTTPServerTask(_make_server_def(spoofed_host=""), MockServerSession()) headers = {"Host": "original.com"} spoofed = task._spoof_host(headers) assert spoofed["Host"] == "original.com" class TestI2PDestHeaders: def test_inject_dest_headers(self): task = HTTPServerTask(_make_server_def(), MockServerSession()) headers = {"Host": "mysite.i2p"} remote_dest = "A" * 516 injected = task._inject_i2p_headers(headers, remote_dest) assert "X-I2P-DestB64" in injected assert injected["X-I2P-DestB64"] == remote_dest assert "X-I2P-DestHash" in injected assert "X-I2P-DestB32" in injected def test_dest_hash_format(self): task = HTTPServerTask(_make_server_def(), MockServerSession()) headers = {} remote_dest = "A" * 516 injected = task._inject_i2p_headers(headers, remote_dest) # Hash should be a hex string assert len(injected["X-I2P-DestHash"]) == 64 # SHA256 hex class TestResponseStripping: def test_strip_server_headers(self): task = HTTPServerTask(_make_server_def(), MockServerSession()) headers = { "Content-Type": "text/html", "Server": "Apache/2.4", "Date": "Mon, 01 Jan 2024 00:00:00 GMT", "X-Powered-By": "PHP/8.0", "X-Runtime": "0.123", "Proxy": "evil", } stripped = task._strip_response_headers(headers) assert "Content-Type" in stripped assert "Server" not in stripped assert "Date" not in stripped assert "X-Powered-By" not in stripped assert "X-Runtime" not in stripped assert "Proxy" not in stripped def test_preserves_content_length(self): task = HTTPServerTask(_make_server_def(), MockServerSession()) headers = {"Content-Length": "1234", "Server": "nginx"} stripped = task._strip_response_headers(headers) assert stripped["Content-Length"] == "1234" class TestPostRateLimiting: def test_post_allowed_under_limit(self): task = HTTPServerTask(_make_server_def(), MockServerSession()) task._max_posts = 5 task._post_check_time = 60 assert task._check_post_limit("peer1") is True def test_post_rejected_at_limit(self): task = HTTPServerTask(_make_server_def(), MockServerSession()) task._max_posts = 2 task._post_check_time = 60 task._record_post("peer1") task._record_post("peer1") assert task._check_post_limit("peer1") is False def test_post_global_limit(self): task = HTTPServerTask(_make_server_def(), MockServerSession()) task._max_total_posts = 3 task._post_check_time = 60 task._record_post("peer1") task._record_post("peer2") task._record_post("peer3") assert task._check_post_limit("peer4") is False class TestInproxyRejection: def test_reject_via_header(self): task = HTTPServerTask(_make_server_def(), MockServerSession()) task._reject_inproxy = True headers = {"Via": "1.1 proxy.example.com", "Host": "mysite.i2p"} assert task._is_inproxy_request(headers) is True def test_reject_x_forwarded(self): task = HTTPServerTask(_make_server_def(), MockServerSession()) task._reject_inproxy = True headers = {"X-Forwarded-For": "1.2.3.4"} assert task._is_inproxy_request(headers) is True def test_allow_direct_request(self): task = HTTPServerTask(_make_server_def(), MockServerSession()) task._reject_inproxy = True headers = {"Host": "mysite.i2p"} assert task._is_inproxy_request(headers) is False def test_inproxy_check_disabled(self): task = HTTPServerTask(_make_server_def(), MockServerSession()) task._reject_inproxy = False headers = {"Via": "1.1 proxy"} assert task._is_inproxy_request(headers) is False