entracte_lib/
license_redact.rs1pub fn redact_license_shapes(input: &str) -> String {
15 let mut out = String::with_capacity(input.len());
16 let bytes = input.as_bytes();
17 let mut i = 0;
18 while i < bytes.len() {
19 if i + 5 <= bytes.len() && &bytes[i..i + 5] == b"ENT1-" {
20 let mut j = i + 5;
21 while j < bytes.len()
22 && (bytes[j].is_ascii_alphanumeric() || bytes[j] == b'-' || bytes[j] == b'_')
23 {
24 j += 1;
25 }
26 if j - i >= 12 {
27 out.push_str("[REDACTED-MANUAL-TOKEN]");
28 i = j;
29 continue;
30 }
31 }
32 if looks_like_ls_key_at(bytes, i) {
33 out.push_str("[REDACTED-LS-KEY]");
34 i = ls_key_end(bytes, i);
35 continue;
36 }
37 out.push(bytes[i] as char);
38 i += 1;
39 }
40 out
41}
42
43fn looks_like_ls_key_at(bytes: &[u8], start: usize) -> bool {
44 if start + 19 > bytes.len() {
45 return false;
46 }
47 if !bytes[start..start + 4]
48 .iter()
49 .all(|b| b.is_ascii_alphanumeric())
50 {
51 return false;
52 }
53 let mut groups = 1;
54 let mut i = start + 4;
55 while groups < 4 && i + 5 <= bytes.len() && bytes[i] == b'-' {
56 if !bytes[i + 1..i + 5]
57 .iter()
58 .all(|b| b.is_ascii_alphanumeric())
59 {
60 return false;
61 }
62 i += 5;
63 groups += 1;
64 }
65 groups >= 4
66}
67
68fn ls_key_end(bytes: &[u8], start: usize) -> usize {
69 let mut i = start + 4;
70 while i + 5 <= bytes.len() && bytes[i] == b'-' && {
71 bytes[i + 1..i + 5]
72 .iter()
73 .all(|b| b.is_ascii_alphanumeric())
74 } {
75 i += 5;
76 }
77 i
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn masks_lemon_squeezy_key() {
86 let got = redact_license_shapes("activated ABCD-1111-2222-3333 ok");
87 assert!(got.contains("[REDACTED-LS-KEY]"), "got: {got}");
88 assert!(!got.contains("ABCD-1111-2222-3333"));
89 }
90
91 #[test]
92 fn masks_manual_token() {
93 let got = redact_license_shapes("token=ENT1-AAAAAAAAAAAA_BBBB done");
94 assert!(got.contains("[REDACTED-MANUAL-TOKEN]"), "got: {got}");
95 assert!(!got.contains("ENT1-AAAAA"));
96 }
97
98 #[test]
99 fn passes_innocent_text_through() {
100 let got = redact_license_shapes("Error: undefined is not a function (foo.js:42)");
101 assert_eq!(got, "Error: undefined is not a function (foo.js:42)");
102 }
103
104 #[test]
105 fn keeps_short_ent1_prefix_intact() {
106 let got = redact_license_shapes("filename ENT1-AB.log");
110 assert_eq!(got, "filename ENT1-AB.log");
111 }
112
113 #[test]
114 fn rejects_ls_shape_with_non_alnum_group() {
115 let got = redact_license_shapes("ABCD-1111-22!2-3333-4444");
118 assert!(!got.contains("[REDACTED-LS-KEY]"), "got: {got}");
119 }
120
121 #[test]
122 fn extends_past_four_groups() {
123 let got = redact_license_shapes("key=ABCD-1111-2222-3333-4444 end");
125 assert!(got.contains("[REDACTED-LS-KEY]"));
126 assert!(!got.contains("4444"));
127 assert!(got.contains("end"));
128 }
129}