0

I'm am looking for a JavaScript equivalent for this (Java) code: org.apache.commons.lang.StringEscapeUtils.escapeJavaScript(String)

So I can use it in JavaScript like:

loop ... foo = something;
  elem.setAttribute("onclick", "bar(event, 'this:" + used_here(foo) + "');");
  foo = something else;
repeat loop

So what is missing here is the function used_here(), which would properly escape its parameter according to JavaScript string rules.

Is there such a function that is standard (supported by major browsers) ?

--

Explanation for false duplicate: This is not about HTML escaping.

So NOT THIS: " -> "
But this:    " -> \"
David Balažic
  • 1,319
  • 1
  • 23
  • 50
  • I found https://github.com/joliss/js-string-escape which suggests there is no standard function. – David Balažic Oct 15 '18 at 10:59
  • Possible duplicate of [Can I escape html special chars in javascript?](https://stackoverflow.com/questions/6234773/can-i-escape-html-special-chars-in-javascript) – StudioTime Oct 15 '18 at 11:00
  • 6
    Is there a reason why you can't simply use listeners? `elem.addListener('click', () => ...` – Blue Oct 15 '18 at 11:00
  • @DarrenSweeney [escapeJavaScript](https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/lang/StringEscapeUtils.html#escapeJavaScript(java.lang.String)) escapes stuff for strings, namely quotes and such. – VLAZ Oct 15 '18 at 11:07
  • @vlaz My apologies, I understood your comment as a suggestion to use that Java method. I will remove the comments as unnecessary. – Amadan Oct 15 '18 at 11:19

2 Answers2

2

Begging your pardon for any offense, but this is like asking how to implement an address book app for a rotary phone, or an anti-arrow armour for homing pigeons. :) In this day and age, you should almost never have the reason to have JavaScript as string in a HTML attribute.

elem.setAttribute("onclick", "alert('this:" + used_here(foo) + "');");

is these days very cleanly written as

let foo = `Chief O'Brien & Dwayne "The Rock" Johnson can now have any special chars they want!`;
let elem = document.querySelector('button');

elem.addEventListener('click', evt => {
  alert(foo);
});
<button>Click me!</button>

EDIT: the loop.

let elems = document.querySelectorAll('button');

elems.forEach((elem, i) => {
  let foo = `Button ${i + 1}`; // foo is different, but it is local
  elem.addEventListener('click', evt => {
    alert(foo);
  });
});
<button>Click me!</button>
<button>No, click me!</button>
<button>No, me!</button>

Or, to support IE8, with jQuery, intentionally not using .each, because I want to demonstrate old-school for loop where closure over foo has to be prevented:

var elems = $('button');

var i, elem, foo;
for (i = 0; i < elems.length; i++) {
  elem = elems[i];
  foo = "Button " + (i + 1);
  (function(foo) {
    $(elem).on('click', function(evt) {
      alert(foo);
    });
  })(foo);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button>Click me!</button>
<button>No, click me!</button>
<button>No, me!</button>
Blue
  • 22,608
  • 7
  • 62
  • 92
Amadan
  • 191,408
  • 23
  • 240
  • 301
  • Have to give you an upvote for the analogies alone. While my answer does clear cut answer the question: I have to go along with Amadan's answer here. You should rarely have a need to escape javascript like I did in my answer. This appears to be an [X/Y Problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). What are you trying to achieve @DavidBalažic? What do you _need_ this for? – Blue Oct 15 '18 at 11:18
  • I am fixing XSS and similar holes in a legacy application. It might need to run on IE 8 or so. – David Balažic Oct 15 '18 at 11:23
  • Amadan, when/how is the reference to foo in the function evaluated? – David Balažic Oct 15 '18 at 11:24
  • Indeed you are right, in my code it would be evaluated at click time. It is very easy to change it to pre-evaluated though - just evaluate it and let it sit in a variable. As for IE8 or so, you might have to juggle `.addEventListener` with `.attachEvent`, or rely on a library (such as jQuery) to do it for you - still nicer and safer than JS-in-attribute. However, I understand that legacy projects sometimes don't give you freedom to not be waist-deep in muck (COBOL is still a thing), so yeah, that wouldn't really fall under my "this day and age" :( – Amadan Oct 15 '18 at 11:29
  • @Amadan I did some updates to the question to make it a bit more clear, regarding these issues... – David Balažic Oct 15 '18 at 11:36
  • @Amadan Wouldn't the IE8 code be clearer if not using the name _foo_ for the inner variable (the function parameter) and outer variable? I could be wrong, but they are two different things. – David Balažic Oct 15 '18 at 12:06
  • You're right in that they _are_ two different variables. It would be better to name them differently in order to teach someone about closures or how/why this works, but it is idiomatic to name them the same in this case as this construct serves to freeze the value of the variable inside the IIFE; changing the name of the variable might obscure the fact that the only reason IIFE (and thus the new variable) are needed is to trick JavaScript into not closing over it in the event handler closure. I.e. changing the var name makes the code more readable by beginners; not changing it, for veterans. – Amadan Oct 15 '18 at 12:10
0

I converted over the escapeJavaStyleString from org.apache.commons.lang.StringEscapeUtils.escapeJavaScript into javascript:

function escapeJavaStyleString(str, escapeSingleQuote, escapeForwardSlash) {
    if (str == null) {
        return '';
    }
    const sz = str.length;
    let out = '';
    for (let i = 0; i < sz; i++) {
        const ch = str.charAt(i);
        // handle unicode
        if (ch > 0xfff) {
            out += "\\u" + ch.toString(16);
        } else if (ch > 0xff) {
            out += "\\u0" + ch.toString(16);
        } else if (ch > 0x7f) {
            out += "\\u00" + ch.toString(16);
        } else if (ch < 32) {
            switch (ch) {
                case '\b':
                    out += '\\';
                    out += 'b';
                    break;
                case '\n' :
                    out += '\\';
                    out += 'n';
                    break;
                case '\t' :
                    out += '\\';
                    out += 't';
                    break;
                case '\f' :
                    out += '\\';
                    out += 'f';
                    break;
                case '\r' :
                    out += '\\';
                    out += 'r';
                    break;
                default :
                    if (ch > 0xf) {
                        out += "\\u00" + ch.toString(16);
                    } else {
                        out += "\\u000" + ch.toString(16);
                    }
                    break;
            }
        } else {
            switch (ch) {
                case '\'' :
                    if (escapeSingleQuote) {
                        out += '\\';
                    }
                    out += '\'';
                    break;
                case '"' :
                    out += '\\';
                    out += '"';
                    break;
                case '\\' :
                    out += '\\';
                    out += '\\';
                    break;
                case '/' :
                    if (escapeForwardSlash) {
                        out += '\\';
                    }
                    out += '/';
                    break;
                default :
                    out += ch;
                    break;
            }
        }
    }
    
    return out;
}

console.log(escapeJavaStyleString('testing("bleh")'));
Blue
  • 22,608
  • 7
  • 62
  • 92
  • As mentioned in the question, I already found an implementation. Here: https://github.com/joliss/js-string-escape/blob/master/index.js – David Balažic Oct 15 '18 at 11:22
  • @DavidBalažic So use that, or see the other posted answers about listeners. You're asking for a non-archaic way to solve an archaic issue. Callbacks aren't done like this anymore: Period. It's bad coding, which is why your app is probably open up for XSS attacks. Rewrite the code for listeners, or use the solution you've already found. – Blue Oct 15 '18 at 11:29