6

Consider this:

<%
str = "http://domain.com/?foo=1&bar=2"
%>

Now these cases:

<%=str%>
# output:http://domain.com/?foo=1&amp;bar=2

<%=str.html_safe%>
# output:http://domain.com/?foo=1&bar=2

<%="#{str.html_safe}"%>
# output:http://domain.com/?foo=1&amp;bar=2

<%=""+str.html_safe%>
# output:http://domain.com/?foo=1&amp;bar=2

I need to output the URL with other strings. How can I guarantee that the ampersand will be unescaped? For reasons beyond my control I can't send &amp;.

Please help! Pulling my hair here :\

EDIT: To clarify, I actually have an array like so:

@images = [{:id=>"fooid",:url=>"http://domain.com/?foo=1&bar=2"},...]

I am creating a JS array (the image_array var) to use in my app this way:

image_array.push(<%=@images.map{|x|"{id:'#{x[:id]}',url:'#{x[:url].html_safe}'}"}.join(",")%>);

This generates:

image_array.push({id:'fooid',url:'http://domain.com/?foo=1&amp;bar=2'},...);

Which does not work in my specific case. I need the url without the amp; part.

Phrogz
  • 296,393
  • 112
  • 651
  • 745
mga
  • 1,960
  • 1
  • 23
  • 31
  • that's where the "reasons beyond my control" part comes to play... I didn't do the receiver and have no control on that... and it is an image `src`... no that it makes any difference – mga Apr 19 '12 at 16:31
  • FWIW try this: http://images.nypl.org/index.php?id=g92f032_015zf&t=r vs http://images.nypl.org/index.php?id=g92f032_015zf&t=r – mga Apr 19 '12 at 16:32
  • @phrogz added a clarification which might help... thanks – mga Apr 19 '12 at 16:45
  • I've deleted all my noise. Note that you still need to encode the ampersand if you are inside a ` – Phrogz Apr 19 '12 at 16:48
  • got it. I rephrased the question so it is more clear what I'm trying to solve. I don't really want to know the difference btw `<%=` and `<%=#{` but how to output an html_safe within a group of concatenated strings... as you can see from case 4 up there: `<%=""+str.html_safe` doesn't work either – mga Apr 19 '12 at 16:54

4 Answers4

9

When you write:

"#{foo.bar}"

this is ~equivalent to writing

foo.bar.to_s

So what you are actually doing is:

<%=str.html_safe.to_s%>

…which Rails no longer sees as being safe, and so it hits your resulting string with a round of HTML escaping.

I don't know the internals of Rails, but I assume that the html_safe method extends the string object with an instance variable flagging it as OK, but when you wrap that in another string via interpolation you are getting a new string without that flag.

Edit: To answer your needs, use raw or call html_safe on your final string:

<%=raw "foo#{str}"%>
<%="foo#{str}".html_safe%>

or in your case:

image_array.push(<%=raw @images.map{…}.join(',')%>);
image_array.push(<%=@images.map{…}.join(',').html_safe%>);

See also this question.

Community
  • 1
  • 1
Phrogz
  • 296,393
  • 112
  • 651
  • 745
2

Use this

    <%=str.html_safe.to_s%>

or

   <%=raw(str)%>   

give you better results

Kashiftufail
  • 10,815
  • 11
  • 45
  • 79
0
image_array.push(<%= @images.map{|x| "{id:'#{x[:id]}',url:'#{x[:url]}'}".html_safe }.join(",") %>);
Phrogz
  • 296,393
  • 112
  • 651
  • 745
pduey
  • 3,706
  • 2
  • 23
  • 31
  • No, once you call `join` not the array you will create a new string that is no longer flagged as being safe. – Phrogz Apr 19 '12 at 17:05
  • I would agree, if I hadn't tested it myself on RoR 3.2. Printed result to the log and it worked. Although I'd question if it's the desired result, the & is correctly unescaped. – pduey Apr 19 '12 at 17:19
0

what you would do to be safe is:

image_array.push(<%= @images.map { |image| image.as_json(only: [:id, :url]) }.to_json } %>)

this will escape the <, >, etc. properly like this:

[{"name":"\u003ch1\u003eAAAA\u003c/h1\u003e"}]

and for people coming here like me who want to concatenate strings, it's just not safe to do it, the best way is to concatenate tags, e.g.

    content_tag(:p) do
      content_tag(:span, "<script>alert(1)</script>") +
        link_to("show", user)
    end

will work fine and properly escape the first string

localhostdotdev
  • 1,795
  • 16
  • 21