rack 1.4.7 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +77 -0
  3. data/{COPYING → MIT-LICENSE} +4 -2
  4. data/README.rdoc +122 -456
  5. data/Rakefile +32 -31
  6. data/SPEC +119 -29
  7. data/bin/rackup +1 -0
  8. data/contrib/rack_logo.svg +164 -111
  9. data/example/lobster.ru +2 -0
  10. data/example/protectedlobster.rb +4 -2
  11. data/example/protectedlobster.ru +3 -1
  12. data/lib/rack/auth/abstract/handler.rb +7 -5
  13. data/lib/rack/auth/abstract/request.rb +8 -6
  14. data/lib/rack/auth/basic.rb +5 -2
  15. data/lib/rack/auth/digest/md5.rb +10 -8
  16. data/lib/rack/auth/digest/nonce.rb +6 -3
  17. data/lib/rack/auth/digest/params.rb +5 -4
  18. data/lib/rack/auth/digest/request.rb +4 -2
  19. data/lib/rack/body_proxy.rb +11 -9
  20. data/lib/rack/builder.rb +63 -20
  21. data/lib/rack/cascade.rb +10 -9
  22. data/lib/rack/chunked.rb +45 -11
  23. data/lib/rack/{commonlogger.rb → common_logger.rb} +24 -15
  24. data/lib/rack/{conditionalget.rb → conditional_get.rb} +20 -6
  25. data/lib/rack/config.rb +7 -0
  26. data/lib/rack/content_length.rb +12 -6
  27. data/lib/rack/content_type.rb +4 -2
  28. data/lib/rack/core_ext/regexp.rb +14 -0
  29. data/lib/rack/deflater.rb +73 -42
  30. data/lib/rack/directory.rb +77 -56
  31. data/lib/rack/etag.rb +25 -13
  32. data/lib/rack/events.rb +156 -0
  33. data/lib/rack/file.rb +4 -143
  34. data/lib/rack/files.rb +178 -0
  35. data/lib/rack/handler/cgi.rb +18 -17
  36. data/lib/rack/handler/fastcgi.rb +21 -17
  37. data/lib/rack/handler/lsws.rb +14 -12
  38. data/lib/rack/handler/scgi.rb +27 -21
  39. data/lib/rack/handler/thin.rb +19 -5
  40. data/lib/rack/handler/webrick.rb +66 -24
  41. data/lib/rack/handler.rb +29 -19
  42. data/lib/rack/head.rb +21 -14
  43. data/lib/rack/lint.rb +259 -65
  44. data/lib/rack/lobster.rb +17 -10
  45. data/lib/rack/lock.rb +19 -10
  46. data/lib/rack/logger.rb +4 -2
  47. data/lib/rack/media_type.rb +43 -0
  48. data/lib/rack/method_override.rb +52 -0
  49. data/lib/rack/mime.rb +43 -6
  50. data/lib/rack/mock.rb +109 -44
  51. data/lib/rack/multipart/generator.rb +11 -12
  52. data/lib/rack/multipart/parser.rb +302 -115
  53. data/lib/rack/multipart/uploaded_file.rb +4 -3
  54. data/lib/rack/multipart.rb +40 -9
  55. data/lib/rack/null_logger.rb +39 -0
  56. data/lib/rack/query_parser.rb +218 -0
  57. data/lib/rack/recursive.rb +14 -11
  58. data/lib/rack/reloader.rb +12 -5
  59. data/lib/rack/request.rb +484 -270
  60. data/lib/rack/response.rb +196 -77
  61. data/lib/rack/rewindable_input.rb +5 -14
  62. data/lib/rack/runtime.rb +13 -6
  63. data/lib/rack/sendfile.rb +44 -20
  64. data/lib/rack/server.rb +175 -61
  65. data/lib/rack/session/abstract/id.rb +276 -133
  66. data/lib/rack/session/cookie.rb +75 -40
  67. data/lib/rack/session/memcache.rb +4 -87
  68. data/lib/rack/session/pool.rb +24 -18
  69. data/lib/rack/show_exceptions.rb +392 -0
  70. data/lib/rack/{showstatus.rb → show_status.rb} +11 -9
  71. data/lib/rack/static.rb +65 -38
  72. data/lib/rack/tempfile_reaper.rb +24 -0
  73. data/lib/rack/urlmap.rb +40 -15
  74. data/lib/rack/utils.rb +316 -285
  75. data/lib/rack.rb +78 -23
  76. data/rack.gemspec +26 -19
  77. metadata +44 -209
  78. data/KNOWN-ISSUES +0 -30
  79. data/lib/rack/backports/uri/common_18.rb +0 -56
  80. data/lib/rack/backports/uri/common_192.rb +0 -52
  81. data/lib/rack/backports/uri/common_193.rb +0 -29
  82. data/lib/rack/handler/evented_mongrel.rb +0 -8
  83. data/lib/rack/handler/mongrel.rb +0 -100
  84. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  85. data/lib/rack/methodoverride.rb +0 -33
  86. data/lib/rack/nulllogger.rb +0 -18
  87. data/lib/rack/showexceptions.rb +0 -378
  88. data/test/builder/anything.rb +0 -5
  89. data/test/builder/comment.ru +0 -4
  90. data/test/builder/end.ru +0 -5
  91. data/test/builder/line.ru +0 -1
  92. data/test/builder/options.ru +0 -2
  93. data/test/cgi/assets/folder/test.js +0 -1
  94. data/test/cgi/assets/fonts/font.eot +0 -1
  95. data/test/cgi/assets/images/image.png +0 -1
  96. data/test/cgi/assets/index.html +0 -1
  97. data/test/cgi/assets/javascripts/app.js +0 -1
  98. data/test/cgi/assets/stylesheets/app.css +0 -1
  99. data/test/cgi/lighttpd.conf +0 -26
  100. data/test/cgi/lighttpd.errors +0 -1
  101. data/test/cgi/rackup_stub.rb +0 -6
  102. data/test/cgi/sample_rackup.ru +0 -5
  103. data/test/cgi/test +0 -9
  104. data/test/cgi/test+directory/test+file +0 -1
  105. data/test/cgi/test.fcgi +0 -8
  106. data/test/cgi/test.ru +0 -5
  107. data/test/gemloader.rb +0 -10
  108. data/test/multipart/bad_robots +0 -259
  109. data/test/multipart/binary +0 -0
  110. data/test/multipart/content_type_and_no_filename +0 -6
  111. data/test/multipart/empty +0 -10
  112. data/test/multipart/fail_16384_nofile +0 -814
  113. data/test/multipart/file1.txt +0 -1
  114. data/test/multipart/filename_and_modification_param +0 -7
  115. data/test/multipart/filename_with_escaped_quotes +0 -6
  116. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  117. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  118. data/test/multipart/filename_with_unescaped_percentages +0 -6
  119. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  120. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  121. data/test/multipart/filename_with_unescaped_quotes +0 -6
  122. data/test/multipart/ie +0 -6
  123. data/test/multipart/mixed_files +0 -21
  124. data/test/multipart/nested +0 -10
  125. data/test/multipart/none +0 -9
  126. data/test/multipart/semicolon +0 -6
  127. data/test/multipart/text +0 -15
  128. data/test/multipart/three_files_three_fields +0 -31
  129. data/test/multipart/webkit +0 -32
  130. data/test/rackup/config.ru +0 -31
  131. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  132. data/test/spec_auth.rb +0 -57
  133. data/test/spec_auth_basic.rb +0 -81
  134. data/test/spec_auth_digest.rb +0 -259
  135. data/test/spec_body_proxy.rb +0 -69
  136. data/test/spec_builder.rb +0 -207
  137. data/test/spec_cascade.rb +0 -61
  138. data/test/spec_cgi.rb +0 -102
  139. data/test/spec_chunked.rb +0 -87
  140. data/test/spec_commonlogger.rb +0 -57
  141. data/test/spec_conditionalget.rb +0 -102
  142. data/test/spec_config.rb +0 -22
  143. data/test/spec_content_length.rb +0 -86
  144. data/test/spec_content_type.rb +0 -45
  145. data/test/spec_deflater.rb +0 -187
  146. data/test/spec_directory.rb +0 -88
  147. data/test/spec_etag.rb +0 -98
  148. data/test/spec_fastcgi.rb +0 -107
  149. data/test/spec_file.rb +0 -200
  150. data/test/spec_handler.rb +0 -59
  151. data/test/spec_head.rb +0 -48
  152. data/test/spec_lint.rb +0 -515
  153. data/test/spec_lobster.rb +0 -58
  154. data/test/spec_lock.rb +0 -167
  155. data/test/spec_logger.rb +0 -23
  156. data/test/spec_methodoverride.rb +0 -72
  157. data/test/spec_mock.rb +0 -269
  158. data/test/spec_mongrel.rb +0 -182
  159. data/test/spec_multipart.rb +0 -479
  160. data/test/spec_nulllogger.rb +0 -23
  161. data/test/spec_recursive.rb +0 -72
  162. data/test/spec_request.rb +0 -955
  163. data/test/spec_response.rb +0 -313
  164. data/test/spec_rewindable_input.rb +0 -118
  165. data/test/spec_runtime.rb +0 -49
  166. data/test/spec_sendfile.rb +0 -90
  167. data/test/spec_server.rb +0 -121
  168. data/test/spec_session_abstract_id.rb +0 -43
  169. data/test/spec_session_cookie.rb +0 -361
  170. data/test/spec_session_memcache.rb +0 -321
  171. data/test/spec_session_pool.rb +0 -209
  172. data/test/spec_showexceptions.rb +0 -92
  173. data/test/spec_showstatus.rb +0 -84
  174. data/test/spec_static.rb +0 -145
  175. data/test/spec_thin.rb +0 -86
  176. data/test/spec_urlmap.rb +0 -213
  177. data/test/spec_utils.rb +0 -554
  178. data/test/spec_webrick.rb +0 -143
  179. data/test/static/another/index.html +0 -1
  180. data/test/static/index.html +0 -1
  181. data/test/testrequest.rb +0 -78
  182. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  183. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -1,102 +1,159 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
