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.
Related
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
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
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.
I have a controller which does the following line before rendering the view and outputting an error.
flash[:error]="Flash error"
flash[:info] = "Flash info"
I would like to format this nicely. For that I wrote a helper which looks like this
def show_flash
a=""
[:success, :info, :error, :warning].each do |key|
a += content_tag(:div, flash[key], :id => key, :class => "#{key}") unless flash[key].blank?
end
end
In my view, I call:
<%= show_flash %>
When I try to run this, the web page renders the full text of show_flash, including the div tags, angle brackets and all. When I inspect the element (using Firefox or Chrome), it shows the text surrounded with double quotes.
Then I tried changing one line in the helper as follows:
a = content_tag(:div, flash[key], :id=>key, :class=>"#{key]") unless flash[key].blank?
i.e. I would only capture the last content tag (error) instead of both of them.
In the second case, the web browser rendered the div tag formatted properly with my CSS rules for the "error" class. I didn't see any div tags printed out in the browser.
Why did concatenating two content_tag elements cause me this grief?
I appreciate any help you can give me.
Because "" wasn't marked as html_safe. This is part of Rails' XSS protection that is enabled by default in Rails 3.
You may find this Railscast on XSS protection informative.
It turns out that when going from Rails 2 to Rails 3, html escaping is enabled by default, and you must explicitly disable it before concatenating content_tag strings. The code looks like:
def show_flash
a=content_tag(:span, "",:escape=>false)
[:success, :info, :error, :warning].each do |key|
a = a+content_tag(:div, flash[key], :id => key, :class => "#{key}", :escape=>false) unless flash[key].blank?
end
a
end
That option, :escape=>false is what it took to make it work.
Andrew Marshall pointed me in the right direction, and after some searching, I stumbled on the words of wisdom from Yehuda. That's where the :escape clause became obvious.
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