15

User enter chat messages, which gets rendered directly to the page using Mustache templates. Obviously, HTML should be escaped to prevent HTML injection, but then again links should be rendered as <a href='...'>.

There are different approaches I've tried to use {{{ ... }}} to return the unescaped HTML content, which means the link would get rendered and I need to take care of HTML escaping myself. Is there a safe way of doing that without relying on a half-baked solution I write myself?

jQuery.text() would be great, but I guess it will render the <a> again as text.

What else can I do here?

Mahoni
  • 7,088
  • 17
  • 58
  • 115

4 Answers4

10

If you don't want to write your own escaping or parsing solution there is a jQuery plugin to handle links called Linkify. You could simply escape messages and then parse them client-side.

Example of how it works:

var text = "<div>Test<br>Test<br>Test http://stackoverflow.com</div>";
$('div').text(text);

// Before: &lt;div&gt;Test&lt;br&gt;Test&lt;br&gt;Test http://stackoverflow.com&lt;/div&gt;

$('div').linkify();

// After: lt;div&gt;Test&lt;br&gt;Test&lt;br&gt;Test <a href="http://stackoverflow.com" class="linkified" target="_blank">http://stackoverflow.com</a>&lt;/div&gt;
JacobMiki
  • 286
  • 2
  • 11
0

Just an idea: You could build your own escaping function

escape : function () {
    return function(val, render) {
        var $s = $(val);
        var $elements = $s.find("*").not("a"); //add other white-listed elements seperated by comma 
        for (var i = $elements.length - 1; i >= 0; i--) {
            var e = $elements[i];
            $(e).replaceWith(e.innerHTML);
        }
        return $s.html();
    }
}

You can call the function by

{{#escape}}{{{YOUR_TEXT}}}{{/escape}}

I have not tested this. This solution needs jQuery. The code above is based on this solution: https://stackoverflow.com/a/27764431/1479486

Community
  • 1
  • 1
Tim Rasim
  • 655
  • 7
  • 20
0

try inserting first in .text() and then use regexp for render the link with .html(). Here you can see a vanilla example:

var a="see formula a<b>c in http://test.com or https://x.com?p=3";

var hold=document.createElement('div');
hold.textContent=a;

hold.innerHTML=hold.innerHTML.replace(
    /(https?:\/\/[-$A-Za-z0-9%_?&.~+\/=]+)/g,
    '<a href="$1">$1</a>'
);

window.addEventListener('load',function(){
    document.body.appendChild(hold);
});

For a more acurate regexp you can see here

Community
  • 1
  • 1
Emilio Platzer
  • 2,327
  • 21
  • 29
0

If you end up going down the regex route the following filter and regex was the most aggressive one i found for picking up all kinds of urls that your users will try to type.

Heres a regexr to play around with it: http://regexr.com/3bjk9

(function () {
    'use strict';

    angular
        .module('core.filters')
        .filter('urlToA', urlToA);

    // --------------------

    function urlToA () {
        return function (string, noClick) {
            var urlPattern = /((?:(http|https|Http|Https|rtsp|Rtsp):\/\/(?:(?:[a-zA-Z0-9\$\-\_\.\+\!\*\'\(\)\,\;\?\&\=]|(?:\%[a-fA-F0-9]{2})){1,64}(?:\:(?:[a-zA-Z0-9\$\-\_\.\+\!\*\'\(\)\,\;\?\&\=]|(?:\%[a-fA-F0-9]{2})){1,25})?\@)?)?((?:(?:[a-zA-Z0-9][a-zA-Z0-9\-]{0,64}\.)+(?:(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])|(?:biz|b[abdefghijmnorstvwyz])|(?:cat|com|coop|c[acdfghiklmnoruvxyz])|d[ejkmoz]|(?:edu|e[cegrstu])|f[ijkmor]|(?:gov|g[abdefghilmnpqrstuwy])|h[kmnrtu]|(?:info|int|i[delmnoqrst])|(?:jobs|j[emop])|k[eghimnrwyz]|l[abcikrstuvy]|(?:mil|mobi|museum|m[acdghklmnopqrstuvwxyz])|(?:name|net|n[acefgilopruz])|(?:org|om)|(?:pro|p[aefghklmnrstwy])|qa|r[eouw]|s[abcdeghijklmnortuvyz]|(?:tel|travel|t[cdfghjklmnoprtvwz])|u[agkmsyz]|v[aceginu]|w[fs]|y[etu]|z[amw]))|(?:(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])))(?:\:\d{1,5})?)(\/(?:(?:[a-zA-Z0-9\;\/\?\:\@\&\=\#\~\-\.\+\!\*\'\(\)\,\_])|(?:\%[a-fA-F0-9]{2}))*)?(?:\b|$)/gi; // jshint ignore:line

            return string ? string.replace(urlPattern, replace) : string;

            function replace (url) {
                var httpUrl = url.indexOf('http') === -1 ? 'http://' + url : url;

                if (noClick) {
                    return '<a>' + url + '</a>';
                } else {
                    return '<a href="' + httpUrl + '">' + url + '</a>';
                }
            }
        };
    }

})();
Deminetix
  • 2,866
  • 26
  • 21