diff options
author | Aaron Patterson <aaron.patterson@gmail.com> | 2010-12-18 06:01:13 +0800 |
---|---|---|
committer | Joshua Peek <josh@joshpeek.com> | 2010-12-20 04:00:54 +0800 |
commit | 260fca3dd54d071bd0e69cd6a6381c979acc9f36 (patch) | |
tree | d06e183941f53d2bc28ac0a911bd189d4515d387 | |
parent | a5dae21c4f9044a7422aebd6f1137ef94a20f684 (diff) | |
download | rack-260fca3dd54d071bd0e69cd6a6381c979acc9f36.tar.gz |
Cookies may be configured with an object that will serialize and deserialize the session cookie data. fixes #70, fixes #4
-rw-r--r-- | lib/rack/session/cookie.rb | 65 | ||||
-rw-r--r-- | test/spec_session_cookie.rb | 51 |
2 files changed, 111 insertions, 5 deletions
diff --git a/lib/rack/session/cookie.rb b/lib/rack/session/cookie.rb index d398a0a0..b9bbea91 100644 --- a/lib/rack/session/cookie.rb +++ b/lib/rack/session/cookie.rb @@ -8,8 +8,11 @@ module Rack module Session # Rack::Session::Cookie provides simple cookie based session management. - # The session is a Ruby Hash stored as base64 encoded marshalled data - # set to :key (default: rack.session). + # By default, the session is a Ruby Hash stored as base64 encoded marshalled + # data set to :key (default: rack.session). The object that encodes the + # session data is configurable and must respond to +encode+ and +decode+. + # Both methods must take a string and return a string. + # # When the secret key is set, cookie data is checked for data integrity. # # Example: @@ -21,10 +24,63 @@ module Rack # :secret => 'change_me' # # All parameters are optional. + # + # Example of a cookie with no encoding: + # + # Rack::Session::Cookie.new(application, { + # :coder => Racke::Session::Cookie::Identity.new + # }) + # + # Example of a cookie with custom encoding: + # + # Rack::Session::Cookie.new(application, { + # :coder => Class.new { + # def encode(str); str.reverse; end + # def decode(str); str.reverse; end + # }.new + # }) + # class Cookie < Abstract::ID + # Encode session cookies as Base64 + class Base64 + def encode(str) + [str].pack('m') + end + + def decode(str) + str.unpack('m').first + end + + # Encode session cookies as Marshaled Base64 data + class Marshal < Base64 + def encode(str) + super(::Marshal.dump(str)) + end + + def decode(str) + ::Marshal.load(super(str)) rescue nil + end + end + end + + # Use no encoding for session cookies + class Identity + def encode(str); str; end + def decode(str); str; end + end + + # Reverse string encoding. (trollface) + class Reverse + def encode(str); str.reverse; end + def decode(str); str.reverse; end + end + + attr_reader :coder + def initialize(app, options={}) @secret = options.delete(:secret) + @coder = options.delete(:coder) || Base64::Marshal.new super(app, options.merge!(:cookie_only => true)) end @@ -50,8 +106,7 @@ module Rack session_data = nil unless digest == generate_hmac(session_data) end - data = Marshal.load(session_data.unpack("m*").first) rescue nil - data || {} + coder.decode(session_data) || {} end end @@ -69,7 +124,7 @@ module Rack def set_session(env, session_id, session, options) session = persistent_session_id!(session, session_id) - session_data = [Marshal.dump(session)].pack("m*") + session_data = coder.encode(session) if @secret session_data = "#{session_data}--#{generate_hmac(session_data)}" diff --git a/test/spec_session_cookie.rb b/test/spec_session_cookie.rb index e37141fd..7e6bfb15 100644 --- a/test/spec_session_cookie.rb +++ b/test/spec_session_cookie.rb @@ -18,6 +18,57 @@ describe Rack::Session::Cookie do Rack::Response.new("Nothing").to_a end + describe 'Base64' do + it 'uses base64 to encode' do + coder = Rack::Session::Cookie::Base64.new + str = 'fuuuuu' + coder.encode(str).should.equal [str].pack('m') + end + + it 'uses base64 to decode' do + coder = Rack::Session::Cookie::Base64.new + str = ['fuuuuu'].pack('m') + coder.decode(str).should.equal str.unpack('m').first + end + + describe 'Marshal' do + it 'marshals and base64 encodes' do + coder = Rack::Session::Cookie::Base64::Marshal.new + str = 'fuuuuu' + coder.encode(str).should.equal [::Marshal.dump(str)].pack('m') + end + + it 'marshals and base64 decodes' do + coder = Rack::Session::Cookie::Base64::Marshal.new + str = [::Marshal.dump('fuuuuu')].pack('m') + coder.decode(str).should.equal ::Marshal.load(str.unpack('m').first) + end + + it 'rescues failures on decode' do + coder = Rack::Session::Cookie::Base64::Marshal.new + coder.decode('lulz').should.equal nil + end + end + end + + it 'uses a coder' do + identity = Class.new { + attr_reader :calls + + def initialize + @calls = [] + end + + def encode(str); @calls << :encode; str; end + def decode(str); @calls << :decode; str; end + }.new + cookie = Rack::Session::Cookie.new(incrementor, :coder => identity) + res = Rack::MockRequest.new(cookie).get("/") + res["Set-Cookie"].should.include("rack.session=") + res.body.should.equal '{"counter"=>1}' + identity.calls.should.equal [:decode, :encode] + end + it "creates a new cookie" do res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/") res["Set-Cookie"].should.include("rack.session=") |