Skip to content

Commit

Permalink
Added a first draft of the type metadata.
Browse files Browse the repository at this point in the history
Addresses issue w3c#43.
  • Loading branch information
koto committed May 4, 2019
1 parent bd169ee commit 4685506
Show file tree
Hide file tree
Showing 15 changed files with 806 additions and 148 deletions.
248 changes: 248 additions & 0 deletions dist/cjs/trustedtypes.api_only.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const rejectInputFn = (s) => {
throw new TypeError('undefined conversion');
};

const {toLowerCase, toUpperCase} = String.prototype;

/**
* @constructor
* @property {!function(string):TrustedHTML} createHTML
Expand Down Expand Up @@ -199,6 +201,160 @@ const trustedTypesBuilderTestOnly = function() {

lockdownTrustedType(TrustedType, 'TrustedType');

/**
* A map of attribute / property names to allowed types.
* @type {!TrustedTypesTypeMap}
*/
let HTML_TYPE_MAP = {
// TODO(slekies): Add SVG Elements here
// TODO(koto): Figure out what to to with <link>
'A': {
attributes: {
'href': TrustedURL.name,
},
properties: {},
},
'AREA': {
attributes: {
'href': TrustedURL.name,
},
properties: {},
},
'AUDIO': {
attributes: {
'src': TrustedURL.name,
},
properties: {},
},
'BASE': {
attributes: {
'href': TrustedURL.name,
},
properties: {},
},
'BUTTON': {
attributes: {
'formaction': TrustedURL.name,
},
properties: {},
},
'EMBED': {
attributes: {
'src': TrustedScriptURL.name,
},
properties: {},
},

'FORM': {
attributes: {
'action': TrustedURL.name,
},
properties: {},
},
'FRAME': {
attributes: {
'src': TrustedURL.name,
},
properties: {},
},
'IFRAME': {
attributes: {
'src': TrustedURL.name,
'srcdoc': TrustedHTML.name,
},
properties: {},
},
'IMG': {
attributes: {
'src': TrustedURL.name,
// TODO(slekies): add special handling for srcset
},
properties: {},
},
'INPUT': {
attributes: {
'src': TrustedURL.name,
'formaction': TrustedURL.name,
},
properties: {},
},
'LINK': {
attributes: {
'href': TrustedURL.name,
},
properties: {},
},
'OBJECT': {
attributes: {
'data': TrustedScriptURL.name,
'codebase': TrustedScriptURL.name,
},
properties: {},
},
// TODO(koto): Figure out what to do with portals.
'SCRIPT': {
attributes: {
'src': TrustedScriptURL.name,
'text': TrustedScript.name,
},
properties: {
'innerText': TrustedScript.name,
'textContent': TrustedScript.name,
'text': TrustedScript.name,
},
},
'SOURCE': {
attributes: {
'src': TrustedURL.name,
},
properties: {},
},
'TRACK': {
attributes: {
'src': TrustedURL.name,
},
properties: {},
},
'VIDEO': {
attributes: {
'src': TrustedURL.name,
},
properties: {},
},
'*': {
attributes: {},
properties: {
'innerHTML': TrustedHTML.name,
'outerHTML': TrustedHTML.name,
},
},
};

/**
* A map of element property to HTML attribute names.
* @type {!Object<string, string>}
*/
const ATTR_PROPERTY_MAP = {
'codebase': 'codeBase',
'formaction': 'formAction',
};

// Clone for properties.
for (let tag of Object.keys(HTML_TYPE_MAP)) {
for (let attr of Object.keys(HTML_TYPE_MAP[tag].attributes)) {
HTML_TYPE_MAP[tag].properties[
ATTR_PROPERTY_MAP[attr] ? ATTR_PROPERTY_MAP[attr] : attr
] = HTML_TYPE_MAP[tag].attributes[attr];
}
}

// Add inline event handlers attribute names.
for (let name of getOwnPropertyNames(HTMLElement.prototype)) {
if (name.slice(0, 2) === 'on') {
HTML_TYPE_MAP['*'].attributes[name] = 'TrustedScript';
}
}

