Detect homoglyphs in Cyrillic Supplement and other unlisted non-Latin blocks
All checks were successful
CI / check (push) Successful in 4m9s
All checks were successful
CI / check (push) Successful in 4m9s
This commit is contained in:
parent
86444286d8
commit
224bc78641
1 changed files with 14 additions and 3 deletions
|
|
@ -2276,8 +2276,8 @@ pub fn confusable_reason(name: &str) -> Option<&'static str> {
|
||||||
fn script_id(cp: u32) -> Option<(Script, u8)> {
|
fn script_id(cp: u32) -> Option<(Script, u8)> {
|
||||||
match cp {
|
match cp {
|
||||||
0x41..=0x5A | 0x61..=0x7A | 0xC0..=0x24F => Some((Script::Latin, 0)),
|
0x41..=0x5A | 0x61..=0x7A | 0xC0..=0x24F => Some((Script::Latin, 0)),
|
||||||
0x370..=0x3FF => Some((Script::Other, 1)), // Greek
|
0x370..=0x3FF | 0x1F00..=0x1FFF => Some((Script::Other, 1)), // Greek (+ Greek Extended)
|
||||||
0x400..=0x4FF => Some((Script::Other, 2)), // Cyrillic
|
0x400..=0x52F | 0x1C80..=0x1C8F | 0x2DE0..=0x2DFF | 0xA640..=0xA69F => Some((Script::Other, 2)), // Cyrillic (+ Supplement/Extended)
|
||||||
0x530..=0x58F => Some((Script::Other, 3)), // Armenian
|
0x530..=0x58F => Some((Script::Other, 3)), // Armenian
|
||||||
0x590..=0x5FF => Some((Script::Other, 4)), // Hebrew
|
0x590..=0x5FF => Some((Script::Other, 4)), // Hebrew
|
||||||
0x600..=0x6FF => Some((Script::Other, 5)), // Arabic
|
0x600..=0x6FF => Some((Script::Other, 5)), // Arabic
|
||||||
|
|
@ -2333,6 +2333,16 @@ pub fn confusable_reason(name: &str) -> Option<&'static str> {
|
||||||
if is_latin_confusable(cp) {
|
if is_latin_confusable(cp) {
|
||||||
confusables += 1;
|
confusables += 1;
|
||||||
}
|
}
|
||||||
|
} else if ch.is_alphabetic() {
|
||||||
|
// A letter outside every listed range (e.g. a Cyrillic Supplement
|
||||||
|
// homoglyph, or any other non-Latin script) still counts as a distinct
|
||||||
|
// non-Latin alphabet — so a Latin + look-alike mix is caught rather than
|
||||||
|
// silently skipped. Monolingual text in such a script stays one bucket.
|
||||||
|
const UNKNOWN: u8 = 99;
|
||||||
|
letters += 1;
|
||||||
|
if !scripts.contains(&UNKNOWN) {
|
||||||
|
scripts.push(UNKNOWN);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if scripts.len() > 1 {
|
if scripts.len() > 1 {
|
||||||
|
|
@ -2465,12 +2475,13 @@ mod tests {
|
||||||
fn confusable_names_rejected_but_french_passes() {
|
fn confusable_names_rejected_but_french_passes() {
|
||||||
// Genuine names pass — including accented French (which is Latin), other
|
// Genuine names pass — including accented French (which is Latin), other
|
||||||
// monolingual scripts, digits/symbols, and emoji.
|
// monolingual scripts, digits/symbols, and emoji.
|
||||||
for ok in ["Zoé", "José", "Amélie", "sœur", "Noël", "#café", "crème-brûlée", "user_42", "привет", "日本語", "Ελληνικά", "Zoé😀", "#général"] {
|
for ok in ["Zoé", "José", "Amélie", "sœur", "Noël", "#café", "crème-brûlée", "user_42", "привет", "приве\u{0501}", "日本語", "Ελληνικά", "Zoé😀", "#général"] {
|
||||||
assert!(confusable_reason(ok).is_none(), "should pass: {ok}");
|
assert!(confusable_reason(ok).is_none(), "should pass: {ok}");
|
||||||
}
|
}
|
||||||
// Look-alike / spoof names are rejected.
|
// Look-alike / spoof names are rejected.
|
||||||
for bad in [
|
for bad in [
|
||||||
"аdmin", // Cyrillic а + Latin — mixed script
|
"аdmin", // Cyrillic а + Latin — mixed script
|
||||||
|
"a\u{0501}ice", // Latin + Cyrillic-Supplement ԁ (U+0501) — an unlisted-block homoglyph mix
|
||||||
"#frее", // Latin fr + Cyrillic ее — mixed script
|
"#frее", // Latin fr + Cyrillic ее — mixed script
|
||||||
"аеосх", // all-Cyrillic Latin look-alikes — homoglyph
|
"аеосх", // all-Cyrillic Latin look-alikes — homoglyph
|
||||||
"ad\u{200b}min", // zero-width space
|
"ad\u{200b}min", // zero-width space
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue