DomainUtils.kt

2.7 KB · raw

1package com.peachpasswords.shared23import okhttp3.HttpUrl.Companion.toHttpUrlOrNull4import java.util.Locale56/**7 * eTLD+1 ("bank.co.uk", not "co.uk"), the key everything from autofill matching to8 * vault health compares on.9 *10 * [okhttp3.HttpUrl.topPrivateDomain] is OkHttp's *public* API over the same public11 * suffix list its internal `PublicSuffixDatabase` exposes, so this keeps real PSL12 * accuracy without depending on an `okhttp3.internal` class. Accuracy is not13 * optional here: a "last two labels" heuristic would collapse `evil.co.uk` and14 * `bank.co.uk` to the same key and hand credentials to the wrong site.15 * Returns null for IP literals and bare public suffixes, as before.16 */17fun extractDomain(url: String): String? {18    return try {19        val candidate = url.trim()20        if (candidate.isEmpty()) return null21        val normalized = if (22            candidate.startsWith("http://", ignoreCase = true) ||23            candidate.startsWith("https://", ignoreCase = true)24        ) {25            candidate26        } else {27            "https://$candidate"28        }29        val parsed = normalized.toHttpUrlOrNull() ?: return null30        val host = parsed.host.lowercase(Locale.ROOT)31        // Autofill must never treat local names or IP literals as registrable32        // origins. topPrivateDomain() rejects most of these already, but the33        // explicit guards keep the fail-closed contract stable across OkHttp PSL34        // updates and make the policy obvious to callers.35        if (36            host.isEmpty() ||37            !host.contains('.') ||38            host == "localhost" ||39            host.endsWith(".localhost") ||40            host == "local" ||41            host.endsWith(".local") ||42            host.contains(':') ||43            host.matches(Regex("\\d+(?:\\.\\d+){3}"))44        ) return null45        parsed.topPrivateDomain()?.lowercase(Locale.ROOT)46    } catch (_: Exception) {47        null48    }49}5051fun getDisplayDomain(url: String): String {52    return try {53        val normalized = if (url.startsWith("http")) url else "https://$url"54        java.net.URL(normalized).host55    } catch (_: Exception) {56        url57    }58}5960/**61 * Browser parity with `shared/domain.ts::detectHomograph`: true when the host62 * contains non-ASCII characters or a punycode `xn--` label. This is warning-only;63 * matching/keying remains handled by [extractDomain].64 */65fun detectHomograph(url: String): Boolean {66    return try {67        val normalized = if (url.contains("://")) url else "https://$url"68        val host = java.net.URL(normalized).host69        host.any { it.code > 0x7F } || host.split('.').any { it.startsWith("xn--", ignoreCase = true) }70    } catch (_: Exception) {71        false72    }73}