fix: load markdown in direct.php, allow video-channels, guard modals
This commit is contained in:
+47
-37
@@ -9,6 +9,9 @@ require_once __DIR__ . '/simple-cache.php';
|
||||
// Charger les fonctions WordPress
|
||||
require_once __DIR__ . '/wordpress.php';
|
||||
|
||||
// Charger le partial de carte vidéo (échappement centralisé des données API)
|
||||
require_once __DIR__ . '/partials/video-card.php';
|
||||
|
||||
// Charger d'abord la configuration locale si elle existe
|
||||
$config_local_file = __DIR__ . '/config.local.php';
|
||||
if (file_exists($config_local_file)) {
|
||||
@@ -172,37 +175,13 @@ function callPeerTubeApi($endpoint, $params = []) {
|
||||
|
||||
/**
|
||||
* Valide l'URL PeerTube pour prévenir les attaques SSRF
|
||||
* Alias historique de isValidRemoteUrl() (includes/security.php).
|
||||
*
|
||||
* @param string $url URL à valider
|
||||
* @return bool True si l'URL est valide et sûre
|
||||
*/
|
||||
function isValidPeerTubeUrl($url) {
|
||||
// Vérifier que l'URL est bien formée
|
||||
$parsed = parse_url($url);
|
||||
if (!$parsed || !isset($parsed['scheme']) || !isset($parsed['host'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Autoriser uniquement HTTPS (ou HTTP en développement)
|
||||
if (!in_array($parsed['scheme'], ['https', 'http'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bloquer les adresses IP privées et locales
|
||||
$host = $parsed['host'];
|
||||
if (filter_var($host, FILTER_VALIDATE_IP)) {
|
||||
if (!filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Bloquer localhost et autres domaines dangereux
|
||||
$blockedHosts = ['localhost', '127.0.0.1', '::1', '0.0.0.0', 'metadata.google.internal'];
|
||||
if (in_array(strtolower($host), $blockedHosts)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return isValidRemoteUrl($url);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -230,7 +209,8 @@ function isValidApiEndpoint($endpoint) {
|
||||
'videos/.*', // Pour les endpoints dynamiques comme videos/{id}
|
||||
'videos/.*/comment-threads', // Pour les commentaires
|
||||
'accounts',
|
||||
'accounts/.*/videos' // Pour les vidéos d'un compte spécifique
|
||||
'accounts/.*/videos', // Pour les vidéos d'un compte spécifique
|
||||
'video-channels/.*/videos' // Pour les vidéos d'une chaîne spécifique
|
||||
];
|
||||
|
||||
foreach ($allowedEndpoints as $pattern) {
|
||||
@@ -407,30 +387,35 @@ function formatVideosData($videosData) {
|
||||
$videos = [];
|
||||
|
||||
foreach ($videosData as $video) {
|
||||
// Ignorer les entrées sans uuid : l'identifiant est indispensable
|
||||
if (empty($video['uuid'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Récupérer la vignette (thumbnail)
|
||||
$thumbnail = isset($video['previewPath'])
|
||||
? PEERTUBE_URL . $video['previewPath']
|
||||
: 'img/default-thumbnail.jpg';
|
||||
|
||||
// Récupérer l'avatar de la chaîne
|
||||
$channelAvatar = isset($video['channel']['avatars'][0]['path']) && isset($video['channel']['avatars'][0]['path'])
|
||||
$channelAvatar = isset($video['channel']['avatars'][0]['path'])
|
||||
? PEERTUBE_URL . $video['channel']['avatars'][0]['path']
|
||||
: 'img/default-avatar.png';
|
||||
|
||||
// Formater les données
|
||||
// Formater les données (valeurs par défaut pour les champs absents)
|
||||
$videos[] = [
|
||||
'id' => $video['uuid'],
|
||||
'title' => $video['name'],
|
||||
'title' => $video['name'] ?? '',
|
||||
'thumbnail' => $thumbnail,
|
||||
'duration' => $video['duration'],
|
||||
'channel' => $video['channel']['displayName'],
|
||||
'duration' => $video['duration'] ?? 0,
|
||||
'channel' => $video['channel']['displayName'] ?? '',
|
||||
'channelAvatar' => $channelAvatar,
|
||||
'views' => $video['views'],
|
||||
'date' => $video['publishedAt'],
|
||||
'aspectRatio' => $video['aspectRatio'],
|
||||
'views' => $video['views'] ?? 0,
|
||||
'date' => $video['publishedAt'] ?? '',
|
||||
'aspectRatio' => $video['aspectRatio'] ?? null,
|
||||
'description' => $video['description'] ?? '',
|
||||
'tags' => $video['tags'] ?? [],
|
||||
'isLive' => isset($video['isLive']) ? $video['isLive'] : false
|
||||
'isLive' => $video['isLive'] ?? false
|
||||
];
|
||||
}
|
||||
|
||||
@@ -461,7 +446,18 @@ function formatViewCount($views) {
|
||||
}
|
||||
|
||||
function formatDate($dateString) {
|
||||
$date = new DateTime($dateString);
|
||||
// Chaîne vide : rien à formater (new DateTime('') renverrait « maintenant »)
|
||||
if (!is_string($dateString) || trim($dateString) === '') {
|
||||
return (string) $dateString;
|
||||
}
|
||||
|
||||
try {
|
||||
$date = new DateTime($dateString);
|
||||
} catch (Exception $e) {
|
||||
// Date malformée (données API inattendues) : afficher la chaîne brute
|
||||
return $dateString;
|
||||
}
|
||||
|
||||
$now = new DateTime();
|
||||
$interval = $now->diff($date);
|
||||
|
||||
@@ -667,6 +663,12 @@ function getCastopodEpisodes($castopodUrl = null, $podcastSlugs = null, $count =
|
||||
$podcastSlugs = $podcastSlugs ?? (defined('CASTOPOD_PODCAST_SLUGS') ? CASTOPOD_PODCAST_SLUGS : ['annu_kute_cedric']);
|
||||
$count = $count ?? CASTOPOD_EPISODES_COUNT;
|
||||
|
||||
// Validation de l'URL Castopod pour prévenir SSRF
|
||||
if (!isValidRemoteUrl($castopodUrl)) {
|
||||
error_log('SECURITY: Invalid Castopod URL detected: ' . $castopodUrl);
|
||||
return [];
|
||||
}
|
||||
|
||||
// Convertir en tableau si c'est une chaîne unique (rétrocompatibilité)
|
||||
if (is_string($podcastSlugs)) {
|
||||
$podcastSlugs = [$podcastSlugs];
|
||||
@@ -702,6 +704,7 @@ function getCastopodEpisodes($castopodUrl = null, $podcastSlugs = null, $count =
|
||||
curl_setopt($ch, CURLOPT_URL, $feedUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
|
||||
$xmlContent = curl_exec($ch);
|
||||
@@ -890,6 +893,12 @@ function getFunkwhaleTracks($funkwhaleUrl = null, $count = null) {
|
||||
$funkwhaleUrl = $funkwhaleUrl ?? FUNKWHALE_URL;
|
||||
$count = $count ?? FUNKWHALE_TRACKS_COUNT;
|
||||
|
||||
// Validation de l'URL Funkwhale pour prévenir SSRF
|
||||
if (!isValidRemoteUrl($funkwhaleUrl)) {
|
||||
error_log('SECURITY: Invalid Funkwhale URL detected: ' . $funkwhaleUrl);
|
||||
return [];
|
||||
}
|
||||
|
||||
// Clé de cache - on récupère un grand nombre de morceaux pour le cache
|
||||
$cacheKey = 'funkwhale_' . md5($funkwhaleUrl);
|
||||
$cacheFetchSize = 50; // Nombre de morceaux à mettre en cache
|
||||
@@ -912,6 +921,7 @@ function getFunkwhaleTracks($funkwhaleUrl = null, $count = null) {
|
||||
curl_setopt($ch, CURLOPT_URL, $apiUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
|
||||
$jsonContent = curl_exec($ch);
|
||||
|
||||
Reference in New Issue
Block a user