XF 1.5 Escaping characters (esp. in usernames)

Neilski

Member
As an XF noob I'm still fumbling here so please don't hurt me ;)
I have a bit of Javascript embedded in a template (designed to copy some text, including usernames, to the clipboard).
It worked really nicely until somebody with an apostrophe in their username came along and broke it.

Mike's post here worries me a bit:
There aren't character limits by default.
because even if I find the documentation for the jsEscape helper mentioned here:
For reference:

XenForo_Template_Helper_Core::jsEscape
(I can't currently find it despite searching the forum) I'm guessing it's still going to be tricky to escape an arbitrary set of single and double quotes, along with who knows what else by way of fun characters.

Is there some kind of simple solution to this, that will allow me to have a username inside a bit of Javascript without fear of it breaking because of the characters in the username itself?
 

Neilski

Member
OK, would be very grateful even for just a pointer to the documentation for the jsEscape helper routine mentioned in Jake's post above (it looks like maybe the bold text used to be a link, but it certainly isn't now).

So far, apart from the obvious googling, I've searched the
  • XF1 Manual
  • XF2 Manual
  • XF2 Developer documentation
  • this forum
without any glimmer of something that resembles documentation for this helper...
I must be missing something I guess. Help much appreciated ;)

[Edit: nvm, have just been reading Template/Helper/Core.php and I think I get it now]
 
Last edited:

Neilski

Member
OK, hit the buffers again.
I now have a fix which works fine for a username with a single quote. It breaks on a username with a double-quote.
The code I'm using is in an onclick() handler, which is using double-quotes, so I suspect that's (part of?) the problem.
The code in the template is:
Code:
onclick="setClipboard('[USER={$user.user_id}]{xen:jsescape {xen:raw $user.username}, single}[/USER]')"
and on a pair of usernames, one plain text and the other a little more "unusual" (dfff%2!+~':"'' - this actually ends with double-quote, single-quote, single-quote), I get the following code showing up in my browser, the first line of which works just fine, but the second of course breaks with an "unterminated string literal" error:
Code:
onclick="setClipboard('[USER=1234]username[/USER]')"
onclick="setClipboard('[USER=1234]dfff%2!+~\':" \'\'[="" user]')"=""
I'm still puzzling about how the second line ends up quite the way it does...

Any tips on how to smunge this around so it works? :D
 

indepth

Member
I'm not a javascript guru, but if js is like just about every other programming langauge, the jsescape routine is "escaping" those single quotes by adding a \ in front of them. The programming language then ignores the next character in the sequence if it is a single quote ' when it compiles.
 

indepth

Member
Also, looking back at your code
Code:
onclick="setClipboard('[USER=1234]username[/USER]')"
you have both double and single quotes in that command. So, the username cannot contain either unless they are escaped.

If {xen:jsescape {xen:raw $user.username}, single} only escapes single quotes, you may also need to run it again for double quotes so that both are escaped.
 

Neilski

Member
Also, looking back at your code
Code:
onclick="setClipboard('[USER=1234]username[/USER]')"
you have both double and single quotes in that command. So, the username cannot contain either unless they are escaped.

If {xen:jsescape {xen:raw $user.username}, single} only escapes single quotes, you may also need to run it again for double quotes so that both are escaped.
Hmm, yes I was wondering if it was possible to nest calls to jsEscape... Might have to give that a try.

It's easy to see in the HTML that arrives in the browser that a single quote ends up turning into \` and this does the trick nicely.
I'm just baffled though at how the username with both double and single quotes ends up getting so mangled in the HTML.
 

indepth

Member
Hmm, yes I was wondering if it was possible to nest calls to jsEscape... Might have to give that a try.

It's easy to see in the HTML that arrives in the browser that a single quote ends up turning into \` and this does the trick nicely.
I'm just baffled though at how the username with both double and single quotes ends up getting so mangled in the HTML.
Shouldn't be any problem with running the call twice, since it works based on you defining single or double quotes each time.

