Merged updates from trunk into stable branch
[feedcatcher.git] / vendor / rails / actionpack / lib / action_controller / vendor / rack-1.0 / rack / session / abstract / id.rb
1 # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
2 # bugrep: Andreas Zehnder
3
4 require 'time'
5 require 'rack/request'
6 require 'rack/response'
7
8 module Rack
9
10 module Session
11
12 module Abstract
13
14 # ID sets up a basic framework for implementing an id based sessioning
15 # service. Cookies sent to the client for maintaining sessions will only
16 # contain an id reference. Only #get_session and #set_session are
17 # required to be overwritten.
18 #
19 # All parameters are optional.
20 # * :key determines the name of the cookie, by default it is
21 # 'rack.session'
22 # * :path, :domain, :expire_after, :secure, and :httponly set the related
23 # cookie options as by Rack::Response#add_cookie
24 # * :defer will not set a cookie in the response.
25 # * :renew (implementation dependent) will prompt the generation of a new
26 # session id, and migration of data to be referenced at the new id. If
27 # :defer is set, it will be overridden and the cookie will be set.
28 # * :sidbits sets the number of bits in length that a generated session
29 # id will be.
30 #
31 # These options can be set on a per request basis, at the location of
32 # env['rack.session.options']. Additionally the id of the session can be
33 # found within the options hash at the key :id. It is highly not
34 # recommended to change its value.
35 #
36 # Is Rack::Utils::Context compatible.
37
38 class ID
39 DEFAULT_OPTIONS = {
40 :path => '/',
41 :domain => nil,
42 :expire_after => nil,
43 :secure => false,
44 :httponly => true,
45 :defer => false,
46 :renew => false,
47 :sidbits => 128
48 }
49
50 attr_reader :key, :default_options
51 def initialize(app, options={})
52 @app = app
53 @key = options[:key] || "rack.session"
54 @default_options = self.class::DEFAULT_OPTIONS.merge(options)
55 end
56
57 def call(env)
58 context(env)
59 end
60
61 def context(env, app=@app)
62 load_session(env)
63 status, headers, body = app.call(env)
64 commit_session(env, status, headers, body)
65 end
66
67 private
68
69 # Generate a new session id using Ruby #rand. The size of the
70 # session id is controlled by the :sidbits option.
71 # Monkey patch this to use custom methods for session id generation.
72
73 def generate_sid
74 "%0#{@default_options[:sidbits] / 4}x" %
75 rand(2**@default_options[:sidbits] - 1)
76 end
77
78 # Extracts the session id from provided cookies and passes it and the
79 # environment to #get_session. It then sets the resulting session into
80 # 'rack.session', and places options and session metadata into
81 # 'rack.session.options'.
82
83 def load_session(env)
84 request = Rack::Request.new(env)
85 session_id = request.cookies[@key]
86
87 begin
88 session_id, session = get_session(env, session_id)
89 env['rack.session'] = session
90 rescue
91 env['rack.session'] = Hash.new
92 end
93
94 env['rack.session.options'] = @default_options.
95 merge(:id => session_id)
96 end
97
98 # Acquires the session from the environment and the session id from
99 # the session options and passes them to #set_session. If successful
100 # and the :defer option is not true, a cookie will be added to the
101 # response with the session's id.
102
103 def commit_session(env, status, headers, body)
104 session = env['rack.session']
105 options = env['rack.session.options']
106 session_id = options[:id]
107
108 if not session_id = set_session(env, session_id, session, options)
109 env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
110 [status, headers, body]
111 elsif options[:defer] and not options[:renew]
112 env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE
113 [status, headers, body]
114 else
115 cookie = Hash.new
116 cookie[:value] = session_id
117 cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
118 response = Rack::Response.new(body, status, headers)
119 response.set_cookie(@key, cookie.merge(options))
120 response.to_a
121 end
122 end
123
124 # All thread safety and session retrival proceedures should occur here.
125 # Should return [session_id, session].
126 # If nil is provided as the session id, generation of a new valid id
127 # should occur within.
128
129 def get_session(env, sid)
130 raise '#get_session not implemented.'
131 end
132
133 # All thread safety and session storage proceedures should occur here.
134 # Should return true or false dependant on whether or not the session
135 # was saved or not.
136 def set_session(env, sid, session, options)
137 raise '#set_session not implemented.'
138 end
139 end
140 end
141 end
142 end