Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* Add a way to mark a `Twig\Markup` instance as safe for some escaping strategies only
* Make `Environment::getExtension()` return the runtime of an extension class registered with `AttributeExtension`
* Make `Environment::getRuntime()` fall back to the registered extension of the same class
* Speed up the `html_attr_relaxed` escaping strategy for strings that need no escaping

# 3.30.0 (2026-09-25)

Expand Down
9 changes: 8 additions & 1 deletion src/Runtime/EscaperRuntime.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ final class EscaperRuntime implements RuntimeExtensionInterface
'html_attr_relaxed' => ['html'],
];

private const HTML_ATTR_RELAXED_SAFE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,.-_:@[]';

/** @var array<string, callable(string, string): string> */
private $escapers = [];

Expand Down Expand Up @@ -271,8 +273,13 @@ public function escape($string, string $strategy = 'html', ?string $charset = nu

return $string;

case 'html_attr':
case 'html_attr_relaxed':
// Attribute names, which this strategy is meant for, rarely need escaping: skip the regular expressions then
if ('UTF-8' === $charset && \strlen($string) === strspn($string, self::HTML_ATTR_RELAXED_SAFE_CHARS)) {
return $string;
}
// no break
case 'html_attr':
if ('UTF-8' !== $charset) {
$string = $this->convertEncoding($string, 'UTF-8', $charset);
}
Expand Down
6 changes: 6 additions & 0 deletions tests/Runtime/EscaperRuntimeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,12 @@ public function testHtmlAttributeRelaxedEscapingConvertsSpecialChars(): void
}
}

public function testHtmlAttributeRelaxedEscapingStillEscapesSafeLookingBytesInOtherCharsets(): void
{
// In UTF-16BE, the bytes of "ab" are the single character U+6162, which must be escaped
$this->assertSame(mb_convert_encoding('&#x6162;', 'UTF-16BE', 'UTF-8'), (new EscaperRuntime('UTF-16BE'))->escape('ab', 'html_attr_relaxed'));
}

public function testJavascriptEscapingConvertsSpecialChars(): void
{
foreach ($this->jsSpecialChars as $key => $value) {
Expand Down
Loading