fix(security): safe JSON-LD encoding and URL escaping
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user