Updated README.rdoc again
[feedcatcher.git] / vendor / rails / actionpack / lib / action_controller / session / abstract_store.rb
1 require 'rack/utils'
2
3 module ActionController
4 module Session
5 class AbstractStore
6 ENV_SESSION_KEY = 'rack.session'.freeze
7 ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
8
9 HTTP_COOKIE = 'HTTP_COOKIE'.freeze
10 SET_COOKIE = 'Set-Cookie'.freeze
11
12 class SessionHash < Hash
13 def initialize(by, env)
14 super()
15 @by = by
16 @env = env
17 @loaded = false
18 end
19
20 def session_id
21 ActiveSupport::Deprecation.warn(
22 "ActionController::Session::AbstractStore::SessionHash#session_id " +
23 "has been deprecated. Please use request.session_options[:id] instead.", caller)
24 @env[ENV_SESSION_OPTIONS_KEY][:id]
25 end
26
27 def [](key)
28 load! unless @loaded
29 super
30 end
31
32 def []=(key, value)
33 load! unless @loaded
34 super
35 end
36
37 def to_hash
38 h = {}.replace(self)
39 h.delete_if { |k,v| v.nil? }
40 h
41 end
42
43 def data
44 ActiveSupport::Deprecation.warn(
45 "ActionController::Session::AbstractStore::SessionHash#data " +
46 "has been deprecated. Please use #to_hash instead.", caller)
47 to_hash
48 end
49
50 def inspect
51 load! unless @loaded
52 super
53 end
54
55 private
56 def loaded?
57 @loaded
58 end
59
60 def load!
61 stale_session_check! do
62 id, session = @by.send(:load_session, @env)
63 (@env[ENV_SESSION_OPTIONS_KEY] ||= {})[:id] = id
64 replace(session)
65 @loaded = true
66 end
67 end
68
69 def stale_session_check!
70 yield
71 rescue ArgumentError => argument_error
72 if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
73 begin
74 # Note that the regexp does not allow $1 to end with a ':'
75 $1.constantize
76 rescue LoadError, NameError => const_error
77 raise ActionController::SessionRestoreError, "Session contains objects whose class definition isn\\'t available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: \#{const_error.message} [\#{const_error.class}])\n"
78 end
79
80 retry
81 else
82 raise
83 end
84 end
85 end
86
87 DEFAULT_OPTIONS = {
88 :key => '_session_id',
89 :path => '/',
90 :domain => nil,
91 :expire_after => nil,
92 :secure => false,
93 :httponly => true,
94 :cookie_only => true
95 }
96
97 def initialize(app, options = {})
98 # Process legacy CGI options
99 options = options.symbolize_keys
100 if options.has_key?(:session_path)
101 options[:path] = options.delete(:session_path)
102 end
103 if options.has_key?(:session_key)
104 options[:key] = options.delete(:session_key)
105 end
106 if options.has_key?(:session_http_only)
107 options[:httponly] = options.delete(:session_http_only)
108 end
109
110 @app = app
111 @default_options = DEFAULT_OPTIONS.merge(options)
112 @key = @default_options[:key]
113 @cookie_only = @default_options[:cookie_only]
114 end
115
116 def call(env)
117 session = SessionHash.new(self, env)
118
119 env[ENV_SESSION_KEY] = session
120 env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
121
122 response = @app.call(env)
123
124 session_data = env[ENV_SESSION_KEY]
125 options = env[ENV_SESSION_OPTIONS_KEY]
126
127 if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after]
128 session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?)
129
130 sid = options[:id] || generate_sid
131
132 unless set_session(env, sid, session_data.to_hash)
133 return response
134 end
135
136 cookie = Rack::Utils.escape(@key) + '=' + Rack::Utils.escape(sid)
137 cookie << "; domain=#{options[:domain]}" if options[:domain]
138 cookie << "; path=#{options[:path]}" if options[:path]
139 if options[:expire_after]
140 expiry = Time.now + options[:expire_after]
141 cookie << "; expires=#{expiry.httpdate}"
142 end
143 cookie << "; Secure" if options[:secure]
144 cookie << "; HttpOnly" if options[:httponly]
145
146 headers = response[1]
147 unless headers[SET_COOKIE].blank?
148 headers[SET_COOKIE] << "\n#{cookie}"
149 else
150 headers[SET_COOKIE] = cookie
151 end
152 end
153
154 response
155 end
156
157 private
158 def generate_sid
159 ActiveSupport::SecureRandom.hex(16)
160 end
161
162 def load_session(env)
163 request = Rack::Request.new(env)
164 sid = request.cookies[@key]
165 unless @cookie_only
166 sid ||= request.params[@key]
167 end
168 sid, session = get_session(env, sid)
169 [sid, session]
170 end
171
172 def get_session(env, sid)
173 raise '#get_session needs to be implemented.'
174 end
175
176 def set_session(env, sid, session_data)
177 raise '#set_session needs to be implemented.'
178 end
179 end
180 end
181 end