diff --git a/docs/internals/escaping.md b/docs/internals/escaping.md index 25802a9f0..6b2f2bb32 100644 --- a/docs/internals/escaping.md +++ b/docs/internals/escaping.md @@ -12,9 +12,9 @@ layers that must stay in sync**. `(contentType, state, subType)`, emits a string that **calls** a runtime escaping function (e.g. `LR\HtmlHelpers::escapeText(...)`, `LR\Helpers::escapeCss(...)`; plain-text output goes through the `escape` filter instead). The subType is not - purely state-derived: `enterHtmlAttribute` infers `js` for `on*` and `css` for - `style` attributes, and `enterHtmlText` classifies a ` & {', + 'html' => new Html('bold'), +]; + +// text is escaped for HTML and then for the attribute +Assert::same( + '', + $latte->renderToString('', $params), +); + +Assert::same( + '', + $latte->renderToString("", $params), +); + +Assert::same( + '', + $latte->renderToString('', $params), +); + +// HTML is escaped only for the attribute +Assert::same( + '', + $latte->renderToString('', $params), +); + +// |noescape escapes only quotes +Assert::same( + '', + $latte->renderToString('', $params), +); + +// dynamic attribute +Assert::same( + '', + $latte->renderToString('', $params), +); + +Assert::same( + '', + $latte->renderToString('', $params), +); + +Assert::same( + '', + $latte->renderToString('', ['none' => null]), +); + +// n:attr +Assert::same( + '', + $latte->renderToString('', $params), +); + +Assert::same( + '', + $latte->renderToString('', $params), +); + +// blocks +Assert::same( + '', + $latte->renderToString('{define doc}{$x}{/define}', $params), +); + +$latte->setLoader(new Latte\Loaders\StringLoader([ + 'main' => ' ', + 'html.latte' => ' & ', + 'text.latte' => '{contentType text} & "', + 'js.latte' => '{contentType javascript}a < b', +])); + +Assert::same( + ' ', + $latte->renderToString('main'), +); + +// XML has no srcdoc context +$latte->setLoader(new Latte\Loaders\StringLoader); +Assert::same( + '', + $latte->renderToString('{contentType xml}', $params), +); diff --git a/tests/runtime/HtmlHelpers.escapeHtmlAttr.phpt b/tests/runtime/HtmlHelpers.escapeHtmlAttr.phpt new file mode 100644 index 000000000..ac2569bdd --- /dev/null +++ b/tests/runtime/HtmlHelpers.escapeHtmlAttr.phpt @@ -0,0 +1,17 @@ + & " \'')); +Assert::same('&#123; {<!-- -->{',HtmlHelpers::escapeHtmlAttr('{ {{')); +Assert::same('&amp;amp;', HtmlHelpers::escapeHtmlAttr('&')); +Assert::same('<b title="x">a &amp; b</b>', HtmlHelpers::escapeHtmlAttr(new Html('a & b'))); +Assert::same('{', HtmlHelpers::escapeHtmlAttr(new Html('{'))); diff --git a/tests/runtime/HtmlHelpers.formatSrcdocAttribute.phpt b/tests/runtime/HtmlHelpers.formatSrcdocAttribute.phpt new file mode 100644 index 000000000..ba1e44144 --- /dev/null +++ b/tests/runtime/HtmlHelpers.formatSrcdocAttribute.phpt @@ -0,0 +1,45 @@ +'; + } +} + + +Assert::same('srcdoc', HtmlHelpers::classifyAttributeType('srcdoc')); +Assert::same('srcdoc', HtmlHelpers::classifyAttributeType('SrcDoc')); + +// escaped by escapeHtmlAttr +Assert::same('srcdoc="&lt;b&gt;"', HtmlHelpers::formatSrcdocAttribute('srcdoc', '')); +Assert::same(' srcdoc="<b>"', HtmlHelpers::formatSrcdocAttribute(' srcdoc', new Html(''))); + +// migration warnings +foreach (['x', 'a & b', new StringObject] as $value) { + Assert::error( + fn() => HtmlHelpers::formatSrcdocAttribute('srcdoc', $value, migrationWarnings: true), + E_USER_WARNING, + "Behavior change for attribute 'srcdoc' with string value: previously it was rendered as HTML, now it is escaped as text (wrap trusted HTML in Latte\\Runtime\\Html).", + ); +} + +foreach (['a & b > " {', 1, new Html('x')] as $value) { // renders the same document as before + Assert::noError(fn() => HtmlHelpers::formatSrcdocAttribute('srcdoc', $value, migrationWarnings: true)); +} + +// null & invalid +Assert::same('', HtmlHelpers::formatSrcdocAttribute('srcdoc', null)); +Assert::error( + fn() => Assert::same('', HtmlHelpers::formatSrcdocAttribute('srcdoc', [])), + E_USER_WARNING, + "Invalid value for attribute 'srcdoc': array is not allowed.", +);