use base64::{engine::general_purpose, Engine}; use proxywarden_lib::models::redact_subscription_url; use proxywarden_lib::subscription::{ self, parse_subscription_body, parse_user_info, validate_resolved_subscription_addresses, SubscriptionFetchIdentity, SubscriptionFetchPolicy, }; use std::io::{Read, Write}; use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener}; use std::time::Duration; #[test] fn parses_singbox_json_config_servers() { let parsed = parse_subscription_body( r#"{ "outbounds": [ { "type": "direct", "tag": "direct" }, { "type": "vless", "tag": "nl-1", "server": "nl.example.test", "server_port": 443 }, { "type": "trojan", "tag": "de-1", "server": "de.example.test", "server_port": 8443 } ] }"#, ) .expect("json subscription should parse"); assert_eq!(parsed.servers.len(), 2); assert_eq!(parsed.servers[0].tag, "nl-1"); assert_eq!(parsed.servers[0].server_type, "vless"); assert_eq!(parsed.servers[1].server_port, 8443); } #[test] fn server_ids_are_opaque_and_distinguish_credentials_on_same_endpoint() { let parsed = parse_subscription_body( r#"{ "outbounds": [ { "type": "vless", "tag": "same", "server": "edge.example.test", "server_port": 443, "uuid": "11111111-1111-1111-1111-111111111111" }, { "type": "vless", "tag": "same", "server": "edge.example.test", "server_port": 443, "uuid": "22222222-2222-2222-2222-222222222222" } ] }"#, ) .expect("duplicate endpoint subscription should parse"); assert_ne!(parsed.servers[0].id, parsed.servers[1].id); assert!(parsed .servers .iter() .all(|server| server.id.starts_with("pw-"))); assert!(parsed .servers .iter() .all(|server| !server.id.contains("11111111"))); } #[test] fn parses_base64_vless_link_list() { let link = sample_vless_link("nl-1"); let encoded = general_purpose::STANDARD.encode(format!("{link}\n")); let parsed = parse_subscription_body(&encoded).expect("base64 vless list should parse"); let outbound = &parsed.config["outbounds"][0]; assert_eq!(parsed.servers.len(), 1); assert_eq!(parsed.servers[0].tag, "nl-1"); assert_eq!(outbound["type"], "vless"); assert_eq!(outbound["server"], "nl.example.test"); assert_eq!(outbound["packet_encoding"], "xudp"); } #[test] fn parses_trojan_shadowsocks_and_vmess_link_formats() { let vmess_payload = serde_json::json!({ "v": "2", "ps": "VMess NL", "add": "vmess.example.test", "port": "443", "id": "33333333-3333-3333-3333-333333333333", "scy": "auto", "net": "ws", "host": "cdn.example.test", "path": "/ws", "tls": "tls", "sni": "vmess.example.test" }); let vmess_link = format!( "vmess://{}", general_purpose::STANDARD_NO_PAD.encode(vmess_payload.to_string()) ); let body = format!( "trojan://secret@trojan.example.test:443?sni=edge.example.test#Trojan%20DE\nss://aes-256-gcm:password@ss.example.test:8388#SS%20US\n{vmess_link}" ); let parsed = parse_subscription_body(&body).expect("supported link formats should parse"); assert_eq!(parsed.servers.len(), 3); assert_eq!(parsed.servers[0].server_type, "trojan"); assert_eq!(parsed.servers[0].tag, "Trojan DE"); assert_eq!( parsed.config["outbounds"][0]["tls"]["server_name"], "edge.example.test" ); assert_eq!(parsed.servers[1].server_type, "shadowsocks"); assert_eq!(parsed.config["outbounds"][1]["method"], "aes-256-gcm"); assert_eq!(parsed.servers[2].server_type, "vmess"); assert_eq!(parsed.config["outbounds"][2]["transport"]["type"], "ws"); assert_eq!(parsed.config["outbounds"][2]["tls"]["enabled"], true); } #[test] fn decodes_percent_encoded_vless_fragment_tag() { let link = sample_vless_link( "%D0%A3%D0%BC%D0%BD%D1%8B%D0%B9%20%F0%9F%87%B3%F0%9F%87%B1-%3E%F0%9F%87%B7%F0%9F%87%BA", ); let parsed = parse_subscription_body(&link).expect("vless link should parse"); let outbound = &parsed.config["outbounds"][0]; assert_eq!(parsed.servers[0].tag, "Умный 🇳🇱->🇷🇺"); assert_eq!(outbound["tag"], "Умный 🇳🇱->🇷🇺"); } #[test] fn rejects_body_without_supported_outbounds() { let error = parse_subscription_body(r#"{"outbounds":[{"type":"direct","tag":"direct"}]}"#) .expect_err("unsupported subscription should fail"); assert!(error.message.contains("No supported proxy outbounds found")); } #[test] fn rejects_vless_without_reality_parameters() { let error = parse_subscription_body("vless://uuid@nl.example.test:443#nl-1") .expect_err("missing reality params should fail"); assert!(error.message.contains("pbk and sid")); } #[test] fn parses_subscription_user_info_header() { let user_info = parse_user_info(Some("upload=10; download=20; total=30; expire=bad")); assert_eq!(user_info["upload"], 10); assert_eq!(user_info["download"], 20); assert_eq!(user_info["total"], 30); assert!(!user_info.contains_key("expire")); } #[test] fn rejects_invalid_or_non_http_subscription_url_before_network() { let invalid = subscription::fetch_subscription("not a url") .expect_err("invalid url should fail before request"); let unsupported = subscription::fetch_subscription("file:///C:/subscription.txt") .expect_err("non-http url should fail before request"); assert!(invalid.message.contains("Invalid subscription URL")); assert!(unsupported.message.contains("http or https")); } #[test] fn rejects_unsafe_local_subscription_urls_before_network() { for url in [ "http://127.0.0.1:9/subscription", "http://localhost/subscription", "http://169.254.169.254/latest/meta-data", "http://192.168.0.1/subscription", "http://[::1]/subscription", ] { let error = subscription::fetch_subscription(url) .expect_err("unsafe local URL should fail before request"); assert!( error.message.contains("local, private"), "unexpected error for {url}: {}", error.message ); } } #[test] fn rejects_dns_results_containing_private_or_metadata_addresses() { for ip in [ IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)), IpAddr::V4(Ipv4Addr::new(169, 254, 169, 254)), ] { let error = validate_resolved_subscription_addresses(&[SocketAddr::new(ip, 443)]) .expect_err("unsafe resolved address should be blocked"); assert!(error.message.contains("resolves to")); } validate_resolved_subscription_addresses(&[SocketAddr::new( IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)), 443, )]) .expect("public resolved address should be accepted"); } #[test] fn fetch_subscription_sends_device_hwid_header_when_identity_is_set() { let listener = TcpListener::bind("127.0.0.1:0").expect("bind local test listener"); let url = format!("http://{}/subscription", listener.local_addr().unwrap()); let request_thread = std::thread::spawn(move || { let (mut stream, _) = listener.accept().expect("accept test request"); stream .set_read_timeout(Some(Duration::from_secs(2))) .expect("set read timeout"); let mut request = Vec::new(); let mut buffer = [0_u8; 512]; loop { let bytes_read = stream.read(&mut buffer).expect("read request"); if bytes_read == 0 { break; } request.extend_from_slice(&buffer[..bytes_read]); if request.windows(4).any(|window| window == b"\r\n\r\n") { break; } } let body = r#"{"outbounds":[{"type":"vless","tag":"nl-1","server":"nl.example.test","server_port":443}]}"#; let response = format!( "HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\n\r\n{}", body.len(), body ); stream .write_all(response.as_bytes()) .expect("write response"); String::from_utf8_lossy(&request).to_ascii_lowercase() }); let mut identity = SubscriptionFetchIdentity::with_device_hwid(Some("hwid-abc123")); identity.device_os_version = Some("Windows 11 Pro | 25H2 | build 26200.8655".to_string()); let cache = subscription::fetch_subscription_with_identity_and_policy( &url, &identity, SubscriptionFetchPolicy { allow_unsafe_local_urls: true, }, ) .expect("fetch subscription through local test server"); let request = request_thread.join().expect("request thread"); assert_eq!(cache.servers[0].tag, "nl-1"); assert!(request.contains("x-hwid: hwid-abc123")); assert!(request.contains("user-agent: proxywarden/")); assert!(request.contains("x-app-name: proxywarden")); assert!(request.contains("x-device-os:")); assert!(request.contains("x-device-os-version: windows 11 pro | 25h2 | build 26200.8655")); assert!(request.contains("x-ver-os: windows 11 pro | 25h2 | build 26200.8655")); assert!(request.contains("x-device-model: proxywarden")); } #[test] fn redacts_subscription_url_for_display() { assert_eq!( redact_subscription_url("https://sub.example.test/path?token=secret"), "https://sub.example.test/..." ); assert_eq!( redact_subscription_url("vless://uuid@example.test"), "vless://example.test/..." ); assert_eq!( redact_subscription_url( "https://user:password@sub.example.test:8443/path?token=secret#frag" ), "https://sub.example.test:8443/..." ); } fn sample_vless_link(tag: &str) -> String { format!( "vless://{}@nl.example.test:443?security=reality&sni=example.test&fp=chrome&pbk=public-key&sid=short-id&flow=xtls-rprx-vision#{}", "11111111-1111-1111-1111-111111111111", tag ) }