libsubconverter/parser/explodes/
vmess.rs

1use crate::{
2    models::{Proxy, SOCKS_DEFAULT_GROUP, SS_DEFAULT_GROUP, V2RAY_DEFAULT_GROUP},
3    utils::url_decode,
4};
5use base64::{engine::general_purpose::STANDARD, Engine};
6use regex::Regex;
7use serde_json::Value;
8use std::collections::HashMap;
9use url::Url;
10
11/// Parse a VMess link into a Proxy object
12pub fn explode_vmess(vmess: &str, node: &mut Proxy) -> bool {
13    // Check if the link starts with vmess://
14    if !vmess.starts_with("vmess://") {
15        return false;
16    }
17
18    // Extract the base64 part
19    let encoded = &vmess[8..];
20
21    // Decode base64
22    let decoded = match STANDARD.decode(encoded) {
23        Ok(decoded) => match String::from_utf8(decoded) {
24            Ok(s) => s,
25            Err(_) => return false,
26        },
27        Err(_) => return false,
28    };
29
30    // Try to parse as JSON
31    let json: Value = match serde_json::from_str(&decoded) {
32        Ok(json) => json,
33        Err(_) => return false,
34    };
35
36    // Determine protocol version
37    let version = json["v"].as_u64().unwrap_or(1);
38
39    // Extract common fields
40    let add = json["add"].as_str().unwrap_or("").to_string();
41    let port = json["port"]
42        .as_str()
43        .map(|s| s.to_string())
44        .unwrap_or_else(|| {
45            json["port"]
46                .as_u64()
47                .map_or_else(|| "0".to_string(), |p| p.to_string())
48        });
49    let id = json["id"].as_str().unwrap_or("").to_string();
50    let aid = json["aid"]
51        .as_str()
52        .map(|s| s.to_string())
53        .unwrap_or_else(|| {
54            json["aid"]
55                .as_u64()
56                .map_or_else(|| "0".to_string(), |a| a.to_string())
57        });
58    let net = json["net"].as_str().unwrap_or("tcp").to_string();
59    let type_field = json["type"].as_str().unwrap_or("").to_string();
60    let mut host = json["host"].as_str().unwrap_or("").to_string();
61    let mut path = json["path"].as_str().unwrap_or("").to_string();
62    let tls = json["tls"].as_str().unwrap_or("").to_string();
63    let sni = json["sni"].as_str().unwrap_or("").to_string();
64
65    // Extract remark (ps field)
66    let remark = json["ps"].as_str().unwrap_or("").to_string();
67
68    // Parse port and aid as integers
69    let port = port.parse::<u16>().unwrap_or(0);
70    let aid = aid.parse::<u16>().unwrap_or(0);
71
72    // Handle host and path for different versions
73    if version == 2 {
74        if !host.is_empty() {
75            let host_str = host.clone();
76            let parts: Vec<&str> = host_str.split(';').collect();
77            if parts.len() == 2 {
78                host = parts[0].to_string();
79                path = parts[1].to_string();
80            }
81        }
82    }
83
84    // Create the proxy object
85    *node = Proxy::vmess_construct(
86        "VMess",
87        &remark,
88        &add,
89        port,
90        &type_field,
91        &id,
92        aid,
93        &net,
94        "auto",
95        &path,
96        &host,
97        "",
98        &tls,
99        &sni,
100        None,
101        None,
102        None,
103        None,
104        "",
105    );
106
107    true
108}
109
110/// Parse a standard VMess link into a Proxy object
111/// Format: vmess[+tls]://uuid-alterId@hostname:port[/?network=ws&host=xxx&path=yyy]
112pub fn explode_std_vmess(vmess: &str, node: &mut Proxy) -> bool {
113    // Check if the link starts with vmess:// or vmess+tls://
114    if !vmess.starts_with("vmess://") && !vmess.starts_with("vmess+") {
115        return false;
116    }
117
118    // Extract the protocol part and check TLS
119    let protocol_end = match vmess.find("://") {
120        Some(pos) => pos,
121        None => return false,
122    };
123
124    let protocol = vmess[..protocol_end].to_string();
125    let tls = protocol.contains("+tls");
126
127    // Extract the rest of the URL
128    let url_part = &vmess[protocol_end + 3..];
129
130    // Split URL and fragment (remark)
131    let (url_without_fragment, remark) = match url_part.find('#') {
132        Some(pos) => (url_part[..pos].to_string(), url_part[pos + 1..].to_string()),
133        None => (url_part.to_string(), String::new()),
134    };
135
136    // Parse the URL-like string
137    let re = Regex::new(
138        r"^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})-(\d+)@([^:]+):(\d+)(.*)$",
139    )
140    .unwrap();
141
142    let caps = match re.captures(&url_without_fragment) {
143        Some(c) => c,
144        None => return false,
145    };
146
147    let id = caps.get(1).map_or("", |m| m.as_str()).to_string();
148    let aid = caps
149        .get(2)
150        .map_or("0", |m| m.as_str())
151        .parse::<u16>()
152        .unwrap_or(0);
153    let host = caps.get(3).map_or("", |m| m.as_str()).to_string();
154    let port = caps
155        .get(4)
156        .map_or("0", |m| m.as_str())
157        .parse::<u16>()
158        .unwrap_or(0);
159    let query = caps.get(5).map_or("", |m| m.as_str()).to_string();
160
161    // Default values
162    let mut net = "tcp".to_string();
163    let mut path = "/".to_string();
164    let mut host_header = host.clone();
165    let mut tls_str = if tls {
166        "tls".to_string()
167    } else {
168        String::new()
169    };
170    let mut sni = String::new();
171
172    // Parse query parameters
173    if !query.is_empty() && query.starts_with("/?") {
174        for param in query[2..].split('&') {
175            let mut kv = param.split('=');
176            if let (Some(k), Some(v)) = (kv.next(), kv.next()) {
177                match k {
178                    "network" => net = v.to_string(),
179                    "host" => host_header = v.to_string(),
180                    "path" => path = v.to_string(),
181                    "tls" => tls_str = v.to_string(),
182                    "sni" => sni = v.to_string(),
183                    _ => {}
184                }
185            }
186        }
187    }
188
189    // Create formatted remark if empty
190    let formatted_remark = if remark.is_empty() {
191        format!("{} ({})", host, port)
192    } else {
193        remark
194    };
195
196    // Create the proxy object
197    *node = Proxy::vmess_construct(
198        "VMess",
199        &formatted_remark,
200        &host,
201        port,
202        "",
203        &id,
204        aid,
205        &net,
206        "auto",
207        &path,
208        &host_header,
209        "",
210        &tls_str,
211        &sni,
212        None,
213        None,
214        None,
215        None,
216        "",
217    );
218
219    true
220}
221
222/// Parse a Shadowrocket format VMess link
223pub fn explode_shadowrocket(rocket: &str, node: &mut Proxy) -> bool {
224    // Check if the link starts with vmess://
225    if !rocket.starts_with("vmess://") {
226        return false;
227    }
228
229    // Try to parse as URL
230    let url = match Url::parse(rocket) {
231        Ok(url) => url,
232        Err(_) => return false,
233    };
234
235    // Extract host and port
236    let host = url.host_str().unwrap_or("").to_string();
237    let port = url.port().unwrap_or(0);
238    if port == 0 {
239        return false;
240    }
241
242    // Extract username (contains encoded config)
243    let username = url.username().to_string();
244    if username.is_empty() {
245        return false;
246    }
247
248    // Decode the username
249    let decoded = match STANDARD.decode(username) {
250        Ok(decoded) => match String::from_utf8(decoded) {
251            Ok(s) => s,
252            Err(_) => return false,
253        },
254        Err(_) => return false,
255    };
256
257    // Parse the decoded string
258    let parts: Vec<&str> = decoded.split(':').collect();
259    if parts.len() < 6 {
260        return false;
261    }
262
263    let method = parts[0].to_string();
264    let id = parts[1].to_string();
265    let aid = parts[2].parse::<u16>().unwrap_or(0);
266
267    // Extract parameters from the query string
268    let mut net = "tcp".to_string();
269    let mut path = "/".to_string();
270    let mut host_header = host.clone();
271    let mut tls = String::new();
272    let mut sni = String::new();
273
274    for (key, value) in url.query_pairs() {
275        let value = url_decode(&value);
276        match key.as_ref() {
277            "obfs" => net = value,
278            "path" => path = value,
279            "obfsParam" => host_header = value,
280            "tls" => {
281                tls = if value == "1" {
282                    "tls".to_string()
283                } else {
284                    String::new()
285                }
286            }
287            "peer" => sni = value,
288            _ => {}
289        }
290    }
291
292    // Extract remark from the fragment
293    let remark = url_decode(url.fragment().unwrap_or(""));
294    let formatted_remark = if remark.is_empty() {
295        format!("{} ({})", host, port)
296    } else {
297        remark
298    };
299
300    // Create the proxy object
301    *node = Proxy::vmess_construct(
302        "VMess",
303        &formatted_remark,
304        &host,
305        port,
306        "",
307        &id,
308        aid,
309        &net,
310        &method,
311        &path,
312        &host_header,
313        "",
314        &tls,
315        &sni,
316        None,
317        None,
318        None,
319        None,
320        "",
321    );
322
323    true
324}
325
326/// Parse a Kitsunebi format VMess link
327pub fn explode_kitsunebi(kit: &str, node: &mut Proxy) -> bool {
328    // Check if the link starts with vmess://
329    if !kit.starts_with("vmess://") {
330        return false;
331    }
332
333    // Extract the base64 part
334    let encoded = &kit[8..];
335
336    // Decode base64
337    let decoded = match STANDARD.decode(encoded) {
338        Ok(decoded) => match String::from_utf8(decoded) {
339            Ok(s) => s,
340            Err(_) => return false,
341        },
342        Err(_) => return false,
343    };
344
345    // Split by line breaks
346    let lines: Vec<&str> = decoded.lines().collect();
347    if lines.is_empty() {
348        return false;
349    }
350
351    // Parse the first line (main config)
352    let parts: Vec<&str> = lines[0].split(',').collect();
353    if parts.len() < 4 {
354        return false;
355    }
356
357    let add = parts[0].to_string();
358    let port = parts[1].parse::<u16>().unwrap_or(0);
359    let id = parts[2].to_string();
360    let aid = parts[3].parse::<u16>().unwrap_or(0);
361
362    // Default values
363    let mut net = "tcp".to_string();
364    let mut path = "/".to_string();
365    let mut host = add.clone();
366    let mut tls = String::new();
367    let mut sni = String::new();
368    let mut remark = format!("{} ({})", add, port);
369
370    // Parse additional parameters
371    for i in 4..parts.len() {
372        let kv: Vec<&str> = parts[i].split('=').collect();
373        if kv.len() != 2 {
374            continue;
375        }
376
377        let value = kv[1].to_string();
378        match kv[0] {
379            "net" => net = value,
380            "path" => path = value,
381            "host" => host = value,
382            "tls" => tls = value,
383            "sni" => sni = value,
384            "remarks" | "remark" => remark = value,
385            _ => {}
386        }
387    }
388
389    // Create the proxy object
390    *node = Proxy::vmess_construct(
391        "VMess", &remark, &add, port, "", &id, aid, &net, "auto", &path, &host, "", &tls, &sni,
392        None, None, None, None, "",
393    );
394
395    true
396}
397
398/// Parse a VMess configuration file into a vector of Proxy objects
399pub fn explode_vmess_conf(content: &str, nodes: &mut Vec<Proxy>) -> bool {
400    // Try to parse as JSON
401    let json: Value = match serde_json::from_str(content) {
402        Ok(json) => json,
403        Err(_) => return false,
404    };
405
406    // Check if it's a V2Ray configuration with outbounds
407    if json["outbounds"].is_array() {
408        // Extract outbounds
409        let outbounds = json["outbounds"].as_array().unwrap();
410        let mut success = false;
411
412        for outbound in outbounds {
413            // Check if it's a VMess outbound
414            if outbound["protocol"].as_str().unwrap_or("") != "vmess" {
415                continue;
416            }
417
418            // Extract settings
419            let settings = &outbound["settings"];
420            if !settings["vnext"].is_array() {
421                continue;
422            }
423
424            // Extract vnext
425            let vnext = settings["vnext"].as_array().unwrap();
426
427            for server in vnext {
428                let address = server["address"].as_str().unwrap_or("").to_string();
429                let port = server["port"].as_u64().unwrap_or(0) as u16;
430                if port == 0 {
431                    continue;
432                }
433
434                // Extract users
435                if !server["users"].is_array() {
436                    continue;
437                }
438
439                let users = server["users"].as_array().unwrap();
440
441                for user in users {
442                    let id = user["id"].as_str().unwrap_or("").to_string();
443                    let alter_id = user["alterId"].as_u64().unwrap_or(0) as u16;
444                    let security = user["security"].as_str().unwrap_or("auto").to_string();
445
446                    // Extract stream settings
447                    let stream_settings = &outbound["streamSettings"];
448                    let network = stream_settings["network"]
449                        .as_str()
450                        .unwrap_or("tcp")
451                        .to_string();
452                    let security_type = stream_settings["security"]
453                        .as_str()
454                        .unwrap_or("")
455                        .to_string();
456
457                    // Extract network-specific settings
458                    let mut host = String::new();
459                    let mut path = String::new();
460                    let mut edge = String::new();
461                    let mut tls = String::new();
462                    let mut sni = String::new();
463                    let mut type_field = String::new();
464
465                    match network.as_str() {
466                        "ws" => {
467                            let ws_settings = &stream_settings["wsSettings"];
468                            path = ws_settings["path"].as_str().unwrap_or("").to_string();
469
470                            if let Some(headers) = ws_settings["headers"].as_object() {
471                                if let Some(host_val) = headers.get("Host") {
472                                    host = host_val.as_str().unwrap_or("").to_string();
473                                }
474                                if let Some(edge_val) = headers.get("Edge") {
475                                    edge = edge_val.as_str().unwrap_or("").to_string();
476                                }
477                            }
478                        }
479                        "h2" => {
480                            let h2_settings = &stream_settings["httpSettings"];
481                            path = h2_settings["path"].as_str().unwrap_or("").to_string();
482
483                            if let Some(hosts) = h2_settings["host"].as_array() {
484                                if !hosts.is_empty() {
485                                    host = hosts[0].as_str().unwrap_or("").to_string();
486                                }
487                            }
488                        }
489                        "tcp" => {
490                            let tcp_settings = &stream_settings["tcpSettings"];
491                            if tcp_settings["header"]["type"].as_str().unwrap_or("") == "http" {
492                                type_field = "http".to_string();
493
494                                if let Some(request) = tcp_settings["header"]["request"].as_object()
495                                {
496                                    if let Some(paths) = request.get("path") {
497                                        if let Some(paths_array) = paths.as_array() {
498                                            if !paths_array.is_empty() {
499                                                path = paths_array[0]
500                                                    .as_str()
501                                                    .unwrap_or("")
502                                                    .to_string();
503                                            }
504                                        }
505                                    }
506
507                                    if let Some(headers) = request.get("headers") {
508                                        if let Some(headers_obj) = headers.as_object() {
509                                            if let Some(host_val) = headers_obj.get("Host") {
510                                                host = host_val.as_str().unwrap_or("").to_string();
511                                            }
512                                            if let Some(edge_val) = headers_obj.get("Edge") {
513                                                edge = edge_val.as_str().unwrap_or("").to_string();
514                                            }
515                                        }
516                                    }
517                                }
518                            }
519                        }
520                        _ => {}
521                    }
522
523                    if security_type == "tls" {
524                        tls = "tls".to_string();
525                        let tls_settings = &stream_settings["tlsSettings"];
526                        sni = tls_settings["serverName"]
527                            .as_str()
528                            .unwrap_or("")
529                            .to_string();
530                    }
531
532                    // Create formatted remark for the node
533                    let formatted_remark = format!("{} ({})", address, port);
534
535                    // Create the proxy object
536                    let node = Proxy::vmess_construct(
537                        "VMess",
538                        &formatted_remark,
539                        &address,
540                        port,
541                        &type_field,
542                        &id,
543                        alter_id,
544                        &network,
545                        &security,
546                        &path,
547                        &host,
548                        &edge,
549                        &tls,
550                        &sni,
551                        None,
552                        None,
553                        None,
554                        None,
555                        "",
556                    );
557
558                    nodes.push(node);
559                    success = true;
560                }
561            }
562        }
563
564        if success {
565            return true;
566        }
567    }
568
569    // Handle custom VMess array format if outbounds format didn't match
570    if json["vmess"].is_array() {
571        let mut group_map: HashMap<String, String> = HashMap::new();
572
573        // Extract subItem data for group information
574        if json["subItem"].is_array() {
575            let sub_items = json["subItem"].as_array().unwrap();
576            for sub_item in sub_items {
577                if let (Some(id), Some(remarks)) =
578                    (sub_item["id"].as_str(), sub_item["remarks"].as_str())
579                {
580                    group_map.insert(id.to_string(), remarks.to_string());
581                }
582            }
583        }
584
585        // Process each VMess entry
586        let vmess_entries = json["vmess"].as_array().unwrap();
587        let mut nodes_added = false;
588
589        for (_, entry) in vmess_entries.iter().enumerate() {
590            // Skip invalid entries
591            if entry["address"].is_null() || entry["port"].is_null() || entry["id"].is_null() {
592                continue;
593            }
594
595            // Extract common fields
596            let ps = entry["remarks"].as_str().unwrap_or("").to_string();
597            let add = entry["address"].as_str().unwrap_or("").to_string();
598            let port = entry["port"].as_u64().unwrap_or(0) as u16;
599            if port == 0 {
600                continue;
601            }
602
603            // Extract sub_id for group information
604            let sub_id = entry["subid"].as_str().unwrap_or("").to_string();
605
606            // Determine group name
607            let mut group = V2RAY_DEFAULT_GROUP.to_string();
608            if !sub_id.is_empty() {
609                if let Some(sub_group) = group_map.get(&sub_id) {
610                    group = sub_group.clone();
611                }
612            }
613
614            // Use address:port as remark if ps is empty
615            let remark = if ps.is_empty() {
616                format!("{} ({})", add, port)
617            } else {
618                ps
619            };
620
621            // Extract configType
622            let config_type = entry["configType"].as_u64().unwrap_or(1);
623
624            // Create appropriate proxy based on configType
625            match config_type {
626                1 => {
627                    // VMess config
628                    let type_field = entry["headerType"].as_str().unwrap_or("").to_string();
629                    let id = entry["id"].as_str().unwrap_or("").to_string();
630                    let aid = entry["alterId"].as_u64().unwrap_or(0) as u16;
631                    let net = entry["network"].as_str().unwrap_or("tcp").to_string();
632                    let path = entry["path"].as_str().unwrap_or("").to_string();
633                    let host = entry["requestHost"].as_str().unwrap_or("").to_string();
634                    let tls = entry["streamSecurity"].as_str().unwrap_or("").to_string();
635                    let cipher = entry["security"].as_str().unwrap_or("auto").to_string();
636                    let sni = entry["sni"].as_str().unwrap_or("").to_string();
637
638                    // Extract security settings
639                    let allow_insecure = entry["allowInsecure"].as_bool();
640
641                    let node = Proxy::vmess_construct(
642                        &group,
643                        &remark,
644                        &add,
645                        port,
646                        &type_field,
647                        &id,
648                        aid,
649                        &net,
650                        &cipher,
651                        &path,
652                        &host,
653                        "",
654                        &tls,
655                        &sni,
656                        None,
657                        None,
658                        allow_insecure,
659                        None,
660                        "",
661                    );
662
663                    nodes.push(node);
664                    nodes_added = true;
665                }
666                3 => {
667                    // SS config
668                    let id = entry["id"].as_str().unwrap_or("").to_string();
669                    let cipher = entry["security"].as_str().unwrap_or("").to_string();
670
671                    let allow_insecure = entry["allowInsecure"].as_bool();
672
673                    let node = Proxy::ss_construct(
674                        SS_DEFAULT_GROUP,
675                        &remark,
676                        &add,
677                        port,
678                        &id,
679                        &cipher,
680                        "",
681                        "",
682                        None,
683                        None,
684                        allow_insecure,
685                        None,
686                        "",
687                    );
688
689                    nodes.push(node);
690                    nodes_added = true;
691                }
692                4 => {
693                    // Socks config
694                    let allow_insecure = entry["allowInsecure"].as_bool();
695
696                    let node = Proxy::socks_construct(
697                        SOCKS_DEFAULT_GROUP,
698                        &remark,
699                        &add,
700                        port,
701                        "",
702                        "",
703                        None,
704                        None,
705                        allow_insecure,
706                        "",
707                    );
708
709                    nodes.push(node);
710                    nodes_added = true;
711                }
712                _ => continue,
713            }
714        }
715
716        return nodes_added;
717    }
718
719    false
720}