Posts Tagged ‘rails’

P3P header hell

February 25, 2011

If you’ve ever embedded a Rails app in an iframe, then you have probably come across a big problem: IE will refuse cookies from the embedded site, so your session info gets trashed.

There’s lots of info on the web about this “feature” of IE, and the solution involves setting a P3P header to return details of your “privacy policy”. Chapter and verse here

The simple Rails equivalent is this:

class ApplicationController < ActionController::Base
  
  before_filter :set_p3p
  
  private
  
  # this is required by IE so that we can set session cookies
  def set_p3p
    headers['P3P'] = 'CP="ALL DSP COR CURa ADMa DEVa OUR IND COM NAV"'
  end
end

At first sight this seems to work. But don’t get too excited: after a few more page refreshes, it suddenly stops working, IE trashes the session, and your user is mysteriously logged out of your app.

The reason is described, along with a solution, here. In short, Rails’ etagging functionality, which is enabled by default (at least, it is in 2.3.8) means that sooner or later IE is going to request a page which Rails will know has not changed in the interim. When this happens Rails returns a 304 Not Changed header, which crucially doesn’t include your oh-so-important P3P header. Net result – IE trashes the session.

You can mangle Rails to set the P3P header regardless, but as the W3C prohibits these headers in this type of response, some webservers, including Apache, will strip them out again.

The very helpful solution described above will work, but at the expense of disabling etags for the entire app. This negates the benefit of etags and will result in slower navigation for your users, and increased load on your server. So why should everyone suffer because of Microsoft’s much-loathed browser?

The solution is to disable etags only if the browser is IE, leaving it switched on for everyone else. Stick this in your /lib directory and require it from your environment.rb:

# The purpose of this is to disable etags when processing requests from Internet Explorer
# This is because IE requires a P3P header in order to accept cookies within an iframe
# and if etags are used then subsequent calls for the same page are met with a 304 not changed
# response, which is not allowed (by W3C) to contain P3P headers.  Apache strips them even if we
# patch Rails to set them anyway

# see refs:
# Apache filters P3P headers from 304 responses: http://groups.google.com/group/rack-devel/browse_thread/thread/11da5971522b107b
# general explanation of the problem: http://tempe.st/tag/ruby-on-rails/

module ActionController
  
  class Request
    
    alias_method :etag_matches_original?, :etag_matches?
    
    def etag_matches?(etag)
      !env['HTTP_USER_AGENT'].include?('MSIE') && etag_matches_original?(etag)
    end
  
  end
 
  class Response
    
    alias_method :etag_original?, :etag?
    
    def etag?
      request.env['HTTP_USER_AGENT'].include?('MSIE') || etag_original?
    end
    
    
  end
end

can’t be referred hell

September 15, 2009

Recently we upgraded from Rails 1.2.6 to 2.3.4, which was long overdue. ¬†As you’d expect, lots of things broke, plugins needed updating etc.

Then we got this in the logs:

/!\ FAILSAFE /!\ Tue Sep 15 15:19:22 +0100 2009
Status: 500 Internal Server Error
Card can't be referred
/usr/local/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/session_store.rb:94:in `dump'
/usr/local/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/session_store.rb:94:in `marshal'
/usr/local/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/session_store.rb:150:in `marshal_data!'

Card being one of the models which we were trying to save into the session. I know that not everyone saves entire models into the session – I might write a post about that too. But whatever your preference, it should work, providing you are not serializing an object which can’t be serialized (more on that below).

It took days of messing about to figure out what to do about this. The quick answer is: require the model explicitly in the environment.rb file. No idea why. Only one of our 30+ models was affected, and there is nothing special about it that I can see.

If you’re not in a hurry, here’s the low-down on what we found out.

The error is thrown in Marshal.c, in the following code

00101 static VALUE
00102 class2path(klass)
00103     VALUE klass;
00104 {
00105     VALUE path = rb_class_path(klass);
00106     char *n = RSTRING(path)->ptr;
00107 
00108     if (n[0] == '#') {
00109         rb_raise(rb_eTypeError, "can't dump anonymous %s %s",
00110                  (TYPE(klass) == T_CLASS ? "class" : "module"),
00111                  n);
00112     }
00113     if (rb_path2class(n) != rb_class_real(klass)) {
00114         rb_raise(rb_eTypeError, "%s can't be referred", n);
00115     }
00116     return path;
00117 }

I don’t read C but I am told this means something along the lines of “check if when we recreate this object we end up with the same class and ancestry as the object we are trying to serialize”. For some reason, our Card object was failing this check.

Inserting a ruby-style equivalent in the ActiveRecord::Session::SessionStore code immediately before the call to Marshal.dump eg

data[:card].class.ancestors == Card.new.class.ancestors

failed to pick up the problem. Also, we couldn’t recreate this in the console no matter what – the object always serialized perfectly.

Marhal.c documentation says “Some objects cannot be dumped: if the objects to be dumped include bindings, procedure objects, instances of class IO, or singleton objects, a TypeError will be raised.” None of the above seems to apply – even so we commented our all the model’s custom methods and attributes – effectively making it a vanilla ActiveRecord::Base object – to no effect.

Some sort of namespace collision was the prime suspect but we eliminated all likely possibilities.

Clearly somewhere along the line the Card class definition is getting unloaded. It may be something to do with Rails Engines, which we use heavily. I don’t really like relying on a mystery solution, but so far – it works.