2
4
  # bugrep: Andreas Zehnder
3
5
 
6
+ require 'rack'
4
7
  require 'time'
5
8
  require 'rack/request'
6
9
  require 'rack/response'
7
- begin
8
- require 'securerandom'
9
- rescue LoadError
10
- # We just won't get securerandom
11
- end
10
+ require 'securerandom'
11
+ require 'digest/sha2'
12
12
 
13
13
  module Rack
14
14
 
15
15
  module Session
16
16
 
17
+ class SessionId
18
+ ID_VERSION = 2
19
+
20
+ attr_reader :public_id
21
+
22
+ def initialize(public_id)
23
+ @public_id = public_id
24
+ end
25
+
26
+ def private_id
27
+ "#{ID_VERSION}::#{hash_sid(public_id)}"
28
+ end
29
+
30
+ alias :cookie_value :public_id
31
+ alias :to_s :public_id
32
+
33
+ def empty?; false; end
34
+ def inspect; public_id.inspect; end
35
+
36
+ private
37
+
38
+ def hash_sid(sid)
39
+ Digest::SHA256.hexdigest(sid)
40
+ end
41
+ end
42
+
17
43
  module Abstract
18
- ENV_SESSION_KEY = 'rack.session'.freeze
19
- ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
44
+ # SessionHash is responsible to lazily load the session from store.
45
+
46
+ class SessionHash
47
+ using Module.new {
48
+ refine Hash do
49
+ def transform_keys(&block)
50
+ hash = {}
51
+ each do |key, value|
52
+ hash[block.call(key)] = value
53
+ end
54
+ hash
55
+ end
56
+ end
57
+ } unless {}.respond_to?(:transform_keys)
20
58
 
