Read more

Rails 2's CookieStore produces invalid cookie data, causing tests to break

Arne Hartherz
June 26, 2012Software engineer at makandra GmbH

Note that this seems to affect only recent Rails 2 versions.

Illustration UI/UX Design

UI/UX Design by makandra brand

We make sure that your target audience has the best possible experience with your digital product. You get:

  • Design tailored to your audience
  • Proven processes customized to your needs
  • An expert team of experienced designers
Read more Show archive.org snapshot

You will not encounter this until you are writing to the cookie more than once, but when doing so, integration tests (Cucumber) may break for you with this error:

You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.[] (NoMethodError)

Background

The regular/short cucumber backtrace is not of any help but looking at the full trace reveals that ActionPack's action_controller/integration.rb is breaking while doing this:

cookies = @headers['Set-Cookie']
cookies = cookies.to_s.split("\n") unless cookies.is_a?(Array)
cookies.each do |cookie|
  name, value = cookie.match(/^([^=]*)=([^;]*);/)[1,2]
  @cookies[name] = value
end

This won't work when you there are empty lines in a cookie -- which is exactly what happened for me. That is how my cookie looked when debugging:

(rdb:1) puts headers['Set-Cookie']
remember_token=abcdef123456; path=/; expires=Mon, 31-Dec-2029 23:03:29 GMT

foo=23; path=/; expires=Thu, 30-Jan-2020 23:03:29 GMT

bar=42; path=/; expires=Thu, 30-Jan-2020 23:03:30 GMT
_some_session=foobarbaz; path=/; expires=Thu, 31-Dec-2020 23:03:30 GMT; HttpOnly

Solution

This seems to be a bug in Rails, the CookieStore (for sessions) or Rack::Utils.set_cookie_header -- I did not bother looking closer.

My problems went away when introducing an initializer that cleans up line breaks:

module ActionController
  module Session
    CookieStore.class_eval do

      def call_with_line_break_fix(*args)
        status, headers, body = call_without_line_break_fix(*args)
        headers['Set-Cookie'].gsub! "\n\n", "\n" if headers['Set-Cookie'].present?
        [ status, headers, body ]
      end

      alias_method_chain :call, :line_break_fix

    end
  end
end

Put that into a file like config/intializers/cookie_store_with_line_break_fix.rb and you are good to go.

If you don't feel like monkey-patching, you could also write a Rack middleware Show archive.org snapshot that tidys up your cookie data. I prefer doing API-stable patches, if possible.

Posted by Arne Hartherz to makandra dev (2012-06-26 12:21)