Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wraps Trusted Types in a Safe Type object implementation. #310

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 36 additions & 32 deletions src/internals/html_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,43 @@

import '../environment/dev';

/* g3_import_pure from './pure' */
import {ensureTokenIsValid, secretToken} from './secrets';
import {getTrustedTypes, getTrustedTypesPolicy} from './trusted_types';


/**
* Runtime implementation of `TrustedHTML` in browsers that don't support it.
* String that is safe to use in HTML contexts in DOM APIs and HTML documents.
* See
* https://github.com/google/safevalues/blob/main/src/README.md#trustedresourceurl
*/
class HtmlImpl {
readonly privateDoNotAccessOrElseWrappedHtml: string;
export abstract class SafeHtml {
// tslint:disable-next-line:no-unused-variable
// @ts-ignore
private readonly brand!: never; // To prevent structural typing.
}

/** Implementation for `SafeHtml` */
class HtmlImpl extends SafeHtml {
readonly privateDoNotAccessOrElseWrappedHtml: TrustedHTML|string;

constructor(html: string, token: object) {
ensureTokenIsValid(token);
constructor(html: TrustedHTML|string, token: object) {
super();
if (process.env.NODE_ENV !== 'production') {
ensureTokenIsValid(token);
}
this.privateDoNotAccessOrElseWrappedHtml = html;
}

toString(): string {
override toString(): string {
return this.privateDoNotAccessOrElseWrappedHtml.toString();
}
}

function createTrustedHtmlOrPolyfill(
html: string, trusted?: TrustedHTML): SafeHtml {
return (trusted ?? new HtmlImpl(html, secretToken)) as SafeHtml;
function createHtmlInstance(html: string, trusted?: TrustedHTML): SafeHtml {
return new HtmlImpl(trusted ?? html, secretToken);
}

const GlobalTrustedHTML =
(typeof window !== undefined) ? window.TrustedHTML : undefined;

/**
* String that is safe to use in HTML contexts in DOM APIs and HTML
documents.
*/
export type SafeHtml = TrustedHTML;

/**
* Also exports the constructor so that instanceof checks work.
*/
export const SafeHtml =
(GlobalTrustedHTML ?? HtmlImpl) as unknown as TrustedHTML;

/**
* Builds a new `SafeHtml` from the given string, without enforcing safety
* guarantees. It may cause side effects by creating a Trusted Types policy.
Expand All @@ -54,7 +51,7 @@ export const SafeHtml =
export function createHtmlInternal(html: string): SafeHtml {
/** @noinline */
const noinlineHtml = html;
return createTrustedHtmlOrPolyfill(
return createHtmlInstance(
noinlineHtml, getTrustedTypesPolicy()?.createHTML(noinlineHtml));
}

Expand All @@ -64,26 +61,33 @@ export function createHtmlInternal(html: string): SafeHtml {
*/
export const EMPTY_HTML: SafeHtml =
/* #__PURE__ */ (
() => createTrustedHtmlOrPolyfill('', getTrustedTypes()?.emptyHTML))();
() => createHtmlInstance('', getTrustedTypes()?.emptyHTML))();

/**
* Checks if the given value is a `SafeHtml` instance.
*/
export function isHtml(value: unknown): value is SafeHtml {
return getTrustedTypes()?.isHTML(value) || value instanceof HtmlImpl;
return value instanceof HtmlImpl;
}

/**
* Returns the value of the passed `SafeHtml` object while ensuring it
* has the correct type.
*
* Returns a native `TrustedHTML` or a string if Trusted Types are disabled.
*
* The strange return type is to ensure the value can be used at sinks without a
* cast despite the TypeScript DOM lib not supporting Trusted Types.
* (https://github.com/microsoft/TypeScript/issues/30024)
*
* Note that while the return type is compatible with `string`, you shouldn't
* use any string functions on the result as that will fail in browsers
* supporting Trusted Types.
*/
export function unwrapHtml(value: SafeHtml): TrustedHTML|string {
if (getTrustedTypes()?.isHTML(value)) {
return value;
} else if (value instanceof HtmlImpl) {
return value.privateDoNotAccessOrElseWrappedHtml;
export function unwrapHtml(value: SafeHtml): TrustedHTML&string {
if (value instanceof HtmlImpl) {
const unwrapped = value.privateDoNotAccessOrElseWrappedHtml;
return unwrapped as TrustedHTML & string;
} else {
let message = '';
if (process.env.NODE_ENV !== 'production') {
Expand Down
69 changes: 36 additions & 33 deletions src/internals/resource_url_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,40 @@
import '../environment/dev';

import {ensureTokenIsValid, secretToken} from './secrets';
import {getTrustedTypes, getTrustedTypesPolicy} from './trusted_types';
import {getTrustedTypesPolicy} from './trusted_types';


/**
* Runtime implementation of `TrustedScriptURL` in browsers that don't support
* it.
* String that is safe to use in all URL contexts in DOM APIs and HTML
* documents; even as a reference to resources that may load in the current
* origin (e.g. scripts and stylesheets).

* See
https://github.com/google/safevalues/blob/main/src/README.md#trustedresourceurl
*/
class ResourceUrlImpl {
readonly privateDoNotAccessOrElseWrappedResourceUrl: string;
export abstract class TrustedResourceUrl {
// tslint:disable-next-line:no-unused-variable
// @ts-ignore
private readonly brand!: never; // To prevent structural typing.
}

constructor(url: string, token: object) {
ensureTokenIsValid(token);
/** Implementation for `TrustedResourceUrl` */
class ResourceUrlImpl extends TrustedResourceUrl {
readonly privateDoNotAccessOrElseWrappedResourceUrl: TrustedScriptURL|string;

constructor(url: TrustedScriptURL|string, token: object) {
super();
if (process.env.NODE_ENV !== 'production') {
ensureTokenIsValid(token);
}
this.privateDoNotAccessOrElseWrappedResourceUrl = url;
}

toString(): string {
override toString(): string {
return this.privateDoNotAccessOrElseWrappedResourceUrl.toString();
}
}

const GlobalTrustedScriptURL =
(typeof window !== undefined) ? window.TrustedScriptURL : undefined;

/**
* String that is safe to use in all URL contexts in DOM APIs and HTML
* documents; even as a reference to resources that may load in the current
* origin (e.g. scripts and stylesheets).
*/
export type TrustedResourceUrl = TrustedScriptURL;

/**
* Also exports the constructor so that instanceof checks work.
*/
export const TrustedResourceUrl =
(GlobalTrustedScriptURL ?? ResourceUrlImpl) as unknown as TrustedScriptURL;

/**
* Builds a new `TrustedResourceUrl` from the given string, without
* enforcing safety guarantees. It may cause side effects by creating a Trusted
Expand All @@ -53,16 +51,14 @@ export function createResourceUrlInternal(url: string): TrustedResourceUrl {
const noinlineUrl = url;
const trustedScriptURL =
getTrustedTypesPolicy()?.createScriptURL(noinlineUrl);
return (trustedScriptURL ?? new ResourceUrlImpl(noinlineUrl, secretToken)) as
TrustedResourceUrl;
return new ResourceUrlImpl(trustedScriptURL ?? noinlineUrl, secretToken);
}

/**
* Checks if the given value is a `TrustedResourceUrl` instance.
*/
export function isResourceUrl(value: unknown): value is TrustedResourceUrl {
return getTrustedTypes()?.isScriptURL(value) ||
value instanceof ResourceUrlImpl;
return value instanceof ResourceUrlImpl;
}

/**
Expand All @@ -71,13 +67,20 @@ export function isResourceUrl(value: unknown): value is TrustedResourceUrl {
*
* Returns a native `TrustedScriptURL` or a string if Trusted Types are
* disabled.
*
* The strange return type is to ensure the value can be used at sinks without a
* cast despite the TypeScript DOM lib not supporting Trusted Types.
* (https://github.com/microsoft/TypeScript/issues/30024)
*
* Note that while the return type is compatible with `string`, you shouldn't
* use any string functions on the result as that will fail in browsers
* supporting Trusted Types.
*/
export function unwrapResourceUrl(value: TrustedResourceUrl): TrustedScriptURL|
export function unwrapResourceUrl(value: TrustedResourceUrl): TrustedScriptURL&
string {
if (getTrustedTypes()?.isScriptURL(value)) {
return value;
} else if (value instanceof ResourceUrlImpl) {
return value.privateDoNotAccessOrElseWrappedResourceUrl;
if (value instanceof ResourceUrlImpl) {
const unwrapped = value.privateDoNotAccessOrElseWrappedResourceUrl;
return unwrapped as TrustedScriptURL & string;
} else {
let message = '';
if (process.env.NODE_ENV !== 'production') {
Expand Down
69 changes: 37 additions & 32 deletions src/internals/script_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,46 @@

import '../environment/dev';

/* g3_import_pure from './pure' */
import {ensureTokenIsValid, secretToken} from './secrets';
import {getTrustedTypes, getTrustedTypesPolicy} from './trusted_types';



/**
* Runtime implementation of `TrustedScript` in browswers that don't support it.
* JavaScript code that is safe to evaluate and use as the content of an HTML
* script element.
*
* See https://github.com/google/safevalues/blob/main/src/README.md#safescript
*/
class ScriptImpl {
readonly privateDoNotAccessOrElseWrappedScript: string;
export abstract class SafeScript {
// tslint:disable-next-line:no-unused-variable
// @ts-ignore
private readonly brand!: never; // To prevent structural typing.
}

/** Implementation for `SafeScript` */
class ScriptImpl extends SafeScript {
readonly privateDoNotAccessOrElseWrappedScript: TrustedScript|string;

constructor(script: string, token: object) {
ensureTokenIsValid(token);
constructor(script: TrustedScript|string, token: object) {
super();
if (process.env.NODE_ENV !== 'production') {
ensureTokenIsValid(token);
}
this.privateDoNotAccessOrElseWrappedScript = script;
}

toString(): string {
override toString(): string {
return this.privateDoNotAccessOrElseWrappedScript.toString();
}
}

function createTrustedScriptOrPolyfill(
function createScriptInstance(
script: string, trusted?: TrustedScript): SafeScript {
return (trusted ?? new ScriptImpl(script, secretToken)) as SafeScript;
return new ScriptImpl(trusted ?? script, secretToken);
}

const GlobalTrustedScript =
(typeof window !== undefined) ? window.TrustedScript : undefined;

/**
* JavaScript code that is safe to evaluate and use as the content of an HTML
* script element.
*/
export type SafeScript = TrustedScript;

/**
* Also exports the constructor so that instanceof checks work.
*/
export const SafeScript =
(GlobalTrustedScript ?? ScriptImpl) as unknown as TrustedScript;

/**
* Builds a new `SafeScript` from the given string, without enforcing
* safety guarantees. It may cause side effects by creating a Trusted Types
Expand All @@ -55,7 +54,7 @@ export const SafeScript =
export function createScriptInternal(script: string): SafeScript {
/** @noinline */
const noinlineScript = script;
return createTrustedScriptOrPolyfill(
return createScriptInstance(
noinlineScript, getTrustedTypesPolicy()?.createScript(noinlineScript));
}

Expand All @@ -65,27 +64,33 @@ export function createScriptInternal(script: string): SafeScript {
*/
export const EMPTY_SCRIPT: SafeScript =
/* #__PURE__ */ (
() => createTrustedScriptOrPolyfill(
'', getTrustedTypes()?.emptyScript))();
() => createScriptInstance('', getTrustedTypes()?.emptyScript))();

/**
* Checks if the given value is a `SafeScript` instance.
*/
export function isScript(value: unknown): value is SafeScript {
return getTrustedTypes()?.isScript(value) || value instanceof ScriptImpl;
return value instanceof ScriptImpl;
}

/**
* Returns the value of the passed `SafeScript` object while ensuring it
* has the correct type.
*
* Returns a native `TrustedScript` or a string if Trusted Types are disabled.
*
* The strange return type is to ensure the value can be used at sinks without a
* cast despite the TypeScript DOM lib not supporting Trusted Types.
* (https://github.com/microsoft/TypeScript/issues/30024)
*
* Note that while the return type is compatible with `string`, you shouldn't
* use any string functions on the result as that will fail in browsers
* supporting Trusted Types.
*/
export function unwrapScript(value: SafeScript): TrustedScript|string {
if (getTrustedTypes()?.isScript(value)) {
return value;
} else if (value instanceof ScriptImpl) {
return value.privateDoNotAccessOrElseWrappedScript;
export function unwrapScript(value: SafeScript): TrustedScript&string {
if (value instanceof ScriptImpl) {
const unwrapped = value.privateDoNotAccessOrElseWrappedScript;
return unwrapped as TrustedScript & string;
} else {
let message = '';
if (process.env.NODE_ENV !== 'production') {
Expand Down
Loading