guard_core_engine/patterns/
binary.rs1#[must_use]
19pub fn build_binary_prefix(text: &str) -> Vec<u32> {
20 let mut counts = Vec::with_capacity(text.chars().count() + 1);
21 counts.push(0u32);
22 let mut count = 0u32;
23 for c in text.chars() {
24 if is_binary_artifact(c) {
25 count += 1;
26 }
27 counts.push(count);
28 }
29 counts
30}
31
32const BINARY_DENSITY_RADIUS: usize = 64;
33const BINARY_DENSITY_LIMIT: u32 = 4;
34
35#[must_use]
36pub fn match_is_binary_density(
37 binary_prefix: &[u32],
38 match_start: usize,
39 match_end: usize,
40) -> bool {
41 if binary_prefix.is_empty() {
42 return false;
43 }
44 let high = (match_end + BINARY_DENSITY_RADIUS).min(binary_prefix.len() - 1);
45 let low = match_start.saturating_sub(BINARY_DENSITY_RADIUS);
46 binary_prefix[high].saturating_sub(binary_prefix[low]) >= BINARY_DENSITY_LIMIT
47}
48
49#[must_use]
50fn is_binary_artifact(c: char) -> bool {
51 let cp = c as u32;
52 if (cp < 0x20 && !matches!(cp, 0x09 | 0x0A | 0x0D)) || cp == 0x7F {
54 return true;
55 }
56 if cp == 0xFFFD {
58 return true;
59 }
60 if (0xDC80..=0xDCFF).contains(&cp) {
62 return true;
63 }
64 if (0x80..=0x024F).contains(&cp) {
66 return !is_text_allowlist(c);
67 }
68 false
69}
70
71#[must_use]
76fn is_text_allowlist(c: char) -> bool {
77 let cp = c as u32;
78 if (0x00C0..=0x00FF).contains(&cp) {
79 return true;
80 }
81 if matches!(
82 cp,
83 0x00A3 | 0x00A5 | 0x00AA | 0x00B0 | 0x00B1 | 0x00B2 | 0x00B3 | 0x00B5 | 0x00B9 | 0x00BA
84 ) {
85 return true;
86 }
87 (0x0100..=0x017F).contains(&cp)
88}
89
90const BINARY_CONTENT_RATIO_THRESHOLD: f64 = 0.2;
91
92#[must_use]
94pub fn looks_like_binary_content(content: &str) -> bool {
95 if content.is_empty() {
96 return false;
97 }
98 let mut non_text = 0usize;
99 let mut len = 0usize;
100 for c in content.chars() {
101 len += 1;
102 let cp = c as u32;
103 let is_ws = matches!(c, '\t' | '\r' | '\n');
104 let printable = !c.is_control() && !matches!(cp, 0xFFFD);
105 if !is_ws && (!printable || cp == 0xFFFD) {
106 non_text += 1;
107 }
108 }
109 let ratio = non_text as f64 / len as f64;
110 ratio >= BINARY_CONTENT_RATIO_THRESHOLD
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn pure_text_has_zero_artifacts() {
119 let prefix = build_binary_prefix("hello <script> world\t\n");
120 assert_eq!(prefix.last(), Some(&0));
121 }
122
123 #[test]
124 fn control_bytes_count() {
125 let prefix = build_binary_prefix("a\u{1}b\u{2}c\u{3}d\u{4}");
126 assert!(match_is_binary_density(&prefix, 0, 8));
127 }
128
129 #[test]
130 fn accented_text_is_allowed() {
131 let prefix = build_binary_prefix("caf\u{00e9} cr\u{00e8}me br\u{00fb}l\u{00e9}");
132 assert_eq!(prefix.last(), Some(&0));
133 }
134
135 #[test]
136 fn artifact_classes_match_reference_exactly() {
137 const ARTIFACT_RANGES: &[(u32, u32)] = &[
141 (0x00, 0x08),
142 (0x0B, 0x0C),
143 (0x0E, 0x1F),
144 (0x7F, 0xA2),
145 (0xA4, 0xA4),
146 (0xA6, 0xA9),
147 (0xAB, 0xAF),
148 (0xB4, 0xB4),
149 (0xB6, 0xB8),
150 (0xBB, 0xBF),
151 (0x0180, 0x024F),
152 ];
153 let is_ref_artifact = |cp: u32| {
154 ARTIFACT_RANGES
155 .iter()
156 .any(|(lo, hi)| (*lo..=*hi).contains(&cp))
157 };
158 for cp in 0u32..=0x3000 {
159 let c = char::from_u32(cp).expect("BMP scalar");
160 assert_eq!(
161 is_binary_artifact(c),
162 is_ref_artifact(cp),
163 "artifact mismatch at U+{cp:04X}"
164 );
165 }
166 }
167
168 #[test]
169 fn latin1_punctuation_signs_are_text() {
170 let prefix = build_binary_prefix("3 \u{00d7} 4 \u{00f7} 2");
173 assert_eq!(prefix.last(), Some(&0));
174 }
175}