Skip to content

Commit 1d959eb

Browse files
committed
ExpressionAttributeNode: optionally rendered depending on the content
1 parent 05fd677 commit 1d959eb

File tree

10 files changed

+118
-53
lines changed

10 files changed

+118
-53
lines changed

src/Latte/Compiler/Escaper.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ final class Escaper
3535
HtmlBogusTag = 'html/bogus',
3636
HtmlRawText = 'html/raw',
3737
HtmlTag = 'html/tag',
38-
HtmlAttribute = 'html/attr';
38+
HtmlAttribute = 'html/attr',
39+
HtmlAttributeExpression = 'html/attr-e';
3940

4041
private const Convertors = [
4142
self::Text => [
@@ -154,7 +155,7 @@ public function enterHtmlRaw(string $subType): void
154155

155156
public function enterHtmlAttribute(?string $name, bool $expression = false): void
156157
{
157-
$this->state = self::HtmlAttribute;
158+
$this->state = $expression ? self::HtmlAttributeExpression : self::HtmlAttribute;
158159
$this->subType = '';
159160

160161
if ($this->contentType === ContentType::Html && is_string($name)) {
@@ -166,8 +167,9 @@ public function enterHtmlAttribute(?string $name, bool $expression = false): voi
166167
} elseif ($expression && ((in_array($name, ['href', 'src', 'action', 'formaction'], true)
167168
|| ($name === 'data' && strcasecmp($this->tag, 'object') === 0)))
168169
) {
169-
$this->subType = self::Url;
170+
$this->subType = self::Url; // TODO: jen simple, jako AttributeUrl
170171
}
172+
// TODO: AttributeBool, AttributeList, AttributeData, AttributeAria
171173
}
172174
}
173175

@@ -191,11 +193,16 @@ public function escape(string $str): string
191193
self::HtmlText => 'LR\HtmlHelpers::escapeText(' . $str . ')',
192194
self::HtmlTag => 'LR\HtmlHelpers::escapeTag(' . $str . ')',
193195
self::HtmlAttribute => match ($this->subType) {
194-
'',
195-
self::Url => 'LR\HtmlHelpers::escapeAttr(' . $str . ')',
196+
'' => 'LR\HtmlHelpers::escapeAttr(' . $str . ')',
196197
self::JavaScript => 'LR\HtmlHelpers::escapeAttr(LR\Helpers::escapeJs(' . $str . '))',
197198
self::Css => 'LR\HtmlHelpers::escapeAttr(LR\Helpers::escapeCss(' . $str . '))',
198199
},
200+
self::HtmlAttributeExpression => match ($this->subType) {
201+
'',
202+
self::Url => 'LR\HtmlHelpers::formatAttributeValue(' . $str . ')',
203+
self::JavaScript => 'LR\HtmlHelpers::formatAttributeValue(LR\Helpers::escapeJs(' . $str . '))',
204+
self::Css => 'LR\HtmlHelpers::formatAttributeValue(LR\Helpers::escapeCss(' . $str . '))',
205+
},
199206
self::HtmlComment => 'LR\HtmlHelpers::escapeComment(' . $str . ')',
200207
self::HtmlBogusTag => 'LR\HtmlHelpers::escapeTag(' . $str . ')',
201208
self::HtmlRawText => match ($this->subType) {
@@ -210,6 +217,7 @@ public function escape(string $str): string
210217
self::HtmlText => 'LR\XmlHelpers::escapeText(' . $str . ')',
211218
self::HtmlBogusTag => 'LR\XmlHelpers::escapeTag(' . $str . ')',
212219
self::HtmlAttribute => 'LR\XmlHelpers::escapeAttr(' . $str . ')',
220+
self::HtmlAttributeExpression => 'LR\XmlHelpers::formatAttributeValue(' . $str . ')',
213221
self::HtmlComment => 'LR\HtmlHelpers::escapeComment(' . $str . ')',
214222
self::HtmlTag => 'LR\XmlHelpers::escapeTag(' . $str . ')',
215223
default => throw new \LogicException("Unknown context $this->contentType, $this->state."),
@@ -228,6 +236,7 @@ public function escapeMandatory(string $str, ?Position $position = null): string
228236
return match ($this->contentType) {
229237
ContentType::Html => match ($this->state) {
230238
self::HtmlAttribute => "LR\\HtmlHelpers::escapeQuotes($str)",
239+
self::HtmlAttributeExpression => "'\"' . LR\\HtmlHelpers::escapeQuotes($str) . '\"'",
231240
self::HtmlRawText => match ($this->subType) {
232241
self::HtmlText => 'LR\HtmlHelpers::convertHtmlToRawText(' . $str . ')',
233242
default => "LR\\HtmlHelpers::convertJSToRawText($str)",
@@ -237,6 +246,7 @@ public function escapeMandatory(string $str, ?Position $position = null): string
237246
},
238247
ContentType::Xml => match ($this->state) {
239248
self::HtmlAttribute => "LR\\HtmlHelpers::escapeQuotes($str)",
249+
self::HtmlAttributeExpression => "'\"' . LR\\HtmlHelpers::escapeQuotes($str) . '\"'",
240250
self::HtmlComment => throw new Latte\CompileException('Using |noescape is not allowed in this context.', $position),
241251
default => $str,
242252
},
@@ -256,7 +266,7 @@ public function escapeContent(string $str): string
256266

257267
public function check(string $str): string
258268
{
259-
if ($this->state === self::HtmlAttribute && $this->subType === self::Url) {
269+
if ($this->state === self::HtmlAttributeExpression && $this->subType === self::Url) {
260270
$str = 'LR\Filters::safeUrl(' . $str . ')';
261271
}
262272
return $str;

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@ public function print(PrintContext $context): string
3333
$escaper = $context->beginEscape();
3434
$escaper->enterHtmlAttribute($this->name, expression: true);
3535
$res = $context->format(
36-
'echo %3.dump; echo %modify(%node) %line; echo \'"\';',
36+
'if ($ʟ_av = %modify(%node)) %line { echo %dump; echo $ʟ_av === true ? %dump : %dump . $ʟ_av; } ',
3737
$this->modifier,
3838
$this->value,
3939
$this->value->position,
40-
$this->indentation . $this->name . '="',
40+
$this->indentation,
41+
$this->name,
42+
$this->name . '=',
4143
);
4244
$context->restoreEscape();
4345
return $res;

src/Latte/Runtime/HtmlHelpers.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,12 @@ public static function formatAttribute(string $name, mixed $value): ?string
190190
}
191191

192192

193+
public static function formatAttributeValue(mixed $value): string
194+
{
195+
return '"' . self::escapeAttr($value) . '"';
196+
}
197+
198+
193199
/**
194200
* Checks if the given tag name represents a void (empty) HTML element.
195201
*/

src/Latte/Runtime/XmlHelpers.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ public static function formatAttribute(string $name, mixed $value): ?string
9696
}
9797

9898

99+
public static function formatAttributeValue(mixed $value): string
100+
{
101+
return '"' . self::escapeAttr($value) . '"';
102+
}
103+
104+
99105
/**
100106
* Checks that the HTML tag name can be changed.
101107
*/

tests/common/Safe.url.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ Assert::match(
7474

7575

7676
Assert::contains(
77-
'LR\HtmlHelpers::escapeAttr(LR\Filters::safeUrl(($this->filters->upper)($url1)))',
77+
'LR\HtmlHelpers::formatAttributeValue(LR\Filters::safeUrl(($this->filters->upper)($url1)))',
7878
$latte->compile('<a href="{$url1|upper}"></a>'),
7979
);
8080

tests/common/expected/Compiler.unquoted.attrs.php

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,23 @@ final class Template%a% extends Latte\Runtime\Template
66
public function main(array $ʟ_args): void
77
{
88
%A%
9-
echo '<span title="';
10-
echo LR\HtmlHelpers::escapeAttr($x) /* line %d%:%d% */;
11-
echo '" class="';
12-
echo LR\HtmlHelpers::escapeAttr($x) /* line %d%:%d% */;
13-
echo '"></span>
9+
echo '<span';
10+
if ($ʟ_av = LR\HtmlHelpers::formatAttributeValue($x)) /* line %d%:%d% */ {
11+
echo ' ';
12+
echo $ʟ_av === true ? 'title' : 'title=' . $ʟ_av;
13+
}
14+
if ($ʟ_av = LR\HtmlHelpers::formatAttributeValue($x)) /* line %d%:%d% */ {
15+
echo ' ';
16+
echo $ʟ_av === true ? 'class' : 'class=' . $ʟ_av;
17+
}
18+
echo '></span>
1419
15-
<span title="';
16-
echo LR\HtmlHelpers::escapeAttr($x) /* line %d%:%d% */;
17-
echo '" ';
20+
<span';
21+
if ($ʟ_av = LR\HtmlHelpers::formatAttributeValue($x)) /* line %d%:%d% */ {
22+
echo ' ';
23+
echo $ʟ_av === true ? 'title' : 'title=' . $ʟ_av;
24+
}
25+
echo ' ';
1826
echo LR\HtmlHelpers::escapeTag($x) /* line %d%:%d% */;
1927
echo '></span>
2028
@@ -36,9 +44,12 @@ public function main(array $ʟ_args): void
3644
echo LR\HtmlHelpers::escapeAttr($x) /* line %d%:%d% */;
3745
echo 'd"></span>
3846
39-
<span onclick="';
40-
echo LR\HtmlHelpers::escapeAttr(LR\Helpers::escapeJs($x)) /* line %d%:%d% */;
41-
echo '" ';
47+
<span';
48+
if ($ʟ_av = LR\HtmlHelpers::formatAttributeValue(LR\Helpers::escapeJs($x))) /* line %d%:%d% */ {
49+
echo ' ';
50+
echo $ʟ_av === true ? 'onclick' : 'onclick=' . $ʟ_av;
51+
}
52+
echo ' ';
4253
echo LR\HtmlHelpers::escapeTag($x) /* line %d%:%d% */;
4354
echo '></span>
4455

tests/common/expected/contentType.xml.php

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,13 @@ public function main(array $ʟ_args): void
7171
echo '"\'
7272
style="color:';
7373
echo LR\XmlHelpers::escapeAttr($id) /* line %d%:%d% */;
74-
echo ';\'"
75-
alt="';
76-
echo LR\XmlHelpers::escapeAttr($el) /* line %d%:%d% */;
77-
echo '"
74+
echo ';\'"';
75+
if ($ʟ_av = LR\XmlHelpers::formatAttributeValue($el)) /* line %d%:%d% */ {
76+
echo '
77+
';
78+
echo $ʟ_av === true ? 'alt' : 'alt=' . $ʟ_av;
79+
}
80+
echo '
7881
onfocus="alert(';
7982
echo LR\XmlHelpers::escapeAttr($el) /* line %d%:%d% */;
8083
echo ')"
@@ -119,13 +122,19 @@ public function main(array $ʟ_args): void
119122
}
120123
echo '"> </p>
121124
122-
<p val="';
123-
echo LR\XmlHelpers::escapeAttr($xss) /* line %d%:%d% */;
124-
echo '" > </p>
125+
<p';
126+
if ($ʟ_av = LR\XmlHelpers::formatAttributeValue($xss)) /* line %d%:%d% */ {
127+
echo ' ';
128+
echo $ʟ_av === true ? 'val' : 'val=' . $ʟ_av;
129+
}
130+
echo ' > </p>
125131
126-
<p onclick="';
127-
echo LR\XmlHelpers::escapeAttr($xss) /* line %d%:%d% */;
128-
echo '"> </p>
132+
<p';
133+
if ($ʟ_av = LR\XmlHelpers::formatAttributeValue($xss)) /* line %d%:%d% */ {
134+
echo ' ';
135+
echo $ʟ_av === true ? 'onclick' : 'onclick=' . $ʟ_av;
136+
}
137+
echo '> </p>
129138
';
130139
}
131140

tests/tags/expected/general.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,12 @@ public function main(array $ʟ_args): void
4343
echo '
4444
<li id="item-';
4545
echo LR\HtmlHelpers::escapeAttr($iterator->getCounter()) /* line %d%:%d% */;
46-
echo '" class="';
47-
echo LR\HtmlHelpers::escapeAttr($iterator->isOdd() ? 'odd' : 'even') /* line %d%:%d% */;
48-
echo '">';
46+
echo '"';
47+
if ($ʟ_av = LR\HtmlHelpers::formatAttributeValue($iterator->isOdd() ? 'odd' : 'even')) /* line %d%:%d% */ {
48+
echo ' ';
49+
echo $ʟ_av === true ? 'class' : 'class=' . $ʟ_av;
50+
}
51+
echo '>';
4952
echo LR\HtmlHelpers::escapeText($person) /* line %d%:%d% */;
5053
echo '</li>
5154
';

tests/tags/expected/ifchanged.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,12 @@
126126
echo ' ';
127127
ob_start(fn() => '');
128128
try /* line 22:55 */ {
129-
echo '<span class="';
130-
echo LR\HtmlHelpers::escapeAttr($i) /* line 22:50 */;
131-
echo '"></span>';
129+
echo '<span';
130+
if ($ʟ_av = LR\HtmlHelpers::formatAttributeValue($i)) /* line 22:50 */ {
131+
echo ' ';
132+
echo $ʟ_av === true ? 'class' : 'class=' . $ʟ_av;
133+
}
134+
echo '></span>';
132135
} finally {
133136
$ʟ_tmp = ob_get_clean();
134137
}

tests/tags/expected/print.xss.php

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,24 @@
44
echo LR\HtmlHelpers::escapeText($el2) /* line %d%:%d% */;
55
echo '
66

7-
<p val="';
8-
echo LR\HtmlHelpers::escapeAttr($xss) /* line %d%:%d% */;
9-
echo '" > </p>
10-
<p onclick="';
11-
echo LR\HtmlHelpers::escapeAttr(LR\Helpers::escapeJs($xss)) /* line %d%:%d% */;
12-
echo '"> </p>
13-
<p ONCLICK="';
14-
echo LR\HtmlHelpers::escapeAttr(LR\Helpers::escapeJs($xss)) /* line %d%:%d% */;
15-
echo '" ';
7+
<p';
8+
if ($ʟ_av = LR\HtmlHelpers::formatAttributeValue($xss)) /* line %d%:%d% */ {
9+
echo ' ';
10+
echo $ʟ_av === true ? 'val' : 'val=' . $ʟ_av;
11+
}
12+
echo ' > </p>
13+
<p';
14+
if ($ʟ_av = LR\HtmlHelpers::formatAttributeValue(LR\Helpers::escapeJs($xss))) /* line %d%:%d% */ {
15+
echo ' ';
16+
echo $ʟ_av === true ? 'onclick' : 'onclick=' . $ʟ_av;
17+
}
18+
echo '> </p>
19+
<p';
20+
if ($ʟ_av = LR\HtmlHelpers::formatAttributeValue(LR\Helpers::escapeJs($xss))) /* line %d%:%d% */ {
21+
echo ' ';
22+
echo $ʟ_av === true ? 'ONCLICK' : 'ONCLICK=' . $ʟ_av;
23+
}
24+
echo ' ';
1625
echo LR\HtmlHelpers::escapeTag($xss) /* line %d%:%d% */;
1726
echo '> </p>
1827

@@ -58,16 +67,22 @@
5867

5968
<p onclick=\'alert(';
6069
echo LR\HtmlHelpers::escapeAttr(LR\Helpers::escapeJs($xss)) /* line %d%:%d% */;
61-
echo ');alert("hello");\'
62-
title="';
63-
echo LR\HtmlHelpers::escapeAttr($xss) /* line %d%:%d% */;
64-
echo '"
70+
echo ');alert("hello");\'';
71+
if ($ʟ_av = LR\HtmlHelpers::formatAttributeValue($xss)) /* line %d%:%d% */ {
72+
echo '
73+
';
74+
echo $ʟ_av === true ? 'title' : 'title=' . $ʟ_av;
75+
}
76+
echo '
6577
STYLE="color:';
6678
echo LR\HtmlHelpers::escapeAttr(LR\Helpers::escapeCss($xss)) /* line %d%:%d% */;
67-
echo ';"
68-
rel="';
69-
echo LR\HtmlHelpers::escapeAttr($xss) /* line %d%:%d% */;
70-
echo '"
79+
echo ';"';
80+
if ($ʟ_av = LR\HtmlHelpers::formatAttributeValue($xss)) /* line %d%:%d% */ {
81+
echo '
82+
';
83+
echo $ʟ_av === true ? 'rel' : 'rel=' . $ʟ_av;
84+
}
85+
echo '
7186
onblur="alert(';
7287
echo LR\HtmlHelpers::escapeAttr(LR\Helpers::escapeJs($xss)) /* line %d%:%d% */;
7388
echo ')"

0 commit comments

Comments
 (0)