/**
* @type {!Object<string,!Function>}
*/
Expand Down Expand Up @@ -278,6 +434,91 @@ const trustedTypesBuilderTestOnly = function() {
return exposedPolicies.get(pName) || null;
}

/**
* Returns the name of the trusted type required for a given element
* attribute.
* @param {string} tagName The name of the tag of the element.
* @param {string} attribute The name of the attribute.
* @param {string=} elementNs Element namespace.
* @param {string=} attributeNs The attribute namespace.
* @return {string|undefined} Required type name or undefined, if a Trusted
* Type is not required.
*/
function getAttributeType(tagName, attribute, elementNs = '',
attributeNs = '') {
// TODO: Support namespaces.
const canonicalAttr = toLowerCase.apply(String(attribute));
return getTypeInternal_(tagName, 'attributes', canonicalAttr);
}

/**
* Returns a type name from a type map.
* @param {string} tag A tag name.
* @param {string} container 'attributes' or 'properties'
* @param {string} property The attribute / property name.
* @return {string|undefined}
* @private
*/
function getTypeInternal_(tag, container, property) {
if (HTML_TYPE_MAP['*'][container][property]) {
return HTML_TYPE_MAP['*'][container][property];
}

const canonicalTag = toUpperCase.apply(String(tag));

if (!HTML_TYPE_MAP[canonicalTag]) {
return undefined;
}

return HTML_TYPE_MAP[canonicalTag] &&
HTML_TYPE_MAP[canonicalTag][container][property] ?
HTML_TYPE_MAP[canonicalTag][container][property] :
undefined;
}

/**
* Returns the name of the trusted type required for a given element property.
* @param {string} tagName The name of the tag of the element.
* @param {string} property The property.
* @param {string=} elementNs Element namespace.
* @return {string|undefined} Required type name or undefined, if a Trusted
* Type is not required.
*/
function getPropertyType(tagName, property, elementNs = '') {
// TODO: Support namespaces.
return getTypeInternal_(tagName, 'properties', String(property));
}

/**
* Returns the type map-like object, that resolves a name of a type for a
* given tag + attribute / property in a given namespace.
* The keys of the map are uppercase tag names. Map entry has mappings between
* a lowercase attribute name / case-sensitive property name and a name of the
* type that is required for that attribute / property.
* Example entry for 'IMG': {"attributes": {"src": "TrustedHTML"}}
* @param {string=} namespaceUri The namespace URI (will use the current
* document namespace URI if omitted).
* @return {TrustedTypesTypeMap}
*/
function getTypeMapping(namespaceUri = '') {
const HTML_NS = 'http://www.w3.org/1999/xhtml';
if (!namespaceUri) {
try {
namespaceUri = document.documentElement.namespaceURI;
} catch (e) {
namespaceUri = HTML_NS;
}
}
switch (namespaceUri) {
case HTML_NS:
return /** @type {TrustedTypesTypeMap} */ (JSON.parse(
JSON.stringify(HTML_TYPE_MAP)));
// TODO(koto): Add support for other namespaces.
default:
return /** @type {TrustedTypesTypeMap} */ ({});
}
}

/**
* Returns all configured policy names (even for non-exposed policies).
* @return {!Array<string>}
Expand Down Expand Up @@ -388,6 +629,10 @@ const trustedTypesBuilderTestOnly = function() {
isScriptURL: isTrustedTypeChecker(TrustedScriptURL),
isScript: isTrustedTypeChecker(TrustedScript),

getAttributeType,
getPropertyType,
getTypeMapping,

TrustedHTML: TrustedHTML,
TrustedURL: TrustedURL,
TrustedScriptURL: TrustedScriptURL,
Expand Down Expand Up @@ -436,6 +681,9 @@ function setupPolyfill() {
'createPolicy': tt.createPolicy,
'getExposedPolicy': tt.getExposedPolicy,
'getPolicyNames': tt.getPolicyNames,
'getAttributeType': tt.getAttributeType,
'getPropertyType': tt.getPropertyType,
'getTypeMapping': tt.getTypeMapping,
'_isPolyfill_': true,
});
window['TrustedTypes'] = Object.freeze(publicApi);
Expand Down
Loading

0 comments on commit 4685506

Please sign in to comment.