fix(description): render leaked markdown headers instead of showing raw
AI-generated descriptions ("Letra" / "Chorus" / "Translation") landed
in the DB with markdown prefixes that the sanitiser treated as plain
text — so viewers saw literal "## Letra" and "### Chorus" instead of
headings.
HtmlSanitizer::render() now promotes `##` / `###` at the start of a
line into <h2>/<h3> before sanitising, so the existing allowlist
accepts them and the reader sees real headings. Only touches lines
whose entire content is a heading — markdown mid-sentence is left
alone to avoid false positives.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
9eb9535c83
commit
36295fcd6a
1 file changed
+25
@@ -77,6 +77,11 @@ class HtmlSanitizer
|
|||||||
/**
|
/**
|
||||||
* Produce safe display HTML. Legacy plain-text values (no tags) are
|
* Produce safe display HTML. Legacy plain-text values (no tags) are
|
||||||
* escaped and converted to <br>; rich values are run through clean().
|
* escaped and converted to <br>; rich values are run through clean().
|
||||||
|
*
|
||||||
|
* Also detects a handful of common markdown-ish prefixes that leaked
|
||||||
|
* in through AI-generated descriptions ("## Letra", "### Chorus") and
|
||||||
|
* turns them into real <h2>/<h3> tags before sanitising. Without this
|
||||||
|
* step a reader saw literal "## Letra" instead of a heading.
|
||||||
*/
|
*/
|
||||||
public static function render(?string $value): string
|
public static function render(?string $value): string
|
||||||
{
|
{
|
||||||
@@ -85,6 +90,8 @@ class HtmlSanitizer
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$value = self::promoteLeakedMarkdown($value);
|
||||||
|
|
||||||
if (strip_tags($value) === $value) {
|
if (strip_tags($value) === $value) {
|
||||||
return nl2br(e($value), false);
|
return nl2br(e($value), false);
|
||||||
}
|
}
|
||||||
@@ -92,6 +99,24 @@ class HtmlSanitizer
|
|||||||
return self::clean($value);
|
return self::clean($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert markdown-style heading prefixes at the start of a line into
|
||||||
|
* real HTML tags, so the sanitiser can keep them. Only touches lines
|
||||||
|
* that start with ##, ###, or **…**; everything else is untouched.
|
||||||
|
*/
|
||||||
|
private static function promoteLeakedMarkdown(string $value): string
|
||||||
|
{
|
||||||
|
$lines = preg_split("/\r?\n/", $value);
|
||||||
|
foreach ($lines as $i => $line) {
|
||||||
|
if (preg_match('/^\s*###\s+(.+?)\s*$/u', $line, $m)) {
|
||||||
|
$lines[$i] = '<h3>' . e($m[1]) . '</h3>';
|
||||||
|
} elseif (preg_match('/^\s*##\s+(.+?)\s*$/u', $line, $m)) {
|
||||||
|
$lines[$i] = '<h2>' . e($m[1]) . '</h2>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return implode("\n", $lines);
|
||||||
|
}
|
||||||
|
|
||||||
private static function sanitizeChildren(DOMNode $node): void
|
private static function sanitizeChildren(DOMNode $node): void
|
||||||
{
|
{
|
||||||
foreach (iterator_to_array($node->childNodes) as $child) {
|
foreach (iterator_to_array($node->childNodes) as $child) {
|
||||||
|
|||||||
Reference in new issue
Block a user