fix: markdown double-encoding and robust date/API access

This commit is contained in:
2026-07-27 01:55:21 +04:00
parent d9da213341
commit d81dd71a46
3 changed files with 156 additions and 36 deletions
+55 -25
View File
@@ -13,26 +13,30 @@
function markdown_to_html($markdown) {
// Échapper tout le contenu pour éviter les injections XSS
$markdown = htmlspecialchars($markdown, ENT_QUOTES, 'UTF-8');
// Tableau pour stocker les liens convertis
$links = [];
$link_count = 0;
// Note : le texte étant déjà échappé ci-dessus, les URLs extraites le sont
// aussi (« & » est devenu « & »). Il ne faut PAS les ré-échapper dans
// les callbacks ci-dessous, sinon on obtient un double encodage (« & »).
// Conversion des liens Markdown [texte](url)
$markdown = preg_replace_callback('/\[([^\]]+)\]\(([^)]+)\)/s', function($matches) use (&$links, &$link_count) {
$text = $matches[1];
$url = $matches[2];
// Assurer que l'URL est correctement formée
if (!preg_match('/^https?:\/\//i', $url)) {
// Ajouter http:// si l'URL ne commence pas par http:// ou https://
$url = 'http://' . $url;
}
$placeholder = "___LINK_{$link_count}___";
$links[$placeholder] = '<a href="' . htmlspecialchars($url, ENT_QUOTES, 'UTF-8') . '" target="_blank" rel="noopener noreferrer">' . $text . '</a>';
$links[$placeholder] = '<a href="' . $url . '" target="_blank" rel="noopener noreferrer">' . $text . '</a>';
$link_count++;
return $placeholder;
}, $markdown);
@@ -41,29 +45,29 @@ function markdown_to_html($markdown) {
$protocolUrlPattern = '/(https?:\/\/[^\s<]+[^\s<\.)])/i';
$markdown = preg_replace_callback($protocolUrlPattern, function($matches) use (&$links, &$link_count) {
$url = $matches[1];
$placeholder = "___LINK_{$link_count}___";
$links[$placeholder] = '<a href="' . htmlspecialchars($url, ENT_QUOTES, 'UTF-8') . '" target="_blank" rel="noopener noreferrer">' . $url . '</a>';
$links[$placeholder] = '<a href="' . $url . '" target="_blank" rel="noopener noreferrer">' . $url . '</a>';
$link_count++;
return $placeholder;
}, $markdown);
// 2. Domaines sans protocole (comme "o-k-i.net", "gong.gp", "NUVEL.NU")
// Détecte les domaines avec TLD communs qui ne font pas partie d'autre chose
$domainPattern = '/\b([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,63})\b/';
$markdown = preg_replace_callback($domainPattern, function($matches) use (&$links, &$link_count) {
$domain = $matches[0];
// Éviter de convertir des éléments qui ressemblent à des versions/numéros ou qui sont déjà dans des liens
if (preg_match('/^v?\d+\.\d+/', $domain) || strpos($domain, '___LINK_') !== false) {
return $domain;
}
$placeholder = "___LINK_{$link_count}___";
$links[$placeholder] = '<a href="http://' . htmlspecialchars($domain, ENT_QUOTES, 'UTF-8') . '" target="_blank" rel="noopener noreferrer">' . $domain . '</a>';
$links[$placeholder] = '<a href="http://' . $domain . '" target="_blank" rel="noopener noreferrer">' . $domain . '</a>';
$link_count++;
return $placeholder;
}, $markdown);
@@ -71,19 +75,45 @@ function markdown_to_html($markdown) {
$markdown = preg_replace('/\*\*(.*?)\*\*/s', '<strong>$1</strong>', $markdown);
$markdown = preg_replace('/\*(.*?)\*/s', '<em>$1</em>', $markdown);
// Conversion des listes à puces
$markdown = preg_replace('/^- (.*?)$/m', '<li>$1</li>', $markdown);
$markdown = preg_replace('/(<li>.*?<\/li>\n?)+/s', '<ul>$0</ul>', $markdown);
// Conversion des listes numérotées
$markdown = preg_replace('/^\d+\. (.*?)$/m', '<li>$1</li>', $markdown);
$markdown = preg_replace('/(<li>.*?<\/li>\n?)+/s', '<ol>$0</ol>', $markdown);
// Conversion des listes (à puces et numérotées) en une seule passe ligne à
// ligne : les items consécutifs de même type forment une seule liste ; une
// ligne hors liste ou un changement de type ferme la liste courante.
$lines = explode("\n", $markdown);
$markdown = '';
$listType = null; // 'ul', 'ol' ou null (hors liste)
foreach ($lines as $line) {
$itemType = null;
$itemText = null;
if (preg_match('/^- (.*)$/', $line, $matches)) {
$itemType = 'ul';
$itemText = $matches[1];
} elseif (preg_match('/^\d+\. (.*)$/', $line, $matches)) {
$itemType = 'ol';
$itemText = $matches[1];
}
if ($itemType !== $listType) {
if ($listType !== null) {
$markdown .= '</' . $listType . ">\n";
}
if ($itemType !== null) {
$markdown .= '<' . $itemType . ">\n";
}
$listType = $itemType;
}
$markdown .= ($itemType !== null ? '<li>' . $itemText . '</li>' : $line) . "\n";
}
if ($listType !== null) {
$markdown .= '</' . $listType . ">\n";
}
$markdown = rtrim($markdown, "\n");
// Gestion des retours à la ligne
$markdown = nl2br($markdown);
// Nettoyage des balises br dans les listes
$markdown = preg_replace('/<\/li><br \/>/', '</li>', $markdown);
// Nettoyage des balises br autour des listes
$markdown = preg_replace('/(<\/li>|<ul>|<ol>)<br \/>/', '$1', $markdown);
// Restaurer les liens
foreach ($links as $placeholder => $link) {