mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-13 01:45:12 -04:00
Add common two level public TLDs to autofill matching implementations (#1264)
This commit is contained in:
@@ -36,6 +36,90 @@ function extractDomain(url: string): string {
|
||||
return domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract root domain from a domain string.
|
||||
* E.g., "sub.example.com" -> "example.com"
|
||||
* E.g., "sub.example.com.au" -> "example.com.au"
|
||||
* E.g., "sub.example.co.uk" -> "example.co.uk"
|
||||
*/
|
||||
function extractRootDomain(domain: string): string {
|
||||
const parts = domain.split('.');
|
||||
if (parts.length < 2) return domain;
|
||||
|
||||
// Common two-level public TLDs
|
||||
const twoLevelTlds = new Set([
|
||||
// Australia
|
||||
'com.au', 'net.au', 'org.au', 'edu.au', 'gov.au', 'asn.au', 'id.au',
|
||||
// United Kingdom
|
||||
'co.uk', 'org.uk', 'net.uk', 'ac.uk', 'gov.uk', 'plc.uk', 'ltd.uk', 'me.uk',
|
||||
// Canada
|
||||
'co.ca', 'net.ca', 'org.ca', 'gc.ca', 'ab.ca', 'bc.ca', 'mb.ca', 'nb.ca', 'nf.ca', 'nl.ca', 'ns.ca', 'nt.ca', 'nu.ca',
|
||||
'on.ca', 'pe.ca', 'qc.ca', 'sk.ca', 'yk.ca',
|
||||
// India
|
||||
'co.in', 'net.in', 'org.in', 'edu.in', 'gov.in', 'ac.in', 'res.in', 'gen.in', 'firm.in', 'ind.in',
|
||||
// Japan
|
||||
'co.jp', 'ne.jp', 'or.jp', 'ac.jp', 'ad.jp', 'ed.jp', 'go.jp', 'gr.jp', 'lg.jp',
|
||||
// South Africa
|
||||
'co.za', 'net.za', 'org.za', 'edu.za', 'gov.za', 'ac.za', 'web.za',
|
||||
// New Zealand
|
||||
'co.nz', 'net.nz', 'org.nz', 'edu.nz', 'govt.nz', 'ac.nz', 'geek.nz', 'gen.nz', 'kiwi.nz', 'maori.nz', 'mil.nz', 'school.nz',
|
||||
// Brazil
|
||||
'com.br', 'net.br', 'org.br', 'edu.br', 'gov.br', 'mil.br', 'art.br', 'etc.br', 'adv.br', 'arq.br', 'bio.br', 'cim.br',
|
||||
'cng.br', 'cnt.br', 'ecn.br', 'eng.br', 'esp.br', 'eti.br', 'far.br', 'fnd.br', 'fot.br', 'fst.br', 'g12.br', 'geo.br',
|
||||
'ggf.br', 'jor.br', 'lel.br', 'mat.br', 'med.br', 'mus.br', 'not.br', 'ntr.br', 'odo.br', 'ppg.br', 'pro.br', 'psc.br',
|
||||
'psi.br', 'qsl.br', 'rec.br', 'slg.br', 'srv.br', 'tmp.br', 'trd.br', 'tur.br', 'tv.br', 'vet.br', 'zlg.br',
|
||||
// Russia
|
||||
'com.ru', 'net.ru', 'org.ru', 'edu.ru', 'gov.ru', 'int.ru', 'mil.ru', 'spb.ru', 'msk.ru',
|
||||
// China
|
||||
'com.cn', 'net.cn', 'org.cn', 'edu.cn', 'gov.cn', 'mil.cn', 'ac.cn', 'ah.cn', 'bj.cn', 'cq.cn', 'fj.cn', 'gd.cn', 'gs.cn',
|
||||
'gz.cn', 'gx.cn', 'ha.cn', 'hb.cn', 'he.cn', 'hi.cn', 'hk.cn', 'hl.cn', 'hn.cn', 'jl.cn', 'js.cn', 'jx.cn', 'ln.cn', 'mo.cn',
|
||||
'nm.cn', 'nx.cn', 'qh.cn', 'sc.cn', 'sd.cn', 'sh.cn', 'sn.cn', 'sx.cn', 'tj.cn', 'tw.cn', 'xj.cn', 'xz.cn', 'yn.cn', 'zj.cn',
|
||||
// Mexico
|
||||
'com.mx', 'net.mx', 'org.mx', 'edu.mx', 'gob.mx',
|
||||
// Argentina
|
||||
'com.ar', 'net.ar', 'org.ar', 'edu.ar', 'gov.ar', 'mil.ar', 'int.ar',
|
||||
// Chile
|
||||
'com.cl', 'net.cl', 'org.cl', 'edu.cl', 'gov.cl', 'mil.cl',
|
||||
// Colombia
|
||||
'com.co', 'net.co', 'org.co', 'edu.co', 'gov.co', 'mil.co', 'nom.co',
|
||||
// Venezuela
|
||||
'com.ve', 'net.ve', 'org.ve', 'edu.ve', 'gov.ve', 'mil.ve', 'web.ve',
|
||||
// Peru
|
||||
'com.pe', 'net.pe', 'org.pe', 'edu.pe', 'gob.pe', 'mil.pe', 'nom.pe',
|
||||
// Ecuador
|
||||
'com.ec', 'net.ec', 'org.ec', 'edu.ec', 'gov.ec', 'mil.ec', 'med.ec', 'fin.ec', 'pro.ec', 'info.ec',
|
||||
// Europe
|
||||
'co.at', 'or.at', 'ac.at', 'gv.at', 'priv.at',
|
||||
'co.be', 'ac.be',
|
||||
'co.dk', 'ac.dk',
|
||||
'co.il', 'net.il', 'org.il', 'ac.il', 'gov.il', 'idf.il', 'k12.il', 'muni.il',
|
||||
'co.no', 'ac.no', 'priv.no',
|
||||
'co.pl', 'net.pl', 'org.pl', 'edu.pl', 'gov.pl', 'mil.pl', 'nom.pl', 'com.pl',
|
||||
'co.th', 'net.th', 'org.th', 'edu.th', 'gov.th', 'mil.th', 'ac.th', 'in.th',
|
||||
'co.kr', 'net.kr', 'org.kr', 'edu.kr', 'gov.kr', 'mil.kr', 'ac.kr', 'go.kr', 'ne.kr', 'or.kr', 'pe.kr', 're.kr', 'seoul.kr',
|
||||
'kyonggi.kr',
|
||||
// Others
|
||||
'co.id', 'net.id', 'org.id', 'edu.id', 'gov.id', 'mil.id', 'web.id', 'ac.id', 'sch.id',
|
||||
'co.ma', 'net.ma', 'org.ma', 'edu.ma', 'gov.ma', 'ac.ma', 'press.ma',
|
||||
'co.ke', 'net.ke', 'org.ke', 'edu.ke', 'gov.ke', 'ac.ke', 'go.ke', 'info.ke', 'me.ke', 'mobi.ke', 'sc.ke',
|
||||
'co.ug', 'net.ug', 'org.ug', 'edu.ug', 'gov.ug', 'ac.ug', 'sc.ug', 'go.ug', 'ne.ug', 'or.ug',
|
||||
'co.tz', 'net.tz', 'org.tz', 'edu.tz', 'gov.tz', 'ac.tz', 'go.tz', 'hotel.tz', 'info.tz', 'me.tz', 'mil.tz', 'mobi.tz',
|
||||
'ne.tz', 'or.tz', 'sc.tz', 'tv.tz',
|
||||
]);
|
||||
|
||||
// Check if the last two parts form a known two-level TLD
|
||||
if (parts.length >= 3) {
|
||||
const lastTwoParts = parts.slice(-2).join('.');
|
||||
if (twoLevelTlds.has(lastTwoParts)) {
|
||||
// Take the last three parts for two-level TLDs
|
||||
return parts.slice(-3).join('.');
|
||||
}
|
||||
}
|
||||
|
||||
// Default to last two parts for regular TLDs
|
||||
return parts.length >= 2 ? parts.slice(-2).join('.') : domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if two domains match, supporting partial matches
|
||||
* @param domain1 - First domain
|
||||
@@ -60,13 +144,9 @@ function domainsMatch(domain1: string, domain2: string): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Extract root domains for comparison
|
||||
const d1Parts = d1.split('.');
|
||||
const d2Parts = d2.split('.');
|
||||
|
||||
// Get the last 2 parts (domain.tld) for comparison
|
||||
const d1Root = d1Parts.slice(-2).join('.');
|
||||
const d2Root = d2Parts.slice(-2).join('.');
|
||||
// Check root domain match
|
||||
const d1Root = extractRootDomain(d1);
|
||||
const d2Root = extractRootDomain(d2);
|
||||
|
||||
return d1Root === d2Root;
|
||||
}
|
||||
|
||||
@@ -292,6 +292,44 @@ describe('Filter - Credential URL Matching', () => {
|
||||
expect(matches[0].ServiceName).toBe('Reddit');
|
||||
});
|
||||
|
||||
// [#20] - Test multi-part TLDs like .com.au don't match incorrectly
|
||||
it('should handle multi-part TLDs correctly without false matches', () => {
|
||||
// Create test data with different .com.au domains
|
||||
const australianCredentials = [
|
||||
createTestCredential('Example Site AU', 'https://example.com.au', 'user@example.com.au'),
|
||||
createTestCredential('BlaBla AU', 'https://blabla.blabla.com.au', 'user@blabla.com.au'),
|
||||
createTestCredential('Another AU', 'https://another.com.au', 'user@another.com.au'),
|
||||
createTestCredential('UK Site', 'https://example.co.uk', 'user@example.co.uk'),
|
||||
];
|
||||
|
||||
// Test that blabla.blabla.com.au doesn't match other .com.au sites
|
||||
const blablaMatches = filterCredentials(
|
||||
australianCredentials,
|
||||
'https://blabla.blabla.com.au',
|
||||
''
|
||||
);
|
||||
expect(blablaMatches).toHaveLength(1);
|
||||
expect(blablaMatches[0].ServiceName).toBe('BlaBla AU');
|
||||
|
||||
// Test that example.com.au doesn't match blabla.blabla.com.au
|
||||
const exampleMatches = filterCredentials(
|
||||
australianCredentials,
|
||||
'https://example.com.au',
|
||||
''
|
||||
);
|
||||
expect(exampleMatches).toHaveLength(1);
|
||||
expect(exampleMatches[0].ServiceName).toBe('Example Site AU');
|
||||
|
||||
// Test that .co.uk domains work correctly too
|
||||
const ukMatches = filterCredentials(
|
||||
australianCredentials,
|
||||
'https://example.co.uk',
|
||||
''
|
||||
);
|
||||
expect(ukMatches).toHaveLength(1);
|
||||
expect(ukMatches[0].ServiceName).toBe('UK Site');
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates the shared test credential dataset used across all platforms.
|
||||
* Note: when making changes to this list, make sure to update the corresponding list for iOS and Android tests as well.
|
||||
|
||||
@@ -51,10 +51,89 @@ object CredentialMatcher {
|
||||
/**
|
||||
* Extract root domain from a domain string.
|
||||
* E.g., "sub.example.com" -> "example.com"
|
||||
* E.g., "sub.example.com.au" -> "example.com.au"
|
||||
* E.g., "sub.example.co.uk" -> "example.co.uk"
|
||||
*/
|
||||
private fun extractRootDomain(domain: String): String {
|
||||
val parts = domain.split(".")
|
||||
return if (parts.size >= 2) parts.takeLast(2).joinToString(".") else domain
|
||||
if (parts.size < 2) return domain
|
||||
|
||||
// Common two-level public TLDs
|
||||
val twoLevelTlds = setOf(
|
||||
// Australia
|
||||
"com.au", "net.au", "org.au", "edu.au", "gov.au", "asn.au", "id.au",
|
||||
// United Kingdom
|
||||
"co.uk", "org.uk", "net.uk", "ac.uk", "gov.uk", "plc.uk", "ltd.uk", "me.uk",
|
||||
// Canada
|
||||
"co.ca", "net.ca", "org.ca", "gc.ca", "ab.ca", "bc.ca", "mb.ca", "nb.ca", "nf.ca", "nl.ca", "ns.ca", "nt.ca", "nu.ca",
|
||||
"on.ca", "pe.ca", "qc.ca", "sk.ca", "yk.ca",
|
||||
// India
|
||||
"co.in", "net.in", "org.in", "edu.in", "gov.in", "ac.in", "res.in", "gen.in", "firm.in", "ind.in",
|
||||
// Japan
|
||||
"co.jp", "ne.jp", "or.jp", "ac.jp", "ad.jp", "ed.jp", "go.jp", "gr.jp", "lg.jp",
|
||||
// South Africa
|
||||
"co.za", "net.za", "org.za", "edu.za", "gov.za", "ac.za", "web.za",
|
||||
// New Zealand
|
||||
"co.nz", "net.nz", "org.nz", "edu.nz", "govt.nz", "ac.nz", "geek.nz", "gen.nz", "kiwi.nz", "maori.nz", "mil.nz", "school.nz",
|
||||
// Brazil
|
||||
"com.br", "net.br", "org.br", "edu.br", "gov.br", "mil.br", "art.br", "etc.br", "adv.br", "arq.br", "bio.br", "cim.br",
|
||||
"cng.br", "cnt.br", "ecn.br", "eng.br", "esp.br", "eti.br", "far.br", "fnd.br", "fot.br", "fst.br", "g12.br", "geo.br",
|
||||
"ggf.br", "jor.br", "lel.br", "mat.br", "med.br", "mus.br", "not.br", "ntr.br", "odo.br", "ppg.br", "pro.br", "psc.br",
|
||||
"psi.br", "qsl.br", "rec.br", "slg.br", "srv.br", "tmp.br", "trd.br", "tur.br", "tv.br", "vet.br", "zlg.br",
|
||||
// Russia
|
||||
"com.ru", "net.ru", "org.ru", "edu.ru", "gov.ru", "int.ru", "mil.ru", "spb.ru", "msk.ru",
|
||||
// China
|
||||
"com.cn", "net.cn", "org.cn", "edu.cn", "gov.cn", "mil.cn", "ac.cn", "ah.cn", "bj.cn", "cq.cn", "fj.cn", "gd.cn", "gs.cn",
|
||||
"gz.cn", "gx.cn", "ha.cn", "hb.cn", "he.cn", "hi.cn", "hk.cn", "hl.cn", "hn.cn", "jl.cn", "js.cn", "jx.cn", "ln.cn", "mo.cn",
|
||||
"nm.cn", "nx.cn", "qh.cn", "sc.cn", "sd.cn", "sh.cn", "sn.cn", "sx.cn", "tj.cn", "tw.cn", "xj.cn", "xz.cn", "yn.cn", "zj.cn",
|
||||
// Mexico
|
||||
"com.mx", "net.mx", "org.mx", "edu.mx", "gob.mx",
|
||||
// Argentina
|
||||
"com.ar", "net.ar", "org.ar", "edu.ar", "gov.ar", "mil.ar", "int.ar",
|
||||
// Chile
|
||||
"com.cl", "net.cl", "org.cl", "edu.cl", "gov.cl", "mil.cl",
|
||||
// Colombia
|
||||
"com.co", "net.co", "org.co", "edu.co", "gov.co", "mil.co", "nom.co",
|
||||
// Venezuela
|
||||
"com.ve", "net.ve", "org.ve", "edu.ve", "gov.ve", "mil.ve", "web.ve",
|
||||
// Peru
|
||||
"com.pe", "net.pe", "org.pe", "edu.pe", "gob.pe", "mil.pe", "nom.pe",
|
||||
// Ecuador
|
||||
"com.ec", "net.ec", "org.ec", "edu.ec", "gov.ec", "mil.ec", "med.ec", "fin.ec", "pro.ec", "info.ec",
|
||||
// Europe
|
||||
"co.at", "or.at", "ac.at", "gv.at", "priv.at",
|
||||
"co.be", "ac.be",
|
||||
"co.dk", "ac.dk",
|
||||
"co.il", "net.il", "org.il", "ac.il", "gov.il", "idf.il", "k12.il", "muni.il",
|
||||
"co.no", "ac.no", "priv.no",
|
||||
"co.pl", "net.pl", "org.pl", "edu.pl", "gov.pl", "mil.pl", "nom.pl", "com.pl",
|
||||
"co.th", "net.th", "org.th", "edu.th", "gov.th", "mil.th", "ac.th", "in.th",
|
||||
"co.kr", "net.kr", "org.kr", "edu.kr", "gov.kr", "mil.kr", "ac.kr", "go.kr", "ne.kr", "or.kr", "pe.kr", "re.kr", "seoul.kr",
|
||||
"kyonggi.kr",
|
||||
// Others
|
||||
"co.id", "net.id", "org.id", "edu.id", "gov.id", "mil.id", "web.id", "ac.id", "sch.id",
|
||||
"co.ma", "net.ma", "org.ma", "edu.ma", "gov.ma", "ac.ma", "press.ma",
|
||||
"co.ke", "net.ke", "org.ke", "edu.ke", "gov.ke", "ac.ke", "go.ke", "info.ke", "me.ke", "mobi.ke", "sc.ke",
|
||||
"co.ug", "net.ug", "org.ug", "edu.ug", "gov.ug", "ac.ug", "sc.ug", "go.ug", "ne.ug", "or.ug",
|
||||
"co.tz", "net.tz", "org.tz", "edu.tz", "gov.tz", "ac.tz", "go.tz", "hotel.tz", "info.tz", "me.tz", "mil.tz", "mobi.tz",
|
||||
"ne.tz", "or.tz", "sc.tz", "tv.tz",
|
||||
)
|
||||
|
||||
// Check if the last two parts form a known two-level TLD
|
||||
if (parts.size >= 3) {
|
||||
val lastTwoParts = parts.takeLast(2).joinToString(".")
|
||||
if (twoLevelTlds.contains(lastTwoParts)) {
|
||||
// Take the last three parts for two-level TLDs
|
||||
return parts.takeLast(3).joinToString(".")
|
||||
}
|
||||
}
|
||||
|
||||
// Default to last two parts for regular TLDs
|
||||
return if (parts.size >= 2) {
|
||||
parts.takeLast(2).joinToString(".")
|
||||
} else {
|
||||
domain
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -295,6 +295,42 @@ class AutofillTest {
|
||||
assertEquals("Reddit", matches[0].service.name)
|
||||
}
|
||||
|
||||
// [#20] - Test multi-part TLDs like .com.au don't match incorrectly
|
||||
@Test
|
||||
fun testMultiPartTldNoFalseMatches() {
|
||||
// Create test data with different .com.au domains
|
||||
val australianCredentials = listOf(
|
||||
createTestCredential("Example Site AU", "https://example.com.au", "user@example.com.au"),
|
||||
createTestCredential("BlaBla AU", "https://blabla.blabla.com.au", "user@blabla.com.au"),
|
||||
createTestCredential("Another AU", "https://another.com.au", "user@another.com.au"),
|
||||
createTestCredential("UK Site", "https://example.co.uk", "user@example.co.uk"),
|
||||
)
|
||||
|
||||
// Test that blabla.blabla.com.au doesn't match other .com.au sites
|
||||
val blablaMatches = CredentialMatcher.filterCredentialsByAppInfo(
|
||||
australianCredentials,
|
||||
"https://blabla.blabla.com.au",
|
||||
)
|
||||
assertEquals(1, blablaMatches.size, "Should only match the exact domain, not all .com.au sites")
|
||||
assertEquals("BlaBla AU", blablaMatches[0].service.name)
|
||||
|
||||
// Test that example.com.au doesn't match blabla.blabla.com.au
|
||||
val exampleMatches = CredentialMatcher.filterCredentialsByAppInfo(
|
||||
australianCredentials,
|
||||
"https://example.com.au",
|
||||
)
|
||||
assertEquals(1, exampleMatches.size, "Should only match example.com.au")
|
||||
assertEquals("Example Site AU", exampleMatches[0].service.name)
|
||||
|
||||
// Test that .co.uk domains work correctly too
|
||||
val ukMatches = CredentialMatcher.filterCredentialsByAppInfo(
|
||||
australianCredentials,
|
||||
"https://example.co.uk",
|
||||
)
|
||||
assertEquals(1, ukMatches.size, "Should only match the .co.uk domain")
|
||||
assertEquals("UK Site", ukMatches[0].service.name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the shared test credential dataset used across all platforms.
|
||||
* This ensures consistent testing across Browser Extension, iOS, and Android.
|
||||
|
||||
@@ -58,10 +58,83 @@ public class CredentialFilter {
|
||||
|
||||
/// Extract root domain from a domain string.
|
||||
/// - Parameter domain: Domain string
|
||||
/// - Returns: Root domain (e.g., "sub.example.com" -> "example.com")
|
||||
/// - Returns: Root domain (e.g., "sub.example.com" -> "example.com", "sub.example.com.au" -> "example.com.au", "sub.example.co.uk" -> "example.co.uk")
|
||||
private static func extractRootDomain(from domain: String) -> String {
|
||||
let parts = domain.components(separatedBy: ".")
|
||||
return parts.count >= 2 ? parts.suffix(2).joined(separator: ".") : domain
|
||||
guard parts.count >= 2 else { return domain }
|
||||
|
||||
// Common two-level public TLDs
|
||||
let twoLevelTlds: Set<String> = [
|
||||
// Australia
|
||||
"com.au", "net.au", "org.au", "edu.au", "gov.au", "asn.au", "id.au",
|
||||
// United Kingdom
|
||||
"co.uk", "org.uk", "net.uk", "ac.uk", "gov.uk", "plc.uk", "ltd.uk", "me.uk",
|
||||
// Canada
|
||||
"co.ca", "net.ca", "org.ca", "gc.ca", "ab.ca", "bc.ca", "mb.ca", "nb.ca", "nf.ca", "nl.ca", "ns.ca", "nt.ca", "nu.ca", "on.ca", "pe.ca", "qc.ca", "sk.ca", "yk.ca",
|
||||
// India
|
||||
"co.in", "net.in", "org.in", "edu.in", "gov.in", "ac.in", "res.in", "gen.in", "firm.in", "ind.in",
|
||||
// Japan
|
||||
"co.jp", "ne.jp", "or.jp", "ac.jp", "ad.jp", "ed.jp", "go.jp", "gr.jp", "lg.jp",
|
||||
// South Africa
|
||||
"co.za", "net.za", "org.za", "edu.za", "gov.za", "ac.za", "web.za",
|
||||
// New Zealand
|
||||
"co.nz", "net.nz", "org.nz", "edu.nz", "govt.nz", "ac.nz", "geek.nz", "gen.nz", "kiwi.nz", "maori.nz", "mil.nz", "school.nz",
|
||||
// Brazil
|
||||
"com.br", "net.br", "org.br", "edu.br", "gov.br", "mil.br", "art.br", "etc.br", "adv.br", "arq.br", "bio.br", "cim.br", "cng.br", "cnt.br", "ecn.br", "eng.br",
|
||||
"esp.br", "eti.br", "far.br", "fnd.br", "fot.br", "fst.br", "g12.br", "geo.br", "ggf.br", "jor.br", "lel.br", "mat.br", "med.br", "mus.br", "not.br", "ntr.br",
|
||||
"odo.br", "ppg.br", "pro.br", "psc.br", "psi.br", "qsl.br", "rec.br", "slg.br", "srv.br", "tmp.br", "trd.br", "tur.br", "tv.br", "vet.br", "zlg.br",
|
||||
// Russia
|
||||
"com.ru", "net.ru", "org.ru", "edu.ru", "gov.ru", "int.ru", "mil.ru", "spb.ru", "msk.ru",
|
||||
// China
|
||||
"com.cn", "net.cn", "org.cn", "edu.cn", "gov.cn", "mil.cn", "ac.cn", "ah.cn", "bj.cn", "cq.cn", "fj.cn", "gd.cn", "gs.cn", "gz.cn", "gx.cn", "ha.cn", "hb.cn",
|
||||
"he.cn", "hi.cn", "hk.cn", "hl.cn", "hn.cn", "jl.cn", "js.cn", "jx.cn", "ln.cn", "mo.cn", "nm.cn", "nx.cn", "qh.cn", "sc.cn", "sd.cn", "sh.cn", "sn.cn",
|
||||
"sx.cn", "tj.cn", "tw.cn", "xj.cn", "xz.cn", "yn.cn", "zj.cn",
|
||||
// Mexico
|
||||
"com.mx", "net.mx", "org.mx", "edu.mx", "gob.mx",
|
||||
// Argentina
|
||||
"com.ar", "net.ar", "org.ar", "edu.ar", "gov.ar", "mil.ar", "int.ar",
|
||||
// Chile
|
||||
"com.cl", "net.cl", "org.cl", "edu.cl", "gov.cl", "mil.cl",
|
||||
// Colombia
|
||||
"com.co", "net.co", "org.co", "edu.co", "gov.co", "mil.co", "nom.co",
|
||||
// Venezuela
|
||||
"com.ve", "net.ve", "org.ve", "edu.ve", "gov.ve", "mil.ve", "web.ve",
|
||||
// Peru
|
||||
"com.pe", "net.pe", "org.pe", "edu.pe", "gob.pe", "mil.pe", "nom.pe",
|
||||
// Ecuador
|
||||
"com.ec", "net.ec", "org.ec", "edu.ec", "gov.ec", "mil.ec", "med.ec", "fin.ec", "pro.ec", "info.ec",
|
||||
// Europe
|
||||
"co.at", "or.at", "ac.at", "gv.at", "priv.at",
|
||||
"co.be", "ac.be",
|
||||
"co.dk", "ac.dk",
|
||||
"co.il", "net.il", "org.il", "ac.il", "gov.il", "idf.il", "k12.il", "muni.il",
|
||||
"co.no", "ac.no", "priv.no",
|
||||
"co.pl", "net.pl", "org.pl", "edu.pl", "gov.pl", "mil.pl", "nom.pl", "com.pl",
|
||||
"co.th", "net.th", "org.th", "edu.th", "gov.th", "mil.th", "ac.th", "in.th",
|
||||
"co.kr", "net.kr", "org.kr", "edu.kr", "gov.kr", "mil.kr", "ac.kr", "go.kr", "ne.kr", "or.kr", "pe.kr", "re.kr", "seoul.kr", "kyonggi.kr",
|
||||
// Others
|
||||
"co.id", "net.id", "org.id", "edu.id", "gov.id", "mil.id", "web.id", "ac.id", "sch.id",
|
||||
"co.ma", "net.ma", "org.ma", "edu.ma", "gov.ma", "ac.ma", "press.ma",
|
||||
"co.ke", "net.ke", "org.ke", "edu.ke", "gov.ke", "ac.ke", "go.ke", "info.ke", "me.ke", "mobi.ke", "sc.ke",
|
||||
"co.ug", "net.ug", "org.ug", "edu.ug", "gov.ug", "ac.ug", "sc.ug", "go.ug", "ne.ug", "or.ug",
|
||||
"co.tz", "net.tz", "org.tz", "edu.tz", "gov.tz", "ac.tz", "go.tz", "hotel.tz", "info.tz", "me.tz", "mil.tz", "mobi.tz", "ne.tz", "or.tz", "sc.tz", "tv.tz"
|
||||
]
|
||||
|
||||
// Check if the last two parts form a known two-level TLD
|
||||
if parts.count >= 3 {
|
||||
let lastTwoParts = parts.suffix(2).joined(separator: ".")
|
||||
if twoLevelTlds.contains(lastTwoParts) {
|
||||
// Take the last three parts for two-level TLDs
|
||||
return parts.suffix(3).joined(separator: ".")
|
||||
}
|
||||
}
|
||||
|
||||
// Default to last two parts for regular TLDs
|
||||
if parts.count >= 2 {
|
||||
return parts.suffix(2).joined(separator: ".")
|
||||
}
|
||||
|
||||
return domain
|
||||
}
|
||||
|
||||
/// Check if two domains match, supporting partial matches.
|
||||
|
||||
@@ -189,6 +189,32 @@ final class CredentialFilterTests: XCTestCase {
|
||||
XCTAssertEqual(matches.first?.service.name, "Reddit")
|
||||
}
|
||||
|
||||
// [#20] - Test multi-part TLDs like .com.au don't match incorrectly
|
||||
func testMultiPartTldNoFalseMatches() {
|
||||
// Create test data with different .com.au domains
|
||||
let australianCredentials = [
|
||||
createTestCredential(serviceName: "Example Site AU", serviceUrl: "https://example.com.au", username: "user@example.com.au"),
|
||||
createTestCredential(serviceName: "BlaBla AU", serviceUrl: "https://blabla.blabla.com.au", username: "user@blabla.com.au"),
|
||||
createTestCredential(serviceName: "Another AU", serviceUrl: "https://another.com.au", username: "user@another.com.au"),
|
||||
createTestCredential(serviceName: "UK Site", serviceUrl: "https://example.co.uk", username: "user@example.co.uk"),
|
||||
]
|
||||
|
||||
// Test that blabla.blabla.com.au doesn't match other .com.au sites
|
||||
let blablaMatches = CredentialFilter.filterCredentials(australianCredentials, searchText: "https://blabla.blabla.com.au")
|
||||
XCTAssertEqual(blablaMatches.count, 1, "Should only match the exact domain, not all .com.au sites")
|
||||
XCTAssertEqual(blablaMatches.first?.service.name, "BlaBla AU")
|
||||
|
||||
// Test that example.com.au doesn't match blabla.blabla.com.au
|
||||
let exampleMatches = CredentialFilter.filterCredentials(australianCredentials, searchText: "https://example.com.au")
|
||||
XCTAssertEqual(exampleMatches.count, 1, "Should only match example.com.au")
|
||||
XCTAssertEqual(exampleMatches.first?.service.name, "Example Site AU")
|
||||
|
||||
// Test that .co.uk domains work correctly too
|
||||
let ukMatches = CredentialFilter.filterCredentials(australianCredentials, searchText: "https://example.co.uk")
|
||||
XCTAssertEqual(ukMatches.count, 1, "Should only match the .co.uk domain")
|
||||
XCTAssertEqual(ukMatches.first?.service.name, "UK Site")
|
||||
}
|
||||
|
||||
// MARK: - Shared Test Data
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user