@adobe/htlengine
Version:
Javascript Based HTL (Sightly) parser
120 lines (103 loc) • 3.7 kB
JavaScript
/*
* Copyright 2018 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
const xss = require('./xss_api');
const VALID_ATTRIBUTE = /^[a-zA-Z_:][-a-zA-Z0-9_:.]*$/;
const REJECTLIST_ATTRIBUTE = /^(style|(on.*))$/;
const ELEMENT_NAME_ACCEPTLIST = [
'a', 'abbbr', 'address', 'article', 'aside',
'b', 'bdi', 'bdo', 'blockquote', 'br', 'caption',
'cite', 'code', 'col', 'colgroup', 'data', 'dd',
'del', 'dfn', 'div', 'dl', 'dt', 'em', 'figcaption',
'figure', 'footer', 'h1', 'h2', 'h3', 'h4',
'h5', 'h6', 'header', 'i', 'ins', 'kbd', 'li', 'main',
'mark', 'nav', 'ol', 'p', 'pre', 'q', 'rp', 'rt',
'ruby', 's', 'samp', 'section', 'small', 'span',
'strong', 'sub', 'sup', 'table', 'tbody', 'td',
'tfoot', 'th', 'thead', 'time', 'tr', 'u', 'ul',
'var', 'wbr',
].reduce((set, e) => {
set[e] = true; // eslint-disable-line no-param-reassign
return set;
}, {});
function isUriAttribute(name) {
return name === 'href' || name === 'src';
}
function filterAttributeName(name) {
const trimmedName = (name || '').trim();
if (VALID_ATTRIBUTE.test(trimmedName) && !REJECTLIST_ATTRIBUTE.test(trimmedName)) {
return name;
}
return '';
}
function escapeAttributeValue(input, attributeName) {
if (isUriAttribute(attributeName)) {
return xss.getValidHref(input);
}
return xss.encodeForHTMLAttribute(input);
}
function filterElementName(input, def) {
const trimmedInput = (input || '').trim();
if (trimmedInput.toLowerCase() in ELEMENT_NAME_ACCEPTLIST) {
return trimmedInput;
}
return def;
}
module.exports = function formatXss(value, ctx, hint) {
const isArray = Array.isArray(value);
if (typeof value === 'boolean' || value === undefined || (!isArray && typeof value === 'object')) {
return value;
}
const stringValue = isArray ? value.join(',') : `${value}`;
switch (ctx) {
case 'null':
return value;
case 'unsafe':
return stringValue;
case 'html':
return xss.filterHTML(stringValue);
case 'comment':
case 'text':
return xss.encodeForHTML(stringValue);
case 'elementName':
return filterElementName(stringValue, 'div');
case 'elementNameNoFallback':
return filterElementName(stringValue, '');
case 'attributeName':
return filterAttributeName(stringValue);
case 'attribute':
return escapeAttributeValue(stringValue, hint);
case 'uri':
return xss.getValidHref(stringValue);
case 'scriptComment':
case 'styleComment':
return xss.getValidMultiLineComment(stringValue, '');
case 'scriptToken':
return xss.getValidJSToken(stringValue, '');
case 'scriptString':
return xss.encodeForJSString(stringValue);
case 'scriptRegexp':
throw new Error('Not implemented');
case 'number': {
const num = Number.parseFloat(stringValue);
if (!Number.isNaN(num)) {
return stringValue;
}
return 0;
}
case 'styleToken':
return xss.getValidStyleToken(stringValue, '');
case 'styleString':
return xss.encodeForCSSString(stringValue);
default:
throw new Error(`Invalid markup context: ${ctx}`);
}
};