21
- # Thin wrapper around Hash that allows us to lazily load session id into session_options.
59
+ include Enumerable
60
+ attr_writer :id
22
61
 
23
- class OptionsHash < Hash #:nodoc:
24
- def initialize(by, env, default_options)
25
- @by = by
26
- @env = env
27
- @session_id_loaded = false
28
- merge!(default_options)
62
+ Unspecified = Object.new
63
+
64
+ def self.find(req)
65
+ req.get_header RACK_SESSION
29
66
  end
30
67
 
31
- def [](key)
32
- load_session_id! if key == :id && session_id_not_loaded?
33
- super
68
+ def self.set(req, session)
69
+ req.set_header RACK_SESSION, session
34
70
  end
35
71
 
36
- private
72
+ def self.set_options(req, options)
73
+ req.set_header RACK_SESSION_OPTIONS, options.dup
74
+ end
37
75
 
38
- def session_id_not_loaded?
39
- !(@session_id_loaded || key?(:id))
76
+ def initialize(store, req)
77
+ @store = store
78
+ @req = req
79
+ @loaded = false
40
80
  end
41
81
 
42
- def load_session_id!
43
- self[:id] = @by.send(:extract_session_id, @env)
44
- @session_id_loaded = true
82
+ def id
83
+ return @id if @loaded or instance_variable_defined?(:@id)
84
+ @id = @store.send(:extract_session_id, @req)
45
85
  end
