1use crate::models::{Proxy, ProxyType};
2use lazy_static::lazy_static;
3use regex::Regex;
4use std::collections::HashMap;
5use std::str::FromStr;
6
7lazy_static! {
8 static ref GROUPID_REGEX: Regex =
9 Regex::new(r"^!!(?:GROUPID|INSERT)=([\d\-+!,]+)(?:!!(.*))?$").unwrap();
10 static ref GROUP_REGEX: Regex = Regex::new(r"^!!(?:GROUP)=(.+?)(?:!!(.*))?$").unwrap();
11 static ref TYPE_REGEX: Regex = Regex::new(r"^!!(?:TYPE)=(.+?)(?:!!(.*))?$").unwrap();
12 static ref PORT_REGEX: Regex = Regex::new(r"^!!(?:PORT)=(.+?)(?:!!(.*))?$").unwrap();
13 static ref SERVER_REGEX: Regex = Regex::new(r"^!!(?:SERVER)=(.+?)(?:!!(.*))?$").unwrap();
14 static ref PROTOCOL_REGEX: Regex = Regex::new(r"^!!(?:PROTOCOL)=(.+?)(?:!!(.*))?$").unwrap();
15 static ref UDPSUPPORT_REGEX: Regex =
16 Regex::new(r"^!!(?:UDPSUPPORT)=(.+?)(?:!!(.*))?$").unwrap();
17 static ref SECURITY_REGEX: Regex = Regex::new(r"^!!(?:SECURITY)=(.+?)(?:!!(.*))?$").unwrap();
18 static ref REMARKS_REGEX: Regex = Regex::new(r"^!!(?:REMARKS)=(.+?)(?:!!(.*))?$").unwrap();
19 static ref PROXY_TYPES: HashMap<ProxyType, &'static str> = {
20 let mut m = HashMap::new();
21 m.insert(ProxyType::Shadowsocks, "SS");
22 m.insert(ProxyType::ShadowsocksR, "SSR");
23 m.insert(ProxyType::VMess, "VMESS");
24 m.insert(ProxyType::Trojan, "TROJAN");
25 m.insert(ProxyType::Snell, "SNELL");
26 m.insert(ProxyType::HTTP, "HTTP");
27 m.insert(ProxyType::HTTPS, "HTTPS");
28 m.insert(ProxyType::Socks5, "SOCKS5");
29 m.insert(ProxyType::WireGuard, "WIREGUARD");
30 m.insert(ProxyType::Hysteria, "HYSTERIA");
31 m.insert(ProxyType::Hysteria2, "HYSTERIA2");
32 m.insert(ProxyType::Unknown, "UNKNOWN");
33 m
34 };
35}
36
37pub fn apply_matcher(rule: &str, real_rule: &mut String, node: &Proxy) -> bool {
65 if rule.starts_with("!!GROUP=") {
66 if let Some(captures) = GROUP_REGEX.captures(rule) {
67 let target = captures.get(1).map_or("", |m| m.as_str());
68 *real_rule = captures.get(2).map_or("", |m| m.as_str()).to_string();
69 return reg_find(&node.group, target);
70 }
71 } else if rule.starts_with("!!GROUPID=") || rule.starts_with("!!INSERT=") {
72 let dir = if rule.starts_with("!!INSERT=") { -1 } else { 1 };
73 if let Some(captures) = GROUPID_REGEX.captures(rule) {
74 let target = captures.get(1).map_or("", |m| m.as_str());
75 *real_rule = captures.get(2).map_or("", |m| m.as_str()).to_string();
76 return match_range(target, dir * (node.group_id as i32));
77 }
78 } else if rule.starts_with("!!TYPE=") {
79 if let Some(captures) = TYPE_REGEX.captures(rule) {
80 let target = captures.get(1).map_or("", |m| m.as_str());
81 *real_rule = captures.get(2).map_or("", |m| m.as_str()).to_string();
82 if node.proxy_type == ProxyType::Unknown {
83 return false;
84 }
85
86 let type_str = PROXY_TYPES.get(&node.proxy_type).unwrap_or(&"UNKNOWN");
87 return reg_match(type_str, target);
88 }
89 } else if rule.starts_with("!!PORT=") {
90 if let Some(captures) = PORT_REGEX.captures(rule) {
91 let target = captures.get(1).map_or("", |m| m.as_str());
92 *real_rule = captures.get(2).map_or("", |m| m.as_str()).to_string();
93 return match_range(target, node.port as i32);
94 }
95 } else if rule.starts_with("!!SERVER=") {
96 if let Some(captures) = SERVER_REGEX.captures(rule) {
97 let target = captures.get(1).map_or("", |m| m.as_str());
98 *real_rule = captures.get(2).map_or("", |m| m.as_str()).to_string();
99 return reg_find(&node.hostname, target);
100 }
101 } else if rule.starts_with("!!PROTOCOL=") {
102 if let Some(captures) = PROTOCOL_REGEX.captures(rule) {
103 let target = captures.get(1).map_or("", |m| m.as_str());
104 *real_rule = captures.get(2).map_or("", |m| m.as_str()).to_string();
105 let protocol = match &node.protocol {
106 Some(proto) => proto,
107 None => return false,
108 };
109 return reg_find(protocol, target);
110 }
111 } else if rule.starts_with("!!UDPSUPPORT=") {
112 if let Some(captures) = UDPSUPPORT_REGEX.captures(rule) {
113 let target = captures.get(1).map_or("", |m| m.as_str());
114 *real_rule = captures.get(2).map_or("", |m| m.as_str()).to_string();
115
116 match node.udp {
117 Some(true) => return reg_match("yes", target),
118 Some(false) => return reg_match("no", target),
119 None => return reg_match("undefined", target),
120 }
121 }
122 } else if rule.starts_with("!!SECURITY=") {
123 if let Some(captures) = SECURITY_REGEX.captures(rule) {
124 let target = captures.get(1).map_or("", |m| m.as_str());
125 *real_rule = captures.get(2).map_or("", |m| m.as_str()).to_string();
126
127 let mut features = String::new();
129
130 if node.tls_secure {
131 features.push_str("TLS,");
132 }
133
134 if let Some(true) = node.allow_insecure {
135 features.push_str("INSECURE,");
136 }
137
138 if let Some(true) = node.tls13 {
139 features.push_str("TLS13,");
140 }
141
142 if !features.is_empty() {
143 features.pop(); } else {
145 features.push_str("NONE");
146 }
147
148 return reg_find(&features, target);
149 }
150 } else if rule.starts_with("!!REMARKS=") {
151 if let Some(captures) = REMARKS_REGEX.captures(rule) {
152 let target = captures.get(1).map_or("", |m| m.as_str());
153 *real_rule = captures.get(2).map_or("", |m| m.as_str()).to_string();
154 return reg_find(&node.remark, target);
155 }
156 } else {
157 *real_rule = rule.to_string();
158 }
159
160 true
161}
162
163pub fn match_range(range: &str, target: i32) -> bool {
179 let mut negate = false;
180 let mut matched = false;
181
182 for range_part in range.split(',') {
183 let mut part = range_part.trim();
184
185 if part.starts_with('!') {
186 negate = true;
187 part = &part[1..];
188 }
189
190 if part.contains('-') {
191 let bounds: Vec<&str> = part.split('-').collect();
192 if bounds.len() == 2 {
193 let lower = bounds[0].parse::<i32>().unwrap_or(i32::MIN);
194 let upper = bounds[1].parse::<i32>().unwrap_or(i32::MAX);
195
196 if target >= lower && target <= upper {
197 matched = true;
198 break;
199 }
200 }
201 } else if let Ok(exact) = part.parse::<i32>() {
202 if target == exact {
203 matched = true;
204 break;
205 }
206 }
207 }
208
209 if negate {
210 !matched
211 } else {
212 matched
213 }
214}
215
216pub fn reg_find(text: &str, pattern: &str) -> bool {
226 if pattern.is_empty() {
227 return true;
228 }
229
230 match Regex::new(&format!("(?i){}", pattern)) {
231 Ok(re) => re.is_match(text),
232 Err(_) => false,
233 }
234}
235
236pub fn reg_match(text: &str, pattern: &str) -> bool {
246 if pattern.is_empty() {
247 return true;
248 }
249
250 match Regex::new(&format!("(?i)^{}$", pattern)) {
251 Ok(re) => re.is_match(text),
252 Err(_) => false,
253 }
254}
255
256#[derive(Debug, Clone)]
257pub struct CompiledRange {
258 lower: i32,
259 upper: i32,
260}
261
262#[derive(Debug, Clone)]
263pub enum CompiledMatcher {
264 Group(Regex),
266 GroupId {
268 ranges: Vec<CompiledRange>,
269 negate: bool,
270 },
271 Type(Regex),
273 Port {
275 ranges: Vec<CompiledRange>,
276 negate: bool,
277 },
278 Server(Regex),
280 Protocol(Regex),
282 UdpSupport(Regex),
285 Security(Regex),
288 Remarks(Regex),
290 Plain(Regex),
292 AlwaysTrue,
294 Invalid,
296}
297
298#[derive(Debug, Clone)]
299pub struct CompiledRule {
300 pub matcher: CompiledMatcher,
301 pub sub_rule: Option<Box<CompiledRule>>, }
303
304fn parse_range_string(range_str: &str) -> (Vec<CompiledRange>, bool) {
305 let mut negate = false;
306 let mut ranges = Vec::new();
307 let mut effective_range_str = range_str;
308
309 if let Some(stripped) = range_str.strip_prefix('!') {
310 negate = true;
311 effective_range_str = stripped;
312 }
313
314 for range_part in effective_range_str.split(',') {
315 let part = range_part.trim();
316 if part.is_empty() {
317 continue;
318 }
319
320 if part.contains('-') {
321 let bounds: Vec<&str> = part.split('-').collect();
322 if bounds.len() == 2 {
323 let lower = bounds[0].parse::<i32>().unwrap_or_else(|_| i32::MIN);
325 let upper = bounds[1].parse::<i32>().unwrap_or_else(|_| i32::MAX);
326 if lower <= upper {
327 ranges.push(CompiledRange { lower, upper });
328 } }
330 } else if let Ok(exact) = part.parse::<i32>() {
331 ranges.push(CompiledRange {
332 lower: exact,
333 upper: exact,
334 });
335 }
336 }
338 (ranges, negate)
339}
340
341pub fn compile_rule(rule: &str) -> CompiledRule {
346 let mut sub_rule_str: Option<&str> = None;
347 let matcher = if let Some(captures) = GROUP_REGEX.captures(rule) {
348 sub_rule_str = captures.get(2).map(|m| m.as_str());
349 let target = captures.get(1).map_or("", |m| m.as_str());
350 Regex::new(&format!("(?i){}", target))
351 .map(CompiledMatcher::Group)
352 .unwrap_or(CompiledMatcher::Invalid)
353 } else if let Some(captures) = GROUPID_REGEX.captures(rule) {
354 sub_rule_str = captures.get(2).map(|m| m.as_str());
355 let target = captures.get(1).map_or("", |m| m.as_str());
356 let dir = if rule.starts_with("!!INSERT=") { -1 } else { 1 }; let (ranges, negate) = parse_range_string(target);
358 CompiledMatcher::GroupId { ranges, negate }
360 } else if let Some(captures) = TYPE_REGEX.captures(rule) {
361 sub_rule_str = captures.get(2).map(|m| m.as_str());
362 let target = captures.get(1).map_or("", |m| m.as_str());
363 Regex::new(&format!("(?i)^{}$", target))
364 .map(CompiledMatcher::Type)
365 .unwrap_or(CompiledMatcher::Invalid)
366 } else if let Some(captures) = PORT_REGEX.captures(rule) {
367 sub_rule_str = captures.get(2).map(|m| m.as_str());
368 let target = captures.get(1).map_or("", |m| m.as_str());
369 let (ranges, negate) = parse_range_string(target);
370 CompiledMatcher::Port { ranges, negate }
371 } else if let Some(captures) = SERVER_REGEX.captures(rule) {
372 sub_rule_str = captures.get(2).map(|m| m.as_str());
373 let target = captures.get(1).map_or("", |m| m.as_str());
374 Regex::new(&format!("(?i){}", target))
375 .map(CompiledMatcher::Server)
376 .unwrap_or(CompiledMatcher::Invalid)
377 } else if let Some(captures) = PROTOCOL_REGEX.captures(rule) {
378 sub_rule_str = captures.get(2).map(|m| m.as_str());
379 let target = captures.get(1).map_or("", |m| m.as_str());
380 Regex::new(&format!("(?i){}", target))
381 .map(CompiledMatcher::Protocol)
382 .unwrap_or(CompiledMatcher::Invalid)
383 } else if let Some(captures) = UDPSUPPORT_REGEX.captures(rule) {
384 sub_rule_str = captures.get(2).map(|m| m.as_str());
385 let target = captures.get(1).map_or("", |m| m.as_str());
386 Regex::new(&format!("(?i)^{}$", target))
387 .map(CompiledMatcher::UdpSupport)
388 .unwrap_or(CompiledMatcher::Invalid)
389 } else if let Some(captures) = SECURITY_REGEX.captures(rule) {
390 sub_rule_str = captures.get(2).map(|m| m.as_str());
391 let target = captures.get(1).map_or("", |m| m.as_str());
392 Regex::new(&format!("(?i){}", target))
393 .map(CompiledMatcher::Security)
394 .unwrap_or(CompiledMatcher::Invalid)
395 } else if let Some(captures) = REMARKS_REGEX.captures(rule) {
396 sub_rule_str = captures.get(2).map(|m| m.as_str());
397 let target = captures.get(1).map_or("", |m| m.as_str());
398 Regex::new(&format!("(?i){}", target))
399 .map(CompiledMatcher::Remarks)
400 .unwrap_or(CompiledMatcher::Invalid)
401 } else {
402 if rule.is_empty() {
404 CompiledMatcher::AlwaysTrue
405 } else {
406 Regex::new(&format!("(?i){}", rule))
407 .map(CompiledMatcher::Plain)
408 .unwrap_or(CompiledMatcher::Invalid)
409 }
410 };
411
412 let sub_rule = sub_rule_str
413 .filter(|s| !s.is_empty()) .map(|s| Box::new(compile_rule(s)));
415
416 CompiledRule { matcher, sub_rule }
417}
418
419pub fn apply_compiled_rule(compiled_rule: &CompiledRule, node: &Proxy) -> bool {
428 let primary_match = match &compiled_rule.matcher {
429 CompiledMatcher::Group(re) => re.is_match(&node.group),
430 CompiledMatcher::GroupId { ranges, negate } => {
431 let target = node.group_id as i32; let mut matched = false;
444 for r in ranges {
445 if target >= r.lower && target <= r.upper {
446 matched = true;
447 break;
448 }
449 }
450 if *negate {
451 !matched
452 } else {
453 matched
454 }
455 }
456 CompiledMatcher::Type(re) => {
457 if node.proxy_type == ProxyType::Unknown {
458 false
459 } else {
460 let type_str = PROXY_TYPES.get(&node.proxy_type).unwrap_or(&"UNKNOWN");
461 re.is_match(type_str)
462 }
463 }
464 CompiledMatcher::Port { ranges, negate } => {
465 let target = node.port as i32;
466 let mut matched = false;
467 for r in ranges {
468 if target >= r.lower && target <= r.upper {
469 matched = true;
470 break;
471 }
472 }
473 if *negate {
474 !matched
475 } else {
476 matched
477 }
478 }
479 CompiledMatcher::Server(re) => re.is_match(&node.hostname),
480 CompiledMatcher::Protocol(re) => node.protocol.as_ref().map_or(false, |p| re.is_match(p)),
481 CompiledMatcher::UdpSupport(re) => {
482 let udp_str = match node.udp {
483 Some(true) => "yes",
484 Some(false) => "no",
485 None => "undefined",
486 };
487 re.is_match(udp_str)
488 }
489 CompiledMatcher::Security(re) => {
490 let mut features = String::new();
491 if node.tls_secure {
492 features.push_str("TLS,");
493 }
494 if let Some(true) = node.allow_insecure {
495 features.push_str("INSECURE,");
496 }
497 if let Some(true) = node.tls13 {
498 features.push_str("TLS13,");
499 }
500 if !features.is_empty() {
501 features.pop();
502 } else {
503 features.push_str("NONE");
504 }
505 re.is_match(&features)
506 }
507 CompiledMatcher::Remarks(re) | CompiledMatcher::Plain(re) => re.is_match(&node.remark),
508 CompiledMatcher::AlwaysTrue => true,
509 CompiledMatcher::Invalid => false, };
511
512 match &compiled_rule.sub_rule {
515 Some(sub) => primary_match && apply_compiled_rule(sub, node),
516 None => primary_match,
517 }
518}
519
520pub fn apply_compiled_rule_to_string(compiled_rule: &CompiledRule, text: &str) -> bool {
523 let primary_match = match &compiled_rule.matcher {
526 CompiledMatcher::Plain(re) | CompiledMatcher::Remarks(re) => re.is_match(text),
527 CompiledMatcher::AlwaysTrue => true,
528 CompiledMatcher::Invalid => false,
529 _ => false,
531 };
532
533 if compiled_rule.sub_rule.is_some() {
540 false
541 } else {
542 primary_match
543 }
544}
545
546pub fn replace_with_compiled_regex(
560 text: &str,
561 re: &Regex,
562 replacement: &str,
563 replace_all: bool,
564 literal: bool,
565) -> String {
566 let result = if replace_all {
567 if literal {
568 re.replace_all(text, regex::NoExpand(replacement))
569 } else {
570 re.replace_all(text, replacement)
571 }
572 } else {
573 if literal {
575 re.replacen(text, 1, regex::NoExpand(replacement))
576 } else {
577 re.replacen(text, 1, replacement)
578 }
579 };
580 result.into_owned() }
582
583#[cfg(test)]
584mod tests {
585 use super::*;
586 use crate::models::ProxyType;
587
588 fn create_test_proxy() -> Proxy {
590 Proxy {
591 id: 1,
592 group_id: 2,
593 group: "TestGroup".to_string(),
594 remark: "TestRemark".to_string(),
595 hostname: "example.com".to_string(),
596 port: 8080,
597 proxy_type: ProxyType::Shadowsocks,
598 protocol: Some("origin".to_string()),
599 udp: Some(true),
600 tls_secure: true,
601 tls13: Some(true),
602 ..Default::default()
603 }
604 }
605
606 #[test]
607 fn test_match_range_simple() {
608 assert!(match_range("5", 5));
609 assert!(!match_range("5", 6));
610 }
611
612 #[test]
613 fn test_match_range_with_ranges() {
614 assert!(match_range("1-10", 5));
615 assert!(!match_range("1-10", 11));
616 }
617
618 #[test]
619 fn test_match_range_with_negation() {
620 assert!(!match_range("!5", 5));
621 assert!(match_range("!5", 6));
622 assert!(!match_range("!1-10", 5));
623 assert!(match_range("!1-10", 11));
624 }
625
626 #[test]
627 fn test_match_range_with_multiple() {
628 assert!(match_range("1-5,10-15", 3));
629 assert!(match_range("1-5,10-15", 12));
630 assert!(!match_range("1-5,10-15", 7));
631 }
632
633 #[test]
634 fn test_match_range_complex() {
635 assert!(match_range("!1-5,10,15-20", 12));
636 assert!(!match_range("!1-5,10,15-20", 10));
637 assert!(!match_range("!1-5,10,15-20", 3));
638 assert!(match_range("!1-5,10,15-20", 6));
639 }
640
641 #[test]
642 fn test_reg_find() {
643 assert!(reg_find("This is a test", "test"));
644 assert!(reg_find("This is a test", "TEST")); assert!(!reg_find("This is a test", "banana"));
646 assert!(reg_find("This is a test", "")); }
648
649 #[test]
650 fn test_reg_match() {
651 assert!(reg_match("12345", r"^\d+$"));
652 assert!(!reg_match("12345a", r"^\d+$"));
653 assert!(reg_match("HELLO", r"(?i)hello"));
654 }
655
656 #[test]
657 fn test_apply_matcher_group() {
658 let node = create_test_proxy();
659 let mut real_rule = String::new();
660
661 assert!(apply_matcher("!!GROUP=TestGroup", &mut real_rule, &node));
662 assert_eq!(real_rule, "");
663
664 real_rule.clear();
665 assert!(!apply_matcher("!!GROUP=OtherGroup", &mut real_rule, &node));
666 }
667
668 #[test]
669 fn test_apply_matcher_type() {
670 let node = create_test_proxy();
671 let mut real_rule = String::new();
672
673 assert!(apply_matcher("!!TYPE=SS", &mut real_rule, &node));
674 assert_eq!(real_rule, "");
675
676 real_rule.clear();
677 assert!(!apply_matcher("!!TYPE=VMess", &mut real_rule, &node));
678 }
679
680 #[test]
681 fn test_apply_matcher_port() {
682 let node = create_test_proxy();
683 let mut real_rule = String::new();
684
685 assert!(apply_matcher("!!PORT=8080", &mut real_rule, &node));
686 assert_eq!(real_rule, "");
687
688 real_rule.clear();
689 assert!(apply_matcher("!!PORT=8000-9000", &mut real_rule, &node));
690
691 real_rule.clear();
692 assert!(!apply_matcher("!!PORT=443", &mut real_rule, &node));
693 }
694
695 #[test]
696 fn test_apply_matcher_server() {
697 let node = create_test_proxy();
698 let mut real_rule = String::new();
699
700 assert!(apply_matcher("!!SERVER=example", &mut real_rule, &node));
701 assert_eq!(real_rule, "");
702
703 real_rule.clear();
704 assert!(!apply_matcher("!!SERVER=google", &mut real_rule, &node));
705 }
706
707 #[test]
708 fn test_apply_matcher_protocol() {
709 let node = create_test_proxy();
710 let mut real_rule = String::new();
711
712 assert!(apply_matcher("!!PROTOCOL=origin", &mut real_rule, &node));
713 assert_eq!(real_rule, "");
714
715 real_rule.clear();
716 assert!(!apply_matcher(
717 "!!PROTOCOL=auth_sha1",
718 &mut real_rule,
719 &node
720 ));
721 }
722
723 #[test]
724 fn test_apply_matcher_udp_support() {
725 let node = create_test_proxy();
726 let mut real_rule = String::new();
727
728 assert!(apply_matcher("!!UDPSUPPORT=yes", &mut real_rule, &node));
729 assert_eq!(real_rule, "");
730
731 real_rule.clear();
732 assert!(!apply_matcher("!!UDPSUPPORT=no", &mut real_rule, &node));
733
734 let mut node_no_udp = node.clone();
736 node_no_udp.udp = None;
737
738 real_rule.clear();
739 assert!(apply_matcher(
740 "!!UDPSUPPORT=undefined",
741 &mut real_rule,
742 &node_no_udp
743 ));
744 }
745
746 #[test]
747 fn test_apply_matcher_security() {
748 let node = create_test_proxy();
749 let mut real_rule = String::new();
750
751 assert!(apply_matcher("!!SECURITY=TLS", &mut real_rule, &node));
752 assert_eq!(real_rule, "");
753
754 real_rule.clear();
755 assert!(apply_matcher("!!SECURITY=TLS13", &mut real_rule, &node));
756
757 real_rule.clear();
758 assert!(!apply_matcher("!!SECURITY=INSECURE", &mut real_rule, &node));
759
760 let mut node_insecure = node.clone();
762 node_insecure.allow_insecure = Some(true);
763
764 real_rule.clear();
765 assert!(apply_matcher(
766 "!!SECURITY=INSECURE",
767 &mut real_rule,
768 &node_insecure
769 ));
770 }
771
772 #[test]
773 fn test_apply_matcher_remarks() {
774 let node = create_test_proxy();
775 let mut real_rule = String::new();
776
777 assert!(apply_matcher("!!REMARKS=Test", &mut real_rule, &node));
778 assert_eq!(real_rule, "");
779
780 real_rule.clear();
781 assert!(!apply_matcher("!!REMARKS=Premium", &mut real_rule, &node));
782 }
783
784 #[test]
785 fn test_apply_matcher_with_trailing_rule() {
786 let node = create_test_proxy();
787 let mut real_rule = String::new();
788
789 assert!(apply_matcher(
790 "!!GROUP=TestGroup!!.+",
791 &mut real_rule,
792 &node
793 ));
794 assert_eq!(real_rule, ".+");
795
796 }
799
800 fn create_proxy_for_compile_test(
802 group: &str,
803 group_id: i32,
804 ptype: ProxyType,
805 port: u16,
806 hostname: &str,
807 protocol: Option<&str>,
808 udp: Option<bool>,
809 tls: bool,
810 insecure: Option<bool>,
811 tls13: Option<bool>,
812 remark: &str,
813 ) -> Proxy {
814 Proxy {
815 id: 1, group_id: group_id,
817 group: group.to_string(),
818 remark: remark.to_string(),
819 hostname: hostname.to_string(),
820 port,
821 proxy_type: ptype,
822 protocol: protocol.map(|s| s.to_string()),
823 udp,
824 tls_secure: tls,
825 allow_insecure: insecure,
826 tls13,
827 ..Default::default()
828 }
829 }
830
831 #[test]
832 fn test_compile_rule_plain_regex() {
833 let rule = compile_rule("some_remark_pattern");
834 assert!(matches!(rule.matcher, CompiledMatcher::Plain(_)));
835 assert!(rule.sub_rule.is_none());
836 let node = create_proxy_for_compile_test(
837 "G",
838 1,
839 ProxyType::HTTP,
840 80,
841 "h",
842 None,
843 None,
844 false,
845 None,
846 None,
847 "this matches some_remark_pattern",
848 );
849 assert!(apply_compiled_rule(&rule, &node));
850 assert!(apply_compiled_rule_to_string(
851 &rule,
852 "this matches some_remark_pattern"
853 ));
854 assert!(!apply_compiled_rule_to_string(&rule, "no match here"));
855 }
856
857 #[test]
858 fn test_compile_rule_group() {
859 let rule = compile_rule("!!GROUP=Test.*");
860 assert!(matches!(rule.matcher, CompiledMatcher::Group(_)));
861 assert!(rule.sub_rule.is_none());
862 let node = create_proxy_for_compile_test(
863 "TestGroup",
864 1,
865 ProxyType::HTTP,
866 80,
867 "h",
868 None,
869 None,
870 false,
871 None,
872 None,
873 "remark",
874 );
875 assert!(apply_compiled_rule(&rule, &node));
876 let node2 = create_proxy_for_compile_test(
877 "OtherGroup",
878 1,
879 ProxyType::HTTP,
880 80,
881 "h",
882 None,
883 None,
884 false,
885 None,
886 None,
887 "remark",
888 );
889 assert!(!apply_compiled_rule(&rule, &node2));
890 }
891
892 #[test]
893 fn test_compile_rule_group_with_subrule() {
894 let rule = compile_rule("!!GROUP=Test.*!!PORT=80");
895 assert!(matches!(rule.matcher, CompiledMatcher::Group(_)));
896 assert!(rule.sub_rule.is_some());
897 assert!(matches!(
898 rule.sub_rule.as_ref().unwrap().matcher,
899 CompiledMatcher::Port { .. }
900 ));
901
902 let node_match = create_proxy_for_compile_test(
903 "TestGroup",
904 1,
905 ProxyType::HTTP,
906 80,
907 "h",
908 None,
909 None,
910 false,
911 None,
912 None,
913 "remark",
914 );
915 let node_no_group = create_proxy_for_compile_test(
916 "OtherGroup",
917 1,
918 ProxyType::HTTP,
919 80,
920 "h",
921 None,
922 None,
923 false,
924 None,
925 None,
926 "remark",
927 );
928 let node_no_port = create_proxy_for_compile_test(
929 "TestGroup",
930 1,
931 ProxyType::HTTP,
932 81,
933 "h",
934 None,
935 None,
936 false,
937 None,
938 None,
939 "remark",
940 );
941
942 assert!(apply_compiled_rule(&rule, &node_match));
943 assert!(!apply_compiled_rule(&rule, &node_no_group));
944 assert!(!apply_compiled_rule(&rule, &node_no_port));
945 }
946
947 #[test]
948 fn test_compile_rule_groupid() {
949 let rule = compile_rule("!!GROUPID=1-5,10");
952 assert!(matches!(rule.matcher, CompiledMatcher::GroupId { .. }));
953 let node = create_proxy_for_compile_test(
954 "G",
955 3,
956 ProxyType::HTTP,
957 80,
958 "h",
959 None,
960 None,
961 false,
962 None,
963 None,
964 "remark",
965 );
966 assert!(apply_compiled_rule(&rule, &node));
967 let node2 = create_proxy_for_compile_test(
968 "G",
969 7,
970 ProxyType::HTTP,
971 80,
972 "h",
973 None,
974 None,
975 false,
976 None,
977 None,
978 "remark",
979 );
980 assert!(!apply_compiled_rule(&rule, &node2));
981 let node3 = create_proxy_for_compile_test(
982 "G",
983 10,
984 ProxyType::HTTP,
985 80,
986 "h",
987 None,
988 None,
989 false,
990 None,
991 None,
992 "remark",
993 );
994 assert!(apply_compiled_rule(&rule, &node3));
995 }
996
997 #[test]
998 fn test_compile_rule_port_negated() {
999 let rule = compile_rule("!!PORT=!80,443");
1000 assert!(matches!(
1001 rule.matcher,
1002 CompiledMatcher::Port { negate: true, .. }
1003 ));
1004 let node_80 = create_proxy_for_compile_test(
1005 "G",
1006 1,
1007 ProxyType::HTTP,
1008 80,
1009 "h",
1010 None,
1011 None,
1012 false,
1013 None,
1014 None,
1015 "remark",
1016 );
1017 let node_443 = create_proxy_for_compile_test(
1018 "G",
1019 1,
1020 ProxyType::HTTPS,
1021 443,
1022 "h",
1023 None,
1024 None,
1025 false,
1026 None,
1027 None,
1028 "remark",
1029 );
1030 let node_other = create_proxy_for_compile_test(
1031 "G",
1032 1,
1033 ProxyType::Socks5,
1034 1080,
1035 "h",
1036 None,
1037 None,
1038 false,
1039 None,
1040 None,
1041 "remark",
1042 );
1043 assert!(!apply_compiled_rule(&rule, &node_80));
1044 assert!(!apply_compiled_rule(&rule, &node_443));
1045 assert!(apply_compiled_rule(&rule, &node_other));
1046 }
1047
1048 #[test]
1049 fn test_compile_rule_type() {
1050 let rule = compile_rule("!!TYPE=S(S|OCKS5)"); assert!(matches!(rule.matcher, CompiledMatcher::Type(_)));
1052 let node_ss = create_proxy_for_compile_test(
1053 "G",
1054 1,
1055 ProxyType::Shadowsocks,
1056 80,
1057 "h",
1058 None,
1059 None,
1060 false,
1061 None,
1062 None,
1063 "remark",
1064 );
1065 let node_socks = create_proxy_for_compile_test(
1066 "G",
1067 1,
1068 ProxyType::Socks5,
1069 1080,
1070 "h",
1071 None,
1072 None,
1073 false,
1074 None,
1075 None,
1076 "remark",
1077 );
1078 let node_vmess = create_proxy_for_compile_test(
1079 "G",
1080 1,
1081 ProxyType::VMess,
1082 80,
1083 "h",
1084 None,
1085 None,
1086 false,
1087 None,
1088 None,
1089 "remark",
1090 );
1091 assert!(apply_compiled_rule(&rule, &node_ss));
1092 assert!(apply_compiled_rule(&rule, &node_socks));
1093 assert!(!apply_compiled_rule(&rule, &node_vmess));
1094 }
1095
1096 #[test]
1097 fn test_compile_rule_security() {
1098 let rule = compile_rule("!!SECURITY=TLS,TLS13");
1099 assert!(matches!(rule.matcher, CompiledMatcher::Security(_)));
1100
1101 let node_match = create_proxy_for_compile_test(
1102 "G",
1103 1,
1104 ProxyType::Trojan,
1105 443,
1106 "h",
1107 None,
1108 None,
1109 true,
1110 None,
1111 Some(true),
1112 "remark",
1113 );
1114 let node_no_tls13 = create_proxy_for_compile_test(
1115 "G",
1116 1,
1117 ProxyType::Trojan,
1118 443,
1119 "h",
1120 None,
1121 None,
1122 true,
1123 None,
1124 Some(false),
1125 "remark",
1126 );
1127 let node_insecure = create_proxy_for_compile_test(
1128 "G",
1129 1,
1130 ProxyType::Trojan,
1131 443,
1132 "h",
1133 None,
1134 None,
1135 true,
1136 Some(true),
1137 Some(true),
1138 "remark",
1139 ); assert!(apply_compiled_rule(&rule, &node_match)); assert!(apply_compiled_rule(&rule, &node_no_tls13)); assert!(apply_compiled_rule(&rule, &node_insecure)); }
1145}