Skip to content

Commit

Permalink
Cookies may be configured with an object that will serialize and dese…
Browse files Browse the repository at this point in the history
…rialize the session cookie data. fixes #70, fixes #4
  • Loading branch information
tenderlove authored and josh committed Dec 19, 2010
1 parent a5dae21 commit 260fca3
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 5 deletions.
65 changes: 60 additions & 5 deletions lib/rack/session/cookie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -21,10 +24,63 @@ module Session
# :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

Expand All @@ -50,8 +106,7 @@ def unpacked_cookie_data(env)
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

Expand All @@ -69,7 +124,7 @@ def set_cookie(env, headers, cookie)

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)}"
Expand Down
51 changes: 51 additions & 0 deletions test/spec_session_cookie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,57 @@
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=")
Expand Down

0 comments on commit 260fca3

Please sign in to comment.