1pub mod rules;
2mod entropy;
3mod tool;
4
5use rayon::prelude::*;
6use std::borrow::Cow;
7use std::fmt;
8use std::fmt::Formatter;
9
10pub type UsizeRange = (usize, usize);
11
12#[derive(Debug)]
13pub struct Hit<'a, D> {
14 pub describe: Cow<'a, str>,
15 pub importance: u8,
16 pub data: D,
17}
18
19impl<'a> fmt::Display for Hit<'a, RuleResult<'a>> {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 let (reset, yellow, grey) = if f.alternate() {
22 ("\x1b[0m", "\x1b[33m", "\x1b[90m")
23 } else {
24 ("", "", "")
25 };
26
27 writeln!(
29 f,
30 " {}↳{} [{}{}{}]",
31 grey, reset, yellow, self.describe, reset
32 )?;
33
34 for (i, extracted) in self.data.discovered.iter().enumerate() {
36 let cleaned_extracted = extracted.replace('\n', "\n ");
37 write!(f, " {}↳{} {}", grey, reset, cleaned_extracted)?;
38 if i < self.data.discovered.len() - 1 {
40 writeln!(f)?;
41 }
42 }
43
44 Ok(())
45 }
46}
47
48#[derive(Debug)]
49pub struct State<'a> {
50 pub input: &'a str,
51 pub ranges: Vec<UsizeRange>,
52}
53
54impl<'a> State<'a> {
55 pub fn new(input: &'a str) -> Self {
56 Self {
57 input,
58 ranges: Vec::new(),
59 }
60 }
61
62 pub fn retain<F>(&mut self, mut f: F)
63 where
64 F: FnMut(&'a str, UsizeRange) -> bool,
65 {
66 let input = self.input;
67 self.ranges.retain(|&r| f(input, r));
68 }
69}
70impl<'a> From<&'a str> for State<'a> {
71 fn from(s: &'a str) -> State<'a> {
72 State::new(s)
73 }
74}
75
76#[derive(Debug)]
77pub struct RuleResult<'a> {
78 pub complete_input: &'a str,
79 pub ranges: Vec<UsizeRange>,
80 pub discovered: Vec<&'a str>,
81}
82
83impl<'a> From<State<'a>> for RuleResult<'a> {
84 fn from(state: State<'a>) -> Self {
85 let discovered = state
86 .ranges
87 .iter()
88 .map(|&(s, e)| &state.input[s..e])
89 .collect();
90 Self {
91 complete_input: state.input,
92 ranges: state.ranges,
93 discovered,
94 }
95 }
96}
97
98pub struct AnyRule {
99 pub flow: Vec<Box<dyn for<'a> Fn(&mut State<'a>) -> bool + Send + Sync>>,
100 pub out: Box<dyn for<'a> Fn(State<'a>) -> Hit<'a, RuleResult<'a>> + Send + Sync>,
101}
102
103impl AnyRule {
104 pub fn new<O>(out: O) -> Self
105 where
106 O: for<'a> Fn(State<'a>) -> Hit<'a, RuleResult<'a>> + Send + Sync + 'static,
107 {
108 Self {
109 flow: Vec::new(),
110 out: Box::new(out),
111 }
112 }
113
114 pub fn add_flow<F>(mut self, flow: F) -> Self
115 where
116 F: for<'a> Fn(&mut State<'a>) -> bool + Send + Sync + 'static,
117 {
118 self.flow.push(Box::new(flow));
119 self
120 }
121
122 pub fn detect<'a>(&self, input: &'a str) -> Option<Hit<'a, RuleResult<'a>>> {
123 let mut state = State::new(input);
124
125 for flow in &self.flow {
126 if !flow(&mut state) {
127 return None;
128 }
129 }
130 Some((self.out)(state))
131 }
132}
133
134pub fn analyze_with<'a>(input: &'a str, rules: &[AnyRule]) -> AnalyzeResult<'a> {
135 AnalyzeResult {
136 input,
137 results: rules
138 .par_iter()
139 .filter_map(|rule| rule.detect(input))
140 .collect(),
141 }
142}
143
144pub struct AnalyzeResult<'a> {
145 pub input: &'a str,
146 pub results: Vec<Hit<'a, RuleResult<'a>>>,
147}
148
149impl<'a> AnalyzeResult<'a> {
150 pub fn new(input: &'a str, results: Vec<Hit<'a, RuleResult<'a>>>) -> Self {
151 Self { input, results }
152 }
153
154 pub fn score(&self) -> u8 {
156 if self.results.is_empty() {
157 return 0;
158 }
159 let mut fail_prob = 1.0f64;
160 for hit in &self.results {
161 let prob = (hit.importance as f64) / 100.0;
162 fail_prob *= 1.0 - prob;
163 }
164 ((1.0 - fail_prob) * 100.0).round() as u8
165 }
166}
167
168impl fmt::Display for AnalyzeResult<'_> {
169 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
170 if self.results.is_empty() {
171 return write!(f, "{}", self.input);
172 }
173
174 let ansi = f.alternate();
176 let (reset, red) = if ansi {
177 ("\x1b[0m", "\x1b[31m")
178 } else {
179 ("", "")
180 };
181
182 let mut all_ranges: Vec<UsizeRange> = self
184 .results
185 .iter()
186 .flat_map(|hit| hit.data.ranges.iter().copied())
187 .collect();
188
189 all_ranges.sort_unstable_by_key(|r| r.0);
190
191 let mut merged_ranges: Vec<UsizeRange> = Vec::with_capacity(all_ranges.len());
192 for r in all_ranges {
193 if let Some(last) = merged_ranges.last_mut() {
194 if r.0 <= last.1 {
195 last.1 = last.1.max(r.1);
196 continue;
197 }
198 }
199 merged_ranges.push(r);
200 }
201
202 let mut cursor = 0;
204 for &(start, end) in &merged_ranges {
205 if !self.input.is_char_boundary(start) || !self.input.is_char_boundary(end) {
206 continue;
207 }
208
209 if start > cursor {
210 write!(f, "{}", &self.input[cursor..start])?;
211 }
212 write!(f, "{}{}{}", red, &self.input[start..end], reset)?;
213
214 cursor = end;
215 }
216 if cursor < self.input.len() {
217 write!(f, "{}", &self.input[cursor..])?;
218 }
219
220 writeln!(f)?; for (i, hit) in self.results.iter().enumerate() {
224 if ansi {
225 write!(f, "{:#}", hit)?;
226 } else {
227 write!(f, "{}", hit)?;
228 }
229
230 if i < self.results.len() - 1 {
231 writeln!(f)?;
232 }
233 }
234
235 Ok(())
236 }
237}
238
239
240#[cfg(test)]
241mod tests {
242 use crate::rules::ALL_RULES;
243 use super::*;
244
245 #[test]
246 fn test_comprehensive() {
247 let input = r#"
248Base64: mTyqm7wjODkrNLcWl0eqO8K8gc1BPk1GNLgUpI== 444 m7wjODkrNLcWl0eqO8K8gc1BPk1GNLgUpI==
249api_key =
250"12345678abcdefgh";
251 "#;
252
253 let result = analyze_with(input, ALL_RULES.as_slice());
254 let score = result.score();
255
256 println!("[综合测试评分: {score}]\n{:#}", result);
257 }
258}