I have a Ruby hash that contains a user-generated string. I need to pass this hash over to JavaScript so it can be processed. I have tried using JSON, which works most of the time, but breaks when the hash contains the text </script>. Here is the simplest code that breaks in Chrome and FF8:
<%
obj = {
:foo => '</script>'
}
%>
<script type="text/javascript">
var obj = <%= obj.to_json %>; // Results in JS syntax error
</script>
This results in the following HTML:
<script type="text/javascript">
var obj = {"foo":"</script>"};
</script>
I have also tried wrapping the JSON in quotes (both single and double) so it can be parsed via jQuery.parseJSON(), but it results in the same syntax error:
<%
obj = {
:foo => '</script>'
}
%>
<script type="text/javascript">
var json = '<%= obj.to_json %>'; // Again results in JS syntax error
</script>
It appears that the syntax error is a result of the browser trying to interpret the </script> text as the end of the script block. My current solution is to escape the JSON string as HTML, escape remaining backslashes, write it to JS as a string, unescape the HTML, and then finally parse the JSON:
<%
obj = {
:foo => '</script>'
}
json = obj.to_json
escaped_json = CGI.escapeHTML(json)
slashed_escaped_json = escaped_json..gsub(/['"\\\x0]/,'\\\\\0')
%>
<script type="text/javascript">
var json = "<%= slashed_escaped_json %>";
json = $('<div/>').html(json).text(); // unescape HTML
var obj = jQuery.parseJSON(json); // parse JSON safely
console.log(obj);
</script>
It works, but it’s ugly. Is there a better way? I don’t want to strip out </script> because I am escaping the content to HTML entities before rendering it on the page.
It turns out this is a bug in the “json” gem for Ruby. I’ve located the source on GitHub and found this pull request sitting in the queue:
https://github.com/flori/json/pull/51
The solution is to escape the forward slash, resulting in JSON like this:
Thanks for all your help and I hope this solution helps someone else!