123 lines
5.5 KiB
PHP
123 lines
5.5 KiB
PHP
<?php
|
|
/**
|
|
* Tests unitaires pour includes/structured-data.php
|
|
* (encodage JSON-LD anti-injection et construction d'URL de confiance)
|
|
*/
|
|
|
|
// --- JSON-LD : neutralisation de "</script>" ---------------------------------
|
|
|
|
$maliciousTitle = 'Vidéo </script><script>alert("xss")</script> test';
|
|
|
|
$videoData = [
|
|
'thumbnailPath' => '/static/thumbnails/test.jpg',
|
|
];
|
|
$video = [
|
|
'id' => '9c5de94d-8e1a-4b3c-9d2e-1234567890ab',
|
|
'title' => $maliciousTitle,
|
|
'description' => 'Une description',
|
|
'duration' => 120,
|
|
'channel' => 'Chaîne </script>',
|
|
'views' => 42,
|
|
'date' => '2024-01-15 10:00:00',
|
|
'tags' => ['test'],
|
|
];
|
|
|
|
$jsonLd = generateVideoObjectJsonLd($videoData, $video);
|
|
assertNotContains('</script>', $jsonLd, 'generateVideoObjectJsonLd ne reproduit pas "</script>" en clair');
|
|
|
|
// L'équivalent encodé de "</script>" (séquences \u00XX via JSON_HEX_TAG)
|
|
// doit apparaître à la place de la balise en clair
|
|
$encodedClosingTag = trim(json_encode('</script>', 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('</script>', $websiteLd, 'generateWebSiteJsonLd ne contient pas "</script>"');
|
|
assertTrue(json_decode($websiteLd, true) !== null, 'generateWebSiteJsonLd produit un JSON valide');
|
|
|
|
$breadcrumbLd = generateBreadcrumbJsonLd([
|
|
['name' => 'Accueil', 'url' => 'https://test.local'],
|
|
['name' => 'Page </script>', 'url' => 'https://test.local/page'],
|
|
]);
|
|
assertNotContains('</script>', $breadcrumbLd, 'generateBreadcrumbJsonLd neutralise "</script>" dans un nom de fil d\'Ariane');
|
|
|
|
$collectionLd = generateVideoCollectionJsonLd('Collection </script>', 'Description', [$video], 'https://test.local/col');
|
|
assertNotContains('</script>', $collectionLd, 'generateVideoCollectionJsonLd neutralise "</script>" dans le nom de collection');
|
|
|
|
$podcastLd = generatePodcastJsonLd([
|
|
[
|
|
'title' => 'Épisode </script>',
|
|
'link' => 'https://podcast.example/ep1',
|
|
'pubDate' => '2024-01-01',
|
|
],
|
|
]);
|
|
assertNotContains('</script>', $podcastLd, 'generatePodcastJsonLd neutralise "</script>" 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"><script>'), 'isValidAppHostName refuse guillemets et chevrons');
|
|
assertFalse(isValidAppHostName("evil.com\r\nX-Injected: 1"), 'isValidAppHostName refuse les CRLF (injection d\'en-tête)');
|
|
assertFalse(isValidAppHostName('http://evil.com'), 'isValidAppHostName refuse une URL avec schéma');
|
|
assertFalse(isValidAppHostName('evil com'), 'isValidAppHostName refuse les espaces');
|
|
assertFalse(isValidAppHostName('evil.com/path'), 'isValidAppHostName refuse un chemin');
|
|
|
|
// --- getBaseUrl / getAppHostName : hôte de confiance --------------------------
|
|
|
|
// Un en-tête Host malveillant ne doit pas influencer l'URL de base
|
|
$_SERVER['HTTP_HOST'] = 'evil.example"><script>alert(1)</script>';
|
|
$_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"><meta http-equiv="refresh" content="0;url=https://phishing.example">';
|
|
$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'] = '/';
|