ServerUrlPolicy.kt 59 lignes · 2582 octets
package fr.ebii.card2vcf.sync

/**
 * Politique d'URL serveur : HTTPS obligatoire par défaut.
 * Le HTTP clair n'est accepté que si l'utilisateur a coché l'option
 * « Autoriser HTTP (clair) » (dev / réseau interne / démo).
 */
object ServerUrlPolicy {
    const val ERROR_HTTPS_REQUIRED =
        "L'URL doit commencer par https:// (cochez « Autoriser HTTP (clair) » pour le développement)."
    const val ERROR_INVALID_URL =
        "URL invalide : utilisez https://… (ou http://… si HTTP clair autorisé)."
    const val ERROR_LAN_HTTPS_NO_PORT =
        "URL HTTPS sans port sur une IP locale : Projectiaon écoute en HTTP sur le port 3000. " +
            "Utilisez http://192.168.x.x:3000 et cochez « Autoriser HTTP (clair) »."
    const val ERROR_LAN_HTTP_NO_PORT =
        "URL sans port : Projectiaon écoute sur le port 3000 (ex. http://192.168.x.x:3000)."

    fun isHttps(url: String): Boolean = url.trim().startsWith("https://", ignoreCase = true)

    fun isHttp(url: String): Boolean = url.trim().startsWith("http://", ignoreCase = true)

    /** Racine API : sans slash final ni chemin de test navigateur (`/version`). */
    fun normalizeBaseUrl(url: String): String =
        url.trim().trimEnd('/').removeSuffix("/version")

    /** `null` si l'URL est acceptable, sinon message d'erreur à afficher. */
    fun validate(url: String, allowCleartext: Boolean): String? {
        val trimmed = normalizeBaseUrl(url)
        if (trimmed.isEmpty()) return ERROR_INVALID_URL
        return when {
            isHttps(trimmed) -> lanHostWithoutPortError(trimmed)
            isHttp(trimmed) -> when {
                !allowCleartext -> ERROR_HTTPS_REQUIRED
                else -> lanHostWithoutPortError(trimmed)
            }
            else -> ERROR_INVALID_URL
        }
    }

    private fun lanHostWithoutPortError(url: String): String? {
        val uri = runCatching { java.net.URI(url) }.getOrNull() ?: return null
        if (uri.port != -1) return null
        val host = uri.host ?: return null
        if (!isPrivateLanHost(host)) return null
        return when {
            isHttps(url) -> ERROR_LAN_HTTPS_NO_PORT
            isHttp(url) -> ERROR_LAN_HTTP_NO_PORT
            else -> null
        }
    }

    private fun isPrivateLanHost(host: String): Boolean {
        if (host.equals("localhost", ignoreCase = true) || host == "127.0.0.1") return true
        if (host.startsWith("192.168.")) return true
        if (host.startsWith("10.")) return true
        return host.matches(Regex("""172\.(1[6-9]|2\d|3[01])\..+"""))
    }
}