Skip to content

Commit bd34421

Browse files
Add trustedHTML via alias/re-export of htmlSafe (#20939)
1 parent 8dca411 commit bd34421

File tree

4 files changed

+122
-15
lines changed

4 files changed

+122
-15
lines changed

packages/@ember/-internals/glimmer/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,14 @@ export {
457457
type FunctionBasedHelper,
458458
type FunctionBasedHelperInstance,
459459
} from './lib/helper';
460-
export { SafeString, htmlSafe, isHTMLSafe } from './lib/utils/string';
460+
export {
461+
TrustedHTML,
462+
SafeString,
463+
trustHTML,
464+
isTrustedHTML,
465+
htmlSafe,
466+
isHTMLSafe,
467+
} from './lib/utils/string';
461468
export { Renderer, _resetRenderers, renderSettled } from './lib/renderer';
462469
export {
463470
getTemplate,

packages/@ember/-internals/glimmer/lib/utils/string.ts

Lines changed: 103 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,39 @@
55
import type { SafeString as GlimmerSafeString } from '@glimmer/runtime';
66

77
/**
8-
A wrapper around a string that has been marked as safe ("trusted"). **When
8+
A wrapper around a string that has been marked as "trusted". **When
99
rendered in HTML, Ember will not perform any escaping.**
1010
1111
Note:
1212
1313
1. This does not *make* the string safe; it means that some code in your
14-
application has *marked* it as safe using the `htmlSafe()` function.
14+
application has *marked* it as trusted using the `trustHTML()` function.
1515
16-
2. The only public API for getting a `SafeString` is calling `htmlSafe()`. It
16+
2. The only public API for getting a `TrutsedHTML` is calling `trustHTML()`. It
1717
is *not* user-constructible.
1818
1919
If a string contains user inputs or other untrusted data, you must sanitize
20-
the string before using the `htmlSafe` method. Otherwise your code is
20+
the string before using the `trustHTML` method. Otherwise your code is
2121
vulnerable to [Cross-Site Scripting][xss]. There are many open source
2222
sanitization libraries to choose from, both for front end and server-side
2323
sanitization.
2424
2525
[xss]: https://owasp.org/www-community/attacks/DOM_Based_XSS
2626
2727
```javascript
28-
import { htmlSafe } from '@ember/template';
28+
import { trustHTML } from '@ember/template';
2929
3030
let someTrustedOrSanitizedString = "<div>Hello!</div>"
3131
32-
htmlSafe(someTrustedorSanitizedString);
32+
trustHTML(someTrustedorSanitizedString);
3333
```
3434
3535
@for @ember/template
36-
@class SafeString
37-
@since 4.12.0
36+
@class TrustedHTML
37+
@since 6.7.0
3838
@public
3939
*/
40-
export class SafeString implements GlimmerSafeString {
40+
export class TrustedHTML implements GlimmerSafeString {
4141
private readonly __string: string;
4242

4343
constructor(string: string) {
@@ -67,6 +67,42 @@ export class SafeString implements GlimmerSafeString {
6767
}
6868
}
6969

70+
/**
71+
A wrapper around a string that has been marked as safe ("trusted"). **When
72+
rendered in HTML, Ember will not perform any escaping.**
73+
74+
Note:
75+
76+
1. This does not *make* the string safe; it means that some code in your
77+
application has *marked* it as safe using the `htmlSafe()` function.
78+
79+
2. The only public API for getting a `SafeString` is calling `htmlSafe()`. It
80+
is *not* user-constructible.
81+
82+
If a string contains user inputs or other untrusted data, you must sanitize
83+
the string before using the `htmlSafe` method. Otherwise your code is
84+
vulnerable to [Cross-Site Scripting][xss]. There are many open source
85+
sanitization libraries to choose from, both for front end and server-side
86+
sanitization.
87+
88+
[xss]: https://owasp.org/www-community/attacks/DOM_Based_XSS
89+
90+
```javascript
91+
import { htmlSafe } from '@ember/template';
92+
93+
let someTrustedOrSanitizedString = "<div>Hello!</div>"
94+
95+
htmlSafe(someTrustedorSanitizedString);
96+
```
97+
98+
@for @ember/template
99+
@class SafeString
100+
@since 4.12.0
101+
@public
102+
*/
103+
export const SafeString = TrustedHTML;
104+
export type SafeString = TrustedHTML;
105+
70106
/**
71107
Use this method to indicate that a string should be rendered as HTML
72108
when the string is used in a template. To say this another way,
@@ -96,13 +132,46 @@ export class SafeString implements GlimmerSafeString {
96132
@return {SafeString} A string that will not be HTML escaped by Handlebars.
97133
@public
98134
*/
99-
export function htmlSafe(str: string): SafeString {
135+
export const htmlSafe = trustHTML;
136+
137+
/**
138+
Use this method to indicate that a string should be rendered as HTML
139+
without escaping when the string is used in a template. To say this another way,
140+
strings marked with `trustHTML` will not be HTML escaped.
141+
142+
A word of warning - The `trustHTML` method does not make the string safe;
143+
it only tells the framework to treat the string as if it is safe to render
144+
as HTML - that we trust its contents to be safe. If a string contains user inputs or other untrusted
145+
data, you must sanitize the string before using the `trustHTML` method.
146+
Otherwise your code is vulnerable to
147+
[Cross-Site Scripting](https://owasp.org/www-community/attacks/DOM_Based_XSS).
148+
There are many open source sanitization libraries to choose from,
149+
both for front end and server-side sanitization.
150+
151+
```glimmer-js
152+
import { trustHTML } from '@ember/template';
153+
154+
const someTrustedOrSanitizedString = "<div>Hello!</div>"
155+
156+
<template>
157+
{{trustHTML someTrustedOrSanitizedString}}
158+
</template>
159+
```
160+
161+
@method trustHTML
162+
@for @ember/template
163+
@param str {String} The string to treat as trusted.
164+
@static
165+
@return {TrustedHTML} A string that will not be HTML escaped by Handlebars.
166+
@public
167+
*/
168+
export function trustHTML(str: string): TrustedHTML {
100169
if (str === null || str === undefined) {
101170
str = '';
102171
} else if (typeof str !== 'string') {
103172
str = String(str);
104173
}
105-
return new SafeString(str);
174+
return new TrustedHTML(str);
106175
}
107176

108177
/**
@@ -124,12 +193,33 @@ export function htmlSafe(str: string): SafeString {
124193
@return {Boolean} `true` if the string was decorated with `htmlSafe`, `false` otherwise.
125194
@public
126195
*/
127-
export function isHTMLSafe(str: unknown): str is SafeString {
196+
export const isHTMLSafe = isTrustedHTML;
197+
198+
/**
199+
Detects if a string was decorated using `trustHTML`.
200+
201+
```javascript
202+
import { trustHTML, isTrustedHTML } from '@ember/template';
203+
204+
let plainString = 'plain string';
205+
let safeString = trustHTML('<div>someValue</div>');
206+
207+
isTrustedHTML(plainString); // false
208+
isTrustedHTML(safeString); // true
209+
```
210+
211+
@method isTrustedHTML
212+
@for @ember/template
213+
@static
214+
@return {Boolean} `true` if the string was decorated with `htmlSafe`, `false` otherwise.
215+
@public
216+
*/
217+
export function isTrustedHTML(str: unknown): str is TrustedHTML {
128218
return (
129219
// SAFETY: cast `as SafeString` only present to make this check "legal"; we
130220
// can further improve this by changing the behavior to do an `in` check
131221
// instead, but that's worth landing as a separate change for bisecting if
132222
// it happens to have an impact on e.g. perf.
133-
str !== null && typeof str === 'object' && typeof (str as SafeString).toHTML === 'function'
223+
str !== null && typeof str === 'object' && typeof (str as TrustedHTML).toHTML === 'function'
134224
);
135225
}

packages/@ember/template/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
// NOTE: this intentionally *only* exports the *type* `SafeString`, not its
22
// value, since it should not be constructed by users.
3-
export { htmlSafe, isHTMLSafe, type SafeString } from '@ember/-internals/glimmer';
3+
export {
4+
isTrustedHTML,
5+
trustHTML,
6+
htmlSafe,
7+
isHTMLSafe,
8+
type SafeString,
9+
type TrustedHTML,
10+
} from '@ember/-internals/glimmer';

tests/docs/expected.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ module.exports = {
253253
'helper',
254254
'helperContainer',
255255
'htmlSafe',
256+
'trustHTML',
256257
'if',
257258
'in-element',
258259
'includes',
@@ -292,6 +293,7 @@ module.exports = {
292293
'isFactory',
293294
'isFulfilled',
294295
'isHTMLSafe',
296+
'isTrustedHTML',
295297
'isInteractive',
296298
'isNone',
297299
'isObject',
@@ -599,6 +601,7 @@ module.exports = {
599601
'Service',
600602
'TestAdapter',
601603
'Transition',
604+
'TrustedHTML',
602605
'rsvp',
603606
],
604607
modules: [

0 commit comments

Comments
 (0)