fix(security): safe JSON-LD encoding and URL escaping

This commit is contained in:
2026-07-27 01:54:49 +04:00
parent a28ba8bfec
commit 4778f0e9e3
3 changed files with 204 additions and 12 deletions
+76 -8
View File
@@ -5,6 +5,18 @@
* pour améliorer le SEO et l'affichage dans les moteurs de recherche
*/
/**
* Drapeaux json_encode pour la sortie JSON-LD.
*
* Les JSON_HEX_* encodent <, >, &, ' et " en séquences \u00XX : une valeur
* contenant "</script>" (titre de vidéo, nom de chaîne, titre d'épisode…)
* ne peut plus fermer le bloc <script> ni injecter de HTML dans la page.
* Le JSON reste valide et se décode à l'identique.
*/
if (!defined('JSONLD_ENCODE_FLAGS')) {
define('JSONLD_ENCODE_FLAGS', JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
}
/**
* Génère le JSON-LD pour un objet WebSite
*
@@ -38,7 +50,7 @@ function generateWebSiteJsonLd() {
]
];
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
return json_encode($data, JSONLD_ENCODE_FLAGS);
}
/**
@@ -158,7 +170,7 @@ function generateVideoObjectJsonLd($videoData, $video) {
$data["videoFrameSize"] = "Portrait";
}
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
return json_encode($data, JSONLD_ENCODE_FLAGS);
}
/**
@@ -254,7 +266,7 @@ function generatePodcastJsonLd($episodes) {
"@graph" => array_merge([$series], $episodeItems)
];
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
return json_encode($data, JSONLD_ENCODE_FLAGS);
}
/**
@@ -327,7 +339,7 @@ function generateBreadcrumbJsonLd($breadcrumbs) {
"itemListElement" => $listItems
];
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
return json_encode($data, JSONLD_ENCODE_FLAGS);
}
/**
@@ -372,7 +384,7 @@ function generateVideoCollectionJsonLd($name, $description, $videos, $url) {
]
];
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
return json_encode($data, JSONLD_ENCODE_FLAGS);
}
/**
@@ -440,14 +452,70 @@ function truncateText($text, $length = 200) {
}
/**
* Obtient l'URL de base du site
* Valide un nom d'hôte applicatif (hostname, IPv4 ou localhost, port optionnel)
*
* @param string $host Hôte à valider
* @return bool True si l'hôte est exploitable en toute sécurité dans une URL
*/
function isValidAppHostName($host) {
if (!is_string($host) || $host === '' || strlen($host) > 253) {
return false;
}
// Labels alphanumériques/tirets séparés par des points, port optionnel
return (bool) preg_match('/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:\d{1,5})?$/i', $host);
}
/**
* Retourne le nom d'hôte de l'application, validé une fois à l'initialisation.
*
* Utilise la constante APP_HOST_NAME (configuration) et jamais l'en-tête
* HTTP_HOST fourni par le client, afin d'empêcher l'injection via Host.
*
* @return string Nom d'hôte validé ('localhost' en repli)
*/
function getAppHostName() {
static $validatedHost = null;
if ($validatedHost === null) {
$configuredHost = defined('APP_HOST_NAME') ? (string) APP_HOST_NAME : '';
if (isValidAppHostName($configuredHost)) {
$validatedHost = $configuredHost;
} else {
error_log('SECURITY: APP_HOST_NAME absent ou invalide, repli sur localhost');
$validatedHost = 'localhost';
}
}
return $validatedHost;
}
/**
* Obtient l'URL de base du site (schéma + hôte validé)
*
* @return string URL de base
*/
function getBaseUrl() {
$scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
return $scheme . '://' . $host;
return $scheme . '://' . getAppHostName();
}
/**
* Construit l'URL absolue de la page courante.
*
* L'hôte provient de getBaseUrl() (APP_HOST_NAME validé, pas l'en-tête Host)
* et REQUEST_URI est débarrassé des guillemets, chevrons, espaces et
* caractères de contrôle qui casseraient un attribut HTML ou une URL.
*
* @return string URL absolue de la requête courante
*/
function getCurrentUrl() {
$requestUri = $_SERVER['REQUEST_URI'] ?? '/';
$requestUri = preg_replace('/[\x00-\x20"<>\'`]/', '', $requestUri);
if ($requestUri === '' || $requestUri[0] !== '/') {
$requestUri = '/' . $requestUri;
}
return getBaseUrl() . $requestUri;
}
/**