Redact email addresses whose local part starts with punctuation (#2948)

Found while testing crates/redact/src/patterns.rs:141-146 (email)

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-18 09:48:02 -04:00
committed by GitHub
co-authored by Claude Haiku 5.5
parent 50f261d70c
commit 7e3f413f52
+16 -2
View File
@@ -140,14 +140,16 @@ fn address_char(c: char) -> bool {
fn email(s: &[char], i: usize) -> Option<usize> {
let local = |c: &char| address_char(*c) || "._%+-".contains(*c);
if !address_char(s[i]) || i.checked_sub(1).and_then(|p| s.get(p)).is_some_and(local) {
// A match starts at the first character of a run of local characters, which may be
// punctuation ("_admin@host.com"), but the run needs a letter or digit.
if !local(&s[i]) || i.checked_sub(1).and_then(|p| s.get(p)).is_some_and(local) {
return None;
}
let mut j = i;
while j < s.len() && local(&s[j]) {
j += 1;
}
if s.get(j) != Some(&'@') {
if s.get(j) != Some(&'@') || !s[i..j].iter().any(|c| address_char(*c)) {
return None;
}
let d0 = j + 1;
@@ -401,6 +403,18 @@ mod tests {
);
}
#[test]
fn emails_with_a_leading_punctuation_character() {
for lead in ["_", "-", ".", "%", "+"] {
let address = format!("{lead}john@example.com");
assert_eq!(found(Pattern::Email, &format!("Write to {address} now")), [address.as_str()], "lead {lead:?}");
}
// A run of local characters is still matched whole, never from its middle.
assert_eq!(found(Pattern::Email, "a.b@x.com, foo_john@example.com"), ["a.b@x.com", "foo_john@example.com"]);
// Punctuation alone is no local part.
assert_eq!(found(Pattern::Email, "Write to ..@example.com or -@example.com"), Vec::<String>::new());
}
#[test]
fn internationalized_emails() {
assert_eq!(