libsubconverter/parser/explodes/
surge.rs1use crate::models::{
2 Proxy, HTTP_DEFAULT_GROUP, SNELL_DEFAULT_GROUP, SOCKS_DEFAULT_GROUP, SS_DEFAULT_GROUP,
3 TROJAN_DEFAULT_GROUP, V2RAY_DEFAULT_GROUP,
4};
5
6pub fn explode_surge(content: &str, nodes: &mut Vec<Proxy>) -> bool {
8 let lines: Vec<&str> = content.lines().collect();
10
11 let mut in_proxy_section = false;
13 let mut success = false;
14
15 for line in lines {
16 let line = line.trim();
18 if line.is_empty() || line.starts_with('#') {
19 continue;
20 }
21
22 if line.starts_with('[') && line.ends_with(']') {
24 in_proxy_section = line == "[Proxy]";
25 continue;
26 }
27
28 if !in_proxy_section {
30 continue;
31 }
32
33 let parts: Vec<&str> = line.splitn(2, '=').collect();
35 if parts.len() != 2 {
36 continue;
37 }
38
39 let name = parts[0].trim();
40 let config = parts[1].trim();
41
42 if config.starts_with("direct")
44 || config.starts_with("reject")
45 || config.starts_with("reject-tinygif")
46 {
47 continue;
48 }
49
50 let mut node = Proxy::default();
52
53 if config.starts_with("custom,") {
54 if parse_surge_custom_ss(config, name, &mut node) {
56 nodes.push(node);
57 success = true;
58 }
59 } else if config.starts_with("ss,") || config.starts_with("shadowsocks,") {
60 if parse_surge_ss(config, name, &mut node) {
62 nodes.push(node);
63 success = true;
64 }
65 } else if config.starts_with("socks5") || config.starts_with("socks5-tls") {
66 if parse_surge_socks(config, name, &mut node) {
67 nodes.push(node);
68 success = true;
69 }
70 } else if config.starts_with("vmess,") {
71 if parse_surge_vmess(config, name, &mut node) {
73 nodes.push(node);
74 success = true;
75 }
76 } else if config.starts_with("http") || config.starts_with("https") {
77 if parse_surge_http(config, name, &mut node) {
78 nodes.push(node);
79 success = true;
80 }
81 } else if config.starts_with("trojan") {
82 if parse_surge_trojan(config, name, &mut node) {
83 nodes.push(node);
84 success = true;
85 }
86 } else if config.starts_with("snell") {
87 if parse_surge_snell(config, name, &mut node) {
88 nodes.push(node);
89 success = true;
90 }
91 }
92 }
93
94 success
95}
96
97fn parse_surge_custom_ss(config: &str, name: &str, node: &mut Proxy) -> bool {
99 let parts: Vec<&str> = config.split(',').map(|s| s.trim()).collect();
101
102 if parts.len() < 5 {
104 return false;
105 }
106
107 let server = parts[1];
109 let port_str = parts[2];
110 let port = match port_str.parse::<u16>() {
111 Ok(p) => p,
112 Err(_) => return false,
113 };
114 if port == 0 {
115 return false;
116 }
117
118 let method = parts[3];
119 let password = parts[4];
120
121 let mut plugin = String::new();
123 let mut plugin_opts = String::new();
124 let mut pluginopts_mode = String::new();
125 let mut pluginopts_host = String::new();
126 let mut udp = None;
127 let mut tfo = None;
128 let scv = None;
129
130 for i in 6..parts.len() {
132 if parts[i].contains('=') {
133 let param_parts: Vec<&str> = parts[i].split('=').collect();
134 if param_parts.len() != 2 {
135 continue;
136 }
137 let key = param_parts[0].trim();
138 let value = param_parts[1].trim();
139
140 match key {
141 "obfs" => {
142 plugin = "simple-obfs".to_string();
143 pluginopts_mode = value.to_string();
144 }
145 "obfs-host" => {
146 pluginopts_host = value.to_string();
147 }
148 "udp-relay" => {
149 udp = Some(value == "true" || value == "1");
150 }
151 "tfo" => {
152 tfo = Some(value == "true" || value == "1");
153 }
154 _ => {}
155 }
156 }
157 }
158
159 if !plugin.is_empty() {
161 plugin_opts = format!("obfs={}", pluginopts_mode);
162 if !pluginopts_host.is_empty() {
163 plugin_opts.push_str(&format!(";obfs-host={}", pluginopts_host));
164 }
165 }
166
167 *node = Proxy::ss_construct(
169 SS_DEFAULT_GROUP,
170 name,
171 server,
172 port,
173 password,
174 method,
175 &plugin,
176 &plugin_opts,
177 udp,
178 tfo,
179 scv,
180 None,
181 "",
182 );
183
184 true
185}
186
187fn parse_surge_ss(config: &str, name: &str, node: &mut Proxy) -> bool {
189 let parts: Vec<&str> = config.split(',').map(|s| s.trim()).collect();
191
192 if parts.len() < 3 {
194 return false;
195 }
196
197 let server = parts[1];
199 let port_str = parts[2];
200 let port = match port_str.parse::<u16>() {
201 Ok(p) => p,
202 Err(_) => return false,
203 };
204 if port == 0 {
205 return false;
206 }
207
208 let mut method = String::new();
210 let mut password = String::new();
211 let mut plugin = String::new();
212 let mut plugin_opts = String::new();
213 let mut pluginopts_mode = String::new();
214 let mut pluginopts_host = String::new();
215 let mut udp = None;
216 let mut tfo = None;
217 let mut scv = None;
218
219 for i in 3..parts.len() {
221 if parts[i].contains('=') {
222 let param_parts: Vec<&str> = parts[i].split('=').collect();
223 if param_parts.len() != 2 {
224 continue;
225 }
226 let key = param_parts[0].trim();
227 let value = param_parts[1].trim();
228
229 match key {
230 "encrypt-method" => {
231 method = value.to_string();
232 }
233 "password" => {
234 password = value.to_string();
235 }
236 "obfs" => {
237 plugin = "simple-obfs".to_string();
238 pluginopts_mode = value.to_string();
239 }
240 "obfs-host" => {
241 pluginopts_host = value.to_string();
242 }
243 "udp-relay" => {
244 udp = Some(value == "true" || value == "1");
245 }
246 "tfo" => {
247 tfo = Some(value == "true" || value == "1");
248 }
249 "skip-cert-verify" => {
250 scv = Some(value == "true" || value == "1");
251 }
252 _ => {}
253 }
254 }
255 }
256
257 if !plugin.is_empty() {
259 plugin_opts = format!("obfs={}", pluginopts_mode);
260 if !pluginopts_host.is_empty() {
261 plugin_opts.push_str(&format!(";obfs-host={}", pluginopts_host));
262 }
263 }
264
265 *node = Proxy::ss_construct(
267 SS_DEFAULT_GROUP,
268 name,
269 server,
270 port,
271 &password,
272 &method,
273 &plugin,
274 &plugin_opts,
275 udp,
276 tfo,
277 scv,
278 None,
279 "",
280 );
281
282 true
283}
284
285fn parse_surge_http(config: &str, name: &str, node: &mut Proxy) -> bool {
287 let parts: Vec<&str> = config.split(',').map(|s| s.trim()).collect();
289
290 if parts.len() < 3 {
292 return false;
293 }
294
295 let server = parts[1];
297 let port_str = parts[2];
298 let port = match port_str.parse::<u16>() {
299 Ok(p) => p,
300 Err(_) => return false,
301 };
302
303 let is_https = parts[0] == "https";
305
306 let mut username = "";
308 let mut password = "";
309 let mut tfo = None;
310 let mut scv = None;
311
312 for i in 3..parts.len() {
314 if parts[i].starts_with("username=") {
315 username = &parts[i][9..];
316 } else if parts[i].starts_with("password=") {
317 password = &parts[i][9..];
318 } else if parts[i] == "tfo=true" {
319 tfo = Some(true);
320 } else if parts[i] == "skip-cert-verify=true" {
321 scv = Some(true);
322 }
323 }
324
325 *node = Proxy::http_construct(
327 HTTP_DEFAULT_GROUP,
328 name,
329 server,
330 port,
331 username,
332 password,
333 is_https,
334 tfo,
335 scv,
336 None,
337 "",
338 );
339
340 true
341}
342
343fn parse_surge_socks(config: &str, name: &str, node: &mut Proxy) -> bool {
345 let parts: Vec<&str> = config.split(',').map(|s| s.trim()).collect();
347
348 if parts.len() < 3 {
350 return false;
351 }
352
353 let server = parts[1];
355 let port_str = parts[2];
356 let port = match port_str.parse::<u16>() {
357 Ok(p) => p,
358 Err(_) => return false,
359 };
360
361 let mut username = "";
363 let mut password = "";
364 let mut udp = None;
365 let mut tfo = None;
366 let mut scv = None;
367
368 if parts.len() >= 5 {
370 username = parts[3];
371 password = parts[4];
372 }
373
374 for i in 5..parts.len() {
376 if parts[i].contains('=') {
377 let param_parts: Vec<&str> = parts[i].split('=').collect();
378 if param_parts.len() != 2 {
379 continue;
380 }
381 let key = param_parts[0].trim();
382 let value = param_parts[1].trim();
383
384 match key {
385 "udp-relay" => {
386 udp = Some(value == "true" || value == "1");
387 }
388 "tfo" => {
389 tfo = Some(value == "true" || value == "1");
390 }
391 "skip-cert-verify" => {
392 scv = Some(value == "true" || value == "1");
393 }
394 _ => {}
395 }
396 }
397 }
398
399 *node = Proxy::socks_construct(
401 SOCKS_DEFAULT_GROUP,
402 name,
403 server,
404 port,
405 username,
406 password,
407 udp,
408 tfo,
409 scv,
410 "",
411 );
412
413 true
414}
415
416fn parse_surge_vmess(config: &str, name: &str, node: &mut Proxy) -> bool {
418 let parts: Vec<&str> = config.split(',').map(|s| s.trim()).collect();
420
421 if parts.len() < 3 {
423 return false;
424 }
425
426 let server = parts[1];
428 let port_str = parts[2];
429 let port = match port_str.parse::<u16>() {
430 Ok(p) => p,
431 Err(_) => return false,
432 };
433 if port == 0 {
434 return false;
435 }
436
437 let mut id = String::new();
439 let mut net = "tcp".to_string();
440 let method = "auto".to_string();
441 let mut path = String::new();
442 let mut host = String::new();
443 let mut edge = String::new();
444 let mut tls = String::new();
445 let mut udp = None;
446 let mut tfo = None;
447 let mut scv = None;
448 let mut tls13 = None;
449 let mut aead = "1".to_string(); for i in 3..parts.len() {
453 if parts[i].contains('=') {
454 let param_parts: Vec<&str> = parts[i].split('=').collect();
455 if param_parts.len() != 2 {
456 continue;
457 }
458 let key = param_parts[0].trim();
459 let value = param_parts[1].trim();
460
461 match key {
462 "username" => {
463 id = value.to_string();
464 }
465 "ws" => {
466 net = if value == "true" {
467 "ws".to_string()
468 } else {
469 "tcp".to_string()
470 };
471 }
472 "tls" => {
473 tls = if value == "true" {
474 "tls".to_string()
475 } else {
476 String::new()
477 };
478 }
479 "ws-path" => {
480 path = value.to_string();
481 }
482 "obfs-host" => {
483 host = value.to_string();
484 }
485 "ws-headers" => {
486 let headers: Vec<&str> = value.split('|').collect();
488 for header in headers {
489 let header_parts: Vec<&str> = header.split(':').collect();
490 if header_parts.len() == 2 {
491 let header_name = header_parts[0].trim().to_lowercase();
492 let header_value = header_parts[1].trim();
493 if header_name == "host" {
494 host = header_value.trim_matches('"').to_string();
495 } else if header_name == "edge" {
496 edge = header_value.trim_matches('"').to_string();
497 }
498 }
499 }
500 }
501 "udp-relay" => {
502 udp = Some(value == "true" || value == "1");
503 }
504 "tfo" => {
505 tfo = Some(value == "true" || value == "1");
506 }
507 "skip-cert-verify" => {
508 scv = Some(value == "true" || value == "1");
509 }
510 "tls13" => {
511 tls13 = Some(value == "true" || value == "1");
512 }
513 "vmess-aead" => {
514 aead = if value == "true" {
515 "0".to_string()
516 } else {
517 "1".to_string()
518 };
519 }
520 _ => {}
521 }
522 }
523 }
524
525 *node = Proxy::vmess_construct(
527 V2RAY_DEFAULT_GROUP,
528 name,
529 server,
530 port,
531 "",
532 &id,
533 aead.parse::<u16>().unwrap_or(0),
534 &net,
535 &method,
536 &path,
537 &host,
538 &edge,
539 &tls,
540 "",
541 udp,
542 tfo,
543 scv,
544 tls13,
545 "",
546 );
547
548 true
549}
550
551fn parse_surge_trojan(config: &str, name: &str, node: &mut Proxy) -> bool {
553 let parts: Vec<&str> = config.split(',').map(|s| s.trim()).collect();
555
556 if parts.len() < 4 {
558 return false;
559 }
560
561 let server = parts[1];
563 let port_str = parts[2];
564 let port = match port_str.parse::<u16>() {
565 Ok(p) => p,
566 Err(_) => return false,
567 };
568 if port == 0 {
569 return false;
570 }
571
572 let mut password = String::new();
574 let mut host = String::new();
575 let mut udp = None;
576 let mut tfo = None;
577 let mut scv = None;
578
579 for i in 3..parts.len() {
581 if parts[i].contains('=') {
582 let param_parts: Vec<&str> = parts[i].split('=').collect();
583 if param_parts.len() != 2 {
584 continue;
585 }
586 let key = param_parts[0].trim();
587 let value = param_parts[1].trim();
588
589 match key {
590 "password" => {
591 password = value.to_string();
592 }
593 "sni" => {
594 host = value.to_string();
595 }
596 "udp-relay" => {
597 udp = Some(value == "true" || value == "1");
598 }
599 "tfo" => {
600 tfo = Some(value == "true" || value == "1");
601 }
602 "skip-cert-verify" => {
603 scv = Some(value == "true" || value == "1");
604 }
605 _ => {}
606 }
607 }
608 }
609
610 if password.is_empty() {
612 password = parts[3].to_string();
613 if password.starts_with("password=") {
615 password = password[9..].to_string();
616 }
617 }
618
619 *node = Proxy::trojan_construct(
621 TROJAN_DEFAULT_GROUP.to_string(),
622 name.to_string(),
623 server.to_string(),
624 port,
625 password,
626 None,
627 if host.is_empty() { None } else { Some(host) },
628 None,
629 None,
630 true,
631 udp,
632 tfo,
633 scv,
634 None,
635 None,
636 );
637
638 true
639}
640
641fn parse_surge_snell(config: &str, name: &str, node: &mut Proxy) -> bool {
643 let parts: Vec<&str> = config.split(',').map(|s| s.trim()).collect();
645
646 if parts.len() < 3 {
648 return false;
649 }
650
651 let server = parts[1];
653 let port_str = parts[2];
654 let port = match port_str.parse::<u16>() {
655 Ok(p) => p,
656 Err(_) => return false,
657 };
658 if port == 0 {
659 return false; }
661
662 let mut password = String::new();
664 let mut plugin = String::new();
665 let mut host = String::new();
666 let mut version = String::new();
667 let mut udp = None;
668 let mut tfo = None;
669 let mut scv = None;
670
671 for i in 3..parts.len() {
673 let param_parts: Vec<&str> = parts[i].split('=').collect();
675 if param_parts.len() != 2 {
676 continue;
677 }
678 let key = param_parts[0].trim();
679 let value = param_parts[1].trim();
680
681 match key {
682 "psk" => password = value.to_string(),
683 "obfs" => plugin = value.to_string(),
684 "obfs-host" => host = value.to_string(),
685 "udp-relay" => udp = Some(value == "true" || value == "1"),
686 "tfo" => tfo = Some(value == "true" || value == "1"),
687 "skip-cert-verify" => scv = Some(value == "true" || value == "1"),
688 "version" => version = value.to_string(),
689 _ => {}
690 }
691 }
692
693 if password.is_empty() {
694 return false;
695 }
696
697 *node = Proxy::snell_construct(
699 SNELL_DEFAULT_GROUP.to_string(),
700 name.to_string(),
701 server.to_string(),
702 port,
703 password.to_string(),
704 plugin,
705 host,
706 version.parse::<u16>().unwrap_or(1),
707 udp,
708 tfo,
709 scv,
710 None,
711 );
712
713 true
714}