Rails 3 - raw/html_safe not working in some cases? - ruby-on-rails

I'm having difficulties with my output not being encoded even though I'm using raw or html_safe.
This one is writing out the &nbsp in my final HTML page.
def build_tag_cloud(tag_cloud, style_list)
tag_cloud.sort!{ |x,y| x.permalink <=> y.permalink }
max, min = 0, 0
tag_cloud.each do |tag|
max = tag.followers.to_i if tag.followers.to_i > max
min = tag.followers.to_i if tag.followers.to_i < min
end
divisor = ((max - min) / style_list.size) + 1
html = ""
tag_cloud.each do |tag|
name = raw(tag.name.gsub('&','&').gsub(' ',' '))
link = raw(link_to "#{name}", {:controller => "/shows", :action => "show", :permalink => tag.permalink}, :class => "#{style_list[(tag.followers.to_i - min) / divisor]}")
html += raw("<li>#{link}</li> ")
end
return raw(html.to_s)
end
What is allowed in using raw and html_safe? And how can my example above be fixed?

What class is this code from? The raw method is declared on a helper, so it can only be used on controllers and views.
Source: raw vs. html_safe vs. h to unescape html
Also, unless this method is on a view page (which would just be poor implementation of Rails' MVC), you can't use the link_to function. That helper is only available on views. Also, you shouldn't need to call raw repeatedly in this function. I would think that you could just do this:
def build_tag_cloud(tag_cloud, style_list)
...
html = ""
tag_cloud.each do |tag|
name = tag.name.gsub('&','&').gsub(' ',' ')
html += "<a href='#{shows_show_path(tag.permalink)}' class='#{style_list[(tag.followers.to_i - min) / divisor]}'>#{name}</a>"
end
html.html_safe
end

The raw and html_safe methods both specifically tell rails not to escape your output (raw is preferred, and obviates the explicit to_s).
raw Reference
html_safe Reference
I suspect the reason you are using raw is because of the li tags. I would recommend using a content tag helper for those. Then you shouldn't need to mess around with encoding at all. You may need to put the link_to method directly into the content_for :li for the link to work properly (I would try without first).
content_tag Reference

Related

Render ERB as HTML and ERB from a Rails View

I'm making a style guide where I output the code on the right that is displayed on the left.
I know that adding %% escapes ERB
I have written a helper that takes the contents of a block and renders the code in two places one showing the html and I want the other to show the source ERB that created the html.
The problem is I get back HTML where I wanted ERB.
The View Code
<%= display_code do %>
<%= link_to "Button", "/style_guide, class: "btn" %>
<% end %>
The Helper Code
module StyleGuideHelper
def display_code(&block)
content = with_output_buffer(&block)
html = ""
html << content_tag(:div, content, class: "rendered-code")
html << content_tag(:div, escape_erb(content), class: "source-code-preview")
html.html_safe
end
def escape_erb(code)
code = code.gsub("%=", "%%=")
end
end
Expected Result
Button <%= link_to "Button", "/style_guide, class: "btn" %>
Actual Result
Button Button
Cheers
The issue is that this helper runs the block (link_to "Button", ...) -- it never sees the source code inside the block, just its output. You could replace escape_erb with h to capture the resulting HTML, but that won't pop back up to the ERB that generated it.
As I see it, your options are:
Break out examples into partials, then make a helper that a) renders the partial and b) displays the underlying file.
Specify your ERB fragments as strings (heredocs?), pass the string into the helper, and have the helper a) evaluate it via ERB.new(string).result(binding) to render the result and b) display the string.
Make the helper determine what part of the view invoked it, then parse the .erb well enough to find the block. Catch is, the precise format of what you see in callers is subject to change without notice due to the way views are compiled.
Make a helper that uses crazy metaprogramming juju to evaluate the block in both an ERB context as well as your own special context that intercepts the code being evaluated and turns it back into markup.
...sorted in approximate order of complexity and odds of success.
This code below will allow you to retrieve the code for a given block.
class ERBSource
ERB = ::ActionView::Template::Handlers::ERB
def self.for(block)
new(block).source
end
attr_reader :block, :file, :line_number
def initialize(block)
#block = block
#file, #line_number = *block.source_location
end
def source
lines = File.readlines(file)
relevant_lines = lines[(line_number - 1)..-1] || []
extract_first_expression(relevant_lines)
end
private
def extract_first_expression(lines)
code = lines.slice[0,1].join # add the first two lines so it has to iterate less
lines.each do |line|
code << line
return code if correct_syntax?(compile_erb(code))
end
raise SyntaxError, "unexpected $end"
end
def correct_syntax?(code)
stderr = $stderr
$stderr.reopen(IO::NULL)
RubyVM::InstructionSequence.compile(code)
$stderr.reopen(stderr)
true
rescue Exception
$stderr.reopen(stderr)
false
end
def compile_erb(code)
ERB.erb_implementation.new(
code,
:escape => false,
:trim => (ERB.erb_trim_mode == "-")
).src
end
end
This is what the helper looks like
module StyleGuideHelper
def render_example(name, &block)
code = ERBSource.for(block)
content_tag(:h2, name) +
content_tag(:div, &block) +
content_tag(:pre, content_tag(:code, code))
end
end

How do I use form_tag from within a helper?

