24

In a Rails 3.1 app, how can I safely embed some JSON data into an HTML document?

Suppose I have this in a controller action:

@tags = [
    {name:"tag1", color:"green"}, 
    {name:"</script><b>I can do something bad here</b>", color:"red"}
]

And this in a corresponding view:

<script type="text/javascript" charset="utf-8">
    //<![CDATA[
    var tags_list = <%= @tags.to_json %>;
    // ]]>
</script>

Then I get this in resulting HTML:

var tags_list = [
    {&quot;name&quot;:&quot;tag1&quot;,&quot;color&quot;:&quot;green&quot;},
    {&quot;name&quot;:&quot;&lt;/script&gt;&lt;b&gt;I can do something bad here&lt;/b&gt;&quot;,&quot;color&quot;:&quot;red&quot;}
];

which triggers a SyntaxError: Unexpected token & in Chrome

If I remove the Rails' default HTML escaping with <%=raw tags.to_json %>, then it returns this:

var tags_list = [
    {"name":"tag1","color":"green"},
    {"name":"</script><b>I can do something bad here</b>","color":"red"}
];

which, of course, breaks the HTML document with </script>.

Can I somehow tell to_json() method to return something more like this:

var tags_list = [
    {"name":"tag1","color":"green"},
    {"name":"&lt;/script&gt;&lt;b&gt;I can do something bad here&lt;/b&gt;","color":"red"}
];

I asked this question on rubyonrails-talk mailing list, and I understand now that some people think that's a very bad idea to begin with, but in my case it works very nicely, as long as there are no HTML special chars in the data. So I just want to make the string returned by to_json HTML safe and still have JavaScript parse it properly.

UPDATE: Based on @coreyward comment, I did make it a JS string literal, and that seems to be working great now. Its not quite as elegant of a solution as I was hoping for, but its not too bad either. Here is the code that is working for me:

<% tags = [{name:"tag1", color:"green"}, {name:"</script><b>I can \n\ndo something bad here</b>", color:"red"}] %>

<script type="text/javascript" charset="utf-8">
    //<![CDATA[
    var tags_list = $.parseJSON('<%=j tags.to_json.html_safe %>');
    // ]]>
</script>

which results in:

<script type="text/javascript" charset="utf-8">
    //<![CDATA[
    var tags_list = $.parseJSON('[{\"name\":\"tag1\",\"color\":\"green\"},{\"name\":\"<\/script><b>I can \\n\\ndo something bad here<\/b>\",\"color\":\"red\"}]');
    // ]]>
</script>
nnc
  • 1,003
  • 2
  • 8
  • 9
  • HTML enclosed in a JS string literal shouldn't affect the rendering of the page. It should be treated like any other character in a string. You might want to investigate what is *really* happening when you use `raw tags.to_json`. – coreyward Aug 26 '11 at 14:12
  • You **are** wrapping the JSON in ` – Rudu Aug 26 '11 at 14:19
  • Second code snippet in my question is how I'm embedding it in HTML page. So it is inside ` – nnc Aug 26 '11 at 14:52
  • @coreyward you gave me an idea that I could just make it valid JS string and parse it with jQuery, which actually led me to a solution that I'm reasonably happy with. Thanks for that! :) So instead of doing `var tags_list = <%= @tags.to_json %>;` I'm now doing `var tags_list = $.parseJSON('<%=j tags.to_json.html_safe %>');` and that gets the job done well enough for me. Originally I wanted to have it in HTML as plain JS object/hash, but I'd rather have jQuery do the parsing, then me doing the proper HTML escaping of attributes on server for each object. This seems more generic to me. – nnc Aug 26 '11 at 17:29
  • 3
    Bounty message below should've been: Is there really no standard way of escaping `""` strings inside JSON that's embedded directly into Rails views? – Jordan Brough Sep 30 '11 at 20:31
  • @nnc - do you have a link to your rubyonrails-talk discussion? I'm interested to hear why they think it's a bad idea and what they suggest as alternatives when you want to pass JSON data to your javascript. – Jordan Brough Sep 30 '11 at 20:40
  • found the rubyonrails-talk discussion: https://groups.google.com/group/rubyonrails-talk/browse_thread/thread/4939f2d2f179157e/7dec5995ecb5735a – Jordan Brough Sep 30 '11 at 20:48
  • There's a better and safer way http://stackoverflow.com/questions/4176511/embedding-json-objects-in-script-tags – reconbot Feb 16 '12 at 19:53

5 Answers5

10

Your code using just @tags.to_json works in rails3, if you enable it with:

   ActiveSupport.escape_html_entities_in_json = true

Otherwise, your other option is this:

   var tags_list = <%= raw @tags.to_json.gsub("</", "<\\/") %>;

This saves the client having to parse the whole thing through $

Lawrence Pit
  • 521
  • 5
  • 7
  • actually activesupport escapes <, > and &. I think it would be best to do so yourselve as well if you want to be on the safe side. – markijbema Mar 14 '12 at 13:09
  • Google gson (their java json encoder) makes a good case to replace the following characters with their unicode counterpart. < \u003c, > \u003e, & \u0026, = \u003d, ' \u0027 The reason for this is we as developers tend to stick json in a bunch of crazy places (eg attributes). – reconbot Apr 12 '13 at 19:42
8

The proper way in 2019 is to wrap obj.to_json with json_escape function. json_escape is directly intended for escaping specific HTML symbols inside JSON strings. Example below from the documentation:

json = JSON.generate({ name: "</script><script>alert('PWNED!!!')</script>"})
# => "{\"name\":\"</script><script>alert('PWNED!!!')</script>\"}"

json_escape(json)
# => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}"

JSON.parse(json) == JSON.parse(json_escape(json))
# => true

It seems this page appears on top of Google Search results, that's why I decided to provide a comment with an update :)

Toshakins
  • 138
  • 1
  • 6
  • [docs](https://web.archive.org/web/20170217044954/https://apidock.com/rails/ERB/Util/json_escape) for json_escape – sam-6174 Nov 04 '21 at 21:34
2

btw, this works but is not a good solution in my opinion:

<script type="text/javascript" charset="utf-8">
  //<![CDATA[
  var tags_list = <%=raw @tags.to_json.gsub('/', '\/') %>;
  // ]]>
</script>
Jordan Brough
  • 6,808
  • 3
  • 31
  • 31
  • note: another relevant stack overflow discussion -- http://stackoverflow.com/questions/1580647/json-why-are-forward-slashes-escaped – Jordan Brough Sep 30 '11 at 20:43
1

I think that if you try this it will work:

var tags_list = "<%== @tags.to_json.gsub('/', '\/') %>";

(Notice the double == and the " ")

Hock
  • 5,784
  • 2
  • 19
  • 25
0

For instance with this in app/layouts/application.html.slim:

    javascript:
      window.translations = #{raw t("js").to_json};

And this in the translations:

  js:
    name:
      must_be_present: Must be present<script>alert(1)</script>

The result will be escaped:

<script>window.translations = {"name":{"must_be_present":"Must be present\u003cscript\u003ealert(1)\u003c/script\u003e"}};</script>
Dorian
  • 7,749
  • 4
  • 38
  • 57