46
- end
47
86
 
48
- # SessionHash is responsible to lazily load the session from store.
87
+ def options
88
+ @req.session_options
89
+ end
49
90
 
50
- class SessionHash < Hash
51
- def initialize(by, env)
52
- super()
53
- @by = by
54
- @env = env
55
- @loaded = false
91
+ def each(&block)
92
+ load_for_read!
93
+ @data.each(&block)
56
94
  end
57
95
 
58
96
  def [](key)
59
97
  load_for_read!
60
- super(key.to_s)
98
+ @data[key.to_s]
99
+ end
100
+
101
+ def fetch(key, default = Unspecified, &block)
102
+ load_for_read!
103
+ if default == Unspecified
104
+ @data.fetch(key.to_s, &block)
105
+ else
106
+ @data.fetch(key.to_s, default, &block)
107
+ end
61
108
  end
62
109
 
63
110
  def has_key?(key)
64
111
  load_for_read!
65
- super(key.to_s)
112
+ @data.has_key?(key.to_s)
66
113
  end
67
114
  alias :key? :has_key?
68
115
  alias :include? :has_key?
69
116
 
70
117
  def []=(key, value)
71
118
  load_for_write!
72
- super(key.to_s, value)
119
+ @data[key.to_s] = value
73
120
  end
121
+ alias :store :[]=
74
122
 
75
123
  def clear
76
124
  load_for_write!
77
- super
125
+ @data.clear
126
+ end
127
+
128
+ def destroy
129
+ clear
130
+ @id = @store.send(:delete_session, @req, id, options)
78
131
  end
79
132
 
80
133
  def to_hash
81
134
  load_for_read!
82
- h = {}.replace(self)
83
- h.delete_if { |k,v| v.nil? }
84
- h
135
+ @data.dup
85
136
  end
86
137
 
87
138
  def update(hash)
88
139
  load_for_write!
89
- super(stringify_keys(hash))
140
+ @data.update(stringify_keys(hash))
141
+ end
142
+ alias :merge! :update
143
+
144
+ def replace(hash)
145
+ load_for_write!
146
+ @data.replace(stringify_keys(hash))
90
147
  end
91
148
 
92
149
  def delete(key)
93
150
  load_for_write!
94
- super(key.to_s)
151
+ @data.delete(key.to_s)
95
152
  end
96
153
 
97
154
  def inspect
98
155
  if loaded?
99
- super
156
+ @data.inspect
100
157
  else
