3

I have a controller like this in my project:

class BriefcasesController < ApplicationController
  ...

  def update
    @super_power = SuperPower.find(params[:super_power_id])
    @briefcase.contents.delete(params[:super_power_id].to_s)
    flash[:notice] = "Successfully removed #{view_context.link_to(@super_power.title, super_power_path(@super_power)} from your briefcase."
    redirect_back(fallback_location: '/briefcase'
  end

end

The link_to helper is not rendering a link to the browser, rather it's printing the html: Successfully removed <a href=\"/powers/1\>flying</a> from your briefcase. I've also tried using the method #html_safe on the flash message to no avail. I'm wondering if there's a way to fix this using view_context or if there's a better way to include a link inside a Flash message.

2 Answers2

5

You need to use html_safe when outputting the flash messages - not when storing them.

<% flash.each do |key, msg| -%>
  <%= content_tag :div, msg.html_safe, class: name %>
<% end -%>

.html_safe just sets a flag on the string object that its trusted and should not be escaped.

The flash works by storing flash messages in the session storage - by default this means a cookie in the browser.

So when you do:

flash[:notice] = "foo"

You're storing the raw string "foo" in a cookie* and its unpacked back into the session on the next request. But the string is not the same Ruby object - so the html_safe flag on the string object is not persistent.

max
  • 96,212
  • 14
  • 104
  • 165
  • You may want to use `sanitize msg, tags: %w(strong em a), attributes: %w(href)` as its a lot more restrictive even though its pretty hard for a malicious user to do a XSS attack through the session cookie. – max Apr 12 '17 at 00:46
  • * Its actually a bit more complicated in reality since the session storage cookie is encrypted - but for all intents and purposes this is pretty much whats happing. – max Apr 12 '17 at 00:59
  • Most excellent, this worked!! Thanks much. One quick follow-up you may be able to help with: I realized that I also want to include a hard-coded word in the link text, something like `link_to('#{@super_power.title} power', super_power_path(@super_power))` but that doesn't evaluate correctly in the context above; ideas? – Victoria Vasys Apr 12 '17 at 01:03
  • `link_to(@super_power.title +' power', super_power_path(@super_power))` – max Apr 12 '17 at 01:08
  • Or create a helper method which outputs the link. – max Apr 12 '17 at 01:09
  • Oh my, braindead time of night, that was the problem! Thanks so much for all your help! – Victoria Vasys Apr 12 '17 at 01:18
1

Note: the following only works with relatively old versions of Rails (confirmed in 4.0 and before, possibly in 4.1). Rails used to allow custom objects to be passed in flash messages, but later changed it to only allow primitive objects (documented at https://github.com/rails/rails/issues/15522).

You need to call html_safe on the entire string. Using string interpolation ("#{some_ruby_code}") changes the "safe" link_to string back into a regular string that gets escaped.

Max
  • 1,817
  • 1
  • 10
  • 13
  • Won't work since the flash messages are stored as raw text in a cookie between requests. – max Apr 12 '17 at 00:42
  • I don't think that's true. As you mention in your answer, the flash messages are stored in the session, but the session storage is on the server side. The only thing in the cookie is the session identifier. – Max Apr 12 '17 at 00:45
  • Yes, sorry I didn't clarify; I attempted `flash[:notice] = "Successfully removed #{view_context.link_to(@super_power.title, super_power_path(@super_power)} from your briefcase.".html_safe` and it didn't change anything. – Victoria Vasys Apr 12 '17 at 00:45
  • @VictoriaVasys Please add what you have in your view showing the flash messages. It's possible that you may need to do what max mentioned in his answer, though then you have to be careful with what kind of flash messages you send. – Max Apr 12 '17 at 00:48
  • You're wrong. The default session storage in rails is CookieStorage. "CookieStore saves the session hash directly in a cookie on the client-side." http://guides.rubyonrails.org/security.html#session-storage – max Apr 12 '17 at 00:49
  • I think the same applies for memcached and redis as they don't actually persist the ruby object but rather the string value. Not sure about ActiveRecord store but I don't think it differentiates either. – max Apr 12 '17 at 00:52
  • @max Huh, I guess you're right. I could have sworn I've gotten this to work in the past like this. I'll have to take a longer look. – Max Apr 12 '17 at 00:54
  • 1
    OK, figured it out, at least partially. You used to be able to pass custom objects (like `ActiveSupport::SafeBuffer`) to the flash, and it'd work, but at some point they changed it to only allow primitive types like strings, arrays, and hashes. I'm not sure exactly when the functionality changed, but it's mentioned as already having taken place in https://github.com/rails/rails/issues/15522. – Max Apr 12 '17 at 01:48
  • Thanks for the link - makes a lot of sense since a malicious could potentially execute code on the server by manipulating the serialized objects in the session storage. – max Apr 12 '17 at 14:23