Skip to content

Commit f00d2bf

Browse files
committed
escaping: automatically escapes URL parts
1 parent 1eee22c commit f00d2bf

File tree

4 files changed

+19
-7
lines changed

4 files changed

+19
-7
lines changed

src/Latte/Compiler/Escaper.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ final class Escaper
2727
JavaScript = 'js',
2828
Css = 'css',
2929
ICal = 'ical',
30-
Url = 'url';
30+
Url = 'url',
31+
UrlPart = 'url-part';
3132

3233
public const
3334
HtmlText = 'html',
@@ -101,13 +102,13 @@ public function enterHtmlTag(string $name): void
101102
}
102103

103104

104-
public function enterHtmlAttribute(?string $name = null, string $quote = ''): void
105+
public function enterHtmlAttribute(?string $name = null, string $quote = '', string $subType = ''): void
105106
{
106107
$this->state = self::HtmlAttribute;
107108
$this->quote = $quote;
108-
$this->subType = '';
109+
$this->subType = $subType;
109110

110-
if ($this->contentType === ContentType::Html && is_string($name)) {
111+
if ($this->contentType === ContentType::Html && !$this->subType && $name) {
111112
$name = strtolower($name);
112113
if (str_starts_with($name, 'on')) {
113114
$this->subType = self::JavaScript;
@@ -150,6 +151,7 @@ public function escape(string $str): string
150151
self::HtmlAttribute => match ($this->subType) {
151152
'',
152153
self::Url => $lq . 'LR\Filters::escapeHtmlAttr(' . $str . ')' . $rq,
154+
self::UrlPart => $lq . 'LR\Filters::escapeHtmlAttr(rawurlencode(' . $str . '))' . $rq,
153155
self::JavaScript => $lq . 'LR\Filters::escapeHtmlAttr(LR\Filters::escapeJs(' . $str . '))' . $rq,
154156
self::Css => $lq . 'LR\Filters::escapeHtmlAttr(LR\Filters::escapeCss(' . $str . '))' . $rq,
155157
},

src/Latte/Compiler/Nodes/Html/QuotedValue.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public function print(PrintContext $context): string
3434
if ($this->value instanceof FragmentNode && $escaper->export() === 'html/attr/url') {
3535
foreach ($this->value->children as $child) {
3636
$res .= $child->print($context);
37-
$escaper->enterHtmlAttribute(null, $this->quote);
37+
$escaper->enterHtmlAttribute(null, $this->quote, $escaper::UrlPart);
3838
}
3939
} else {
4040
$res .= $this->value->print($context);

src/Latte/Compiler/Nodes/Php/ModifierNode.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,17 @@ public function printSimple(PrintContext $context, string $expr): string
4949
{
5050
$escape = $this->escape;
5151
$check = $this->check;
52+
$escaper = $context->getEscaper();
53+
$export = $escaper->export();
54+
5255
foreach ($this->filters as $filter) {
5356
$name = $filter->name->name;
5457
if (['nocheck' => 1, 'noCheck' => 1][$name] ?? null) {
5558
$check = false;
5659
} elseif ($name === 'noescape') {
5760
$escape = false;
61+
} elseif ($name === 'escapeUrl' && $export === 'html/attr/url-part') {
62+
// prevent double escaping
5863
} else {
5964
if (['datastream' => 1, 'dataStream' => 1][$name] ?? null) {
6065
$check = false;
@@ -63,7 +68,6 @@ public function printSimple(PrintContext $context, string $expr): string
6368
}
6469
}
6570

66-
$escaper = $context->getEscaper();
6771
if ($check) {
6872
$expr = $escaper->check($expr);
6973
}

tests/common/Safe.url.phpt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Assert::match('
2727
<a href="" src="" action="" formaction="" title="javascript:alert(1)"></a>
2828
<a href=""></a>
2929
<a href="javascript:alert(1)"></a>
30-
<a href="http://nette.org?val=javascript:alert(1)"></a>
30+
<a href="http://nette.org?val=javascript%3Aalert%281%29"></a>
3131
<a data="javascript:alert(1)"></a>
3232
<OBJECT DATA=""></OBJECT>
3333
<a HREF=""></a>
@@ -77,3 +77,9 @@ Assert::contains(
7777
'LR\Filters::escapeHtmlAttr(LR\Filters::safeUrl(($this->filters->upper)($url1)))',
7878
$latte->compile('<a href="{$url1|upper}"></a>'),
7979
);
80+
81+
82+
Assert::contains(
83+
'echo LR\Filters::escapeHtmlAttr(rawurlencode($url1)) /* line 1 */',
84+
$latte->compile('<a href="http://nette.org?val={$url1|escapeUrl}"></a>'),
85+
);

0 commit comments

Comments
 (0)