<?php
/**
 * Fonctions de sécurité pour la validation et l'assainissement des entrées
 */

/**
 * Valide et assainit un ID de vidéo UUID
 * 
 * @param string $id ID à valider
 * @return string|false ID validé ou false si invalide
 */
function validateVideoId($id) {
    if (empty($id)) {
        return false;
    }
    
    // Nettoyer l'entrée
    $id = trim($id);
    
    // Vérifier le format UUID (format PeerTube)
    if (!preg_match('/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i', $id)) {
        return false;
    }
    
    return $id;
}

/**
 * Valide et assainit une requête de recherche
 * 
 * @param string $query Requête à valider
 * @return string|false Requête validée ou false si invalide
 */
function validateSearchQuery($query) {
    if (empty($query)) {
        return false;
    }
    
    // Nettoyer l'entrée
    $query = trim($query);
    
    // Limiter la longueur
    if (strlen($query) > 200) {
        return false;
    }
    
    // Supprimer les caractères dangereux mais garder les caractères utiles pour la recherche
    $query = preg_replace('/[<>"\']/', '', $query);
    
    return $query;
}

/**
 * Valide un numéro de page
 * 
 * @param mixed $page Page à valider
 * @return int Page validée (minimum 1)
 */
function validatePageNumber($page) {
    $page = intval($page);
    return max(1, $page);
}

/**
 * Valide un ID de catégorie
 * 
 * @param mixed $categoryId ID de catégorie à valider
 * @return int|false ID validé ou false si invalide
 */
function validateCategoryId($categoryId) {
    $categoryId = intval($categoryId);
    
    // Les IDs de catégorie PeerTube sont entre 1 et 20
    if ($categoryId < 1 || $categoryId > 20) {
        return false;
    }
    
    return $categoryId;
}

/**
 * Valide et assainit un User-Agent
 * 
 * @param string $userAgent User-Agent à valider
 * @return bool True si valide
 */
function validateUserAgent($userAgent) {
    if (empty($userAgent)) {
        return false;
    }
    
    // Bloquer les User-Agents suspects
    $blockedPatterns = [
        '/curl/i',
        '/wget/i',
        '/python/i',
        '/bot/i',
        '/scanner/i',
        '/sqlmap/i'
    ];
    
    foreach ($blockedPatterns as $pattern) {
        if (preg_match($pattern, $userAgent)) {
            return false;
        }
    }
    
    return true;
}

/**
 * Valide les en-têtes HTTP pour détecter les tentatives d'attaque
 * 
 * @return bool True si les en-têtes sont sûrs
 */
function validateHttpHeaders() {
    // Vérifier le User-Agent
    $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
    if (!validateUserAgent($userAgent)) {
        error_log('SECURITY: Suspicious User-Agent detected: ' . $userAgent);
        return false;
    }
    
    // Vérifier les en-têtes suspects
    $suspiciousHeaders = [
        'HTTP_X_FORWARDED_FOR',
        'HTTP_X_REAL_IP',
        'HTTP_CLIENT_IP'
    ];
    
    foreach ($suspiciousHeaders as $header) {
        if (isset($_SERVER[$header])) {
            $value = $_SERVER[$header];
            // Bloquer les IPs privées dans les en-têtes de forwarding
            if (filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
                error_log('SECURITY: Suspicious IP in header ' . $header . ': ' . $value);
            }
        }
    }
    
    return true;
}

/**
 * Génère un token CSRF sécurisé
 * 
 * @return string Token CSRF
 */
function generateCSRFToken() {
    // Démarrer la session seulement si les en-têtes n'ont pas été envoyés
    if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
        session_start();
    }
    
    if (!isset($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    
    return $_SESSION['csrf_token'];
}

/**
 * Valide un token CSRF
 * 
 * @param string $token Token à valider
 * @return bool True si le token est valide
 */
function validateCSRFToken($token) {
    if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
        session_start();
    }
    
    if (!isset($_SESSION['csrf_token'])) {
        return false;
    }
    
    return hash_equals($_SESSION['csrf_token'], $token);
}

/**
 * Génère ou récupère le nonce CSP de la requête courante.
 * Doit être appelé après setSecurityHeaders() pour que le nonce soit envoyé dans le header CSP.
 *
 * @return string Nonce CSP
 */
function getCspNonce() {
    if (!isset($GLOBALS['csp_nonce'])) {
        $GLOBALS['csp_nonce'] = base64_encode(random_bytes(16));
    }
    return $GLOBALS['csp_nonce'];
}

/**
 * Applique des en-têtes de sécurité HTTP
 */
