Skip to content

Commit 2f49185

Browse files
committed
properly handle escaping of children
1 parent ec3b0c0 commit 2f49185

5 files changed

Lines changed: 112 additions & 7 deletions

File tree

.idea/php.xml

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Template/Parser/StreamingCompiler.php

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ private function renderCharacterReference($document): Document
112112

113113
private function escapeData(int $selectionStart, Document $document): Closure
114114
{
115+
if($this->blockAttributes) {
116+
return static fn(Closure $x) => $x($document);
117+
}
115118
$end = $document->mark() - 1;
116119
$originalData = substr($document->code, $selectionStart, $end - $selectionStart);
117120
$data = $this->blobber->replaceBlobs($originalData, $this->escaper->escapeHtml(...));
@@ -429,12 +432,18 @@ private function renderOpenTagName(Document $document): Document
429432
case 'title':
430433
case 'textarea':
431434
$this->mustMatch = $tag;
435+
if($this->blockAttributes) {
436+
return $this->renderRCData($document);
437+
}
432438
$now = $document->mark();
433439
return $this->renderRCData($document)
434440
->snip($now, $this->lastTagCloseOpen, $output)
435441
->insert($this->blobber->replaceBlobs($output, $this->escaper->escapeHtml(...)), $now);
436442
case 'style':
437443
$this->mustMatch = $tag;
444+
if($this->blockAttributes) {
445+
return $this->renderRawText($document);
446+
}
438447
$now = $document->mark();
439448
return $this
440449
->renderRawText($document)
@@ -447,12 +456,18 @@ private function renderOpenTagName(Document $document): Document
447456
case 'plaintext':
448457
case 'noframes':
449458
$this->mustMatch = $tag;
459+
if($this->blockAttributes) {
460+
return $this->renderRawText($document);
461+
}
450462
$now = $document->mark();
451463
return $this->renderRawText($document)
452464
->snip($now, $this->lastTagCloseOpen, $output)
453465
->insert($this->blobber->replaceBlobs($output, $this->escaper->escapeHtml(...)), $now);
454466
case 'script':
455467
$this->mustMatch = $tag;
468+
if($this->blockAttributes) {
469+
return $this->renderScriptData($document);
470+
}
456471
$now = $document->mark();
457472
return $this
458473
->renderScriptData($document)
@@ -1180,13 +1195,15 @@ private function renderAfterAttributeValueQuoted(Document $document): Document
11801195

11811196
private function processAttributes(Document $document): Document
11821197
{
1183-
$value = $this->blobber->replaceBlobs(
1184-
$this->attributeValue,
1185-
$this->escaper->escapeHtmlAttr(...)
1186-
);
1198+
if($this->blockAttributes) {
1199+
return $document;
1200+
}
1201+
1202+
$originalValue = $this->blobber->replaceBlobs($this->attributeValue, fn($_) => $_);
1203+
$value = $this->escaper->escapeHtmlAttr($originalValue);
11871204
// escaper doesn't escape single quotes, so we do that here.
11881205
$value = str_replace("'", ''', $value);
1189-
$this->setAttribute($this->attributeName, $value);
1206+
$this->setAttribute($this->attributeName, $originalValue);
11901207
if ($value !== $this->attributeValue) {
11911208
// we need to update the rendered html too...
11921209
$here = $document->mark();

tests/StreamingTest.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,78 @@ public function render(): string
213213
)->getHeader('Set-Cookie')[0]
214214
)->toStartWith('csrf_token=');
215215
});
216+
217+
test('data providers do not escape their children', function () {
218+
$container = containerWithComponents([
219+
'provider' => new class implements \Bottledcode\SwytchFramework\Template\Functional\DataProvider {
220+
221+
public function provideAttributes(): array
222+
{
223+
return ['test' => 'test'];
224+
}
225+
226+
public function provideValues(string $value): mixed
227+
{
228+
return $value;
229+
}
230+
231+
public function render(string $test = ''): string
232+
{
233+
if ($test) {
234+
return 'oh no';
235+
}
236+
return '<children></children>';
237+
}
238+
},
239+
'echo' => new class {
240+
public function render(string $test = ''): string
241+
{
242+
return $test;
243+
}
244+
},
245+
'empty' => new class {
246+
public function render()
247+
{
248+
return '';
249+
}
250+
}
251+
]);
252+
$streamer = $container->get(StreamingCompiler::class);
253+
$document = <<<HTML
254+
<provider><empty /></provider>
255+
<provider>
256+
<echo test="overridden" />
257+
</provider>
258+
<echo test="not overridden" />
259+
HTML;
260+
261+
$result = $streamer->compile($document);
262+
expect($result)->toMatchHtmlSnapshot();
263+
});
264+
265+
it('passes variables correctly', function () {
266+
$container = containerWithComponents([
267+
'echo' => new class {
268+
public function render(string $test = ''): string
269+
{
270+
return $test;
271+
}
272+
},
273+
'child' => new class {
274+
public function render(string $test = ''): string
275+
{
276+
return "<div>{{$test}}<children/></div>";
277+
}
278+
}
279+
]);
280+
281+
$streamer = $container->get(StreamingCompiler::class);
282+
$document = <<<HTML
283+
<echo test="{some text}" />
284+
<child test="{outer text}">
285+
<echo test="{inner text}" />
286+
</child>
287+
HTML;
288+
$result = $streamer->compile($document);
289+
expect($result)->toMatchHtmlSnapshot();
290+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<html><body><p>test
2+
3+
not overridden
4+
5+
</p></body></html>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<html><body>
2+
<p>some text
3+
</p>
4+
<div>outer text
5+
inner text
6+
</div>
7+
8+
</body></html>

0 commit comments

Comments
 (0)