101
158
  "#<#{self.class}:0x#{self.object_id.to_s(16)} not yet loaded>"
102
159
  end
@@ -104,7 +161,8 @@ module Rack
104
161
 
105
162
  def exists?
106
163
  return @exists if instance_variable_defined?(:@exists)
107
- @exists = @by.send(:session_exists?, @env)
164
+ @data = {}
165
+ @exists = @store.send(:session_exists?, @req)
108
166
  end
109
167
 
110
168
  def loaded?
@@ -113,12 +171,17 @@ module Rack
113
171
 
114
172
  def empty?
115
173
  load_for_read!
116
- super
174
+ @data.empty?
117
175
  end
118
176
 
119
- def merge!(hash)
120
- load_for_write!
121
- super
177
+ def keys
178
+ load_for_read!
179
+ @data.keys
180
+ end
181
+
182
+ def values
183
+ load_for_read!
184
+ @data.values
122
185
  end
123
186
 
124
187
  private
@@ -132,31 +195,26 @@ module Rack
132
195
  end
133
196
 
134
197
  def load!
135
- id, session = @by.send(:load_session, @env)
136
- @env[ENV_SESSION_OPTIONS_KEY][:id] = id
137
- replace(stringify_keys(session))
198
+ @id, session = @store.send(:load_session, @req)
199
+ @data = stringify_keys(session)
138
200
  @loaded = true
139
201
  end
140
202
 
141
203
  def stringify_keys(other)
142
- hash = {}
143
- other.each do |key, value|
144
- hash[key.to_s] = value
145
- end
146
- hash
204
+ other.to_hash.transform_keys(&:to_s)
147
205
  end
148
206
  end
149
207
 
150
208
  # ID sets up a basic framework for implementing an id based sessioning
151
209
  # service. Cookies sent to the client for maintaining sessions will only
152
- # contain an id reference. Only #get_session and #set_session are
210
+ # contain an id reference. Only #find_session and #write_session are
153
211
  # required to be overwritten.
154
212
  #
155
213
  # All parameters are optional.
156
214
  # * :key determines the name of the cookie, by default it is
157
215
  # 'rack.session'
158
216
  # * :path, :domain, :expire_after, :secure, and :httponly set the related
159
- # cookie options as by Rack::Response#add_cookie
217
+ # cookie options as by Rack::Response#set_cookie
160
218
  # * :skip will not a set a cookie in the response nor update the session state
161
219
  # * :defer will not set a cookie in the response but still update the session
162
220
  # state if it is used with a backend
@@ -167,33 +225,33 @@ module Rack
167
225
  # id will be.
168
226
  #
169
227
  # These options can be set on a per request basis, at the location of
170
- # env['rack.session.options']. Additionally the id of the session can be
171
- # found within the options hash at the key :id. It is highly not
172
- # recommended to change its value.
228
+ # <tt>env['rack.session.options']</tt>. Additionally the id of the
229
+ # session can be found within the options hash at the key :id. It is
230
+ # highly not recommended to change its value.
173
231
  #
174
232
  # Is Rack::Utils::Context compatible.
175
233
  #
176
234
  # Not included by default; you must require 'rack/session/abstract/id'
177
235
  # to use.
178
236
 
179
- class ID
237
+ class Persisted
180
238
  DEFAULT_OPTIONS = {
181
- :key => 'rack.session',
182
- :path => '/',
183
- :domain => nil,
184
- :expire_after => nil,
185
- :secure => false,
186
- :httponly => true,
187
- :defer => false,
188
- :renew => false,
189
- :sidbits => 128,
190
- :cookie_only => true,
191
- :secure_random => (::SecureRandom rescue false)
192
- }
193
-
194
- attr_reader :key, :default_options
195
-
196
- def initialize(app, options={})
239
+ key: RACK_SESSION,
240
+ path: '/',
241
+ domain: nil,
242
+ expire_after: nil,
243
+ secure: false,
244
+ httponly: true,
245
+ defer: false,
246
+ renew: false,
247
+ sidbits: 128,
248
+ cookie_only: true,
249
+ secure_random: ::SecureRandom
250
+ }.freeze
251
+
252
+ attr_reader :key, :default_options, :sid_secure
253
+
254
+ def initialize(app, options = {})
197
255
  @app = app
