Skip to content

Commit

Permalink
support trusted types via createTrustedScriptURL-config, also polyfil…
Browse files Browse the repository at this point in the history
…l (api-only) trusted types factory if missing, microsoft/vscode#106396
  • Loading branch information
jrieken committed Sep 11, 2020
1 parent 47e5560 commit f7c06be
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 0 deletions.
8 changes: 8 additions & 0 deletions src/core/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,14 @@ namespace AMDLoader {
* Defaults to false.
*/
preferScriptTags?: boolean;
/**
* A callback that enables use of TrustedScriptURL instead of strings, see
* https://w3c.github.io/webappsec-trusted-types/dist/spec/#introduction.
*
* The implementation of this callback should validate the given value (which
* represents a script source value) and throw an error if validation fails.
*/
createTrustedScriptURL?: (value: string) => string;
/**
* A regex to help determine if a module is an AMD module or a node module.
* If defined, then all amd modules in the system must match this regular expression.
Expand Down
81 changes: 81 additions & 0 deletions src/core/scriptLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,66 @@ namespace AMDLoader {
}
}

//#region --- TrustedTypes declarations and tiny polyfill

type TrustedHTML = string;
type TrustedScript = string;
type TrustedScriptURL = string;

interface TrustedTypePolicyOptions {
createHTML?: (value: string) => string
createScript?: (value: string) => string
createScriptURL?: (value: string) => string
}

interface TrustedTypePolicy {
readonly name: string;
createHTML(input: string, ...more: any[]): TrustedHTML
createScript(input: string, ...more: any[]): TrustedScript
createScriptURL(input: string, ...more: any[]): TrustedScriptURL
}

interface TrustedTypePolicyFactory {
createPolicy(policyName: string, object: TrustedTypePolicyOptions): TrustedTypePolicy;
}

declare var trustedTypes: TrustedTypePolicyFactory;

const trustedTypesPolyfill = new class {

installIfNeeded() {
if (typeof globalThis.trustedTypes !== 'undefined') {
return; // already defined
}
const _defaultRules: Required<TrustedTypePolicyOptions> = {
createHTML: () => { throw new Error('Policy\'s TrustedTypePolicyOptions did not specify a \'createHTML\' member') },
createScript: () => { throw new Error('Policy\'s TrustedTypePolicyOptions did not specify a \'createScript\' member') },
createScriptURL: () => { throw new Error('Policy\'s TrustedTypePolicyOptions did not specify a \'createScriptURL\' member') },
}
globalThis.trustedTypes = {
createPolicy(name: string, rules: TrustedTypePolicyOptions): TrustedTypePolicy {
return {
name,
createHTML: rules.createHTML ?? _defaultRules.createHTML,
createScript: rules.createScript ?? _defaultRules.createScript,
createScriptURL: rules.createScriptURL ?? _defaultRules.createScriptURL,
}
}
};
}
}

//#endregion

class BrowserScriptLoader implements IScriptLoader {

private scriptSourceURLPolicy: TrustedTypePolicy;

constructor() {
// polyfill trustedTypes-support if missing
trustedTypesPolyfill.installIfNeeded();
}

/**
* Attach load / error listeners to a script element and remove them when either one has fired.
* Implemented for browssers supporting HTML5 standard 'load' and 'error' events.
Expand Down Expand Up @@ -147,6 +205,13 @@ namespace AMDLoader {

this.attachListeners(script, callback, errorback);

const { createTrustedScriptURL } = moduleManager.getConfig().getOptionsLiteral();
if (createTrustedScriptURL) {
if (!this.scriptSourceURLPolicy) {
this.scriptSourceURLPolicy = trustedTypes.createPolicy('amdLoader', { createScriptURL: createTrustedScriptURL })
}
scriptSrc = this.scriptSourceURLPolicy.createScriptURL(scriptSrc);
}
script.setAttribute('src', scriptSrc);

// Propagate CSP nonce to dynamically created script tag.
Expand All @@ -162,7 +227,23 @@ namespace AMDLoader {

class WorkerScriptLoader implements IScriptLoader {

private scriptSourceURLPolicy: TrustedTypePolicy;

constructor() {
// polyfill trustedTypes-support if missing
trustedTypesPolyfill.installIfNeeded();
}

public load(moduleManager: IModuleManager, scriptSrc: string, callback: () => void, errorback: (err: any) => void): void {

const { createTrustedScriptURL } = moduleManager.getConfig().getOptionsLiteral();
if (createTrustedScriptURL) {
if (!this.scriptSourceURLPolicy) {
this.scriptSourceURLPolicy = trustedTypes.createPolicy('amdLoader', { createScriptURL: createTrustedScriptURL })
}
scriptSrc = this.scriptSourceURLPolicy.createScriptURL(scriptSrc);
}

try {
importScripts(scriptSrc);
callback();
Expand Down
8 changes: 8 additions & 0 deletions src/loader.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,14 @@ declare namespace AMDLoader {
* Defaults to false.
*/
preferScriptTags?: boolean;
/**
* A callback that enables use of TrustedScriptURL instead of strings, see
* https://w3c.github.io/webappsec-trusted-types/dist/spec/#introduction.
*
* The implementation of this callback should validate the given value (which
* represents a script source value) and throw an error if validation fails.
*/
createTrustedScriptURL?: (value: string) => string;
/**
* A regex to help determine if a module is an AMD module or a node module.
* If defined, then all amd modules in the system must match this regular expression.
Expand Down
45 changes: 45 additions & 0 deletions src/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -618,8 +618,37 @@ var AMDLoader;
};
return OnlyOnceScriptLoader;
}());
var trustedTypesPolyfill = new /** @class */ (function () {
function class_1() {
}
class_1.prototype.installIfNeeded = function () {
if (typeof globalThis.trustedTypes !== 'undefined') {
return; // already defined
}
var _defaultRules = {
createHTML: function () { throw new Error('Policy\'s TrustedTypePolicyOptions did not specify a \'createHTML\' member'); },
createScript: function () { throw new Error('Policy\'s TrustedTypePolicyOptions did not specify a \'createScript\' member'); },
createScriptURL: function () { throw new Error('Policy\'s TrustedTypePolicyOptions did not specify a \'createScriptURL\' member'); },
};
globalThis.trustedTypes = {
createPolicy: function (name, rules) {
var _a, _b, _c;
return {
name: name,
createHTML: (_a = rules.createHTML) !== null && _a !== void 0 ? _a : _defaultRules.createHTML,
createScript: (_b = rules.createScript) !== null && _b !== void 0 ? _b : _defaultRules.createScript,
createScriptURL: (_c = rules.createScriptURL) !== null && _c !== void 0 ? _c : _defaultRules.createScriptURL,
};
}
};
};
return class_1;
}());
//#endregion
var BrowserScriptLoader = /** @class */ (function () {
function BrowserScriptLoader() {
// polyfill trustedTypes-support if missing
trustedTypesPolyfill.installIfNeeded();
}
/**
* Attach load / error listeners to a script element and remove them when either one has fired.
Expand Down Expand Up @@ -662,6 +691,13 @@ var AMDLoader;
script.setAttribute('async', 'async');
script.setAttribute('type', 'text/javascript');
this.attachListeners(script, callback, errorback);
var createTrustedScriptURL = moduleManager.getConfig().getOptionsLiteral().createTrustedScriptURL;
if (createTrustedScriptURL) {
if (!this.scriptSourceURLPolicy) {
this.scriptSourceURLPolicy = trustedTypes.createPolicy('amdLoader', { createScriptURL: createTrustedScriptURL });
}
scriptSrc = this.scriptSourceURLPolicy.createScriptURL(scriptSrc);
}
script.setAttribute('src', scriptSrc);
// Propagate CSP nonce to dynamically created script tag.
var cspNonce = moduleManager.getConfig().getOptionsLiteral().cspNonce;
Expand All @@ -675,8 +711,17 @@ var AMDLoader;
}());
var WorkerScriptLoader = /** @class */ (function () {
function WorkerScriptLoader() {
// polyfill trustedTypes-support if missing
trustedTypesPolyfill.installIfNeeded();
}
WorkerScriptLoader.prototype.load = function (moduleManager, scriptSrc, callback, errorback) {
var createTrustedScriptURL = moduleManager.getConfig().getOptionsLiteral().createTrustedScriptURL;
if (createTrustedScriptURL) {
if (!this.scriptSourceURLPolicy) {
this.scriptSourceURLPolicy = trustedTypes.createPolicy('amdLoader', { createScriptURL: createTrustedScriptURL });
}
scriptSrc = this.scriptSourceURLPolicy.createScriptURL(scriptSrc);
}
try {
importScripts(scriptSrc);
callback();
Expand Down

0 comments on commit f7c06be

Please sign in to comment.