"""URL 安全校验纯逻辑单元测试 — wave128.""" import pytest from packages.domain.url_security import ( ALLOWED_PORTS, ALLOWED_SCHEMES, MAGIC_NUMBERS, MAX_URL_LENGTH, UrlSecurityError, check_internal_hostname, check_ssrf_ip, is_ip_address, is_trusted_domain, is_url_basic_safe, validate_magic_number, validate_url_basic, ) # ── 常量校验 ──────────────────────────────────────────────────────────────── class TestConstants: def test_allowed_schemes(self): assert "http" in ALLOWED_SCHEMES assert "https" in ALLOWED_SCHEMES def test_allowed_ports(self): assert 80 in ALLOWED_PORTS assert 443 in ALLOWED_PORTS def test_max_url_length(self): assert MAX_URL_LENGTH == 2048 def test_magic_numbers_has_common_formats(self): assert "audio/mpeg" in MAGIC_NUMBERS assert "image/png" in MAGIC_NUMBERS assert "video/mp4" in MAGIC_NUMBERS # ── 内部主机名检查 ────────────────────────────────────────────────────────── class TestCheckInternalHostname: @pytest.mark.parametrize( "hostname", [ "localhost", "LOCALHOST", "LocalHost", "localhost.localdomain", "ip6-localhost", "ip6-loopback", "metadata", "metadata.google.internal", "169.254.169.254", ], ) def test_internal_hostnames_rejected(self, hostname): with pytest.raises(UrlSecurityError, match="禁止访问内部主机名"): check_internal_hostname(hostname) @pytest.mark.parametrize( "hostname", [ "foo.local", "bar.internal", "baz.localdomain", "sub.foo.local", ], ) def test_internal_domain_suffixes_rejected(self, hostname): with pytest.raises(UrlSecurityError, match="禁止访问内网域名"): check_internal_hostname(hostname) @pytest.mark.parametrize( "hostname", [ "example.com", "www.google.com", "oss-cn-hangzhou.aliyuncs.com", "123.45.67.89", ], ) def test_normal_hostnames_allowed(self, hostname): check_internal_hostname("example.com") # 不抛异常即通过 # ── 可信域名匹配 ──────────────────────────────────────────────────────────── class TestIsTrustedDomain: def test_empty_trusted_always_true(self): assert is_trusted_domain("anything.com", set()) is True def test_exact_match(self): trusted = {"example.com", "foo.bar"} assert is_trusted_domain("example.com", trusted) is True assert is_trusted_domain("foo.bar", trusted) is True def test_exact_no_match(self): trusted = {"example.com"} assert is_trusted_domain("other.com", trusted) is False def test_subdomain_match(self): trusted = {"example.com"} assert is_trusted_domain("sub.example.com", trusted) is True assert is_trusted_domain("a.b.example.com", trusted) is True def test_subdomain_partial_no_match(self): trusted = {"example.com"} # fakeexample.com 不是 example.com 的子域名 assert is_trusted_domain("fakeexample.com", trusted) is False def test_case_insensitive(self): trusted = {"Example.COM"} assert is_trusted_domain("example.com", trusted) is True assert is_trusted_domain("SUB.Example.COM", trusted) is True # ── IP SSRF 检查 ──────────────────────────────────────────────────────────── class TestCheckSrfIp: @pytest.mark.parametrize("ip", ["127.0.0.1", "127.1.2.3", "::1"]) def test_loopback_rejected(self, ip): with pytest.raises(UrlSecurityError, match="回环"): check_ssrf_ip(ip) @pytest.mark.parametrize( "ip", [ "10.0.0.1", "10.255.255.255", "172.16.0.1", "172.31.255.255", "192.168.1.1", "192.168.0.1", "fd00::1", # IPv6 unique local ], ) def test_private_rejected(self, ip): with pytest.raises(UrlSecurityError, match="内网"): check_ssrf_ip(ip) @pytest.mark.parametrize("ip", ["169.254.1.1", "169.254.169.254", "fe80::1"]) def test_link_local_rejected(self, ip): with pytest.raises(UrlSecurityError, match="链路本地"): check_ssrf_ip(ip) @pytest.mark.parametrize("ip", ["224.0.0.1", "239.255.255.255", "ff00::1"]) def test_multicast_rejected(self, ip): with pytest.raises(UrlSecurityError, match="组播"): check_ssrf_ip(ip) @pytest.mark.parametrize("ip", ["0.0.0.0", "::"]) def test_unspecified_rejected(self, ip): with pytest.raises(UrlSecurityError, match="未指定"): check_ssrf_ip(ip) def test_reserved_rejected(self): with pytest.raises(UrlSecurityError): check_ssrf_ip("240.0.0.1") # 保留地址段 @pytest.mark.parametrize( "ip", [ "8.8.8.8", "1.1.1.1", "223.5.5.5", "2001:4860:4860::8888", ], ) def test_public_ip_allowed(self, ip): check_ssrf_ip(ip) # 不抛异常即通过 def test_invalid_ip_raises_value_error(self): with pytest.raises(ValueError): check_ssrf_ip("not-an-ip") # ── IP 地址判断 ───────────────────────────────────────────────────────────── class TestIsIpAddress: @pytest.mark.parametrize( "host", [ "127.0.0.1", "8.8.8.8", "192.168.1.1", "::1", "2001:db8::1", "fe80::1", ], ) def test_ip_addresses(self, host): assert is_ip_address(host) is True @pytest.mark.parametrize( "host", [ "example.com", "www.google.com", "localhost", "not-an-ip", "", ], ) def test_not_ip_addresses(self, host): assert is_ip_address(host) is False # ── URL 基础校验 ──────────────────────────────────────────────────────────── class TestValidateUrlBasic: def test_normal_http_url_passes(self): result = validate_url_basic("http://example.com/file.txt") assert result == "http://example.com/file.txt" def test_normal_https_url_passes(self): result = validate_url_basic("https://www.example.com/path?q=1") assert result == "https://www.example.com/path?q=1" def test_standard_port_80_passes(self): validate_url_basic("http://example.com:80/file") def test_standard_port_443_passes(self): validate_url_basic("https://example.com:443/file") def test_empty_url_rejected(self): with pytest.raises(UrlSecurityError, match="URL 为空"): validate_url_basic("") def test_none_url_rejected(self): with pytest.raises(UrlSecurityError, match="URL 为空"): validate_url_basic(None) # type: ignore def test_too_long_url_rejected(self): long_url = "https://example.com/" + "a" * 2100 with pytest.raises(UrlSecurityError, match="URL 过长"): validate_url_basic(long_url) @pytest.mark.parametrize( "url", [ "ftp://example.com/file", "file:///etc/passwd", "javascript:alert(1)", "data:text/html,