A Python port of the Invisible Internet Project (I2P)
at main 185 lines 6.3 kB view raw
1"""Tests for HTTPClientTask — header filtering, outproxy, address helpers.""" 2 3from unittest.mock import AsyncMock, MagicMock 4 5import pytest 6 7from i2p_apps.i2ptunnel.config import TunnelDefinition, TunnelType 8from i2p_apps.i2ptunnel.http_client import HTTPClientTask, _STRIPPED_OUTBOUND, _I2P_USER_AGENT 9 10 11def _make_task(proxy_list=None): 12 config = TunnelDefinition( 13 name="test-http", 14 type=TunnelType.HTTPCLIENT, 15 listen_port=0, 16 interface="127.0.0.1", 17 proxy_list=proxy_list or [], 18 ) 19 session = MagicMock() 20 session.lookup = AsyncMock(return_value=None) 21 session.connect = AsyncMock() 22 return HTTPClientTask(config, session) 23 24 25class TestHeaderFiltering: 26 def test_strips_privacy_headers(self): 27 task = _make_task() 28 headers = { 29 "Referer": "http://example.com", 30 "Via": "proxy", 31 "X-Forwarded-For": "1.2.3.4", 32 "From": "user@example.com", 33 "Host": "site.i2p", 34 "Accept": "text/html", 35 } 36 filtered = task._filter_outbound_headers(headers, is_i2p=True) 37 assert "Referer" not in filtered 38 assert "Via" not in filtered 39 assert "X-Forwarded-For" not in filtered 40 assert "From" not in filtered 41 assert "Host" in filtered 42 assert "Accept" in filtered 43 44 def test_replaces_user_agent_for_i2p(self): 45 task = _make_task() 46 headers = {"User-Agent": "Mozilla/5.0"} 47 filtered = task._filter_outbound_headers(headers, is_i2p=True) 48 assert filtered["User-Agent"] == _I2P_USER_AGENT 49 50 def test_keeps_user_agent_for_clearnet(self): 51 task = _make_task() 52 headers = {"User-Agent": "Mozilla/5.0"} 53 filtered = task._filter_outbound_headers(headers, is_i2p=False) 54 assert filtered["User-Agent"] == "Mozilla/5.0" 55 56 def test_proxy_headers_stripped(self): 57 task = _make_task() 58 headers = { 59 "Proxy-Connection": "keep-alive", 60 "Proxy-Authorization": "Basic xxx", 61 } 62 filtered = task._filter_outbound_headers(headers, is_i2p=True) 63 assert len(filtered) == 0 64 65 66class TestAddressHelpers: 67 def test_extract_address_helper(self): 68 task = _make_task() 69 host, helper = task._extract_address_helper( 70 "http://site.i2p/page?i2paddresshelper=AAAA" 71 ) 72 assert host == "site.i2p" 73 assert helper == "AAAA" 74 75 def test_no_address_helper(self): 76 task = _make_task() 77 host, helper = task._extract_address_helper("http://site.i2p/page") 78 assert host == "site.i2p" 79 assert helper is None 80 81 def test_cache_and_retrieve(self): 82 task = _make_task() 83 task._cache_address_helper("site.i2p", "DEST_BASE64") 84 assert task._get_cached_helper("site.i2p") == "DEST_BASE64" 85 assert task._get_cached_helper("other.i2p") is None 86 87 88class TestOutproxy: 89 def test_has_outproxy(self): 90 task = _make_task(proxy_list=["proxy.i2p"]) 91 assert task._has_outproxy() 92 93 def test_no_outproxy(self): 94 task = _make_task(proxy_list=[]) 95 assert not task._has_outproxy() 96 97 def test_pick_outproxy(self): 98 task = _make_task(proxy_list=["proxy1.i2p", "proxy2.i2p"]) 99 choice = task._pick_outproxy() 100 assert choice in ("proxy1.i2p", "proxy2.i2p") 101 102 def test_pick_outproxy_avoids_failed(self): 103 task = _make_task(proxy_list=["proxy1.i2p", "proxy2.i2p"]) 104 task._failed_outproxies.add("proxy1.i2p") 105 choice = task._pick_outproxy() 106 assert choice == "proxy2.i2p" 107 108 def test_pick_outproxy_resets_when_all_failed(self): 109 task = _make_task(proxy_list=["proxy1.i2p"]) 110 task._failed_outproxies.add("proxy1.i2p") 111 choice = task._pick_outproxy() 112 assert choice == "proxy1.i2p" 113 assert len(task._failed_outproxies) == 0 114 115 def test_pick_outproxy_empty_list(self): 116 task = _make_task(proxy_list=[]) 117 assert task._pick_outproxy() == "" 118 119 120class TestRequestClassification: 121 def test_is_i2p_request(self): 122 assert HTTPClientTask._is_i2p_request("site.i2p") 123 assert HTTPClientTask._is_i2p_request("a.b32.i2p") 124 assert not HTTPClientTask._is_i2p_request("example.com") 125 126 def test_is_localhost(self): 127 assert HTTPClientTask._is_localhost("127.0.0.1") 128 assert HTTPClientTask._is_localhost("localhost") 129 assert HTTPClientTask._is_localhost("::1") 130 assert HTTPClientTask._is_localhost("0.0.0.0") 131 assert not HTTPClientTask._is_localhost("example.com") 132 133 134class TestErrorPages: 135 def test_error_page_contains_message(self): 136 task = _make_task() 137 page = task._error_page("dnf", 503) 138 assert b"503" in page 139 assert b"Destination Not Found" in page 140 assert b"text/html" in page 141 142 def test_error_page_unknown_type(self): 143 task = _make_task() 144 page = task._error_page("unknown_type", 500) 145 assert b"500" in page 146 assert b"Error" in page 147 148 149class TestRequestRebuilding: 150 def test_rebuild_get(self): 151 result = HTTPClientTask._rebuild_request( 152 "GET", "/path", {"Host": "site.i2p"}, b"" 153 ) 154 assert b"GET /path HTTP/1.1" in result 155 assert b"Host: site.i2p" in result 156 157 def test_rebuild_post_with_body(self): 158 body = b"key=value" 159 result = HTTPClientTask._rebuild_request( 160 "POST", "/submit", {"Content-Length": "9"}, body 161 ) 162 assert b"POST /submit HTTP/1.1" in result 163 assert result.endswith(body) 164 165 166class TestResolve: 167 @pytest.mark.asyncio 168 async def test_b32_passthrough(self): 169 task = _make_task() 170 result = await task._resolve("abcdef.b32.i2p") 171 assert result == "abcdef.b32.i2p" 172 173 @pytest.mark.asyncio 174 async def test_cached_helper(self): 175 task = _make_task() 176 task._cache_address_helper("site.i2p", "CACHED_DEST") 177 result = await task._resolve("site.i2p") 178 assert result == "CACHED_DEST" 179 180 @pytest.mark.asyncio 181 async def test_lookup_fallback(self): 182 task = _make_task() 183 task._session.lookup = AsyncMock(return_value="LOOKED_UP") 184 result = await task._resolve("site.i2p") 185 assert result == "LOOKED_UP"