124 lines
5.2 KiB
PHP
124 lines
5.2 KiB
PHP
<?php
|
|||
|
|
/**
|
||
|
|
* Tests unitaires pour le helper e() (includes/security.php)
|
||
|
|
* et le partial renderVideoCard() (includes/partials/video-card.php)
|
||
|
|
*
|
||
|
|
* Non-régression XSS (SEC-1) : les données de l'API PeerTube (titres,
|
||
|
|
* chaînes, vignettes, avatars) ne doivent jamais ressortir telles quelles
|
||
|
|
* dans le HTML d'une carte vidéo.
|
||
|
|
*/
|
||
|
|
|
||
|
|
// --- e() ---------------------------------------------------------------------
|
||
|
|
|
||
|
|
assertEquals(
|
||
|
|
'<script>alert(1)</script>',
|
||
|
|
e('<script>alert(1)</script>'),
|
||
|
|
'e() échappe les balises HTML'
|
||
|
|
);
|
||
|
|
assertEquals(
|
||
|
|
'"guillemets" 'apostrophes'',
|
||
|
|
e('"guillemets" \'apostrophes\''),
|
||
|
|
'e() échappe guillemets et apostrophes (ENT_QUOTES)'
|
||
|
|
);
|
||
|
|
assertEquals('&', e('&'), 'e() échappe l\'esperluette');
|
||
|
|
assertEquals('', e(null), 'e() convertit null en chaîne vide');
|
||
|
|
assertEquals('42', e(42), 'e() convertit les nombres en chaîne');
|
||
|
|
|
||
|
|
// --- renderVideoCard : structure de base -------------------------------------
|
||
|
|
|
||
|
|
$video = [
|
||
|
|
'id' => 'abc-123',
|
||
|
|
'title' => 'Ma vidéo',
|
||
|
|
'thumbnail' => 'https://videos.example/lazy/abc.jpg',
|
||
|
|
'duration' => 125,
|
||
|
|
'channel' => 'Ma chaîne',
|
||
|
|
'channelAvatar' => 'https://videos.example/avatars/a.png',
|
||
|
|
'views' => 42,
|
||
|
|
'date' => date('Y-m-d H:i:s'),
|
||
|
|
];
|
||
|
|
|
||
|
|
$html = renderVideoCard($video);
|
||
|
|
|
||
|
|
assertContains('class="video-card"', $html, 'renderVideoCard génère une carte vidéo');
|
||
|
|
assertContains('data-video-id="abc-123"', $html, 'renderVideoCard expose l\'identifiant vidéo');
|
||
|
|
assertContains('src="https://videos.example/lazy/abc.jpg"', $html, 'renderVideoCard affiche la vignette');
|
||
|
|
assertContains('<h3 class="video-title">Ma vidéo</h3>', $html, 'renderVideoCard affiche le titre');
|
||
|
|
assertContains('<span class="channel-name">Ma chaîne</span>', $html, 'renderVideoCard affiche la chaîne');
|
||
|
|
assertContains('class="channel-avatar"', $html, 'renderVideoCard affiche l\'avatar personnalisé');
|
||
|
|
assertContains('2:05', $html, 'renderVideoCard affiche la durée formatée');
|
||
|
|
|
||
|
|
// --- renderVideoCard : avatar par défaut --------------------------------------
|
||
|
|
|
||
|
|
$videoDefaultAvatar = $video;
|
||
|
|
$videoDefaultAvatar['channelAvatar'] = 'img/default-avatar.png';
|
||
|
|
$htmlDefault = renderVideoCard($videoDefaultAvatar);
|
||
|
|
|
||
|
|
assertContains('channel-avatar-placeholder', $htmlDefault, 'renderVideoCard utilise un placeholder pour l\'avatar par défaut');
|
||
|
|
assertNotContains('class="channel-avatar"', $htmlDefault, 'renderVideoCard n\'affiche pas l\'image d\'avatar par défaut');
|
||
|
|
|
||
|
|
$videoEmptyAvatar = $video;
|
||
|
|
$videoEmptyAvatar['channelAvatar'] = '';
|
||
|
|
assertContains(
|
||
|
|
'channel-avatar-placeholder',
|
||
|
|
renderVideoCard($videoEmptyAvatar),
|
||
|
|
'renderVideoCard utilise un placeholder si l\'avatar est vide'
|
||
|
|
);
|
||
|
|
|
||
|
|
// --- renderVideoCard : échappement XSS (SEC-1) ---------------------------------
|
||
|
|
|
||
|
|
$maliciousTitle = '"><img src=x onerror=alert(1)><script>alert(2)</script>';
|
||
|
|
$maliciousChannel = '<svg onload=alert(3)>';
|
||
|
|
$maliciousThumbnail = 'https://videos.example/x.jpg" onerror="alert(4)';
|
||
|
|
$maliciousAvatar = 'https://videos.example/a.png\' onerror=\'alert(5)';
|
||
|
|
|
||
|
|
$htmlXss = renderVideoCard([
|
||
|
|
'id' => $maliciousTitle,
|
||
|
|
'title' => $maliciousTitle,
|
||
|
|
'thumbnail' => $maliciousThumbnail,
|
||
|
|
'duration' => 60,
|
||
|
|
'channel' => $maliciousChannel,
|
||
|
|
'channelAvatar' => $maliciousAvatar,
|
||
|
|
'views' => 1,
|
||
|
|
'date' => date('Y-m-d H:i:s'),
|
||
|
|
]);
|
||
|
|
|
||
|
|
assertNotContains($maliciousTitle, $htmlXss, 'renderVideoCard ne ressort pas le titre brut');
|
||
|
|
assertNotContains($maliciousChannel, $htmlXss, 'renderVideoCard ne ressort pas la chaîne brute');
|
||
|
|
assertNotContains($maliciousThumbnail, $htmlXss, 'renderVideoCard ne ressort pas la vignette brute');
|
||
|
|
assertNotContains($maliciousAvatar, $htmlXss, 'renderVideoCard ne ressort pas l\'avatar brut');
|
||
|
|
assertNotContains('<script>', $htmlXss, 'renderVideoCard ne génère aucune balise script injectée');
|
||
|
|
assertNotContains('onerror="', $htmlXss, 'renderVideoCard ne laisse passer aucun attribut d\'événement actif');
|
||
|
|
|
||
|
|
assertContains(
|
||
|
|
htmlspecialchars($maliciousTitle, ENT_QUOTES, 'UTF-8'),
|
||
|
|
$htmlXss,
|
||
|
|
'renderVideoCard affiche le titre échappé'
|
||
|
|
);
|
||
|
|
assertContains(
|
||
|
|
htmlspecialchars($maliciousChannel, ENT_QUOTES, 'UTF-8'),
|
||
|
|
$htmlXss,
|
||
|
|
'renderVideoCard affiche la chaîne échappée'
|
||
|
|
);
|
||
|
|
|
||
|
|
// L'échappement ENT_QUOTES doit aussi protéger les attributs à apostrophes
|
||
|
|
$htmlAttr = renderVideoCard([
|
||
|
|
'id' => 'xyz',
|
||
|
|
'title' => "Titre avec 'apostrophe'",
|
||
|
|
'thumbnail' => 'https://videos.example/x.jpg',
|
||
|
|
'duration' => 10,
|
||
|
|
'channel' => 'Chaîne',
|
||
|
|
'channelAvatar' => 'img/default-avatar.png',
|
||
|
|
'views' => 0,
|
||
|
|
'date' => date('Y-m-d H:i:s'),
|
||
|
|
]);
|
||
|
|
assertContains(''', $htmlAttr, 'renderVideoCard échappe les apostrophes dans les attributs');
|
||
|
|
assertNotContains("'apostrophe'", $htmlAttr, 'renderVideoCard ne laisse pas d\'apostrophe brute');
|
||
|
|
|
||
|
|
// --- renderVideoCard : données incomplètes --------------------------------------
|
||
|
|
|
||
|
|
$htmlMinimal = renderVideoCard(['id' => 'solo-1']);
|
||
|
|
|
||
|
|
assertContains('class="video-card"', $htmlMinimal, 'renderVideoCard tolère une vidéo quasi vide');
|
||
|
|
assertContains('data-video-id="solo-1"', $htmlMinimal, 'renderVideoCard conserve l\'id minimal');
|
||
|
|
assertContains('channel-avatar-placeholder', $htmlMinimal, 'renderVideoCard met un placeholder sans avatar');
|