I have a helper that I am using to generate a form. Parameters that are used to generate the form's fields are passed into the helper. I can't figure out how to use the block outside of a template.
For example:
def generate_form(path, fields)
form_tag(path, method: :get) do
# what do I do in here?
end
end
When I render partials within the block, nothing appears in the rendered web page. If I join together a bunch of tags (field_tag, text_field_tag, etc.), then raw html appears on the page.
I am using Rails 3.1.0
Rails element helpers return strings, so you can do:
def generate_form(path, fields)
s = form_tag(path, method: :get) do
p = input_tag
p << submit_tag #(everything will be wrapped in form tag)
p #returns p from block
end
s.html_safe #returns s and avoids html escaping
end

Rails 3 and HTML escape from within helpers

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.

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.

rails: get a teaser/excerpt for an article

I have a page that will list news articles. To cut down on the page's length, I only want to display a teaser (the first 200 words / 600 letters of the article) and then display a "more..." link, that, when clicked, will expand the rest of the article in a jQuery/Javascript way. Now, I've all that figured out and even found the following helper method on some paste page, which will make sure, that the news article (string) is not chopped up right in the middle of a word:
def shorten (string, count = 30)
if string.length >= count
shortened = string[0, count]
splitted = shortened.split(/\s/)
words = splitted.length
splitted[0, words-1].join(" ") + ' ...'
else
string
end
end
The problem that I have is that the news article bodies that I get from the DB are formatted HTML. So if I'm unlucky, the above helper will chop up my article string right in the middle of an html tag and insert the "more..." string there (e.g. between ""), which will corrupt my html on the page.
Is there any way around this or is there a plugin out there that I can use to generate excerpts/teasers from an HTML string?
You can use a combination of Sanitize and Truncate.
truncate("And they found that many people were sleeping better.",
:omission => "... (continued)", :length => 15)
# => And they found... (continued)
I'm doing a similar task where I have blog posts and I just want to show a quick excerpt. So in my view I simply do:
sanitize(truncate(blog_post.body, length: 150))
That strips out the HTML tags, gives me the first 150 characters and is handled in the view so it's MVC friendly.
Good luck!
My answer here should do work. The original question (err, asked by me) was about truncating markdown, but I ended up converting the markdown to HTML then truncating that, so it should work.
Of course if your site gets much traffic, you should cache the excerpt (perhaps when the post is created/updated, you could store the excerpt in the database?), this would also mean you could allow the user to modify or enter their own excerpt
Usage:
>> puts "<p><b>Something</p>".truncate_html(5, at_end = "...")
=> <p><b>Someth...</b></p>
..and the code (copied from the other answer):
require 'rexml/parsers/pullparser'
class String
def truncate_html(len = 30, at_end = nil)
p = REXML::Parsers::PullParser.new(self)
tags = []
new_len = len
results = ''
while p.has_next? && new_len > 0
p_e = p.pull
case p_e.event_type
when :start_element
tags.push p_e[0]
results << "<#{tags.last}#{attrs_to_s(p_e[1])}>"
when :end_element
results << "</#{tags.pop}>"
when :text
results << p_e[0][0..new_len]
new_len -= p_e[0].length
else
results << "<!-- #{p_e.inspect} -->"
end
end
if at_end
results << "..."
end
tags.reverse.each do |tag|
results << "</#{tag}>"
end
results
end
private
def attrs_to_s(attrs)
if attrs.empty?
''
else
' ' + attrs.to_a.map { |attr| %{#{attr[0]}="#{attr[1]}"} }.join(' ')
end
end
end
Thanks a lot for your answers!
However, in the meantime I stumbled upon the jQuery HTML Truncator plugin, which perfectly fits my purposes and shifts the truncation to the client-side. It doesn't get any easier :-)
you would have to write a more complex parsers if you dont want to split in the middle of html elements. it would have to remember if it is in the middle of a <> block and if its between two tags.
even if you did that, you would still have problems. if some put the whole article into an html element, since the parser couldnt split it anywhere, because of the missing closing tag.
if it is possible at all i would try not to put any tags into the articles or keep it to tags that dont contain anything (no <div> and so on). that way you would only have to check if you are in the middle of a tag which is pretty simple:
def shorten (string, count = 30)
if string.length >= count
shortened = string[0, count]
splitted = shortened.split(/\s/)
words = splitted.length
if(splitted[words-1].include? "<")
splitted[0,words-2].join(" ") + ' ...'
else
splitted[0, words-1].join(" ") + ' ...'
else
string
end
end
I would have sanitized the HTML and extracted the first sentence. Assuming you have an article model, with a 'body' attribute that contains the HTML:
# lib/core_ext/string.rb
class String
def first_sentence
self[/(\A[^.|!|?]+)/, 1]
end
end
# app/models/article.rb
def teaser
HTML::FullSanitizer.new.sanitize(body).first_sentence
end
This would convert "<b>This</b> is an <em>important</em> article! And here is the rest of the article." into "This is an important article".
I solved this using following solution
Install gem 'sanitize'
gem install sanitize
and used following code, here body is text containing html tags.
<%= content_tag :div, Sanitize.clean(truncate(body, length: 200, separator: ' ', omission: "... #{ link_to '(continue)', '#' }"), Sanitize::Config::BASIC).html_safe %>
Gives excerpt with valid html.
I hope it helps somebody.
There is now a gem named HTMLTruncator that takes care of this for you. I've used it to display post excerpts and the like, and it's very robust.
If you are using Active Text, I would suggest first converting the text using to_plain_text.
truncate(sanitize(career.content.body.to_plain_text), length: 150).squish

Resources