Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

innerHTML: How To Avoid

I'm writing a plugin which will convert emoticons to images in a text block for a particular site. The easy answer is to use regular expressions to detect the trigger text on the innerHTML and insert the img tag, then pipe the string back to the dom element in the innerHTML part. The DOM element block may already have anchor <a> and/or text formatting <b>,<i>,<u> in the innerHTML part.

For example

    var textBlock = pItems[i].innerHTML;
    var kissSource = 'https://mail.google.com/mail/e/35D';
    textBlock = textBlock.replace(/(^|[^&lt;]|[^&gt;]):\*/g, "<img class='emoticon' src='" + kissSource + "' />");
 -->      pItems[i].innerHTML = textBlock;  //<-can I avoid this to be in compliance with Mozilla addons reqmnts

I searched for quite a few hours on how I might convert a DOM (or HTML string) to XML (I know: HTML!=XLM How to read HTML as XML? but perhaps a portion of a div?) which in turn could be converted to JSON who in turn .. could be converted back into a DOM following: https://developer.mozilla.org/en-US/docs/XUL/School_tutorial/DOM_Building_and_HTML_Insertion?redirectlocale=en-US&redirectslug=XUL_School%2FDOM_Building_and_HTML_Insertion#JSON_Templating

I do not know jQuery, so please if it is possible with javascript that would be ideal.

Should I be solving this problem without running the regular expression against the innerHTML thus avoiding the problme?

Thanks so much, JennaS

like image 257
JennaS Avatar asked Mar 15 '13 00:03

JennaS


People also ask

Should I avoid using innerHTML?

The use of innerHTML creates a potential security risk for your website. Malicious users can use cross-site scripting (XSS) to add malicious client-side scripts that steal private user information stored in session cookies.

What can I use instead of innerHTML?

For that reason, it is recommended that instead of innerHTML you use: Element. SetHTML() to sanitize the text before it is inserted into the DOM.

What is the disadvantage of innerHTML?

Disadvantages of innerHTMLIt is very slow because as inner HTML already parses the content even we have to parse the content again so that's why it takes time. When we have used the event handlers then the event handlers are not automatically attached to the new elements created by innerHTML.

Is innerHTML safe from XSS?

innerHTML property is debatable and depends on its use. It's a security issue if it inserts user-provided values, but if you use it to insert static data, or something generated without including any inputs from the user, it's not a security concern.


2 Answers

Working Solution

Based on your recent comment to @Giuseppe, and your malformed styling that you borrowed from my post the only solution would be to avoid recursion or iterating through the text strings looking for matching elements for your regEx.

  1. Apply the regex to your string as you are proposing.
  2. Once finished build a DOM from that string using a HTMLify string parser
  3. Replace the node with the new DOM node built from the string.

NB: This is also useful when pulling in AJAX HTML page and you need to parse the HTML results in a temporary DOM object but don't want to merely dump the contents into a the innerHTML of a newly created element. Also note that using createDocumentFragment will not be suitable as you cannot navigate a fragment like a DOM tree.

The steps sound hard but there are a few great posts on Stackoverflow which make it easy!
After doing research for you and running into a now obsolete solution and dom parsers which won't work for you I came across a solution from @rob-w: a dom parser

