mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] runtime: add markup tag function
Allows markup to be called as a tag function. The interpolated strings
are then safely escaped for injection in HTML code.
Example usage:
```js
const maliciousInput = "<script>alert('💥💥')</script>";
const value = markup`<b>${maliciousInput}</b>`;
// no problem, maliciousInput is properly escaped
```
This commit is contained in:
committed by
Géry Debongnie
parent
ac9ccb81ca
commit
fd3c194525
@@ -193,6 +193,15 @@ The first `t-out` will act as a `t-esc` directive, which means that the content
|
||||
of `value1` will be escaped. However, since `value2` has been tagged as a markup,
|
||||
this will be injected as html.
|
||||
|
||||
`markup` can also be used as a tag function, allowing the interpolated values to
|
||||
be safely escaped:
|
||||
|
||||
```js
|
||||
const maliciousInput = "<script>alert('💥💥')</script>";
|
||||
// <b><script>alert('💥💥')</script></b>
|
||||
const value = markup`<b>${maliciousInput}</b>`;
|
||||
```
|
||||
|
||||
### Setting Variables
|
||||
|
||||
QWeb allows creating variables from within the template, to memoize a computation (to use it multiple times), give a piece of data a clearer name, ...
|
||||
|
||||
+42
-2
@@ -81,10 +81,50 @@ export async function loadFile(url: string): Promise<string> {
|
||||
*/
|
||||
export class Markup extends String {}
|
||||
|
||||
function _escapeHtml(str: any): string | Markup {
|
||||
if (str instanceof Markup) {
|
||||
return str;
|
||||
}
|
||||
if (str === undefined) {
|
||||
return "";
|
||||
}
|
||||
if (typeof str === "number") {
|
||||
return String(str);
|
||||
}
|
||||
[
|
||||
["&", "&"],
|
||||
["<", "<"],
|
||||
[">", ">"],
|
||||
["'", "'"],
|
||||
['"', """],
|
||||
["`", "`"],
|
||||
].forEach((pairs) => {
|
||||
str = String(str).replace(new RegExp(pairs[0], "g"), pairs[1]);
|
||||
});
|
||||
return str;
|
||||
}
|
||||
|
||||
/*
|
||||
* Marks a value as safe, that is, a value that can be injected as HTML directly.
|
||||
* It should be used to wrap the value passed to a t-out directive to allow a raw rendering.
|
||||
*
|
||||
* If called as a tag function, the interpolated strings are escaped.
|
||||
*/
|
||||
export function markup(value: any) {
|
||||
return new Markup(value);
|
||||
export function markup(strings: TemplateStringsArray, ...placeholders: unknown[]): Markup;
|
||||
export function markup(value: string): Markup;
|
||||
export function markup(
|
||||
valueOrStrings: string | TemplateStringsArray,
|
||||
...placeholders: unknown[]
|
||||
): Markup {
|
||||
if (!Array.isArray(valueOrStrings)) {
|
||||
return new Markup(valueOrStrings);
|
||||
}
|
||||
const strings = valueOrStrings;
|
||||
let acc = "";
|
||||
let i = 0;
|
||||
for (; i < placeholders.length; ++i) {
|
||||
acc += strings[i] + _escapeHtml(placeholders[i]);
|
||||
}
|
||||
acc += strings[i];
|
||||
return new Markup(acc);
|
||||
}
|
||||
|
||||
+31
-1
@@ -1,4 +1,4 @@
|
||||
import { batched, EventBus } from "../src/runtime/utils";
|
||||
import { batched, EventBus, markup } from "../src/runtime/utils";
|
||||
import { nextMicroTick } from "./helpers";
|
||||
|
||||
describe("event bus behaviour", () => {
|
||||
@@ -71,3 +71,33 @@ describe("batched", () => {
|
||||
expect(n).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
const Markup = markup("").constructor;
|
||||
describe("markup", () => {
|
||||
test("string is flagged as safe", () => {
|
||||
const html = markup("<blink>Hello</blink>");
|
||||
expect(html).toBeInstanceOf(Markup);
|
||||
});
|
||||
describe("tag function", () => {
|
||||
test("interpolated values are escaped", () => {
|
||||
const maliciousInput = "<script>alert('💥💥')</script>";
|
||||
const html = markup`<b>${maliciousInput}</b>`;
|
||||
expect(html.toString()).toBe("<b><script>alert('💥💥')</script></b>");
|
||||
expect(html).toBeInstanceOf(Markup);
|
||||
});
|
||||
test("interpolated markups aren't escaped", () => {
|
||||
const shouldBeEscaped = "<script>alert('should be escaped')</script>";
|
||||
const shouldnt = markup("<b>this is safe</b>");
|
||||
const html = markup`<div>${shouldBeEscaped} ${shouldnt}</div>`;
|
||||
expect(html.toString()).toBe(
|
||||
"<div><script>alert('should be escaped')</script> <b>this is safe</b></div>"
|
||||
);
|
||||
expect(html).toBeInstanceOf(Markup);
|
||||
});
|
||||
test("quotes in interpolated values are escaped", () => {
|
||||
const imgUrl = `lol" onerror="alert('xss')`;
|
||||
const html = markup`<img src="${imgUrl}">`;
|
||||
expect(html.toString()).toBe(`<img src="lol" onerror="alert('xss')">`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user