libsubconverter/parser/explodes/
trojan.rs

1use crate::{models::TROJAN_DEFAULT_GROUP, utils::url_decode, Proxy};
2use std::collections::HashMap;
3use url::Url;
4
5/// Parse a Trojan link into a Proxy object
6pub fn explode_trojan(trojan: &str, node: &mut Proxy) -> bool {
7    // Check if the link starts with trojan://
8    if !trojan.starts_with("trojan://") {
9        return false;
10    }
11
12    // Try to parse as URL
13    let url = match Url::parse(trojan) {
14        Ok(url) => url,
15        Err(_) => return false,
16    };
17
18    // Extract password
19    let password = url.username();
20    if password.is_empty() {
21        return false;
22    }
23
24    // Extract host and port
25    let host = match url.host_str() {
26        Some(host) => host,
27        None => return false,
28    };
29    let port = url.port().unwrap_or(443);
30
31    // Skip if port is 0
32    if port == 0 {
33        return false;
34    }
35
36    // Extract parameters from the query string
37    let mut params = HashMap::new();
38    for (key, value) in url.query_pairs() {
39        params.insert(key.to_string(), url_decode(&value));
40    }
41
42    // Extract SNI - check for both "sni" and "peer" parameters (like in C++)
43    let sni = params
44        .get("sni")
45        .map(|s| s.to_string())
46        .or_else(|| params.get("peer").map(|s| s.to_string()));
47
48    // Extract TLS verification setting
49    let skip_cert_verify = params
50        .get("allowInsecure")
51        .map(|s| s == "1" || s.to_lowercase() == "true");
52
53    // Extract TCP Fast Open setting
54    let tfo = params
55        .get("tfo")
56        .map(|s| s == "1" || s.to_lowercase() == "true");
57
58    // Extract group parameter
59    let group = params
60        .get("group")
61        .map(|s| url_decode(s))
62        .unwrap_or_else(|| TROJAN_DEFAULT_GROUP.to_string());
63
64    // Handle WebSocket support
65    let mut network = None;
66    let mut path = None;
67
68    if params.get("ws").map(|s| s == "1").unwrap_or(false) {
69        network = Some("ws".to_string());
70        path = params.get("wspath").map(|s| s.to_string());
71    } else if params.get("type").map(|s| s == "ws").unwrap_or(false) {
72        network = Some("ws".to_string());
73        if let Some(p) = params.get("path") {
74            let p_str = p.to_string();
75            if p_str.starts_with("%2F") {
76                path = Some(url_decode(&p_str));
77            } else {
78                path = Some(p_str);
79            }
80        }
81    }
82
83    // Extract remark from the fragment
84    let remark = url_decode(&url.fragment().unwrap_or(""));
85    let formatted_remark = if remark.is_empty() {
86        format!("{} ({})", host, port)
87    } else {
88        remark.to_string()
89    };
90
91    // Create the proxy object
92    *node = Proxy::trojan_construct(
93        group,
94        formatted_remark,
95        host.to_string(),
96        port,
97        password.to_string(),
98        network,
99        sni.clone(),
100        path,
101        sni,
102        true,             // tls_secure
103        None,             // udp
104        tfo,              // tfo
105        skip_cert_verify, // allow_insecure
106        None,             // tls13
107        None,             // underlying_proxy
108    );
109
110    true
111}
112
113/// Parse a Trojan-Go link into a Proxy object
114pub fn explode_trojan_go(trojan_go: &str, node: &mut Proxy) -> bool {
115    // Check if the link starts with trojan-go://
116    if !trojan_go.starts_with("trojan-go://") {
117        return false;
118    }
119
120    // Try to parse as URL
121    let url = match Url::parse(trojan_go) {
122        Ok(url) => url,
123        Err(_) => return false,
124    };
125
126    // Extract password
127    let password = url.username();
128    if password.is_empty() {
129        return false;
130    }
131
132    // Extract host and port
133    let host = match url.host_str() {
134        Some(host) => host,
135        None => return false,
136    };
137    let port = url.port().unwrap_or(443);
138
139    // Skip if port is 0
140    if port == 0 {
141        return false;
142    }
143
144    // Extract parameters from the query string
145    let mut params = HashMap::new();
146    for (key, value) in url.query_pairs() {
147        params.insert(key.to_string(), url_decode(&value));
148    }
149
150    // Extract network, host, path
151    let network = params.get("type").map(|s| s.to_string());
152    let host_param = params.get("host").map(|s| s.to_string());
153    let path = params.get("path").map(|s| s.to_string());
154    let sni = params.get("sni").map(|s| s.to_string());
155    // Extract TLS verification setting
156    let skip_cert_verify = params
157        .get("allowInsecure")
158        .map(|s| s == "1" || s.to_lowercase() == "true");
159
160    // Extract TFO setting
161    let tfo = params
162        .get("tfo")
163        .map(|s| s == "1" || s.to_lowercase() == "true");
164
165    // Extract group parameter
166    let group = params
167        .get("group")
168        .map_or_else(|| TROJAN_DEFAULT_GROUP, |s| s);
169
170    // Extract remark from the fragment
171    let remark = url_decode(url.fragment().unwrap_or(""));
172    let formatted_remark = if remark.is_empty() {
173        format!("{} ({})", host, port)
174    } else {
175        remark.to_string()
176    };
177
178    // Create the proxy object
179    *node = Proxy::trojan_construct(
180        group.to_string(),
181        formatted_remark,
182        host.to_string(),
183        port,
184        password.to_string(),
185        network,
186        host_param,
187        path,
188        sni,
189        true,             // tls_secure
190        None,             // udp
191        tfo,              // tfo
192        skip_cert_verify, // allow_insecure
193        None,             // tls13
194        None,             // underlying_proxy
195    );
196
197    true
198}