libsubconverter/parser/explodes/
hysteria2.rs

1use crate::{models::HYSTERIA2_DEFAULT_GROUP, utils::url_decode, Proxy};
2use url::Url;
3
4/// Parse a Hysteria2 link into a Proxy object
5pub fn explode_hysteria2(hysteria2: &str, node: &mut Proxy) -> bool {
6    // Check if the link starts with hysteria2://
7    if !hysteria2.starts_with("hysteria2://") && !hysteria2.starts_with("hy2://") {
8        return false;
9    }
10
11    // Parse the URL
12    let url = match Url::parse(hysteria2) {
13        Ok(url) => url,
14        Err(_) => return false,
15    };
16
17    // Extract host and port
18    let host = url.host_str().unwrap_or("");
19    let port = url.port().unwrap_or(443);
20
21    // Extract password (username in URL)
22    let password = url.username();
23
24    // Extract parameters from the query string
25    let mut up_speed = None;
26    let mut down_speed = None;
27    let mut obfs = String::new();
28    let mut obfs_param = String::new();
29    let mut sni = String::new();
30    let mut fingerprint = String::new();
31    let mut ca = String::new();
32    let mut ca_str = String::new();
33    let mut cwnd = None;
34    let mut allow_insecure = None;
35    let mut ports = String::new();
36    let mut alpn = Vec::new();
37
38    for (key, value) in url.query_pairs() {
39        match key.as_ref() {
40            "up" => up_speed = value.parse::<u32>().ok(),
41            "down" => down_speed = value.parse::<u32>().ok(),
42            "obfs" => obfs = url_decode(&value),
43            "obfs-password" => obfs_param = url_decode(&value),
44            "sni" => sni = url_decode(&value),
45            "insecure" => {
46                allow_insecure =
47                    Some(value.as_ref() == "1" || value.as_ref().to_lowercase() == "true")
48            }
49            "fingerprint" => fingerprint = url_decode(&value),
50            "ca" => ca = url_decode(&value),
51            "caStr" => ca_str = url_decode(&value),
52            "ports" => ports = url_decode(&value),
53            "mport" => ports = url_decode(&value),
54            "cwnd" => cwnd = value.parse::<u32>().ok(),
55            "alpn" => {
56                for a in url_decode(&value).split(',') {
57                    alpn.push(a.to_string());
58                }
59            }
60            _ => {}
61        }
62    }
63
64    // Extract remark from the fragment
65    let remark = url_decode(url.fragment().unwrap_or(""));
66
67    // Create formatted strings
68    let remark_str = if remark.is_empty() {
69        format!("{} ({})", host, port)
70    } else {
71        remark.to_string()
72    };
73
74    // Create the proxy object
75    *node = Proxy::hysteria2_construct(
76        HYSTERIA2_DEFAULT_GROUP.to_string(),
77        remark_str,
78        host.to_string(),
79        port,
80        Some(ports),
81        up_speed,
82        down_speed,
83        password.to_string(),
84        Some(obfs),
85        Some(obfs_param),
86        Some(sni),
87        Some(fingerprint),
88        alpn,
89        Some(ca),
90        Some(ca_str),
91        cwnd,
92        None,
93        allow_insecure,
94        None,
95    );
96
97    true
98}
99
100/// Parse a standard Hysteria2 link into a Proxy object (handles hy2:// scheme)
101pub fn explode_std_hysteria2(hysteria2: &str, node: &mut Proxy) -> bool {
102    // Check if the link starts with hy2://
103    if !hysteria2.starts_with("hy2://") {
104        return false;
105    }
106
107    // Parse the URL
108    let url = match Url::parse(hysteria2) {
109        Ok(url) => url,
110        Err(_) => return false,
111    };
112
113    // Extract host and port
114    let host = url.host_str().unwrap_or("");
115    let port = url.port().unwrap_or(443);
116
117    // Extract password (username in URL)
118    let password = url.username();
119
120    // Extract parameters from the query string
121    let mut up_speed = None;
122    let mut down_speed = None;
123    let mut obfs = String::new();
124    let mut obfs_param = String::new();
125    let mut sni = String::new();
126    let mut fingerprint = String::new();
127    let mut ca = String::new();
128    let ca_str = String::new();
129    let mut cwnd = None;
130    let mut allow_insecure = None;
131    let mut ports = String::new();
132    let mut alpn = Vec::new();
133
134    for (key, value) in url.query_pairs() {
135        let value_decoded = url_decode(&value);
136        match key.as_ref() {
137            "bandwidth" => {
138                let parts: Vec<&str> = value_decoded.split(',').collect();
139                if parts.len() >= 1 {
140                    up_speed = parts[0].parse::<u32>().ok();
141                }
142                if parts.len() >= 2 {
143                    down_speed = parts[1].parse::<u32>().ok();
144                }
145            }
146            "obfs" => obfs = value_decoded,
147            "obfs-password" => obfs_param = value_decoded,
148            "sni" => sni = value_decoded,
149            "insecure" => {
150                allow_insecure =
151                    Some(value.as_ref() == "1" || value.as_ref().to_lowercase() == "true")
152            }
153            "pinSHA256" => fingerprint = value_decoded,
154            "ca" => ca = value_decoded,
155            "ports" => ports = value_decoded,
156            "cwnd" => cwnd = value.parse::<u32>().ok(),
157            "alpn" => {
158                for a in value_decoded.split(',') {
159                    alpn.push(a.to_string());
160                }
161            }
162            _ => {}
163        }
164    }
165
166    // Extract remark from the fragment
167    let remark = url_decode(url.fragment().unwrap_or(""));
168
169    // Create formatted strings
170    let remark_str = if remark.is_empty() {
171        format!("{} ({})", host, port)
172    } else {
173        remark.to_string()
174    };
175
176    // Create the proxy object
177    *node = Proxy::hysteria2_construct(
178        HYSTERIA2_DEFAULT_GROUP.to_string(),
179        remark_str,
180        host.to_string(),
181        port,
182        Some(ports),
183        up_speed,
184        down_speed,
185        password.to_string(),
186        Some(obfs),
187        Some(obfs_param),
188        Some(sni),
189        Some(fingerprint),
190        alpn,
191        Some(ca),
192        Some(ca_str),
193        cwnd,
194        None,
195        allow_insecure,
196        None,
197    );
198
199    true
200}