escape
by jwerre
JavaScript
function escape(str, options = {}) {
let charMap,
entities,
keys,
reEscapedHtml,
reUnescapedHtml;
// Problem: all of these characters are valid CSS operators so.
charMap = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
if (options.characterMap) {
charMap = options.characterMap;
}
keys = Object.keys(charMap);
entities = keys.map(function(k) {
return charMap[k];
});
// default: /&(?:amp|lt|gt|quot|#39);/g
reEscapedHtml = new RegExp(`&(?:${entities.map(function(val) {
return val.replace(/(&|;)/g, '');
}).join('|')});`, 'g');
// default /[&<>"']/g
reUnescapedHtml = new RegExp(`[${keys.join('')}]`, 'g');
if (Object.prototype.toString.call(str) !== '[object String]' || !str.length) {
return '';
}
if ( !( keys.some( (key) => str.includes(key) ) ) ) {
return str;
}
return str.replace(reUnescapedHtml, (char) => {
return charMap[char];
})
}
/*
https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#rule-4-css-encode-and-strictly-validate-before-inserting-untrusted-data-into-html-style-property-values
*/
const css = `
body: {
backgound('https://example.com/xxs/attack')
}
`;
console.log(CSS.escape(css));
console.log( escape(css) );
/* The best way to do this is to escape characters property individually */
console.log(`body:{backgound(${CSS.escape('https://example.com/xxs/attack')})}`);
console.log('body:{backgound('+'<script src="https://example.com/xxs/attack">'+');}');