What is the HtmlSpecialChars equivalent in JavaScript?

Viewed 228980

Apparently, this is harder to find than I thought it would be. And it even is so simple...

Is there a function equivalent to PHP's htmlspecialchars built into JavaScript? I know it's fairly easy to implement that yourself, but using a built-in function, if available, is just nicer.

For those unfamiliar with PHP, htmlspecialchars translates stuff like <htmltag/> into &lt;htmltag/&gt;

I know that escape() and encodeURI() do not work this way.

18 Answers

There is a problem with your solution code--it will only escape the first occurrence of each special character. For example:

escapeHtml('Kip\'s <b>evil</b> "test" code\'s here');
Actual:   Kip&#039;s &lt;b&gt;evil</b> &quot;test" code's here
Expected: Kip&#039;s &lt;b&gt;evil&lt;/b&gt; &quot;test&quot; code&#039;s here

Here is code that works properly:

function escapeHtml(text) {
  return text
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#039;");
}

Update

The following code will produce identical results to the above, but it performs better, particularly on large blocks of text (thanks jbo5112).

function escapeHtml(text) {
  var map = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;'
  };
  
  return text.replace(/[&<>"']/g, function(m) { return map[m]; });
}

Worth a read: http://bigdingus.com/2007/12/29/html-escaping-in-javascript/

escapeHTML: (function() {
 var MAP = {
   '&': '&amp;',
   '<': '&lt;',
   '>': '&gt;',
   '"': '&#34;',
   "'": '&#39;'
 };
  var repl = function(c) { return MAP[c]; };
  return function(s) {
    return s.replace(/[&<>'"]/g, repl);
  };
})()

Note: Only run this once. And don't run it on already encoded strings e.g. &amp; becomes &amp;amp;

function htmlEscape(str){
    return str.replace(/[&<>'"]/g,x=>'&#'+x.charCodeAt(0)+';')
}

This solution uses the numerical code of the characters, for example < is replaced by &#60;.

Although its performance is slightly worse than the solution using a map, it has the advantages:

  • Not dependent on a library or DOM
  • Pretty easy to remember (you don't need to memorize the 5 HTML escape characters)
  • Little code
  • Reasonably fast (it's still faster than 5 chained replace)

I am elaborating a bit on o.k.w.'s answer.

You can use the browser's DOM functions for that.

var utils = {
    dummy: document.createElement('div'),
    escapeHTML: function(s) {
        this.dummy.textContent = s
        return this.dummy.innerHTML
    }
}

utils.escapeHTML('<escapeThis>&')

This returns &lt;escapeThis&gt;&amp;

It uses the standard function createElement to create an invisible element, then uses the function textContent to set any string as its content and then innerHTML to get the content in its HTML representation.

By the books

OWASP recommends that "[e]xcept for alphanumeric characters, [you should] escape all characters with ASCII values less than 256 with the &#xHH; format (or a named entity if available) to prevent switching out of [an] attribute."

So here's a function that does that, with a usage example:

function escapeHTML(unsafe) {
  return unsafe.replace(
    /[\u0000-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u00FF]/g,
    c => '&#' + ('000' + c.charCodeAt(0)).slice(-4) + ';'
  )
}

document.querySelector('div').innerHTML =
  '<span class=' +
  escapeHTML('"fakeclass" onclick="alert("test")') +
  '>' +
  escapeHTML('<script>alert("inspect the attributes")\u003C/script>') +
  '</span>'
<div></div>

You should verify the entity ranges I have provided to validate the safety of the function yourself. You could also use this regular expression which has better readability and should cover the same character codes, but is about 10% less performant in my browser:

/(?![0-9A-Za-z])[\u0000-\u00FF]/g

// Codificamos los caracteres: &, <, >, ", '
function encodeHtml(str) {

  var map = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;'
  };

  return str.replace(/[&<>"']/g, function(m) {return map[m];});
}

// Decodificamos los caracteres: &amp; &lt; &gt; &quot; &#039;
function decodeHtml(str) {

  var map = {
    '&amp;': '&',
    '&lt;': '<',
    '&gt;': '>',
    '&quot;': '"',
    '&#039;': "'"
  };

  return str.replace(/&amp;|&lt;|&gt;|&quot;|&#039;/g, function(m) {return map[m];});
}

var str = `atttt ++ ' ' " " " " " + {}-´ñ+.'aAAAaaaa"`;

var str2 = `atttt ++ &#039; &#039; &quot; &quot; &quot; &quot; &quot; + {}-´ñ+.&#039;aAAAaaaa&quot;`;


console.log(encodeHtml(str));
console.log(decodeHtml(str2));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="string">
<b>- String de entrada</b>: atttt ++ ' ' " " " " " + {}-´ñ+.'aAAAaaaa"  
<br> 
- mira la consola 
</div>

This isn't directly related to this question, but the reverse could be accomplished in JS through:

> String.fromCharCode(8212);
> "—"

That also works with TypeScript.

Related