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 = {
      '&': '&',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#39;'
    };
    
    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">'+');}');