diff --git a/src/Latte/Compiler/Escaper.php b/src/Latte/Compiler/Escaper.php
index d3a077eb06..27f9ee0754 100644
--- a/src/Latte/Compiler/Escaper.php
+++ b/src/Latte/Compiler/Escaper.php
@@ -27,7 +27,8 @@ final class Escaper
JavaScript = 'js',
Css = 'css',
ICal = 'ical',
- Url = 'url';
+ Url = 'url',
+ UrlPart = 'url-part';
public const
HtmlText = 'html',
@@ -101,13 +102,13 @@ public function enterHtmlTag(string $name): void
}
- public function enterHtmlAttribute(?string $name = null, string $quote = ''): void
+ public function enterHtmlAttribute(?string $name = null, string $quote = '', string $subType = ''): void
{
$this->state = self::HtmlAttribute;
$this->quote = $quote;
- $this->subType = '';
+ $this->subType = $subType;
- if ($this->contentType === ContentType::Html && is_string($name)) {
+ if ($this->contentType === ContentType::Html && !$this->subType && $name) {
$name = strtolower($name);
if (str_starts_with($name, 'on')) {
$this->subType = self::JavaScript;
@@ -150,6 +151,7 @@ public function escape(string $str): string
self::HtmlAttribute => match ($this->subType) {
'',
self::Url => $lq . 'LR\Filters::escapeHtmlAttr(' . $str . ')' . $rq,
+ self::UrlPart => $lq . 'LR\Filters::escapeHtmlAttr(rawurlencode(' . $str . '))' . $rq,
self::JavaScript => $lq . 'LR\Filters::escapeHtmlAttr(LR\Filters::escapeJs(' . $str . '))' . $rq,
self::Css => $lq . 'LR\Filters::escapeHtmlAttr(LR\Filters::escapeCss(' . $str . '))' . $rq,
},
diff --git a/src/Latte/Compiler/Nodes/Html/QuotedValue.php b/src/Latte/Compiler/Nodes/Html/QuotedValue.php
index 3a858061c5..853b367601 100644
--- a/src/Latte/Compiler/Nodes/Html/QuotedValue.php
+++ b/src/Latte/Compiler/Nodes/Html/QuotedValue.php
@@ -34,7 +34,7 @@ public function print(PrintContext $context): string
if ($this->value instanceof FragmentNode && $escaper->export() === 'html/attr/url') {
foreach ($this->value->children as $child) {
$res .= $child->print($context);
- $escaper->enterHtmlAttribute(null, $this->quote);
+ $escaper->enterHtmlAttribute(null, $this->quote, $escaper::UrlPart);
}
} else {
$res .= $this->value->print($context);
diff --git a/src/Latte/Compiler/Nodes/Php/ModifierNode.php b/src/Latte/Compiler/Nodes/Php/ModifierNode.php
index 340b83b8fa..e6087fedff 100644
--- a/src/Latte/Compiler/Nodes/Php/ModifierNode.php
+++ b/src/Latte/Compiler/Nodes/Php/ModifierNode.php
@@ -49,12 +49,17 @@ public function printSimple(PrintContext $context, string $expr): string
{
$escape = $this->escape;
$check = $this->check;
+ $escaper = $context->getEscaper();
+ $export = $escaper->export();
+
foreach ($this->filters as $filter) {
$name = $filter->name->name;
if (['nocheck' => 1, 'noCheck' => 1][$name] ?? null) {
$check = false;
} elseif ($name === 'noescape') {
$escape = false;
+ } elseif ($name === 'escapeUrl' && $export === 'html/attr/url-part') {
+ // prevent double escaping
} else {
if (['datastream' => 1, 'dataStream' => 1][$name] ?? null) {
$check = false;
@@ -63,7 +68,6 @@ public function printSimple(PrintContext $context, string $expr): string
}
}
- $escaper = $context->getEscaper();
if ($check) {
$expr = $escaper->check($expr);
}
diff --git a/tests/common/Safe.url.phpt b/tests/common/Safe.url.phpt
index 3679cf9bdf..c947995d2a 100644
--- a/tests/common/Safe.url.phpt
+++ b/tests/common/Safe.url.phpt
@@ -27,7 +27,7 @@ Assert::match('
-
+
@@ -77,3 +77,9 @@ Assert::contains(
'LR\Filters::escapeHtmlAttr(LR\Filters::safeUrl(($this->filters->upper)($url1)))',
$latte->compile(''),
);
+
+
+Assert::contains(
+ 'echo LR\Filters::escapeHtmlAttr(rawurlencode($url1)) /* line 1 */',
+ $latte->compile(''),
+);