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>
8
  • 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. Commented Aug 26, 2011 at 14:12
  • You are wrapping the JSON in <script> tags right? As long as it's within a script, the HTML will be ignored... trust me you can have a JS string inside a script that has an entire page (<html><head>.. etc) of valid tags and it won't mess with rendering. Commented Aug 26, 2011 at 14:19
  • Second code snippet in my question is how I'm embedding it in HTML page. So it is inside <script> tags, and is not inside string literals, but rather like raw JS object. This is working great as long as there are no HTML special chars in the @tags variable. Commented Aug 26, 2011 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. Commented Aug 26, 2011 at 17:29
  • 3
    Bounty message below should've been: Is there really no standard way of escaping "</script>" strings inside JSON that's embedded directly into Rails views? Commented Sep 30, 2011 at 20:31

5 Answers 5

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 $

Sign up to request clarification or add additional context in comments.

2 Comments

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.
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).
9

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 :)

1 Comment

docs for json_escape
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>

1 Comment

note: another relevant stack overflow discussion -- stackoverflow.com/questions/1580647/…
1

I think that if you try this it will work:

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

(Notice the double == and the " ")

Comments

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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.