198
256
  @default_options = self.class::DEFAULT_OPTIONS.merge(options)
199
257
  @key = @default_options.delete(:key)
@@ -205,14 +263,21 @@ module Rack
205
263
  context(env)
206
264
  end
207
265
 
208
- def context(env, app=@app)
209
- prepare_session(env)
210
- status, headers, body = app.call(env)
211
- commit_session(env, status, headers, body)
266
+ def context(env, app = @app)
267
+ req = make_request env
268
+ prepare_session(req)
269
+ status, headers, body = app.call(req.env)
270
+ res = Rack::Response::Raw.new status, headers
271
+ commit_session(req, res)
272
+ [status, headers, body]
212
273
  end
213
274
 
214
275
  private
215
276
 
277
+ def make_request(env)
278
+ Rack::Request.new env
279
+ end
280
+
216
281
  def initialize_sid
217
282
  @sidbits = @default_options[:sidbits]
218
283
  @sid_secure = @default_options[:secure_random]
@@ -225,7 +290,7 @@ module Rack
225
290
 
226
291
  def generate_sid(secure = @sid_secure)
227
292
  if secure
228
- SecureRandom.hex(@sid_length)
293
+ secure.hex(@sid_length)
229
294
  else
230
295
  "%0#{@sid_length}x" % Kernel.rand(2**@sidbits - 1)
231
296
  end
@@ -236,58 +301,58 @@ module Rack
236
301
  # Sets the lazy session at 'rack.session' and places options and session
237
302
  # metadata into 'rack.session.options'.
238
303
 
239
- def prepare_session(env)
240
- session_was = env[ENV_SESSION_KEY]
241
- env[ENV_SESSION_KEY] = SessionHash.new(self, env)
242
- env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
243
- env[ENV_SESSION_KEY].merge! session_was if session_was
304
+ def prepare_session(req)
305
+ session_was = req.get_header RACK_SESSION
306
+ session = session_class.new(self, req)
307
+ req.set_header RACK_SESSION, session
308
+ req.set_header RACK_SESSION_OPTIONS, @default_options.dup
309
+ session.merge! session_was if session_was
244
310
  end
245
311
 
246
312
  # Extracts the session id from provided cookies and passes it and the
247
- # environment to #get_session.
313
+ # environment to #find_session.
248
314
 
249
- def load_session(env)
250
- sid = current_session_id(env)
251
- sid, session = get_session(env, sid)
315
+ def load_session(req)
316
+ sid = current_session_id(req)
317
+ sid, session = find_session(req, sid)
252
318
  [sid, session || {}]
253
319
  end
254
320
 
255
321
  # Extract session id from request object.
256
322
 
257
- def extract_session_id(env)
258
- request = Rack::Request.new(env)
323
+ def extract_session_id(request)
259
324
  sid = request.cookies[@key]
260
325
  sid ||= request.params[@key] unless @cookie_only
261
326
  sid
262
327
  end
263
328
 
264
- # Returns the current session id from the OptionsHash.
329
+ # Returns the current session id from the SessionHash.
265
330
 
266
- def current_session_id(env)
267
- env[ENV_SESSION_OPTIONS_KEY][:id]
331
+ def current_session_id(req)
332
+ req.get_header(RACK_SESSION).id
268
333
  end
269
334
 
270
335
  # Check if the session exists or not.
271
336
 
272
- def session_exists?(env)
273
- value = current_session_id(env)
337
+ def session_exists?(req)
338
+ value = current_session_id(req)
274
339
  value && !value.empty?
275
340
  end
276
341
 
