1use crate::{
2 models::{Proxy, SOCKS_DEFAULT_GROUP, SS_DEFAULT_GROUP, V2RAY_DEFAULT_GROUP},
3 utils::{base64::url_safe_base64_decode, 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
11pub fn explode_vmess(vmess: &str, node: &mut Proxy) -> bool {
13 if !vmess.starts_with("vmess://") {
15 return false;
16 }
17
18 let encoded = &vmess[8..];
20
21 let decoded = url_safe_base64_decode(encoded);
23
24 let json: Value = match serde_json::from_str(&decoded) {
26 Ok(json) => json,
27 Err(_) => return false,
28 };
29
30 let version = json["v"].as_u64().unwrap_or(1);
32
33 let add = json["add"].as_str().unwrap_or("").to_string();
35 let port = json["port"]
36 .as_str()
37 .map(|s| s.to_string())
38 .unwrap_or_else(|| {
39 json["port"]
40 .as_u64()
41 .map_or_else(|| "0".to_string(), |p| p.to_string())
42 });
43 let id = json["id"].as_str().unwrap_or("").to_string();
44 let aid = json["aid"]
45 .as_str()
46 .map(|s| s.to_string())
47 .unwrap_or_else(|| {
48 json["aid"]
49 .as_u64()
50 .map_or_else(|| "0".to_string(), |a| a.to_string())
51 });
52 let net = json["net"].as_str().unwrap_or("tcp").to_string();
53 let type_field = json["type"].as_str().unwrap_or("").to_string();
54 let mut host = json["host"].as_str().unwrap_or("").to_string();
55 let mut path = json["path"].as_str().unwrap_or("").to_string();
56 let tls = json["tls"].as_str().unwrap_or("").to_string();
57 let sni = json["sni"].as_str().unwrap_or("").to_string();
58
59 let remark = json["ps"].as_str().unwrap_or("").to_string();
61
62 let port = port.parse::<u16>().unwrap_or(0);
64 let aid = aid.parse::<u16>().unwrap_or(0);
65
66 if version == 2 {
68 if !host.is_empty() {
69 let host_str = host.clone();
70 let parts: Vec<&str> = host_str.split(';').collect();
71 if parts.len() == 2 {
72 host = parts[0].to_string();
73 path = parts[1].to_string();
74 }
75 }
76 }
77
78 *node = Proxy::vmess_construct(
80 "VMess",
81 &remark,
82 &add,
83 port,
84 &type_field,
85 &id,
86 aid,
87 &net,
88 "auto",
89 &path,
90 &host,
91 "",
92 &tls,
93 &sni,
94 None,
95 None,
96 None,
97 None,
98 "",
99 );
100
101 true
102}
103
104pub fn explode_std_vmess(vmess: &str, node: &mut Proxy) -> bool {
108 if !vmess.starts_with("vmess://") && !vmess.starts_with("vmess+") {
110 return false;
111 }
112
113 let protocol_end = match vmess.find("://") {
115 Some(pos) => pos,
116 None => return false,
117 };
118
119 let protocol = vmess[..protocol_end].to_string();
120 let tls = protocol.contains("+tls");
121
122 let url_part = &vmess[protocol_end + 3..];
124
125 let (url_without_fragment, remark) = match url_part.find('#') {
127 Some(pos) => (url_part[..pos].to_string(), url_part[pos + 1..].to_string()),
128 None => (url_part.to_string(), String::new()),
129 };
130
131 let re = Regex::new(
133 r"^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})-(\d+)@([^:]+):(\d+)(.*)$",
134 )
135 .unwrap();
136
137 let caps = match re.captures(&url_without_fragment) {
138 Some(c) => c,
139 None => {
140 log::warn!("Failed to explode vmess link by regex: {}", vmess);
141 return false;
142 }
143 };
144
145 let id = caps.get(1).map_or("", |m| m.as_str()).to_string();
146 let aid = caps
147 .get(2)
148 .map_or("0", |m| m.as_str())
149 .parse::<u16>()
150 .unwrap_or(0);
151 let host = caps.get(3).map_or("", |m| m.as_str()).to_string();
152 let port = caps
153 .get(4)
154 .map_or("0", |m| m.as_str())
155 .parse::<u16>()
156 .unwrap_or(0);
157 let query = caps.get(5).map_or("", |m| m.as_str()).to_string();
158
159 let mut net = "tcp".to_string();
161 let mut path = "/".to_string();
162 let mut host_header = host.clone();
163 let mut tls_str = if tls {
164 "tls".to_string()
165 } else {
166 String::new()
167 };
168 let mut sni = String::new();
169
170 if !query.is_empty() && query.starts_with("/?") {
172 for param in query[2..].split('&') {
173 let mut kv = param.split('=');
174 if let (Some(k), Some(v)) = (kv.next(), kv.next()) {
175 match k {
176 "network" => net = v.to_string(),
177 "host" => host_header = v.to_string(),
178 "path" => path = v.to_string(),
179 "tls" => tls_str = v.to_string(),
180 "sni" => sni = v.to_string(),
181 _ => {}
182 }
183 }
184 }
185 }
186
187 let formatted_remark = if remark.is_empty() {
189 format!("{} ({})", host, port)
190 } else {
191 remark
192 };
193
194 *node = Proxy::vmess_construct(
196 "VMess",
197 &formatted_remark,
198 &host,
199 port,
200 "",
201 &id,
202 aid,
203 &net,
204 "auto",
205 &path,
206 &host_header,
207 "",
208 &tls_str,
209 &sni,
210 None,
211 None,
212 None,
213 None,
214 "",
215 );
216
217 true
218}
219
220pub fn explode_shadowrocket(rocket: &str, node: &mut Proxy) -> bool {
222 if !rocket.starts_with("vmess://") {
224 return false;
225 }
226
227 let url = match Url::parse(rocket) {
229 Ok(url) => url,
230 Err(_) => return false,
231 };
232
233 let host = url.host_str().unwrap_or("").to_string();
235 let port = url.port().unwrap_or(0);
236 if port == 0 {
237 return false;
238 }
239
240 let username = url.username().to_string();
242 if username.is_empty() {
243 return false;
244 }
245
246 let decoded = match STANDARD.decode(username) {
248 Ok(decoded) => match String::from_utf8(decoded) {
249 Ok(s) => s,
250 Err(_) => return false,
251 },
252 Err(_) => return false,
253 };
254
255 let parts: Vec<&str> = decoded.split(':').collect();
257 if parts.len() < 6 {
258 return false;
259 }
260
261 let method = parts[0].to_string();
262 let id = parts[1].to_string();
263 let aid = parts[2].parse::<u16>().unwrap_or(0);
264
265 let mut net = "tcp".to_string();
267 let mut path = "/".to_string();
268 let mut host_header = host.clone();
269 let mut tls = String::new();
270 let mut sni = String::new();
271
272 for (key, value) in url.query_pairs() {
273 let value = url_decode(&value);
274 match key.as_ref() {
275 "obfs" => net = value,
276 "path" => path = value,
277 "obfsParam" => host_header = value,
278 "tls" => {
279 tls = if value == "1" {
280 "tls".to_string()
281 } else {
282 String::new()
283 }
284 }
285 "peer" => sni = value,
286 _ => {}
287 }
288 }
289
290 let remark = url_decode(url.fragment().unwrap_or(""));
292 let formatted_remark = if remark.is_empty() {
293 format!("{} ({})", host, port)
294 } else {
295 remark
296 };
297
298 *node = Proxy::vmess_construct(
300 "VMess",
301 &formatted_remark,
302 &host,
303 port,
304 "",
305 &id,
306 aid,
307 &net,
308 &method,
309 &path,
310 &host_header,
311 "",
312 &tls,
313 &sni,
314 None,
315 None,
316 None,
317 None,
318 "",
319 );
320
321 true
322}
323
324pub fn explode_kitsunebi(kit: &str, node: &mut Proxy) -> bool {
326 if !kit.starts_with("vmess://") {
328 return false;
329 }
330
331 let encoded = &kit[8..];
333
334 let decoded = match STANDARD.decode(encoded) {
336 Ok(decoded) => match String::from_utf8(decoded) {
337 Ok(s) => s,
338 Err(_) => return false,
339 },
340 Err(_) => return false,
341 };
342
343 let lines: Vec<&str> = decoded.lines().collect();
345 if lines.is_empty() {
346 return false;
347 }
348
349 let parts: Vec<&str> = lines[0].split(',').collect();
351 if parts.len() < 4 {
352 return false;
353 }
354
355 let add = parts[0].to_string();
356 let port = parts[1].parse::<u16>().unwrap_or(0);
357 let id = parts[2].to_string();
358 let aid = parts[3].parse::<u16>().unwrap_or(0);
359
360 let mut net = "tcp".to_string();
362 let mut path = "/".to_string();
363 let mut host = add.clone();
364 let mut tls = String::new();
365 let mut sni = String::new();
366 let mut remark = format!("{} ({})", add, port);
367
368 for i in 4..parts.len() {
370 let kv: Vec<&str> = parts[i].split('=').collect();
371 if kv.len() != 2 {
372 continue;
373 }
374
375 let value = kv[1].to_string();
376 match kv[0] {
377 "net" => net = value,
378 "path" => path = value,
379 "host" => host = value,
380 "tls" => tls = value,
381 "sni" => sni = value,
382 "remarks" | "remark" => remark = value,
383 _ => {}
384 }
385 }
386
387 *node = Proxy::vmess_construct(
389 "VMess", &remark, &add, port, "", &id, aid, &net, "auto", &path, &host, "", &tls, &sni,
390 None, None, None, None, "",
391 );
392
393 true
394}
395
396pub fn explode_vmess_conf(content: &str, nodes: &mut Vec<Proxy>) -> bool {
398 let json: Value = match serde_json::from_str(content) {
400 Ok(json) => json,
401 Err(_) => return false,
402 };
403
404 if json["outbounds"].is_array() {
406 let outbounds = json["outbounds"].as_array().unwrap();
408 let mut success = false;
409
410 for outbound in outbounds {
411 if outbound["protocol"].as_str().unwrap_or("") != "vmess" {
413 continue;
414 }
415
416 let settings = &outbound["settings"];
418 if !settings["vnext"].is_array() {
419 continue;
420 }
421
422 let vnext = settings["vnext"].as_array().unwrap();
424
425 for server in vnext {
426 let address = server["address"].as_str().unwrap_or("").to_string();
427 let port = server["port"].as_u64().unwrap_or(0) as u16;
428 if port == 0 {
429 continue;
430 }
431
432 if !server["users"].is_array() {
434 continue;
435 }
436
437 let users = server["users"].as_array().unwrap();
438
439 for user in users {
440 let id = user["id"].as_str().unwrap_or("").to_string();
441 let alter_id = user["alterId"].as_u64().unwrap_or(0) as u16;
442 let security = user["security"].as_str().unwrap_or("auto").to_string();
443
444 let stream_settings = &outbound["streamSettings"];
446 let network = stream_settings["network"]
447 .as_str()
448 .unwrap_or("tcp")
449 .to_string();
450 let security_type = stream_settings["security"]
451 .as_str()
452 .unwrap_or("")
453 .to_string();
454
455 let mut host = String::new();
457 let mut path = String::new();
458 let mut edge = String::new();
459 let mut tls = String::new();
460 let mut sni = String::new();
461 let mut type_field = String::new();
462
463 match network.as_str() {
464 "ws" => {
465 let ws_settings = &stream_settings["wsSettings"];
466 path = ws_settings["path"].as_str().unwrap_or("").to_string();
467
468 if let Some(headers) = ws_settings["headers"].as_object() {
469 if let Some(host_val) = headers.get("Host") {
470 host = host_val.as_str().unwrap_or("").to_string();
471 }
472 if let Some(edge_val) = headers.get("Edge") {
473 edge = edge_val.as_str().unwrap_or("").to_string();
474 }
475 }
476 }
477 "h2" => {
478 let h2_settings = &stream_settings["httpSettings"];
479 path = h2_settings["path"].as_str().unwrap_or("").to_string();
480
481 if let Some(hosts) = h2_settings["host"].as_array() {
482 if !hosts.is_empty() {
483 host = hosts[0].as_str().unwrap_or("").to_string();
484 }
485 }
486 }
487 "tcp" => {
488 let tcp_settings = &stream_settings["tcpSettings"];
489 if tcp_settings["header"]["type"].as_str().unwrap_or("") == "http" {
490 type_field = "http".to_string();
491
492 if let Some(request) = tcp_settings["header"]["request"].as_object()
493 {
494 if let Some(paths) = request.get("path") {
495 if let Some(paths_array) = paths.as_array() {
496 if !paths_array.is_empty() {
497 path = paths_array[0]
498 .as_str()
499 .unwrap_or("")
500 .to_string();
501 }
502 }
503 }
504
505 if let Some(headers) = request.get("headers") {
506 if let Some(headers_obj) = headers.as_object() {
507 if let Some(host_val) = headers_obj.get("Host") {
508 host = host_val.as_str().unwrap_or("").to_string();
509 }
510 if let Some(edge_val) = headers_obj.get("Edge") {
511 edge = edge_val.as_str().unwrap_or("").to_string();
512 }
513 }
514 }
515 }
516 }
517 }
518 _ => {}
519 }
520
521 if security_type == "tls" {
522 tls = "tls".to_string();
523 let tls_settings = &stream_settings["tlsSettings"];
524 sni = tls_settings["serverName"]
525 .as_str()
526 .unwrap_or("")
527 .to_string();
528 }
529
530 let formatted_remark = format!("{} ({})", address, port);
532
533 let node = Proxy::vmess_construct(
535 "VMess",
536 &formatted_remark,
537 &address,
538 port,
539 &type_field,
540 &id,
541 alter_id,
542 &network,
543 &security,
544 &path,
545 &host,
546 &edge,
547 &tls,
548 &sni,
549 None,
550 None,
551 None,
552 None,
553 "",
554 );
555
556 nodes.push(node);
557 success = true;
558 }
559 }
560 }
561
562 if success {
563 return true;
564 }
565 }
566
567 if json["vmess"].is_array() {
569 let mut group_map: HashMap<String, String> = HashMap::new();
570
571 if json["subItem"].is_array() {
573 let sub_items = json["subItem"].as_array().unwrap();
574 for sub_item in sub_items {
575 if let (Some(id), Some(remarks)) =
576 (sub_item["id"].as_str(), sub_item["remarks"].as_str())
577 {
578 group_map.insert(id.to_string(), remarks.to_string());
579 }
580 }
581 }
582
583 let vmess_entries = json["vmess"].as_array().unwrap();
585 let mut nodes_added = false;
586
587 for (_, entry) in vmess_entries.iter().enumerate() {
588 if entry["address"].is_null() || entry["port"].is_null() || entry["id"].is_null() {
590 continue;
591 }
592
593 let ps = entry["remarks"].as_str().unwrap_or("").to_string();
595 let add = entry["address"].as_str().unwrap_or("").to_string();
596 let port = entry["port"].as_u64().unwrap_or(0) as u16;
597 if port == 0 {
598 continue;
599 }
600
601 let sub_id = entry["subid"].as_str().unwrap_or("").to_string();
603
604 let mut group = V2RAY_DEFAULT_GROUP.to_string();
606 if !sub_id.is_empty() {
607 if let Some(sub_group) = group_map.get(&sub_id) {
608 group = sub_group.clone();
609 }
610 }
611
612 let remark = if ps.is_empty() {
614 format!("{} ({})", add, port)
615 } else {
616 ps
617 };
618
619 let config_type = entry["configType"].as_u64().unwrap_or(1);
621
622 match config_type {
624 1 => {
625 let type_field = entry["headerType"].as_str().unwrap_or("").to_string();
627 let id = entry["id"].as_str().unwrap_or("").to_string();
628 let aid = entry["alterId"].as_u64().unwrap_or(0) as u16;
629 let net = entry["network"].as_str().unwrap_or("tcp").to_string();
630 let path = entry["path"].as_str().unwrap_or("").to_string();
631 let host = entry["requestHost"].as_str().unwrap_or("").to_string();
632 let tls = entry["streamSecurity"].as_str().unwrap_or("").to_string();
633 let cipher = entry["security"].as_str().unwrap_or("auto").to_string();
634 let sni = entry["sni"].as_str().unwrap_or("").to_string();
635
636 let allow_insecure = entry["allowInsecure"].as_bool();
638
639 let node = Proxy::vmess_construct(
640 &group,
641 &remark,
642 &add,
643 port,
644 &type_field,
645 &id,
646 aid,
647 &net,
648 &cipher,
649 &path,
650 &host,
651 "",
652 &tls,
653 &sni,
654 None,
655 None,
656 allow_insecure,
657 None,
658 "",
659 );
660
661 nodes.push(node);
662 nodes_added = true;
663 }
664 3 => {
665 let id = entry["id"].as_str().unwrap_or("").to_string();
667 let cipher = entry["security"].as_str().unwrap_or("").to_string();
668
669 let allow_insecure = entry["allowInsecure"].as_bool();
670
671 let node = Proxy::ss_construct(
672 SS_DEFAULT_GROUP,
673 &remark,
674 &add,
675 port,
676 &id,
677 &cipher,
678 "",
679 "",
680 None,
681 None,
682 allow_insecure,
683 None,
684 "",
685 );
686
687 nodes.push(node);
688 nodes_added = true;
689 }
690 4 => {
691 let allow_insecure = entry["allowInsecure"].as_bool();
693
694 let node = Proxy::socks_construct(
695 SOCKS_DEFAULT_GROUP,
696 &remark,
697 &add,
698 port,
699 "",
700 "",
701 None,
702 None,
703 allow_insecure,
704 "",
705 );
706
707 nodes.push(node);
708 nodes_added = true;
709 }
710 _ => continue,
711 }
712 }
713
714 return nodes_added;
715 }
716
717 false
718}
719
720pub fn explode_std_vmess_new(vmess_str: &str, node: &mut Proxy) -> bool {
730 let url = match Url::parse(vmess_str) {
731 Ok(u) => u,
732 Err(_) => {
733 log::debug!("Failed to parse VMess URL: {}", vmess_str);
734 return false;
735 }
736 };
737
738 let mut initial_tls_str = String::new();
740 match url.scheme() {
741 "vmess" => { }
742 "vmess+tls" => initial_tls_str = "tls".to_string(),
743 s => {
744 log::debug!("Invalid VMess scheme: {}", s);
745 return false;
746 }
747 }
748
749 let server_address = match url.host_str() {
750 Some(h) if !h.is_empty() => h.to_string(),
751 _ => {
752 log::debug!("VMess URL missing or empty host");
753 return false;
754 }
755 };
756
757 let server_port = match url.port() {
758 Some(p) => p,
759 None => {
760 log::debug!("VMess URL missing port");
761 return false;
762 }
763 };
764
765 let user_info_str = url.username();
766 if user_info_str.is_empty() {
767 log::debug!("VMess URL missing user info (uuid)");
768 return false;
769 }
770
771 let id: String;
772 let mut aid: u16 = 0;
773
774 if let Some(last_hyphen_pos) = user_info_str.rfind('-') {
776 if last_hyphen_pos > 0 && last_hyphen_pos < user_info_str.len() - 1 {
779 let potential_id_part = &user_info_str[..last_hyphen_pos];
780 let potential_aid_str = &user_info_str[last_hyphen_pos + 1..];
781 if let Ok(parsed_aid) = potential_aid_str.parse::<u16>() {
782 id = potential_id_part.to_string();
783 aid = parsed_aid;
784 } else {
785 id = user_info_str.to_string();
787 }
788 } else {
789 id = user_info_str.to_string();
791 }
792 } else {
793 id = user_info_str.to_string();
795 }
796
797 if id.is_empty() {
799 log::debug!("Parsed empty ID from VMess URL user info");
800 return false;
801 }
802
803 let mut net = "tcp".to_string();
805 let mut path_query = "/".to_string();
806 let mut host_header = server_address.clone(); let mut tls_str = initial_tls_str; let mut sni = String::new();
809 let mut security_param = "auto".to_string(); for (key_cow, value_cow) in url.query_pairs() {
812 let key = key_cow.as_ref();
813 let value = value_cow.into_owned();
815
816 match key {
817 "type" | "network" => net = value,
818 "host" => host_header = value, "path" => {
820 if value.is_empty() || value == "/" {
821 path_query = "/".to_string();
822 } else if value.starts_with('/') {
823 path_query = value;
824 } else {
825 path_query = format!("/{}", value); }
828 }
829 "tls" => {
830 if value.eq_ignore_ascii_case("true") || value == "1" {
832 tls_str = "tls".to_string();
833 } else if value.eq_ignore_ascii_case("false") || value == "0" {
834 tls_str = String::new();
835 } else {
836 tls_str = value; }
838 }
839 "sni" => sni = value,
840 "encryption" | "security" => security_param = value, _ => { }
842 }
843 }
844
845 let remark_from_fragment = url.fragment().map_or_else(String::new, |f| url_decode(f));
846
847 let formatted_remark = if remark_from_fragment.is_empty() {
848 format!("{} ({})", server_address, server_port)
849 } else {
850 remark_from_fragment
851 };
852
853 *node = Proxy::vmess_construct(
854 "VMess", &formatted_remark, &server_address, server_port, "", &id, aid, &net, &security_param, &path_query, &host_header, "", &tls_str, &sni, None, None, None, None, "", );
875
876 true
877}