allow_core/
fingerprint.rs1use crate::policy::AllowEntry;
2use sha2::{Digest, Sha256};
3
4pub fn normalize_snippet(input: &str) -> String {
5 strip_rust_comments(input)
6 .split_whitespace()
7 .collect::<Vec<_>>()
8 .join(" ")
9}
10
11fn strip_rust_comments(input: &str) -> String {
12 let mut out = String::with_capacity(input.len());
13 let mut chars = input.chars().peekable();
14 while let Some(ch) = chars.next() {
15 match ch {
16 '"' => consume_quoted_string(ch, &mut chars, &mut out),
17 'r' if consume_raw_string(&mut chars, &mut out) => {}
18 '/' => match chars.peek().copied() {
19 Some('/') => {
20 let _ = chars.next();
21 consume_line_comment(&mut chars);
22 out.push(' ');
23 }
24 Some('*') => {
25 let _ = chars.next();
26 consume_block_comment(&mut chars);
27 out.push(' ');
28 }
29 _ => out.push(ch),
30 },
31 _ => out.push(ch),
32 }
33 }
34 out
35}
36
37fn consume_quoted_string(
38 quote: char,
39 chars: &mut std::iter::Peekable<std::str::Chars<'_>>,
40 out: &mut String,
41) {
42 out.push(quote);
43 let mut escaped = false;
44 for ch in chars.by_ref() {
45 out.push(ch);
46 if escaped {
47 escaped = false;
48 } else if ch == '\\' {
49 escaped = true;
50 } else if ch == quote {
51 break;
52 }
53 }
54}
55
56fn consume_raw_string(
57 chars: &mut std::iter::Peekable<std::str::Chars<'_>>,
58 out: &mut String,
59) -> bool {
60 let mut lookahead = chars.clone();
61 let mut hashes = 0usize;
62 while lookahead.peek().copied() == Some('#') {
63 let _ = lookahead.next();
64 hashes += 1;
65 }
66 if lookahead.next() != Some('"') {
67 return false;
68 }
69
70 out.push('r');
71 for _ in 0..hashes {
72 let Some(hash) = chars.next() else {
73 return true;
74 };
75 out.push(hash);
76 }
77 let Some(quote) = chars.next() else {
78 return true;
79 };
80 out.push(quote);
81 consume_raw_string_tail(chars, out, hashes);
82 true
83}
84
85fn consume_raw_string_tail(
86 chars: &mut std::iter::Peekable<std::str::Chars<'_>>,
87 out: &mut String,
88 hashes: usize,
89) {
90 while let Some(ch) = chars.next() {
91 out.push(ch);
92 if ch != '"' {
93 continue;
94 }
95 let mut matched = 0usize;
96 while matched < hashes && chars.peek().copied() == Some('#') {
97 let Some(hash) = chars.next() else {
98 break;
99 };
100 out.push(hash);
101 matched += 1;
102 }
103 if matched == hashes {
104 break;
105 }
106 }
107}
108
109fn consume_line_comment(chars: &mut std::iter::Peekable<std::str::Chars<'_>>) {
110 for ch in chars.by_ref() {
111 if ch == '\n' {
112 break;
113 }
114 }
115}
116
117fn consume_block_comment(chars: &mut std::iter::Peekable<std::str::Chars<'_>>) {
118 let mut depth = 1usize;
119 while let Some(ch) = chars.next() {
120 match (ch, chars.peek().copied()) {
121 ('/', Some('*')) => {
122 let _ = chars.next();
123 depth += 1;
124 }
125 ('*', Some('/')) => {
126 let _ = chars.next();
127 depth = depth.saturating_sub(1);
128 if depth == 0 {
129 break;
130 }
131 }
132 _ => {}
133 }
134 }
135}
136
137pub fn stable_hash_hex(input: &str) -> String {
138 let mut hash: u64 = 0xcbf29ce484222325;
140 for byte in input.as_bytes() {
141 hash ^= u64::from(*byte);
142 hash = hash.wrapping_mul(0x100000001b3);
143 }
144 format!("fnv1a64:{hash:016x}")
145}
146
147const ALLOW_ENTRY_FINGERPRINT_SCHEMA: &str = "cargo-allow.allow-entry-fingerprint.v1";
148
149pub fn allow_entry_content_fingerprint(entry: &AllowEntry) -> String {
158 let mut canonical = Vec::new();
159 write_string(&mut canonical, ALLOW_ENTRY_FINGERPRINT_SCHEMA);
160 write_string(&mut canonical, &entry.id);
161 write_string(&mut canonical, entry.kind.as_str());
162 write_optional_string(&mut canonical, entry.family.as_deref());
163 write_optional_string(
164 &mut canonical,
165 entry.path.as_deref().map(crate::normalize_path).as_deref(),
166 );
167 write_optional_string(
168 &mut canonical,
169 entry.glob.as_deref().map(crate::normalize_path).as_deref(),
170 );
171 write_string(&mut canonical, &entry.owner);
172 write_string(&mut canonical, &entry.classification);
173 write_string(&mut canonical, &entry.reason);
174 write_string_list(&mut canonical, &entry.evidence);
175 write_string_list(&mut canonical, &entry.links);
176 write_optional_u32(&mut canonical, entry.occurrence_limit);
177 write_optional_string(&mut canonical, entry.lifecycle.created.as_deref());
178 write_optional_string(&mut canonical, entry.lifecycle.review_after.as_deref());
179 write_optional_string(&mut canonical, entry.lifecycle.expires.as_deref());
180
181 write_optional_string(&mut canonical, entry.selector.ast_kind.as_deref());
182 write_optional_string(&mut canonical, entry.selector.container.as_deref());
183 write_optional_string(&mut canonical, entry.selector.callee.as_deref());
184 write_optional_string(&mut canonical, entry.selector.macro_name.as_deref());
185 write_optional_string(&mut canonical, entry.selector.lint.as_deref());
186 write_optional_string(&mut canonical, entry.selector.symbol.as_deref());
187 write_optional_string(
188 &mut canonical,
189 entry.selector.receiver_fingerprint.as_deref(),
190 );
191 write_optional_string(&mut canonical, entry.selector.target_fingerprint.as_deref());
192 write_optional_string(
193 &mut canonical,
194 entry.selector.normalized_snippet_hash.as_deref(),
195 );
196 write_optional_u32(&mut canonical, entry.selector.line_hint);
197 write_optional_string(
198 &mut canonical,
199 entry
200 .selector
201 .glob
202 .as_deref()
203 .map(crate::normalize_path)
204 .as_deref(),
205 );
206 match &entry.last_seen {
207 Some(last_seen) => {
208 canonical.push(1);
209 canonical.extend_from_slice(&last_seen.line.to_be_bytes());
210 canonical.extend_from_slice(&last_seen.column.to_be_bytes());
211 }
212 None => canonical.push(0),
213 }
214
215 let digest = Sha256::digest(canonical);
216 let hex = digest
217 .iter()
218 .map(|byte| format!("{byte:02x}"))
219 .collect::<String>();
220 format!("sha256:v1:{hex}")
221}
222
223fn write_string(output: &mut Vec<u8>, value: &str) {
224 output.extend_from_slice(&(value.len() as u64).to_be_bytes());
225 output.extend_from_slice(value.as_bytes());
226}
227
228fn write_optional_string(output: &mut Vec<u8>, value: Option<&str>) {
229 match value {
230 Some(value) => {
231 output.push(1);
232 write_string(output, value);
233 }
234 None => output.push(0),
235 }
236}
237
238fn write_string_list(output: &mut Vec<u8>, values: &[String]) {
239 output.extend_from_slice(&(values.len() as u64).to_be_bytes());
240 for value in values {
241 write_string(output, value);
242 }
243}
244
245fn write_optional_u32(output: &mut Vec<u8>, value: Option<u32>) {
246 match value {
247 Some(value) => {
248 output.push(1);
249 output.extend_from_slice(&value.to_be_bytes());
250 }
251 None => output.push(0),
252 }
253}