diff --git a/countdown.php b/countdown.php index 4b52b5a..ca8701d 100644 --- a/countdown.php +++ b/countdown.php @@ -3,6 +3,8 @@ require_once 'includes/config.php'; // Inclure les fonctions de sécurité require_once 'includes/security.php'; +// Inclure les fonctions de données structurées (getBaseUrl, getCurrentUrl) +require_once 'includes/structured-data.php'; // Appliquer les en-têtes de sécurité setSecurityHeaders(); ?> @@ -14,7 +16,7 @@ setSecurityHeaders(); <?php echo SITE_NAME; ?> - Ouverture prochaine - + @@ -42,8 +44,8 @@ setSecurityHeaders(); setlocale(LC_TIME, 'fr_FR.UTF-8'); echo strftime('%e %B %Y', $targetDate->getTimestamp()); ?>. Restez connectés !"> - - + + @@ -56,7 +58,7 @@ setSecurityHeaders(); setlocale(LC_TIME, 'fr_FR.UTF-8'); echo strftime('%e %B %Y', $targetDate->getTimestamp()); ?>. Restez connectés !"> - + " (titre de vidéo, nom de chaîne, titre d'épisode…) + * ne peut plus fermer le bloc " --------------------------------- + +$maliciousTitle = 'Vidéo test'; + +$videoData = [ + 'thumbnailPath' => '/static/thumbnails/test.jpg', +]; +$video = [ + 'id' => '9c5de94d-8e1a-4b3c-9d2e-1234567890ab', + 'title' => $maliciousTitle, + 'description' => 'Une description', + 'duration' => 120, + 'channel' => 'Chaîne ', + 'views' => 42, + 'date' => '2024-01-15 10:00:00', + 'tags' => ['test'], +]; + +$jsonLd = generateVideoObjectJsonLd($videoData, $video); +assertNotContains('', $jsonLd, 'generateVideoObjectJsonLd ne reproduit pas "" en clair'); + +// L'équivalent encodé de "" (séquences \u00XX via JSON_HEX_TAG) +// doit apparaître à la place de la balise en clair +$encodedClosingTag = trim(json_encode('', JSON_UNESCAPED_SLASHES | JSON_HEX_TAG), '"'); +assertContains( + $encodedClosingTag, + $jsonLd, + 'generateVideoObjectJsonLd encode "<" et ">" en séquences unicode (JSON_HEX_TAG)' +); + +$decoded = json_decode($jsonLd, true); +assertTrue(is_array($decoded), 'Le JSON-LD VideoObject reste un JSON valide'); +assertEquals( + $maliciousTitle, + $decoded['name'], + 'Le titre malveillant est préservé à l\'identique après décodage JSON' +); + +// Les autres générateurs JSON-LD bénéficient des mêmes drapeaux d'encodage +$websiteLd = generateWebSiteJsonLd(); +assertNotContains('', $websiteLd, 'generateWebSiteJsonLd ne contient pas ""'); +assertTrue(json_decode($websiteLd, true) !== null, 'generateWebSiteJsonLd produit un JSON valide'); + +$breadcrumbLd = generateBreadcrumbJsonLd([ + ['name' => 'Accueil', 'url' => 'https://test.local'], + ['name' => 'Page ', 'url' => 'https://test.local/page'], +]); +assertNotContains('', $breadcrumbLd, 'generateBreadcrumbJsonLd neutralise "" dans un nom de fil d\'Ariane'); + +$collectionLd = generateVideoCollectionJsonLd('Collection ', 'Description', [$video], 'https://test.local/col'); +assertNotContains('', $collectionLd, 'generateVideoCollectionJsonLd neutralise "" dans le nom de collection'); + +$podcastLd = generatePodcastJsonLd([ + [ + 'title' => 'Épisode ', + 'link' => 'https://podcast.example/ep1', + 'pubDate' => '2024-01-01', + ], +]); +assertNotContains('', $podcastLd, 'generatePodcastJsonLd neutralise "" dans un titre d\'épisode'); +assertTrue(json_decode($podcastLd, true) !== null, 'generatePodcastJsonLd produit un JSON valide'); + +// --- isValidAppHostName ------------------------------------------------------- + +assertTrue(isValidAppHostName('example.com'), 'isValidAppHostName accepte un nom de domaine'); +assertTrue(isValidAppHostName('test.local'), 'isValidAppHostName accepte un domaine local'); +assertTrue(isValidAppHostName('localhost'), 'isValidAppHostName accepte localhost'); +assertTrue(isValidAppHostName('127.0.0.1:8080'), 'isValidAppHostName accepte une IPv4 avec port'); +assertTrue(isValidAppHostName('sub.example-site.com'), 'isValidAppHostName accepte sous-domaines et tirets'); +assertFalse(isValidAppHostName(''), 'isValidAppHostName refuse une chaîne vide'); +assertFalse(isValidAppHostName('evil.com">'; +$_SERVER['HTTPS'] = 'on'; +assertEquals( + 'https://' . APP_HOST_NAME, + getBaseUrl(), + 'getBaseUrl utilise APP_HOST_NAME et ignore un HTTP_HOST malveillant' +); +assertNotContains('evil.example', getBaseUrl(), 'getBaseUrl ne reflète pas l\'en-tête Host'); +assertEquals(APP_HOST_NAME, getAppHostName(), 'getAppHostName retourne APP_HOST_NAME validé'); + +unset($_SERVER['HTTPS']); +assertEquals('http://' . APP_HOST_NAME, getBaseUrl(), 'getBaseUrl retombe en http sans HTTPS'); + +// --- getCurrentUrl : REQUEST_URI assaini -------------------------------------- + +$_SERVER['REQUEST_URI'] = '/video.php?id=abc">'; +$currentUrl = getCurrentUrl(); +assertNotContains('"', $currentUrl, 'getCurrentUrl supprime les guillemets de REQUEST_URI'); +assertNotContains('<', $currentUrl, 'getCurrentUrl supprime les chevrons de REQUEST_URI'); +assertNotContains(' ', $currentUrl, 'getCurrentUrl supprime les espaces de REQUEST_URI'); +assertEquals( + 0, + strpos($currentUrl, 'http://' . APP_HOST_NAME . '/video.php?id=abc'), + 'getCurrentUrl préfixe par l\'hôte validé et conserve le chemin' +); + +$_SERVER['REQUEST_URI'] = 'sans-slash-initial'; +assertEquals( + 'http://' . APP_HOST_NAME . '/sans-slash-initial', + getCurrentUrl(), + 'getCurrentUrl ajoute le slash initial manquant' +); + +// Nettoyage des superglobales modifiées pour ne pas impacter d'autres tests +unset($_SERVER['HTTPS']); +$_SERVER['HTTP_HOST'] = APP_HOST_NAME; +$_SERVER['REQUEST_URI'] = '/';