'), 'validateSearchQuery supprime les chevrons' ); assertEquals( 'recherche btest/b', validateSearchQuery('recherche test'), 'validateSearchQuery supprime les caractères dangereux <>"\'' ); assertEquals('terme', validateSearchQuery(' terme '), 'validateSearchQuery nettoie les espaces'); assertFalse(validateSearchQuery(''), 'validateSearchQuery refuse une chaîne vide'); assertFalse( validateSearchQuery(str_repeat('a', 201)), 'validateSearchQuery refuse plus de 200 caractères' ); assertEquals( str_repeat('a', 200), validateSearchQuery(str_repeat('a', 200)), 'validateSearchQuery accepte exactement 200 caractères' ); // --- validateCategoryId ----------------------------------------------------- assertEquals(1, validateCategoryId(1), 'validateCategoryId accepte la borne basse 1'); assertEquals(20, validateCategoryId(20), 'validateCategoryId accepte la borne haute 20'); assertEquals(5, validateCategoryId('5'), 'validateCategoryId convertit une chaîne numérique'); assertFalse(validateCategoryId(0), 'validateCategoryId refuse 0'); assertFalse(validateCategoryId(21), 'validateCategoryId refuse 21'); assertFalse(validateCategoryId(-3), 'validateCategoryId refuse un nombre négatif'); assertFalse(validateCategoryId('abc'), 'validateCategoryId refuse une chaîne non numérique'); // --- validatePageNumber ----------------------------------------------------- assertEquals(1, validatePageNumber(0), 'validatePageNumber remonte 0 à 1'); assertEquals(1, validatePageNumber(-5), 'validatePageNumber remonte un négatif à 1'); assertEquals(3, validatePageNumber(3), 'validatePageNumber conserve une page valide'); assertEquals(2, validatePageNumber('2'), 'validatePageNumber convertit une chaîne numérique'); assertEquals(1, validatePageNumber('abc'), 'validatePageNumber ramène une chaîne non numérique à 1'); // --- generateCSRFToken / validateCSRFToken ---------------------------------- $token = generateCSRFToken(); assertContains(':', $token, 'generateCSRFToken produit le format "timestamp:hash"'); [$timestamp, $hash] = explode(':', $token, 2); assertTrue(ctype_digit($timestamp), 'generateCSRFToken : la partie timestamp est numérique'); assertEquals( hash_hmac('sha256', $timestamp, CSRF_SECRET), $hash, 'generateCSRFToken : le hash est un HMAC-SHA256 du timestamp avec CSRF_SECRET' ); assertTrue(validateCSRFToken($token), 'validateCSRFToken accepte un token fraîchement généré'); assertFalse(validateCSRFToken(''), 'validateCSRFToken refuse une chaîne vide'); assertFalse(validateCSRFToken('sans-deux-points'), 'validateCSRFToken refuse un token sans séparateur'); assertFalse(validateCSRFToken('abc:' . $hash), 'validateCSRFToken refuse un timestamp non numérique'); assertFalse( validateCSRFToken($timestamp . ':' . hash_hmac('sha256', $timestamp, 'autre-secret')), 'validateCSRFToken refuse un hash signé avec un autre secret' ); assertFalse( validateCSRFToken($timestamp . ':0' . $hash), 'validateCSRFToken refuse un hash modifié' ); // Token expiré : horodatage d'il y a 2 heures (limite : 1 heure) $oldTimestamp = (string) (time() - 7200); $oldToken = $oldTimestamp . ':' . hash_hmac('sha256', $oldTimestamp, CSRF_SECRET); assertFalse(validateCSRFToken($oldToken), 'validateCSRFToken refuse un token expiré (> 1 heure)'); // Timestamp trop loin dans le futur (l'écart absolu est également plafonné) $futureTimestamp = (string) (time() + 7200); $futureToken = $futureTimestamp . ':' . hash_hmac('sha256', $futureTimestamp, CSRF_SECRET); assertFalse(validateCSRFToken($futureToken), 'validateCSRFToken refuse un timestamp trop futur'); // --- isValidPeerTubeUrl (includes/config.php) ------------------------------- assertTrue(isValidPeerTubeUrl('https://peertube.example.com'), 'isValidPeerTubeUrl accepte une URL HTTPS publique'); assertTrue(isValidPeerTubeUrl('http://peertube.example.com'), 'isValidPeerTubeUrl accepte HTTP (développement)'); assertTrue(isValidPeerTubeUrl('https://8.8.8.8'), 'isValidPeerTubeUrl accepte une IP publique'); assertFalse(isValidPeerTubeUrl('ftp://example.com'), 'isValidPeerTubeUrl refuse un schéma non HTTP(S)'); assertFalse(isValidPeerTubeUrl('pas-une-url'), 'isValidPeerTubeUrl refuse une chaîne mal formée'); assertFalse(isValidPeerTubeUrl('https://localhost'), 'isValidPeerTubeUrl refuse localhost'); assertFalse(isValidPeerTubeUrl('http://127.0.0.1'), 'isValidPeerTubeUrl refuse 127.0.0.1'); assertFalse(isValidPeerTubeUrl('http://192.168.1.1'), 'isValidPeerTubeUrl refuse une IP privée'); assertFalse(isValidPeerTubeUrl('http://10.0.0.5'), 'isValidPeerTubeUrl refuse une IP privée (10.x)'); assertFalse( isValidPeerTubeUrl('https://metadata.google.internal'), 'isValidPeerTubeUrl refuse metadata.google.internal' ); // --- isValidApiEndpoint (includes/config.php) ------------------------------- assertTrue(isValidApiEndpoint('videos'), 'isValidApiEndpoint accepte "videos"'); assertTrue(isValidApiEndpoint('videos/categories'), 'isValidApiEndpoint accepte "videos/categories"'); assertTrue(isValidApiEndpoint('search/videos'), 'isValidApiEndpoint accepte "search/videos"'); assertTrue(isValidApiEndpoint('accounts'), 'isValidApiEndpoint accepte "accounts"'); assertTrue( isValidApiEndpoint('videos/9c5de94d-8e1a-4b3c-9d2e-1234567890ab'), 'isValidApiEndpoint accepte un endpoint vidéo dynamique' ); assertTrue( isValidApiEndpoint('videos/abc/comment-threads'), 'isValidApiEndpoint accepte les fils de commentaires' ); assertTrue( isValidApiEndpoint('accounts/annu_kute_ced/videos'), 'isValidApiEndpoint accepte les vidéos d\'un compte' ); assertFalse(isValidApiEndpoint('../config'), 'isValidApiEndpoint refuse le path traversal'); assertFalse(isValidApiEndpoint('videos/../x'), 'isValidApiEndpoint refuse ".." dans le chemin'); assertFalse(isValidApiEndpoint('videos//categories'), 'isValidApiEndpoint refuse un double slash'); assertFalse(isValidApiEndpoint('wp-admin'), 'isValidApiEndpoint refuse un endpoint hors liste blanche'); assertFalse(isValidApiEndpoint('videos.json'), 'isValidApiEndpoint refuse les caractères non autorisés (.)'); assertFalse(isValidApiEndpoint('videos?count=1'), 'isValidApiEndpoint refuse une query string'); assertFalse(isValidApiEndpoint(''), 'isValidApiEndpoint refuse une chaîne vide'); // --- getCspNonce ------------------------------------------------------------ $nonce = getCspNonce(); assertTrue(is_string($nonce) && $nonce !== '', 'getCspNonce retourne une chaîne non vide'); assertEquals(16, strlen(base64_decode($nonce, true)), 'getCspNonce est un base64 de 16 octets aléatoires'); assertEquals($nonce, getCspNonce(), 'getCspNonce retourne le même nonce durant toute la requête');