diff --git a/src/card.php b/src/card.php
index 7e769859..02fa8add 100644
--- a/src/card.php
+++ b/src/card.php
@@ -2,6 +2,17 @@
declare(strict_types=1);
+/**
+ * Escape text for XML output.
+ *
+ * @param string $text The text to escape
+ * @return string The XML-escaped text
+ */
+function escapeXml(string $text): string
+{
+ return htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE | ENT_DISALLOWED | ENT_XML1, "UTF-8");
+}
+
/**
* Convert date from Y-M-D to more human-readable format
*
@@ -50,7 +61,7 @@ function formatDate(string $dateString, string|null $format, string $locale): st
}
}
// sanitize and return formatted date
- return htmlspecialchars($formatted);
+ return escapeXml($formatted);
}
/**
@@ -246,7 +257,7 @@ function splitLines(string $text, int $maxChars, int $line1Offset): string
$text = utf8WordWrap($text, $maxChars, "\n", true);
}
}
- $text = htmlspecialchars($text);
+ $text = escapeXml($text);
return preg_replace(
"/^(.*)\n(.*)/",
"$1$2",
@@ -619,7 +630,7 @@ function generateCard(array $stats, ?array $params = null): string
function generateErrorCard(string $message, ?array $params = null): string
{
$params = $params ?? $_REQUEST;
- $message = htmlspecialchars($message, ENT_QUOTES | ENT_SUBSTITUTE | ENT_XML1, "UTF-8");
+ $message = escapeXml($message);
// get requested theme, use $_REQUEST if no params array specified
$theme = getRequestedTheme($params);
diff --git a/tests/RenderTest.php b/tests/RenderTest.php
index 4e190f27..25bbe4dc 100644
--- a/tests/RenderTest.php
+++ b/tests/RenderTest.php
@@ -40,6 +40,17 @@ final class RenderTest extends TestCase
"excludedDays" => [],
];
+ /**
+ * Test escapeXml function
+ */
+ public function testEscapeXml(): void
+ {
+ $this->assertSame("<>&"'", escapeXml("<>&\"'"));
+ $this->assertSame("<", escapeXml("<"));
+ $this->assertSame("\u{00E9}", escapeXml("\u{00E9}"));
+ $this->assertSame("\u{FFFD}", escapeXml("\xFF"));
+ }
+
/**
* Test normal card render
*/
@@ -158,7 +169,7 @@ public function testExcludedDaysCannotInjectMarkup(): void
$this->testStats["excludedDays"] = [$day];
$render = generateOutput($this->testStats, $this->testParams)["body"];
$this->assertSafeSvg($render);
- $this->assertStringContainsString(htmlspecialchars($day), $render);
+ $this->assertStringContainsString(escapeXml($day), $render);
}
/**