You should be able to get it working now. Am wondering though, why don't you just prohibit single and double quotes from usernames when people signup. That seems to be the easier solution, especially if it is a possibility you might run into more places where this could be an issue with add-ons and customization in the future.
 

Neilski

Member
You should be able to get it working now. Am wondering though, why don't you just prohibit single and double quotes from usernames when people signup.
Yes, will quite likely do that, but we already have lots of users with some form of quotes in their usernames.

Tbh, I'm rather surprised that XF permits them by default. But, that being the case, all add-ons should just work with any legal username, or the add-on is the thing that's broken (rather like my own customisation is effectively broken because it doesn't handle legal usernames).
 

Neilski

Member
OK, have utterly failed to find a way to nest two calls to jsescape...
Am starting to think it isn't possible. Anybody know different?
 

indepth

Member
Code:
onclick="setClipboard('[USER={$user.user_id}]{xen:jsescape {xen:jsescape {xen:raw $user.username}, single}}[/USER]')"
^This fails to compile? Or it doesn't create the correct result?
 

Neilski

Member
Yes, that's what I tried. It almost works, but gives me this (for username = neil'testas"df)
Code:
onclick="setClipboard('[USER=1234]neil\\'testas\" df[="" user]')"=""
so it's effectively now breaking in two ways: firstly it's putting two slashes in front of the single quote (instead of just one) which I think will probably immediately break things(?) and secondly, although it escapes the double quote, it somehow adds the weird crap at the end which I still don't understand.
NB: that weird crap apparently isn't present in the raw bytes that arrive over the network (like I say I don't understand this). The code I'm using is in the member_card template, which is a pop-up. The template code seems to be fetched as a JSON stream, and the relevant bit inside it actually looks like this (which looks vaguely sensible?!):
Code:
onclick=\"setClipboard('[USER=1234]neil\\\\'testas\\\"df[\/USER]')\"
It's when I right-click on the link in question (in Firefox) and choose "Inspect Element" that I see the code I pasted in the first Code box above.
I'm now very baffled and ready to give up. I even decided that I would just substitute out all double quotes from any usernames, and then discovered that I can't find a way to do that either (!!) - is there any way? :D

Edit: when I do the same with just one jsescape, and a username with only a single quote (neil'testasdf), I get this in the JSON stream:
Code:
onclick=\"setClipboard('[USER=1234]neil\\'testasdf[\/USER]')\"
for whatever that's worth... (NB: this bit of javascript code then works without any problems.)
 

indepth

Member
@Mike Can you tell us if this is the correct implementation of xen:jsescape?

And also whether or not it can be nested if two calls are necessary to remove both single and double quotes?
 

Mike

XenForo developer
Staff member
The JS escaping is specifically for escaping when you pass data in a JS-only context. The context you're in requires both HTML and JS escaping and isn't really something we ever do (inline JS via an on attribute); you may be able to combine escape and jsescape though I'm not positive.

I'd recommend taking a different approach which avoids ever mingling executable code with user data. You'd add 1 or 2 data- attributes to the tag (either with the content you're setting to the clipboard or one for the user ID and one for the name) and then use a listener to add the click behavior. It'd then read the attribute(s) and call setClipboard. Your attributes would only require HTML escaping then and this would happen silently.
 

Neilski

Member
you may be able to combine escape and jsescape though I'm not positive
Well, I tried that and failed, but then again I wasn't really sure how to make escape work (or even in which order to use them both ;)). I can't recall now how many permutations I tried, so it might yet be possible to make that way work.
I'd recommend taking a different approach which avoids ever mingling executable code with user data. You'd add 1 or 2 data- attributes to the tag (either with the content you're setting to the clipboard or one for the user ID and one for the name) and then use a listener to add the click behavior. It'd then read the attribute(s) and call setClipboard. Your attributes would only require HTML escaping then and this would happen silently.
Sounds good. Sadly a bit beyond my skill level but I might try and work up to it (y)

For the time being my cop-out will be to either ignore the failing cases or to insert a preg_match() to check for the presence of double-quotes and do something a little simpler...
 
Top