security: SRI Font Awesome, CSP restrictions, remove X-XSS-Protection, rate limiting
This commit is contained in:
+1
-1
@@ -37,7 +37,7 @@ if (function_exists('setSecurityHeaders')) {
|
|||||||
|
|
||||||
<!-- Styles -->
|
<!-- Styles -->
|
||||||
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime(__DIR__ . '/css/styles.css'); ?>">
|
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime(__DIR__ . '/css/styles.css'); ?>">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" crossorigin="anonymous">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" integrity="sha512-2SwdPD6INVrV/lHTZbO2nodKhrnDdJK9/kg2XD1r9uGqPo1cUbujc+IYdlYdEErWNu69gVcYgdxlmVmzTWnetw==" crossorigin="anonymous">
|
||||||
|
|
||||||
<!-- Favicons -->
|
<!-- Favicons -->
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
||||||
|
|||||||
+1
-1
@@ -40,7 +40,7 @@ if (function_exists('setSecurityHeaders')) {
|
|||||||
|
|
||||||
<!-- Styles -->
|
<!-- Styles -->
|
||||||
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime(__DIR__ . '/css/styles.css'); ?>">
|
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime(__DIR__ . '/css/styles.css'); ?>">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" crossorigin="anonymous">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" integrity="sha512-2SwdPD6INVrV/lHTZbO2nodKhrnDdJK9/kg2XD1r9uGqPo1cUbujc+IYdlYdEErWNu69gVcYgdxlmVmzTWnetw==" crossorigin="anonymous">
|
||||||
|
|
||||||
<!-- Favicons -->
|
<!-- Favicons -->
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
||||||
|
|||||||
+3
-3
@@ -19,8 +19,8 @@ setSecurityHeaders();
|
|||||||
$categoryId = isset($_GET['id']) ? $_GET['id'] : null;
|
$categoryId = isset($_GET['id']) ? $_GET['id'] : null;
|
||||||
$categoryId = $categoryId ? validateCategoryId($categoryId) : null;
|
$categoryId = $categoryId ? validateCategoryId($categoryId) : null;
|
||||||
|
|
||||||
// Récupérer les catégories disponibles
|
// Récupérer les catégories disponibles (chargement paresseux, ARC-2)
|
||||||
$allCategories = PEERTUBE_CATEGORIES;
|
$allCategories = getPeertubeCategories();
|
||||||
|
|
||||||
// Récupérer les vidéos de la catégorie si un ID est fourni
|
// Récupérer les vidéos de la catégorie si un ID est fourni
|
||||||
if ($categoryId && isset($allCategories[$categoryId])) {
|
if ($categoryId && isset($allCategories[$categoryId])) {
|
||||||
@@ -46,7 +46,7 @@ if ($categoryId && isset($allCategories[$categoryId])) {
|
|||||||
<meta name="description" content="Découvrez toutes les vidéos de la catégorie <?php echo htmlspecialchars($categoryName); ?> sur <?php echo SITE_NAME; ?>. Contenu multimédia de qualité et exclusif.">
|
<meta name="description" content="Découvrez toutes les vidéos de la catégorie <?php echo htmlspecialchars($categoryName); ?> sur <?php echo SITE_NAME; ?>. Contenu multimédia de qualité et exclusif.">
|
||||||
<link rel="canonical" href="<?php echo getBaseUrl() . '/categories.php?id=' . $categoryId; ?>">
|
<link rel="canonical" href="<?php echo getBaseUrl() . '/categories.php?id=' . $categoryId; ?>">
|
||||||
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
|
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" integrity="sha512-2SwdPD6INVrV/lHTZbO2nodKhrnDdJK9/kg2XD1r9uGqPo1cUbujc+IYdlYdEErWNu69gVcYgdxlmVmzTWnetw==" crossorigin="anonymous">
|
||||||
|
|
||||||
<!-- Favicons -->
|
<!-- Favicons -->
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
||||||
|
|||||||
+4
-106
@@ -28,7 +28,7 @@ $liveStream = getLiveStream();
|
|||||||
<meta name="description" content="Suivez nos diffusions en direct sur <?php echo SITE_NAME; ?>. Contenu en temps réel, discussions et événements exclusifs.">
|
<meta name="description" content="Suivez nos diffusions en direct sur <?php echo SITE_NAME; ?>. Contenu en temps réel, discussions et événements exclusifs.">
|
||||||
<link rel="canonical" href="<?php echo getBaseUrl(); ?>/direct.php">
|
<link rel="canonical" href="<?php echo getBaseUrl(); ?>/direct.php">
|
||||||
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
|
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" integrity="sha512-2SwdPD6INVrV/lHTZbO2nodKhrnDdJK9/kg2XD1r9uGqPo1cUbujc+IYdlYdEErWNu69gVcYgdxlmVmzTWnetw==" crossorigin="anonymous">
|
||||||
|
|
||||||
<!-- Favicons -->
|
<!-- Favicons -->
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
||||||
@@ -160,111 +160,9 @@ $liveStream = getLiveStream();
|
|||||||
$showNextLiveAnnouncement = defined('NEXT_LIVE_ENABLED') && NEXT_LIVE_ENABLED === true;
|
$showNextLiveAnnouncement = defined('NEXT_LIVE_ENABLED') && NEXT_LIVE_ENABLED === true;
|
||||||
|
|
||||||
if ($showNextLiveAnnouncement) {
|
if ($showNextLiveAnnouncement) {
|
||||||
// Afficher l'annonce du prochain live
|
// Afficher l'annonce du prochain live (partial partagé avec hero-section.php)
|
||||||
// Définir l'image de fond si disponible
|
$nextLiveVariant = 'page';
|
||||||
$bgImageStyle = '';
|
include 'includes/partials/next-live.php';
|
||||||
if (!empty(NEXT_LIVE_IMAGE) && file_exists(NEXT_LIVE_IMAGE)) {
|
|
||||||
$bgImageStyle = 'background-image: url(\'' . htmlspecialchars(NEXT_LIVE_IMAGE) . '\');';
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<?php if (!empty($bgImageStyle)): ?>
|
|
||||||
<style nonce="<?php echo getCspNonce(); ?>">
|
|
||||||
.next-live-announcement { <?php echo $bgImageStyle; ?> }
|
|
||||||
</style>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div class="next-live-announcement">
|
|
||||||
<?php if (!empty(NEXT_LIVE_IMAGE) && file_exists(NEXT_LIVE_IMAGE)): ?>
|
|
||||||
<div class="next-live-image-container">
|
|
||||||
<img src="<?php echo htmlspecialchars(NEXT_LIVE_IMAGE); ?>"
|
|
||||||
alt="<?php echo htmlspecialchars(NEXT_LIVE_TITLE); ?>"
|
|
||||||
class="next-live-image">
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div class="next-live-content">
|
|
||||||
<i class="fas fa-calendar-alt"></i>
|
|
||||||
<?php
|
|
||||||
if (!empty(NEXT_LIVE_DATE)) {
|
|
||||||
$liveDate = new DateTime(NEXT_LIVE_DATE, new DateTimeZone(DEFAULT_TIMEZONE));
|
|
||||||
$dayFormatter = new IntlDateFormatter(
|
|
||||||
'fr_FR',
|
|
||||||
IntlDateFormatter::FULL,
|
|
||||||
IntlDateFormatter::NONE,
|
|
||||||
DEFAULT_TIMEZONE,
|
|
||||||
IntlDateFormatter::GREGORIAN,
|
|
||||||
'EEEE d MMMM'
|
|
||||||
);
|
|
||||||
$formattedDay = $dayFormatter->format($liveDate);
|
|
||||||
$formattedDay = ucfirst($formattedDay);
|
|
||||||
$dynamicTitle = NEXT_LIVE_TITLE . ' - ' . $formattedDay;
|
|
||||||
} else {
|
|
||||||
$dynamicTitle = NEXT_LIVE_TITLE;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<h1><?php echo htmlspecialchars($dynamicTitle); ?></h1>
|
|
||||||
<?php
|
|
||||||
if (!empty(NEXT_LIVE_DATE)) {
|
|
||||||
$liveHour = $liveDate->format('H\hi');
|
|
||||||
$dynamicDescription = 'Rejoignez-nous à ' . $liveHour . '. ' . NEXT_LIVE_DESCRIPTION;
|
|
||||||
} else {
|
|
||||||
$dynamicDescription = NEXT_LIVE_DESCRIPTION;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<p><?php echo nl2br(htmlspecialchars($dynamicDescription)); ?></p>
|
|
||||||
<?php if (!empty(NEXT_LIVE_DATE)): ?>
|
|
||||||
<div class="next-live-datetime">
|
|
||||||
<p class="next-live-date">
|
|
||||||
<i class="fas fa-clock"></i>
|
|
||||||
<?php
|
|
||||||
$formatter = new IntlDateFormatter(
|
|
||||||
'fr_FR',
|
|
||||||
IntlDateFormatter::FULL,
|
|
||||||
IntlDateFormatter::SHORT,
|
|
||||||
DEFAULT_TIMEZONE
|
|
||||||
);
|
|
||||||
echo $formatter->format($liveDate);
|
|
||||||
|
|
||||||
$offset = $liveDate->format('P');
|
|
||||||
echo ' <span class="utc-offset">(UTC' . $offset . ')</span>';
|
|
||||||
?>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- Autres fuseaux horaires -->
|
|
||||||
<div class="next-live-timezones">
|
|
||||||
<?php
|
|
||||||
// Ordre croissant : du plus en retard au plus en avance
|
|
||||||
$timezones = [
|
|
||||||
'Ma\'ohi Nui' => 'Pacific/Tahiti',
|
|
||||||
'Martinique / Guadeloupe' => 'America/Martinique',
|
|
||||||
'Guyane' => 'America/Cayenne',
|
|
||||||
'France' => 'Europe/Paris',
|
|
||||||
'Kanaky' => 'Pacific/Noumea'
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach($timezones as $name => $timezone):
|
|
||||||
$liveDateLocal = clone $liveDate;
|
|
||||||
$liveDateLocal->setTimezone(new DateTimeZone($timezone));
|
|
||||||
|
|
||||||
// Vérifier si c'est un jour différent
|
|
||||||
$dayDiff = $liveDateLocal->format('j') - $liveDate->format('j');
|
|
||||||
|
|
||||||
$dayIndicator = '';
|
|
||||||
if ($dayDiff > 0) {
|
|
||||||
$dayIndicator = ' <span class="day-shift">+1j</span>';
|
|
||||||
} elseif ($dayDiff < 0) {
|
|
||||||
$dayIndicator = ' <span class="day-shift">-1j</span>';
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<span class="timezone-item">
|
|
||||||
<strong><?php echo $name; ?> :</strong> <?php echo $liveDateLocal->format('H\hi'); ?><?php echo $dayIndicator; ?>
|
|
||||||
</span>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<a href="index.php" class="btn-primary">Retour à l'accueil</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php
|
|
||||||
} else {
|
} else {
|
||||||
?>
|
?>
|
||||||
<div class="no-live-message">
|
<div class="no-live-message">
|
||||||
|
|||||||
+1
-1
@@ -83,7 +83,7 @@ function linkUrlsInText(string $text): string {
|
|||||||
<!-- Styles -->
|
<!-- Styles -->
|
||||||
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
|
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
|
||||||
<link rel="stylesheet" href="css/donations.css?v=<?php echo filemtime('css/donations.css'); ?>">
|
<link rel="stylesheet" href="css/donations.css?v=<?php echo filemtime('css/donations.css'); ?>">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" crossorigin="anonymous">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" integrity="sha512-2SwdPD6INVrV/lHTZbO2nodKhrnDdJK9/kg2XD1r9uGqPo1cUbujc+IYdlYdEErWNu69gVcYgdxlmVmzTWnetw==" crossorigin="anonymous">
|
||||||
|
|
||||||
<!-- Favicons -->
|
<!-- Favicons -->
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ if (defined('CASTOPOD_ENABLED') && CASTOPOD_ENABLED && defined('CASTOPOD_URL') &
|
|||||||
<meta name="description" content="<?php echo htmlspecialchars(SITE_DESCRIPTION); ?>">
|
<meta name="description" content="<?php echo htmlspecialchars(SITE_DESCRIPTION); ?>">
|
||||||
<link rel="canonical" href="<?php echo getBaseUrl() . '/'; ?>">
|
<link rel="canonical" href="<?php echo getBaseUrl() . '/'; ?>">
|
||||||
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
|
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" integrity="sha512-2SwdPD6INVrV/lHTZbO2nodKhrnDdJK9/kg2XD1r9uGqPo1cUbujc+IYdlYdEErWNu69gVcYgdxlmVmzTWnetw==" crossorigin="anonymous">
|
||||||
<link rel="stylesheet" href="css/mastodon-timeline.min.css?v=<?php echo filemtime('css/mastodon-timeline.min.css'); ?>">
|
<link rel="stylesheet" href="css/mastodon-timeline.min.css?v=<?php echo filemtime('css/mastodon-timeline.min.css'); ?>">
|
||||||
<?php if (defined('WORDPRESS_ENABLED') && WORDPRESS_ENABLED): ?>
|
<?php if (defined('WORDPRESS_ENABLED') && WORDPRESS_ENABLED): ?>
|
||||||
<link rel="stylesheet" href="css/wordpress-posts.css?v=<?php echo filemtime('css/wordpress-posts.css'); ?>">
|
<link rel="stylesheet" href="css/wordpress-posts.css?v=<?php echo filemtime('css/wordpress-posts.css'); ?>">
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ setSecurityHeaders();
|
|||||||
<meta name="description" content="Consultez les mentions légales de <?php echo SITE_NAME; ?>. Informations légales, conditions d'utilisation et politique de confidentialité.">
|
<meta name="description" content="Consultez les mentions légales de <?php echo SITE_NAME; ?>. Informations légales, conditions d'utilisation et politique de confidentialité.">
|
||||||
<link rel="canonical" href="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . '/mentions-legales.php'; ?>">
|
<link rel="canonical" href="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . '/mentions-legales.php'; ?>">
|
||||||
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
|
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" integrity="sha512-2SwdPD6INVrV/lHTZbO2nodKhrnDdJK9/kg2XD1r9uGqPo1cUbujc+IYdlYdEErWNu69gVcYgdxlmVmzTWnetw==" crossorigin="anonymous">
|
||||||
|
|
||||||
<!-- Favicons -->
|
<!-- Favicons -->
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
||||||
|
|||||||
+27
-20
@@ -24,26 +24,33 @@ $currentSearchPage = validatePageNumber($_GET['page'] ?? 1);
|
|||||||
$isTagSearch = !empty($query) && substr($query, 0, 1) === '#';
|
$isTagSearch = !empty($query) && substr($query, 0, 1) === '#';
|
||||||
$searchTag = $isTagSearch ? substr($query, 1) : '';
|
$searchTag = $isTagSearch ? substr($query, 1) : '';
|
||||||
|
|
||||||
// Rechercher les vidéos via l'API PeerTube si une requête est soumise
|
// Rechercher les vidéos via l'API PeerTube si une requête est soumise.
|
||||||
$searchResults = !empty($query) ? searchVideos($query, COUNT_VIDEO_SEARCH) : [];
|
// La pagination est déléguée à l'API (paramètre start) : chaque page ne
|
||||||
|
// récupère que VIDEOS_PER_PAGE vidéos et $resultsCount reçoit le total réel.
|
||||||
// Définir le nombre total de résultats
|
$resultsCount = 0;
|
||||||
$resultsCount = count($searchResults);
|
|
||||||
|
|
||||||
// Calculer le nombre total de pages
|
|
||||||
$totalPages = ceil($resultsCount / VIDEOS_PER_PAGE);
|
|
||||||
|
|
||||||
// S'assurer que la page actuelle est valide
|
|
||||||
$currentSearchPage = min($currentSearchPage, max(1, $totalPages));
|
|
||||||
|
|
||||||
// Calculer les indices de début et de fin pour la page actuelle
|
|
||||||
$startIndex = ($currentSearchPage - 1) * VIDEOS_PER_PAGE;
|
|
||||||
$endIndex = min($startIndex + VIDEOS_PER_PAGE, $resultsCount);
|
|
||||||
|
|
||||||
// Extraire les vidéos pour la page actuelle
|
|
||||||
$currentPageVideos = [];
|
$currentPageVideos = [];
|
||||||
if ($resultsCount > 0) {
|
$totalPages = 1;
|
||||||
$currentPageVideos = array_slice($searchResults, $startIndex, VIDEOS_PER_PAGE);
|
if (!empty($query)) {
|
||||||
|
$currentPageVideos = searchVideos(
|
||||||
|
$query,
|
||||||
|
VIDEOS_PER_PAGE,
|
||||||
|
($currentSearchPage - 1) * VIDEOS_PER_PAGE,
|
||||||
|
$resultsCount
|
||||||
|
);
|
||||||
|
$totalPages = max(1, (int) ceil($resultsCount / VIDEOS_PER_PAGE));
|
||||||
|
|
||||||
|
// Page demandée au-delà de la dernière : se replacer sur la dernière page valide
|
||||||
|
if ($currentSearchPage > $totalPages) {
|
||||||
|
$currentSearchPage = $totalPages;
|
||||||
|
$currentPageVideos = searchVideos(
|
||||||
|
$query,
|
||||||
|
VIDEOS_PER_PAGE,
|
||||||
|
($currentSearchPage - 1) * VIDEOS_PER_PAGE,
|
||||||
|
$resultsCount
|
||||||
|
);
|
||||||
|
// Le total a pu changer (ex. API en échec sur un start trop élevé)
|
||||||
|
$totalPages = max(1, (int) ceil($resultsCount / VIDEOS_PER_PAGE));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -61,7 +68,7 @@ if ($resultsCount > 0) {
|
|||||||
<link rel="canonical" href="<?php echo getBaseUrl() . '/recherche.php'; ?>">
|
<link rel="canonical" href="<?php echo getBaseUrl() . '/recherche.php'; ?>">
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
|
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" integrity="sha512-2SwdPD6INVrV/lHTZbO2nodKhrnDdJK9/kg2XD1r9uGqPo1cUbujc+IYdlYdEErWNu69gVcYgdxlmVmzTWnetw==" crossorigin="anonymous">
|
||||||
|
|
||||||
<!-- Favicons -->
|
<!-- Favicons -->
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Tests unitaires pour checkRateLimit() (includes/security.php)
|
||||||
|
*/
|
||||||
|
|
||||||
|
$rlDir = sys_get_temp_dir() . '/annu-kute-rl-test-' . getmypid();
|
||||||
|
|
||||||
|
// Nettoyage d'une éventuelle exécution précédente
|
||||||
|
foreach (glob($rlDir . '/rl_*.json') ?: [] as $oldFile) {
|
||||||
|
unlink($oldFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Limite de base -----------------------------------------------------------
|
||||||
|
|
||||||
|
$ip = '203.0.113.10';
|
||||||
|
assertTrue(checkRateLimit($ip, 3, 60, $rlDir), 'rate limit : la 1re requête est autorisée');
|
||||||
|
assertTrue(checkRateLimit($ip, 3, 60, $rlDir), 'rate limit : la 2e requête est autorisée');
|
||||||
|
assertTrue(checkRateLimit($ip, 3, 60, $rlDir), 'rate limit : la 3e requête est autorisée');
|
||||||
|
assertFalse(checkRateLimit($ip, 3, 60, $rlDir), 'rate limit : la 4e requête est refusée (limite 3)');
|
||||||
|
assertFalse(checkRateLimit($ip, 3, 60, $rlDir), 'rate limit : une 5e requête reste refusée');
|
||||||
|
|
||||||
|
// --- Indépendance des identifiants --------------------------------------------
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
checkRateLimit('203.0.113.99', 3, 60, $rlDir),
|
||||||
|
'rate limit : un autre identifiant a son propre compteur'
|
||||||
|
);
|
||||||
|
assertTrue(
|
||||||
|
checkRateLimit($ip, 3, 60, $rlDir . '/autre'),
|
||||||
|
'rate limit : un autre répertoire de stockage a son propre compteur'
|
||||||
|
);
|
||||||
|
|
||||||
|
// --- Réinitialisation de la fenêtre -------------------------------------------
|
||||||
|
|
||||||
|
// Forcer l'expiration de la fenêtre en réécrivant le fichier d'état
|
||||||
|
$stateFile = $rlDir . '/rl_' . hash('sha256', $ip) . '.json';
|
||||||
|
assertTrue(file_exists($stateFile), 'rate limit : le fichier d\'état existe');
|
||||||
|
file_put_contents($stateFile, json_encode(['count' => 3, 'reset' => time() - 1]));
|
||||||
|
assertTrue(
|
||||||
|
checkRateLimit($ip, 3, 60, $rlDir),
|
||||||
|
'rate limit : le compteur repart à zéro après expiration de la fenêtre'
|
||||||
|
);
|
||||||
|
|
||||||
|
// --- Fail-open si le stockage est indisponible ---------------------------------
|
||||||
|
|
||||||
|
// Un chemin qui est un fichier (pas un répertoire) : mkdir doit échouer
|
||||||
|
$notADir = $rlDir . '/fichier-bloquant';
|
||||||
|
file_put_contents($notADir, 'x');
|
||||||
|
assertTrue(
|
||||||
|
@checkRateLimit('198.51.100.5', 1, 60, $notADir),
|
||||||
|
'rate limit : fail-open si le répertoire de stockage est indisponible'
|
||||||
|
);
|
||||||
|
|
||||||
|
// --- Nettoyage -----------------------------------------------------------------
|
||||||
|
|
||||||
|
foreach ([$rlDir, $rlDir . '/autre'] as $dir) {
|
||||||
|
foreach (glob($dir . '/rl_*.json') ?: [] as $file) {
|
||||||
|
unlink($file);
|
||||||
|
}
|
||||||
|
if (is_dir($dir)) {
|
||||||
|
rmdir($dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unlink($notADir);
|
||||||
@@ -255,3 +255,85 @@ assertFalse(
|
|||||||
isValidRemoteUrl('https://metadata.google.internal'),
|
isValidRemoteUrl('https://metadata.google.internal'),
|
||||||
'isValidRemoteUrl refuse metadata.google.internal'
|
'isValidRemoteUrl refuse metadata.google.internal'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// --- cspOriginFromUrl ---------------------------------------------------------
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
'https://peertube.example.com',
|
||||||
|
cspOriginFromUrl('https://peertube.example.com'),
|
||||||
|
'cspOriginFromUrl extrait l\'origine d\'une URL simple'
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
'https://peertube.example.com',
|
||||||
|
cspOriginFromUrl('https://peertube.example.com/chemin/page?x=1'),
|
||||||
|
'cspOriginFromUrl ignore le chemin et la query string'
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
'http://127.0.0.1',
|
||||||
|
cspOriginFromUrl('http://127.0.0.1'),
|
||||||
|
'cspOriginFromUrl conserve le schéma http'
|
||||||
|
);
|
||||||
|
assertEquals('', cspOriginFromUrl(''), 'cspOriginFromUrl refuse une chaîne vide');
|
||||||
|
assertEquals('', cspOriginFromUrl('pas-une-url'), 'cspOriginFromUrl refuse une URL mal formée');
|
||||||
|
|
||||||
|
// --- buildContentSecurityPolicy ------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extrait la valeur d'une directive CSP pour les assertions
|
||||||
|
*/
|
||||||
|
function csp_directive($csp, $name) {
|
||||||
|
if (preg_match('/(?:^| )' . preg_quote($name, '/') . ' ([^;]+)/', $csp, $m)) {
|
||||||
|
return $m[1];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$testNonce = 'nonce-de-test';
|
||||||
|
$csp = buildContentSecurityPolicy($testNonce);
|
||||||
|
|
||||||
|
assertContains(
|
||||||
|
"'nonce-{$testNonce}",
|
||||||
|
csp_directive($csp, 'script-src'),
|
||||||
|
'CSP : script-src porte le nonce de la requête'
|
||||||
|
);
|
||||||
|
assertContains(
|
||||||
|
"'nonce-{$testNonce}",
|
||||||
|
csp_directive($csp, 'style-src'),
|
||||||
|
'CSP : style-src porte le nonce de la requête'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Domaines réellement utilisés présents
|
||||||
|
$imgSrc = csp_directive($csp, 'img-src');
|
||||||
|
$mediaSrc = csp_directive($csp, 'media-src');
|
||||||
|
assertContains(cspOriginFromUrl(PEERTUBE_URL), $imgSrc, 'CSP : img-src autorise le domaine PeerTube');
|
||||||
|
assertContains(cspOriginFromUrl(MASTODON_INSTANCE_URL), $imgSrc, 'CSP : img-src autorise le domaine Mastodon');
|
||||||
|
assertContains(cspOriginFromUrl(PEERTUBE_URL), $mediaSrc, 'CSP : media-src autorise le domaine PeerTube');
|
||||||
|
assertContains(cspOriginFromUrl(CASTOPOD_URL), $mediaSrc, 'CSP : media-src autorise le domaine Castopod');
|
||||||
|
assertContains(cspOriginFromUrl(CASTOPOD_URL), $imgSrc, 'CSP : img-src autorise les pochettes Castopod');
|
||||||
|
|
||||||
|
// Pas de joker https:/http: hors développement local (HTTP_HOST absent en CLI)
|
||||||
|
assertTrue(
|
||||||
|
preg_match('/(^|\s)https?:($|\s)/', $imgSrc) === 0,
|
||||||
|
'CSP : img-src ne contient pas de joker https: en production'
|
||||||
|
);
|
||||||
|
assertTrue(
|
||||||
|
preg_match('/(^|\s)https?:($|\s)/', $mediaSrc) === 0,
|
||||||
|
'CSP : media-src ne contient pas de joker https: en production'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Directives de verrouillage toujours présentes
|
||||||
|
assertContains("object-src 'none'", $csp, 'CSP : object-src none est présent');
|
||||||
|
assertContains("frame-ancestors 'self'", $csp, 'CSP : frame-ancestors self est présent');
|
||||||
|
|
||||||
|
// En développement local, le joker HTTP(S) est réintroduit pour le contenu fédéré
|
||||||
|
$_SERVER['HTTP_HOST'] = 'localhost:8080';
|
||||||
|
$cspDev = buildContentSecurityPolicy($testNonce);
|
||||||
|
assertTrue(
|
||||||
|
preg_match('/(^|\s)https?:($|\s)/', csp_directive($cspDev, 'img-src')) === 1,
|
||||||
|
'CSP : img-src contient le joker https: en développement local'
|
||||||
|
);
|
||||||
|
assertTrue(
|
||||||
|
preg_match('/(^|\s)https?:($|\s)/', csp_directive($cspDev, 'media-src')) === 1,
|
||||||
|
'CSP : media-src contient le joker https: en développement local'
|
||||||
|
);
|
||||||
|
unset($_SERVER['HTTP_HOST']);
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ if (empty($videoData) || isset($videoData['error'])) {
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
|
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
|
||||||
<link rel="stylesheet" href="css/video-page.css?v=<?php echo filemtime('css/video-page.css'); ?>">
|
<link rel="stylesheet" href="css/video-page.css?v=<?php echo filemtime('css/video-page.css'); ?>">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" integrity="sha512-2SwdPD6INVrV/lHTZbO2nodKhrnDdJK9/kg2XD1r9uGqPo1cUbujc+IYdlYdEErWNu69gVcYgdxlmVmzTWnetw==" crossorigin="anonymous">
|
||||||
|
|
||||||
<!-- Favicons -->
|
<!-- Favicons -->
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
||||||
|
|||||||
Reference in New Issue
Block a user