Rails 3 and HTML escape from within helpers - ruby-on-rails

From one ERB view, I have this helper call:
<p><%=progress #object.progress %></p>
This is the helper method (I've simplified it):
def progress(value)
s = content_tag(:span, "pre:")
s += " <strong>#{value} %</strong>"
return s.html_safe
end
It seems that if you merge those two types of HTML strings, the latest part is not rendered properly. You'll see this:
pre: <strong>40 %</strong>
If you combine the strings like so:
def progress(value)
s = content_tag(:span, "pre:")
s += content_tag(:strong, " #{value} %")
return s.html_safe
end
everything work!

String returned from content_tag is marked as html_safe, when you add other unsafe string it's escaped before concat.
Here's a nice explanation on how the SafeBuffers (the class that does the html_safe magic) work: http://yehudakatz.com/2010/02/01/safebuffers-and-rails-3-0/

I think, it's happened, because string returned from content_tag marked as html_safe. So, if you try to add something to this string, it's automaticly escaped.

If you are using first example then add to_s for a type transformation.

Related

Getting a double line break with a concat content_tag [duplicate]

My helper works like this:
def some_help(in_string)
in_string + " and more"
end
But I want it do to a before the output and I keep getting the < br > characters themselves literally, i.e. not a break but what I want is a < br > that is the problem.
so
def some_help(in_string)
"<br/>" + in_string + " and more"
end
doesn't work right.
Use tag(:br) instead of "<br/>".
content_tag(:br) creates opening and closing br tags and using raw or html_safe is just ugly (not to mention dangerous).
you can also use the "content_tag" view helper.
http://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-content_tag
def some_help
content_tag(:br) + "some help"
end
I'm not sure I understand you. You add <br /> in your string, and it stays in plain while you want it to have the effect of a newline ? If it is that, you have to mark your string as html-safe. you do this with "somestring".html_safe.
Rails automatically escapes HTML characters. Use .html_safe on the string.

Rails escapes string inside label tag

I have a label tag, whose content is loaded from a en.yml file.
html.erb
<%=label_tag(:name, t(:name, scope:[:helpers, :form], name: person_name(person))).html_safe%>
person_name is a helper and outputs a string
persons_helper.rb
def person_name(person)
content_tag(:span,
formatted_name(person.name) || t("helpers.persons.default_name"),
class: 'name').html_safe
end
output string from the helper is passed on t method and concatenated as following
en.yml
name: "Person Name: (%{name})"
I want the output to be like
<label for="person">
Person Name:
<span class='name> John Doe </span>
</label>
but Instead I get
<label for="person">
Person Name:(<span class="name">John Doe</span>)
</label>
I understand that it got to do with html_safe, raw and escaping strings but I just could not get it to work!
Thanks!
Call .html_safe on the method call inside the label_tag. E.g:
<%=label_tag(:name, t(:name, scope:[:helpers, :form], name: person_name(person).html_safe))%>
It appears that the I18n.t method does not return a SafeBuffer (i.e. an html_safe string). So you should call .html_safe on the output from this method.
<%= label_tag(:name, t(:name, scope:[:helpers, :form], name: person_name(person)).html_safe) %>
Note the .html_safe call has been moved in one parenthesis from where you had it. This can also be made marginally easier to see by using the block form of the label_tag helper.
<%= label_tag(:name) { t("helpers.form.name", name: person_name(person)).html_safe } %>
Note: I also switched to the "helpers.form.name" method of selecting the I18n translation in this example to further increase readability (but this may be just a personal preference -- so use your original style if you prefer!).
Finally, for security purposes -- so that a user's name doesn't come through unescaped -- you should remove the .html_safe from your person_name helper and add a strict html_escape (or sanitize) so that it looks like this:
def person_name(person)
content_tag(:span,
h(formatted_name(person.name)) || t("helpers.persons.default_name"),
class: 'name')
end
In this form, the content_tag will make sure everything is html_safe except for the content. Meaning that the person.name will come through as is and be escaped as needed. However, this is not needed if the formatted_name method returns an already escaped or html_safe name. Basically the point is that you don't want to blindly mark strings as html_safe when they come from user inputted values because you don't know if they contain script tags or what. Hopefully this didn't confuse. :) In general, only mark strings as html_safe when you are 100% sure that they are actually always going to be safe (i.e. they come from within your system and not from user input of any sort).
Rails translations can be automatically marked as html_safe but using a naming convention. If a translation is suffixed with _html then the string is marked as html_safe, likewise keys named html are also marked as html_safe.
# config/locales/en.yml
en:
welcome: <b>welcome!</b>
hello_html: <b>hello!</b>
title:
html: <b>title!</b>
In the above t('hello_html') and t('title.html') will be html_safe strings and will not require a call to raw or .html_safe where as t('welcome') will not be html_safe and will require calling raw or .html_safe to avoid the html in the string being escaped.
See http://guides.rubyonrails.org/i18n.html#using-safe-html-translations

Returning HTML from a Ruby Gem

I'm trying to create a Ruby gem that returns html mark up like so:
class Hola
def self.hi(name = "world")
"hello #{name}"
end
def self.hi_with_markup(name = "world")
"<strong>hello #{name}</strong>"
end
end
However, whenever I try to use it in a test.html.erb file like so:
<%= Hola.hi_with_markup(", please work!") %>
It returns the string with the tags printed instead of actually rendering the html. How can I fix this from the gem side?
Thanks!
In Rails 3 the default changed from "not" escaping HTML to escaping HTML (i.e. converting things like '>' to >) for any String deemed to be unsafe; which is generally any string that has the potential to have user characters, including the output of your gem. There are two ways around this raw() and .html_safe.
Here's a comprehensive answer: raw vs. html_safe vs. h to unescape html
The short answer is to do this:
<%= Hola.hi_with_markup(", please work!").html_safe %>
or
<%= raw(Hola.hi_with_markup(", please work!")) %>
Try this:
class Hola
def self.hi(name = "world")
"hello #{name}"
end
def self.hi_with_markup(name = "world")
"<strong>hello #{name}</strong>".to_html
end
end

Rails 3.0.2 Array#join HTML Safe?

I have a rails gem that uses a snippet like:
components = []
components << label_for(attribute)
components << ...
components << text_field(attribute)
return components.join
The gem worked fine in Rails 3.0.1, however it escapes (renders as text in a browser) all HTML after updating to Rails 3.0.2. What am I doing something wrong? Thanks.
As #sj26 points out, either use the rails built-in helper:
<%= safe_join(components) %>
Or use my rails_join gem to make Array#join html-safe aware, in which case your original code will work as is.
String#join isn't SafeBuffer-aware.
String#html_safe marks that you string is already HTML-escaped, preventing users sneaking bits of HTML into your pages. Check out this post by Yehuda Katz on SafeBuffer and why/how you should be using them.
If you have an array of String and SafeBuffer you want to concatenate, make sure you've run #html_safe on them all, or #concat them into a SafeBuffer like so:
['<one>', '<p>two</p>'.html_safe].inject ''.html_safe, &:concat
=> "<one><p>two</p>"
Rails has a built-in helper called safe_join which will do this for you.
http://makandra.com/notes/954-don-t-mix-array-join-and-string-html_safe
class Array
def html_safe_join(delimiter='')
''.html_safe.tap do |str|
each_with_index do |element, i|
str << delimiter if i > 0
str << element
end
end
end
end
[safe_string, unsafe_string].html_safe_join(' ')
# '<span>foo</span><span&t;bar</span>'
Strings are automatically HTML-escaped in Rails3. You need to change that last line to:
return components.join.html_safe
alternately, if editing the gem is too much hassle you can do it from the view:
<%= helper_name.html_safe %>
how about manual quoting?
<%= raw ['<div>', 'any', '</div>'].map{|val| h(val)}.join('<br />') %>

Don't escape html in ruby on rails

rails 3 seems to escape everything, including html. I have tried using raw() but it still escapes html. Is there a workaround? This is my helper that I am using (/helpers/application_helper.rb):
module ApplicationHelper
def good_time(status = true)
res = ""
if status == true
res << "Status is true, with a long message attached..."
else
res << "Status is false, with another long message"
end
end
end
I am calling the helper in my view using this code:
<%= raw(good_time(true)) %>
You can use .html_safe like this:
def good_time(status = true)
if status
"Status is true, with a long message attached...".html_safe
else
"Status is false, with another long message".html_safe
end
end
<%= good_time(true) %>
I ran into this same thing and discovered a safer solution than using html_safe, especially once you introduce strings which are dynamic.
First, the updated code:
def good_time(long_message1, long_message2, status = true)
html = "".html_safe
html << "Status is #{status}, "
if status
html << long_message1
else
html << long_message2
end
html
end
<%= good_time(true) %>
This escapes long_message content if it is unsafe, but leaves it unescaped if it is safe.
This allows "long message for success & such." to display properly, but also escapes "malicious message <script>alert('foo')</script>".
The explanation boils down to this -- 'foo'.html_safe returns an ActiveSupport::SafeBuffer which acts like a String in every way except one: When you append a String to a SafeBuffer (by calling + or <<), that other String is HTML-escaped before it is appended to the SafeBuffer. When you append another SafeBuffer to a SafeBuffer, no escaping will occur. Rails is rendering all of your views under the hood using SafeBuffers, so the updated method above ends up providing Rails with a SafeBuffer that we've controlled to perform escaping on the long_message "as-needed" rather than "always".
Now, the credit for this answer goes entirely to Henning Koch, and is explained in far more detail at Everything you know about html_safe is wrong -- my recap above attempts only to provide the essence of the explanation in the event that this link ever dies.

Resources