Your code would include the DOM parser from @rob-w link plus:

     /* 
      * DOMParser HTML extension 
      * 2012-02-02 
      * 
      * By Eli Grey, http://eligrey.com 
      * Public domain. 
      * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 
      */
     /*! @source https://gist.github.com/1129031 */
     /*global document, DOMParser*/ 
             (function (DOMParser) {
         "use strict";
         var DOMParser_proto = DOMParser.prototype;
         var real_parseFromString = DOMParser_proto.parseFromString;

         // Firefox/Opera/IE throw errors on unsupported types  
         try {
             // WebKit returns null on unsupported types  
             if ((new DOMParser).parseFromString("", "text/html")) {
                 // text/html parsing is natively supported  
                 return;
             }
         } catch (ex) {}

         DOMParser_proto.parseFromString = function (markup, type) {
             if (/^\s*text\/html\s*(?:;|$)/i.test(type)) {
                 var doc = document.implementation.createHTMLDocument("");
                 var doc_elt = doc.documentElement;
                 var first_elt;

                 doc_elt.innerHTML = markup;
                 first_elt = doc_elt.firstElementChild;

                 if (doc_elt.childElementCount === 1 && first_elt.localName.toLowerCase() === "html") {
                     doc.replaceChild(first_elt, doc_elt);
                 }

                 return doc;
             } else {
                 return real_parseFromString.apply(this, arguments);
             }
         };
     }(DOMParser));

     autostyle = function (str) {
         var boldPattern = /(?![^<]*<\/a>)(^|<.>|[\s\W_])\*(\S.*?\S)\*($|<\/.>|[\s\W_])/g;
         var italicsPattern = /(?![^<]*<\/a>)(^|<.>|[\s\W])_(\S.*?\S)_($|<\/.>|[\s\W])/g;
         var strikethroughPattern = /(?![^<]*<\/a>)(^|<.>|[\s\W_])-(\S.*?\S)-($|<\/.>|[\s\W_])/gi;
         var underlinePattern = /(?![^<]*<\/a>)(^|<.>|[\s\W_])!(\S.*?\S)!($|<\/.>|[\s\W_])/gi;
         str = str.replace(strikethroughPattern, '$1<s>$2</s>$3');
         str = str.replace(italicsPattern, '$1<i>$2</i>$3');
         str = str.replace(boldPattern, '$1<b>$2</b>$3');
         str = str.replace(underlinePattern, '$1<u>$2</u>$3');
         return str;
     };

     emoticonRegexFunction = function(str) {
         //do something
         return str;
     }

     RegexWithoutInnerHTML = function () {
         pItems = document.getElementsByTagName('p');
         for (var k = 0; k < pItems.length; k++) {
             var str = pItems[k].textContent;
             str = autostyle(str);
             str = emoticonRegexFunction(str);
             var doc = new DOMParser().parseFromString('<p>' + str + '</p>', 'text/html');
             pItems[k].parentNode.replaceChild(doc.getElementsByTagName('p')[0], pItems[k]);
             //       pItems[k].innerHTML = str;  //<-now do not need innerHTML
         }
     };

Full working example on jsbin at: http://jsbin.com/itiwek/12/edit

Enjoy.

like image 126
Mike Wolfe Avatar answered Oct 21 '22 20:10

Mike Wolfe


If I understand you correctly, you want to convert say ":)" to an emoticon. To do that, you need to parse the text and replace those characters with an img element. So you want to do something like:

<script>

function toArray(collection) {
  var arr = [];
  for (var i=0, iLen=collection.length; i<iLen; i++) {
    arr[i] = collection[i];
  }
  return arr;
}

function replaceTextWithEmoticon(el) {
  var re0 = /\:\)/g;
  var emo0 = document.createElement('img');
  emo0.src = 'https://mail.google.com/mail/e/35D'; 
  var node, txt, frag;
  var nodes = toArray(el.childNodes);

  for (var i=0, iLen=nodes.length; i<iLen; i++) {
    node = nodes[i];

    // If it's an element node, process the content
    if (node.nodeType == 1) {
      replaceTextWithEmoticon(node);

    // If it's a text node, look for matching text and replace with emoticon
    } else if (node.nodeType == 3 && re0.test(node.data)) {
      txt = node.data.split(re0);
      frag = document.createDocumentFragment();

      for (var j=0, jLen=txt.length - 1; j<jLen; j++) {

        frag.appendChild(document.createTextNode(txt[j]));
        frag.appendChild(emo0.cloneNode(false));
      }

      if (txt[j] && txt[j].length) {
        frag.appendChild(document.createTextNode(txt[j]));
      }
    }
    node.parentNode.replaceChild(frag, node);
  }
}

</script>

<p id="p0">here is a smily:) and another two:):)</p>
<button onclick="
  replaceTextWithEmoticon(document.getElementById('p0'));
">Replace with emoticon</button>

The above needs to be modified to deal with multiple different emoticons in each text node, but I think you get the idea.

like image 21
RobG Avatar answered Oct 21 '22 19:10

RobG