HTML escaped in Rails 3
I have a method call in my view like this
<%= Navigation.with(params) do |menu|
if current_user && current_user.can_verify?
menu.item("Listings", manage_listings_path())
menu.item("Listing changes", needing_change_approval_manage_listings_path())
menu.item("Flagged Items", flagged_manage_listings_path())
menu.item("Transfers", manage_listing_transfers_path())
menu.item("Reviews", 开发者_Python百科manage_listing_reviews_path())
end
if current_user && current_user.admin?
menu.item("Log", manage_verifications_path())
menu.item("Indexer Compensations", manage_compensations_path())
menu.item("Users", manage_users_path())
end
end%>
that splits out the below string
"<li><a href="/manage/listings" class="active">Listings</a></li> <li><a href="/manage/listings/needing_change_approval">Listing changes</a></li> <li><a href="/manage/listings/flagged">Flagged Items</a></li> <li><a href="/manage/listing_transfers">Transfers</a></li> <li><a href="/manage/listing_reviews">Reviews</a></li> <li><a href="/manage/verifications">Log</a></li> <li><a href="/manage/compensations">Indexer Compensations</a></li> <li><a href="/manage/users">Users</a></li>"
I just get this string in my page. I wanted them to be menus nicely styled by CSS. I am just getting the above raw text in my page. How do I convert this string to be treated as HTML by the browser.
Please help
Here is the navigation class
class NavigationMenu < ActionView::Base
def initialize(params)
@params = params
end
def item(title, path, options={})
@items ||= Array.new
unless (route = Rails.application.routes.recognize_path(path,:method => options[:method]|| :get))
raise "Unrecognised path #{path}, are you sure it's in routes.rb?"
end
@items << content_tag(:li, link_to(title,path, :class => (@params[:controller] == route[:controller] && @params[:action] == route[:action])? 'active' : nil))
end
def output
return '' if @items.blank?
content_tag(:ul, @items.join("\n"), :id => 'navigation')
end
end
class Navigation
def self.with(params, &block)
menu = NavigationMenu.new(params)
yield menu
menu.output
end
end
You have to add a call to the raw
method:
<%= raw ... %>
This is necessary, because in Rails 3 every string is escaped by default, unless you use the raw
method.
It's like an inverse of the h
method in Rails 2, where every string is unescaped by default, unless you use the h
method.
Example:
This code in Rails 2...
<%= h "String which must be escaped" %>
<%= "String which must be output raw %>
... must be this in Rails 3:
<%= "String which must be escaped" %>
<%= raw "String which must be output raw %>
(Although an additional call to h
doesn't do any harm in Rails 3)
You need to append .html_safe
to the string - this will stop rails from escaping it when it's time to output text. Probably best to put it in the item
method that you call repeatedly.
I recently wrote an article regarding XSS protection in Rails 3 when upgrading from Rails 2: http://developer.uservoice.com/entries/upgrading-to-rails-3-printing-escaped-strings
The idea is to hook code to printing HTML so that we can determine when we are actually printing something we don't want to:
module ActionView
module Helpers
module TextHelper
def simple_format_with_double_escape_reporting(*args)
HtmlDoubleEscapeReporter.assert_sane(simple_format_without_double_escape_reporting(*args))
end
alias_method_chain :simple_format, :double_escape_reporting
end
module TagHelper
private
def content_tag_string_with_double_escape_reporting(*args)
HtmlDoubleEscapeReporter.assert_sane(content_tag_string_without_double_escape_reporting(*args))
end
alias_method_chain :content_tag_string, :double_escape_reporting
end
module UrlHelper
def link_to_with_double_escape_reporting(*args, &block)
HtmlDoubleEscapeReporter.assert_sane(link_to_without_double_escape_reporting(*args, &block))
end
alias_method_chain :link_to, :double_escape_reporting
end
end
end
Method HtmlDoubleEscapeReporter.assert_sane can be written, for example, like this:
class HtmlDoubleEscapeReporter
def self.assert_sane(str)
if (str.match(/<[a-z]/) || str.match(/&(quot|rarr|larr|amp|#)/)) &&
!str.match(/looks something you do not want to print/
send_problem_report('#{str}' looks something you do not want to print")
end
return str
end
end
Here, 'looks something you do not want to print' is used to prevent the possibility of infinite loops. The line send_problem_report('#{str}' looks something you do not want to print") can be replaced with a call to "debugger" (from ruby-debug gem) so that you are able to check the backtrace and see where the problem is coming from.
Here is the new class. At last... I got that bug.
class NavigationMenu < ActionView::Base
def initialize(params)
@params = params
end
def item(title, path, options={})
@items ||= Array.new
unless (route = Rails.application.routes.recognize_path(path,:method => options[:method]|| :get))
raise "Unrecognised path #{path}, are you sure it's in routes.rb?"
end
@items << content_tag(:li, link_to(title,path, :class => (@params[:controller] == route[:controller] && @params[:action] == route[:action])? 'active' : nil))
end
def output
@items = @items.join("\n").html_safe
return '' if @items.blank?
content_tag(:ul, @items, :id => 'navigation')
end
end
class Navigation
def self.with(params, &block)
menu = NavigationMenu.new(params)
yield menu
menu.output
end
end
精彩评论