277
- # Session should be commited if it was loaded, any of specific options like :renew, :drop
342
+ # Session should be committed if it was loaded, any of specific options like :renew, :drop
278
343
  # or :expire_after was given and the security permissions match. Skips if skip is given.
279
344
 
280
- def commit_session?(env, session, options)
345
+ def commit_session?(req, session, options)
281
346
  if options[:skip]
282
347
  false
283
348
  else
284
349
  has_session = loaded_session?(session) || forced_session_update?(session, options)
285
- has_session && security_matches?(env, options)
350
+ has_session && security_matches?(req, options)
286
351
  end
287
352
  end
288
353
 
289
354
  def loaded_session?(session)
290
- !session.is_a?(SessionHash) || session.loaded?
355
+ !session.is_a?(session_class) || session.loaded?
291
356
  end
292
357
 
293
358
  def forced_session_update?(session, options)
@@ -295,81 +360,159 @@ module Rack
295
360
  end
296
361
 
297
362
  def force_options?(options)
298
- options.values_at(:renew, :drop, :defer, :expire_after).any?
363
+ options.values_at(:max_age, :renew, :drop, :defer, :expire_after).any?
299
364
  end
300
365
 
301
- def security_matches?(env, options)
366
+ def security_matches?(request, options)
302
367
  return true unless options[:secure]
303
- request = Rack::Request.new(env)
304
368
  request.ssl?
305
369
  end
306
370
 
307
371
  # Acquires the session from the environment and the session id from
308
- # the session options and passes them to #set_session. If successful
372
+ # the session options and passes them to #write_session. If successful
309
373
  # and the :defer option is not true, a cookie will be added to the
310
374
  # response with the session's id.
311
375
 
312
- def commit_session(env, status, headers, body)
313
- session = env['rack.session']
314
- options = env['rack.session.options']
376
+ def commit_session(req, res)
377
+ session = req.get_header RACK_SESSION
378
+ options = session.options
315
379
 
316
380
  if options[:drop] || options[:renew]
317
- session_id = destroy_session(env, options[:id] || generate_sid, options)
318
- return [status, headers, body] unless session_id
381
+ session_id = delete_session(req, session.id || generate_sid, options)
382
+ return unless session_id
319
383
  end
320
384
 
321
- return [status, headers, body] unless commit_session?(env, session, options)
385
+ return unless commit_session?(req, session, options)
322
386
 
323
387
  session.send(:load!) unless loaded_session?(session)
324
- session = session.to_hash
325
- session_id ||= options[:id] || generate_sid
388
+ session_id ||= session.id
389
+ session_data = session.to_hash.delete_if { |k, v| v.nil? }
326
390
 
327
- if not data = set_session(env, session_id, session, options)
328
- env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
391
+ if not data = write_session(req, session_id, session_data, options)
392
+ req.get_header(RACK_ERRORS).puts("Warning! #{self.class.name} failed to save session. Content dropped.")
329
393
  elsif options[:defer] and not options[:renew]
330
- env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE
394
+ req.get_header(RACK_ERRORS).puts("Deferring cookie for #{session_id}") if $VERBOSE
331
395
  else
332
396
  cookie = Hash.new
333
- cookie[:value] = data
397
+ cookie[:value] = cookie_value(data)
334
398
  cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
335
- set_cookie(env, headers, cookie.merge!(options))
399
+ cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
400
+ set_cookie(req, res, cookie.merge!(options))
336
401
  end
402
+ end
403
+ public :commit_session
337
404
 
338
- [status, headers, body]
405
+ def cookie_value(data)
406
+ data
339
407
  end
340
408
 
341
409
  # Sets the cookie back to the client with session id. We skip the cookie
342
410
  # setting if the value didn't change (sid is the same) or expires was given.
343
411
 
344
- def set_cookie(env, headers, cookie)
345
- request = Rack::Request.new(env)
412
+ def set_cookie(request, res, cookie)
346
413
  if request.cookies[@key] != cookie[:value] || cookie[:expires]