function setSecurityHeaders() {
    // Protection contre le clickjacking (permettre les iframes du même site)
    header('X-Frame-Options: SAMEORIGIN');

    // Protection contre le MIME sniffing
    header('X-Content-Type-Options: nosniff');

    // Protection XSS basique
    header('X-XSS-Protection: 1; mode=block');

    // Politique de référent
    header('Referrer-Policy: strict-origin-when-cross-origin');

    // Isolation cross-origin
    header('Cross-Origin-Resource-Policy: same-origin');
    header('Cross-Origin-Opener-Policy: same-origin');
    // COEP retiré : require-corp bloque les iframes/images/vidéos cross-origin (PeerTube, Mastodon, Castopod, Funkwhale)
    // car ces services ne renvoient pas le header CORP approprié.

    // Permissions Policy (feature policy)
    header('Permissions-Policy: accelerometer=(), autoplay=(), camera=(), cross-origin-isolated=(), display-capture=(), encrypted-media=(), fullscreen=(self), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(self), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()');

    // Content Security Policy avec support Mastodon et PeerTube
    $nonce = getCspNonce();
    $mastodonDomain = '';
    $peertubeDomain = '';

    // Extraire le domaine Mastodon si configuré
    if (defined('MASTODON_INSTANCE_URL')) {
        $mastodonParsed = parse_url(MASTODON_INSTANCE_URL);
        if ($mastodonParsed && isset($mastodonParsed['host'])) {
            $mastodonDomain = $mastodonParsed['scheme'] . '://' . $mastodonParsed['host'];
        }
    }

    // Extraire le domaine PeerTube si configuré
    if (defined('PEERTUBE_URL')) {
        $peertubeParsed = parse_url(PEERTUBE_URL);
        if ($peertubeParsed && isset($peertubeParsed['host'])) {
            $peertubeDomain = $peertubeParsed['scheme'] . '://' . $peertubeParsed['host'];
        }
    }

    // Détecter si on est en développement local
    $isLocalDev = in_array($_SERVER['HTTP_HOST'] ?? '', ['127.0.0.1:8080', '127.0.0.1:8001', 'localhost:8080', 'localhost:8001', '127.0.0.1', 'localhost']);

    $csp = "default-src 'self'; ";
    $csp .= "style-src 'self' 'nonce-{$nonce}' https://cdnjs.cloudflare.com; ";
    $csp .= "script-src 'self' 'nonce-{$nonce}' https://cdnjs.cloudflare.com https://plausible.io; ";

    // Images : autoriser les domaines connus plus HTTPS général pour le contenu fédéré
    $imgSrc = "'self' data: " . ($mastodonDomain ? $mastodonDomain : '') . " " . ($peertubeDomain ? $peertubeDomain : '');
    if ($isLocalDev) {
        $imgSrc .= " https: http:";
    } else {
        $imgSrc .= " https:";
    }
    $csp .= "img-src " . $imgSrc . "; ";

    $csp .= "font-src 'self' https://cdnjs.cloudflare.com; ";

    // Frames : autoriser PeerTube uniquement
    $frameSrc = "'self' " . ($peertubeDomain ? $peertubeDomain : '');
    if ($isLocalDev) {
        $frameSrc .= " https: http:";
    }
    $csp .= "frame-src " . $frameSrc . "; ";

    // Connexions : autoriser Mastodon, PeerTube et Plausible
    $connectSrc = "'self' https://plausible.io " . ($mastodonDomain ? $mastodonDomain : '') . " " . ($peertubeDomain ? $peertubeDomain : '');
    if ($isLocalDev) {
        $connectSrc .= " ws: wss:"; // WebSockets pour le dev
    }
    $csp .= "connect-src " . $connectSrc . "; ";

    // Médias : autoriser 'self', Mastodon, PeerTube et S3 Mastodon
    $mediaSrc = "'self'";

    if ($mastodonDomain) {
        $mediaSrc .= " " . $mastodonDomain;
    }

    if ($peertubeDomain) {
        $mediaSrc .= " " . $peertubeDomain;
    }

    // Ajouter l'URL S3 Mastodon si configurée (pour les médias externalisés)
    if (defined('MASTODON_S3_MEDIA_URL') && !empty(MASTODON_S3_MEDIA_URL)) {
        $s3Parsed = parse_url(MASTODON_S3_MEDIA_URL);
        if ($s3Parsed && isset($s3Parsed['host'])) {
            $s3Domain = $s3Parsed['scheme'] . '://' . $s3Parsed['host'];
            $mediaSrc .= " " . $s3Domain;
        }
    }

    if ($isLocalDev) {
        $mediaSrc .= " https: http:";
    } else {
        $mediaSrc .= " https:";
    }
    $csp .= "media-src " . $mediaSrc . "; ";

    $csp .= "object-src 'none'; ";
    $csp .= "base-uri 'self'; ";
    $csp .= "frame-ancestors 'self';";

    header('Content-Security-Policy: ' . $csp);

    // HTTPS strict transport security (seulement si HTTPS)
    if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
        header('Strict-Transport-Security: max-age=63072000; includeSubDomains; preload');
    }
}

/**
 * Valide l'origine de la requête pour les requêtes AJAX
 * 
 * @return bool True si l'origine est valide
 */
function validateAjaxOrigin() {
    $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
    $host = $_SERVER['HTTP_HOST'] ?? '';
    
    if (empty($origin) || empty($host)) {
        return false;
    }
    
    $expectedOrigin = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http') . '://' . $host;
    
    return $origin === $expectedOrigin;
}
?>