libsubconverter/parser/explodes/
quan.rs

1use crate::models::{
2    Proxy, HTTP_DEFAULT_GROUP, SSR_DEFAULT_GROUP, SS_DEFAULT_GROUP, TROJAN_DEFAULT_GROUP,
3    V2RAY_DEFAULT_GROUP,
4};
5
6/// Parse Quantumult configuration into a vector of Proxy objects
7/// Consistent with the C++ implementation in explodeQuan
8pub fn explode_quan(content: &str, nodes: &mut Vec<Proxy>) -> bool {
9    // Split the content into lines
10    let lines: Vec<&str> = content.lines().collect();
11
12    let mut success = false;
13
14    for line in lines {
15        // Skip empty lines and comments
16        let line = line.trim();
17        if line.is_empty() || line.starts_with('#') || line.starts_with(';') {
18            continue;
19        }
20
21        // Check if this is a proxy line
22        let mut node = Proxy::default();
23        if parse_quan_line(line, &mut node) {
24            nodes.push(node);
25            success = true;
26        }
27    }
28
29    success
30}
31
32/// Parse a single line from Quantumult configuration
33/// Returns true if parsing was successful
34fn parse_quan_line(line: &str, node: &mut Proxy) -> bool {
35    // Different formats for Quantumult configuration lines:
36
37    // Format: [name] = [type], [params...]
38    let parts: Vec<&str> = line.splitn(2, " = ").collect();
39    if parts.len() != 2 {
40        return false;
41    }
42
43    let name = parts[0].trim();
44    let config = parts[1].trim();
45
46    let config_parts: Vec<&str> = config.split(',').map(|s| s.trim()).collect();
47    if config_parts.is_empty() {
48        return false;
49    }
50
51    // Determine the proxy type and parse accordingly
52    match config_parts[0] {
53        "vmess" => parse_quan_vmess(name, config_parts, node),
54        "shadowsocks" => parse_quan_ss(name, config_parts, node),
55        "shadowsocksr" => parse_quan_ssr(name, config_parts, node),
56        "http" => parse_quan_http(name, config_parts, node),
57        "trojan" => parse_quan_trojan(name, config_parts, node),
58        _ => false,
59    }
60}
61
62/// Parse a Quantumult Shadowsocks line
63fn parse_quan_ss(name: &str, config_parts: Vec<&str>, node: &mut Proxy) -> bool {
64    // Format: shadowsocks, [server], [port], [method], [password], [options]
65    if config_parts.len() < 5 {
66        return false;
67    }
68
69    // Extract basic parameters
70    let server = config_parts[1];
71    let port = match config_parts[2].parse::<u16>() {
72        Ok(p) => p,
73        Err(_) => return false,
74    };
75    if port == 0 {
76        return false; // Skip if port is 0
77    }
78    let method = config_parts[3];
79    let password = config_parts[4];
80
81    // Default values
82    let mut plugin = "";
83    let mut plugin_opts = "";
84    let mut udp = None;
85    let mut tfo = None;
86    let mut scv = None;
87
88    // Parse additional options
89    for i in 5..config_parts.len() {
90        if config_parts[i].starts_with("obfs=") {
91            plugin = "obfs";
92
93            let obfs_parts: Vec<&str> = config_parts[i][5..].split(',').collect();
94            if !obfs_parts.is_empty() {
95                let mut opts = format!("obfs={}", obfs_parts[0]);
96
97                if obfs_parts.len() > 1 {
98                    opts.push_str(&format!(";obfs-host={}", obfs_parts[1]));
99                }
100
101                plugin_opts = Box::leak(opts.into_boxed_str());
102            }
103        } else if config_parts[i] == "udp-relay=true" {
104            udp = Some(true);
105        } else if config_parts[i] == "fast-open=true" {
106            tfo = Some(true);
107        } else if config_parts[i] == "tls-verification=false" {
108            scv = Some(true);
109        }
110    }
111
112    *node = Proxy::ss_construct(
113        SS_DEFAULT_GROUP,
114        name,
115        server,
116        port,
117        password,
118        method,
119        plugin,
120        plugin_opts,
121        udp,
122        tfo,
123        scv,
124        None,
125        "",
126    );
127
128    true
129}
130
131/// Parse a Quantumult ShadowsocksR line
132fn parse_quan_ssr(name: &str, config_parts: Vec<&str>, node: &mut Proxy) -> bool {
133    // Format: shadowsocksr, [server], [port], [method], [password], [protocol], [protocol_param], [obfs], [obfs_param], [options]
134    if config_parts.len() < 9 {
135        return false;
136    }
137
138    // Extract basic parameters
139    let server = config_parts[1];
140    let port = match config_parts[2].parse::<u16>() {
141        Ok(p) => p,
142        Err(_) => return false,
143    };
144    if port == 0 {
145        return false; // Skip if port is 0
146    }
147    let method = config_parts[3];
148    let password = config_parts[4];
149    let protocol = config_parts[5];
150    let protocol_param = config_parts[6];
151    let obfs = config_parts[7];
152    let obfs_param = config_parts[8];
153
154    // Default values
155    let mut udp = None;
156    let mut tfo = None;
157    let mut scv = None;
158
159    // Parse additional options
160    for i in 9..config_parts.len() {
161        if config_parts[i] == "udp-relay=true" {
162            udp = Some(true);
163        } else if config_parts[i] == "fast-open=true" {
164            tfo = Some(true);
165        } else if config_parts[i] == "tls-verification=false" {
166            scv = Some(true);
167        }
168    }
169
170    *node = Proxy::ssr_construct(
171        SSR_DEFAULT_GROUP,
172        name,
173        server,
174        port,
175        protocol,
176        method,
177        obfs,
178        password,
179        obfs_param,
180        protocol_param,
181        udp,
182        tfo,
183        scv,
184        "",
185    );
186
187    true
188}
189
190/// Parse a Quantumult VMess line
191/// This implementation follows the C++ version closely
192fn parse_quan_vmess(name: &str, config_parts: Vec<&str>, node: &mut Proxy) -> bool {
193    // Format: vmess, [server], [port], [method], [uuid], [options]
194    if config_parts.len() < 6 {
195        return false;
196    }
197
198    // Extract basic parameters
199    let server = config_parts[1];
200    let port = match config_parts[2].parse::<u16>() {
201        Ok(p) => p,
202        Err(_) => return false,
203    };
204    if port == 0 {
205        return false; // Skip if port is 0
206    }
207    let cipher = config_parts[3];
208    let uuid = config_parts[4].replace("\"", ""); // Remove quotes as in C++ replaceAllDistinct
209
210    // Default values
211    let mut group = V2RAY_DEFAULT_GROUP.to_string();
212    let aid = "0".to_string();
213    let mut net = "tcp".to_string();
214    let mut path = String::new();
215    let mut host = String::new();
216    let mut edge = String::new();
217    let mut tls = String::new();
218    let fake_type = "none".to_string();
219
220    // Parse additional options exactly like the C++ version
221    for i in 5..config_parts.len() {
222        let option_parts: Vec<&str> = config_parts[i].splitn(2, "=").collect();
223        if option_parts.len() < 2 {
224            continue;
225        }
226
227        let item_name = option_parts[0].trim();
228        let item_val = option_parts[1].trim();
229
230        match item_name {
231            "group" => group = item_val.to_string(),
232            "over-tls" => {
233                tls = if item_val == "true" {
234                    "tls".to_string()
235                } else {
236                    String::new()
237                }
238            }
239            "tls-host" => host = item_val.to_string(),
240            "obfs-path" => path = item_val.replace("\"", ""), // Remove quotes as in C++ replaceAllDistinct
241            "obfs-header" => {
242                // Parse headers similar to the C++ implementation
243                let processed_val = item_val
244                    .replace("\"", "")
245                    .replace("\r\n", "|")
246                    .replace("\n", "|");
247                let headers: Vec<&str> = processed_val.split('|').collect();
248
249                for header in headers {
250                    if header.to_lowercase().starts_with("host: ") {
251                        host = header[6..].to_string();
252                    } else if header.to_lowercase().starts_with("edge: ") {
253                        edge = header[6..].to_string();
254                    }
255                }
256            }
257            "obfs" => {
258                if item_val == "ws" {
259                    net = "ws".to_string();
260                }
261            }
262            _ => {}
263        }
264    }
265
266    // Set default path if empty
267    if path.is_empty() {
268        path = "/".to_string();
269    }
270
271    *node = Proxy::vmess_construct(
272        &group,
273        name,
274        server,
275        port,
276        &fake_type,
277        &uuid,
278        aid.parse::<u16>().unwrap_or(0),
279        &net,
280        cipher,
281        &path,
282        &host,
283        &edge,
284        &tls,
285        "", // SNI not set in C++ version
286        None,
287        None,
288        None,
289        None,
290        "",
291    );
292
293    true
294}
295
296/// Parse a Quantumult HTTP/HTTPS line
297fn parse_quan_http(name: &str, config_parts: Vec<&str>, node: &mut Proxy) -> bool {
298    // Format: http, [server], [port], [username], [password], [options]
299    if config_parts.len() < 3 {
300        return false;
301    }
302
303    // Extract basic parameters
304    let server = config_parts[1];
305    let port = match config_parts[2].parse::<u16>() {
306        Ok(p) => p,
307        Err(_) => return false,
308    };
309    if port == 0 {
310        return false; // Skip if port is 0
311    }
312
313    // Default values
314    let mut username = "";
315    let mut password = "";
316    let mut is_https = false;
317    let mut tfo = None;
318    let mut scv = None;
319
320    // Parse additional parameters
321    if config_parts.len() > 3 {
322        username = config_parts[3];
323    }
324
325    if config_parts.len() > 4 {
326        password = config_parts[4];
327    }
328
329    // Parse additional options
330    for i in 5..config_parts.len() {
331        if config_parts[i] == "over-tls=true" {
332            is_https = true;
333        } else if config_parts[i] == "fast-open=true" {
334            tfo = Some(true);
335        } else if config_parts[i] == "tls-verification=false" {
336            scv = Some(true);
337        }
338    }
339
340    *node = Proxy::http_construct(
341        HTTP_DEFAULT_GROUP,
342        name,
343        server,
344        port,
345        username,
346        password,
347        is_https,
348        tfo,
349        scv,
350        None,
351        "",
352    );
353
354    true
355}
356
357/// Parse a Quantumult Trojan line
358fn parse_quan_trojan(name: &str, config_parts: Vec<&str>, node: &mut Proxy) -> bool {
359    // Format: trojan, [server], [port], [password], [options]
360    if config_parts.len() < 4 {
361        return false;
362    }
363
364    // Extract basic parameters
365    let server = config_parts[1];
366    let port = match config_parts[2].parse::<u16>() {
367        Ok(p) => p,
368        Err(_) => return false,
369    };
370    if port == 0 {
371        return false; // Skip if port is 0
372    }
373    let password = config_parts[3];
374
375    // Default values
376    let mut sni = None;
377    let mut udp = None;
378    let mut tfo = None;
379    let mut scv = None;
380
381    // Parse additional options
382    for i in 4..config_parts.len() {
383        if config_parts[i].starts_with("tls-host=") {
384            sni = Some(config_parts[i][9..].to_string());
385        } else if config_parts[i] == "udp-relay=true" {
386            udp = Some(true);
387        } else if config_parts[i] == "fast-open=true" {
388            tfo = Some(true);
389        } else if config_parts[i] == "tls-verification=false" {
390            scv = Some(true);
391        }
392    }
393
394    *node = Proxy::trojan_construct(
395        TROJAN_DEFAULT_GROUP.to_string(),
396        name.to_string(),
397        server.to_string(),
398        port,
399        password.to_string(),
400        None,
401        sni.clone(),
402        None,
403        sni,
404        true,
405        udp,
406        tfo,
407        scv,
408        None,
409        None,
410    );
411
412    true
413}