mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
The Phone Numbers redaction pattern accepted only 10 digits (11 with a country code), the North American form. For "+49 2151 123456" (12 digits) the `+` candidate was rejected and the scan then matched "2151 123456", leaving "+49" visible: a partial redaction in a document the user thinks is clean. The pattern now accepts, still grouped by at least one separator: - E.164 numbers with a leading `+`: 8-15 digits in all; - national numbers with a trunk 0 followed by 6-14 digits, whose first group has at least two digits (so decimals don't match) and which are not a day-first date (left to the date pattern); - North American numbers as before. A separator may now be followed by `(`, so "+49 (0) 2151 123456" is one match instead of a partial "(0) 2151 123456". Refs #525. The other items (page-number and date tokens, stamps) are not addressed here. Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
7c358099ff
commit
bd57377435
2 files changed
+39
-8
No files matched your search
@@ -56,7 +56,7 @@ fn digit_run(s: &[char], start: usize, seps: &[char], max_len: usize) -> (usize,
|
||||
let c = s[i];
|
||||
if c.is_ascii_digit() {
|
||||
n += 1;
|
||||
} else if !(seps.contains(&c) && digit(s.get(i + 1)) || (c == '(' || c == ')') && seps.contains(&c)) {
|
||||
} else if !(seps.contains(&c) && (digit(s.get(i + 1)) || s.get(i + 1) == Some(&'(')) || (c == '(' || c == ')') && seps.contains(&c)) {
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
@@ -73,14 +73,31 @@ fn phone(s: &[char], i: usize) -> Option<usize> {
|
||||
if !start_ok || word(i.checked_sub(1).and_then(|p| s.get(p))) || s.get(i.wrapping_sub(1)) == Some(&'+') {
|
||||
return None;
|
||||
}
|
||||
let first = if s[i] == '+' { i + 1 } else { i };
|
||||
let (end, n) = digit_run(s, first, &[' ', '-', '.', '(', ')'], 20);
|
||||
let seps = s[first..end].iter().filter(|c| !c.is_ascii_digit()).count();
|
||||
// 10 digits (11 with a country code), grouped by at least one separator.
|
||||
if !(10..=11).contains(&n) || seps == 0 || digit(s.get(end)) {
|
||||
let plus = s[i] == '+';
|
||||
let first = if plus { i + 1 } else { i };
|
||||
let (end, n) = digit_run(s, first, &[' ', '-', '.', '(', ')'], 28);
|
||||
let run = s.get(first..end)?;
|
||||
let seps = run.iter().filter(|c| !c.is_ascii_digit()).count();
|
||||
// Always grouped by at least one separator, so plain numbers never match.
|
||||
if seps == 0 || digit(s.get(end)) {
|
||||
return None;
|
||||
}
|
||||
Some(end - i)
|
||||
let ok = if plus {
|
||||
// E.164: a `+` country code and up to 15 digits in all ("+49 2151 123456").
|
||||
(8..=15).contains(&n)
|
||||
} else {
|
||||
// North American: 10 digits, 11 with a country code ("(555) 123-4567").
|
||||
let nanp = (10..=11).contains(&n);
|
||||
// National with a trunk 0 and 6–14 more digits ("0211 123456", "01 23 45 67 89").
|
||||
// The first group needs two digits so decimals ("0.1234567") don't match, and
|
||||
// day-first dates ("01.10.2026") are left to the date pattern.
|
||||
let mut digits = run.iter().skip_while(|c| !c.is_ascii_digit());
|
||||
let trunk_zero = digits.clone().next() == Some(&'0');
|
||||
let lead = digits.by_ref().take_while(|c| c.is_ascii_digit()).count();
|
||||
let trunk = trunk_zero && (7..=15).contains(&n) && lead >= 2 && date(s, i).is_none();
|
||||
nanp || trunk
|
||||
};
|
||||
ok.then_some(end - i)
|
||||
}
|
||||
|
||||
fn email(s: &[char], i: usize) -> Option<usize> {
|
||||
@@ -255,6 +272,20 @@ mod tests {
|
||||
found(Pattern::Phone, "Call (555) 123-4567 or 555.987.6543, +1 555 222 3333; not 12345 or 5551234567890"),
|
||||
["(555) 123-4567", "555.987.6543", "+1 555 222 3333"]
|
||||
);
|
||||
// #525: international numbers match whole; "+49" used to stay visible.
|
||||
assert_eq!(found(Pattern::Phone, "Tel. +49 2151 123456."), ["+49 2151 123456"]);
|
||||
assert_eq!(
|
||||
found(Pattern::Phone, "+44 20 7946 0018, +33 1 23 45 67 89, +49 (0) 2151 123456, (0211) 123456, 030 12345678, 01 23 45 67 89"),
|
||||
["+44 20 7946 0018", "+33 1 23 45 67 89", "+49 (0) 2151 123456", "(0211) 123456", "030 12345678", "01 23 45 67 89"]
|
||||
);
|
||||
// Not phone numbers: day-first dates, decimals, unseparated, too short or too long.
|
||||
assert_eq!(
|
||||
found(
|
||||
Pattern::Phone,
|
||||
"on 01.10.2026 or 09-10-2026, ratio 0.1234567, id 0211123456, +4921511234567, +1 234 567, 0211 12, +49 1234 5678 9012 3456"
|
||||
),
|
||||
Vec::<String>::new()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -5531,7 +5531,7 @@ status = "shipped"
|
||||
commands = ["redact.search"]
|
||||
tools = ["redact_mark"]
|
||||
evidence = ["crates/redact/src/patterns.rs::phone_numbers", "crates/redact/src/patterns.rs::credit_cards_need_a_valid_checksum", "crates/automation/tests/automation.rs::redacting_through_tools"]
|
||||
notes = "phone, e-mail, credit card (Luhn), SSN, dates (English US forms)"
|
||||
notes = "phone (North American, E.164 with + country code, national with trunk 0), e-mail, credit card (Luhn), SSN, dates (English US forms)"
|
||||
|
||||
[[feature]]
|
||||
id = "protect.redact-pattern-locales"
|
||||
|
||||
Reference in new issue
Block a user