347
- Utils.set_cookie_header!(headers, @key, cookie)
414
+ res.set_cookie_header =
415
+ Utils.add_cookie_to_header(res.set_cookie_header, @key, cookie)
416
+ end
417
+ end
418
+
419
+ # Allow subclasses to prepare_session for different Session classes
420
+
421
+ def session_class
422
+ SessionHash
423
+ end
424
+
425
+ # All thread safety and session retrieval procedures should occur here.
426
+ # Should return [session_id, session].
427
+ # If nil is provided as the session id, generation of a new valid id
428
+ # should occur within.
429
+
430
+ def find_session(env, sid)
431
+ raise '#find_session not implemented.'
432
+ end
433
+
434
+ # All thread safety and session storage procedures should occur here.
435
+ # Must return the session id if the session was saved successfully, or
436
+ # false if the session could not be saved.
437
+
438
+ def write_session(req, sid, session, options)
439
+ raise '#write_session not implemented.'
440
+ end
441
+
442
+ # All thread safety and session destroy procedures should occur here.
443
+ # Should return a new session id or nil if options[:drop]
444
+
445
+ def delete_session(req, sid, options)
446
+ raise '#delete_session not implemented'
447
+ end
448
+ end
449
+
450
+ class PersistedSecure < Persisted
451
+ class SecureSessionHash < SessionHash
452
+ def [](key)
453
+ if key == "session_id"
454
+ load_for_read!
455
+ id.public_id if id
456
+ else
457
+ super
458
+ end
459
+ end
460
+ end
461
+
462
+ def generate_sid(*)
463
+ public_id = super
464
+
465
+ SessionId.new(public_id)
466
+ end
467
+
468
+ def extract_session_id(*)
469
+ public_id = super
470
+ public_id && SessionId.new(public_id)
471
+ end
472
+
473
+ private
474
+
475
+ def session_class
476
+ SecureSessionHash
477
+ end
478
+
479
+ def cookie_value(data)
480
+ data.cookie_value
481
+ end
482
+ end
483
+
484
+ class ID < Persisted
485
+ def self.inherited(klass)
486
+ k = klass.ancestors.find { |kl| kl.respond_to?(:superclass) && kl.superclass == ID }
487
+ unless k.instance_variable_defined?(:"@_rack_warned")
488
+ warn "#{klass} is inheriting from #{ID}. Inheriting from #{ID} is deprecated, please inherit from #{Persisted} instead" if $VERBOSE
489
+ k.instance_variable_set(:"@_rack_warned", true)
348
490
  end
491
+ super
349
492
  end
350
493
 
351
- # All thread safety and session retrival proceedures should occur here.
494
+ # All thread safety and session retrieval procedures should occur here.
352
495
  # Should return [session_id, session].
353
496
  # If nil is provided as the session id, generation of a new valid id
354
497
  # should occur within.
355
498
 
356
- def get_session(env, sid)
357
- raise '#get_session not implemented.'
499
+ def find_session(req, sid)
500
+ get_session req.env, sid
358
501
  end
359
502
 
360
- # All thread safety and session storage proceedures should occur here.
361
- # Should return true or false dependant on whether or not the session
362
- # was saved or not.
503
+ # All thread safety and session storage procedures should occur here.
504
+ # Must return the session id if the session was saved successfully, or
505
+ # false if the session could not be saved.
363
506
 
364
- def set_session(env, sid, session, options)
365
- raise '#set_session not implemented.'
507
+ def write_session(req, sid, session, options)
508
+ set_session req.env, sid, session, options
366
509
  end
367
510
 
368
- # All thread safety and session destroy proceedures should occur here.
511
+ # All thread safety and session destroy procedures should occur here.
369
512
  # Should return a new session id or nil if options[:drop]
370
513
 
371
- def destroy_session(env, sid, options)
372
- raise '#destroy_session not implemented'
514
+ def delete_session(req, sid, options)
515
+ destroy_session req.env, sid, options
373
516
  end
374
517
  end
375
518
  end