rack 1.6.11 → 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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -0
  3. data/{COPYING → MIT-LICENSE} +4 -2
  4. data/README.rdoc +89 -139
  5. data/Rakefile +27 -28
  6. data/SPEC +6 -7
  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 +3 -1
  13. data/lib/rack/auth/abstract/request.rb +7 -1
  14. data/lib/rack/auth/basic.rb +4 -1
  15. data/lib/rack/auth/digest/md5.rb +9 -7
  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 +3 -1
  19. data/lib/rack/body_proxy.rb +11 -9
  20. data/lib/rack/builder.rb +42 -18
  21. data/lib/rack/cascade.rb +6 -5
  22. data/lib/rack/chunked.rb +33 -10
  23. data/lib/rack/{commonlogger.rb → common_logger.rb} +11 -10
  24. data/lib/rack/{conditionalget.rb → conditional_get.rb} +3 -1
  25. data/lib/rack/config.rb +2 -0
  26. data/lib/rack/content_length.rb +5 -3
  27. data/lib/rack/content_type.rb +3 -1
  28. data/lib/rack/core_ext/regexp.rb +14 -0
  29. data/lib/rack/deflater.rb +33 -53
  30. data/lib/rack/directory.rb +75 -60
  31. data/lib/rack/etag.rb +8 -5
  32. data/lib/rack/events.rb +156 -0
  33. data/lib/rack/file.rb +4 -149
  34. data/lib/rack/files.rb +178 -0
  35. data/lib/rack/handler/cgi.rb +18 -17
  36. data/lib/rack/handler/fastcgi.rb +17 -16
  37. data/lib/rack/handler/lsws.rb +14 -12
  38. data/lib/rack/handler/scgi.rb +22 -19
  39. data/lib/rack/handler/thin.rb +6 -1
  40. data/lib/rack/handler/webrick.rb +28 -28
  41. data/lib/rack/handler.rb +9 -26
  42. data/lib/rack/head.rb +17 -17
  43. data/lib/rack/lint.rb +54 -51
  44. data/lib/rack/lobster.rb +8 -6
  45. data/lib/rack/lock.rb +17 -10
  46. data/lib/rack/logger.rb +4 -2
  47. data/lib/rack/media_type.rb +43 -0
  48. data/lib/rack/{methodoverride.rb → method_override.rb} +10 -8
  49. data/lib/rack/mime.rb +27 -6
  50. data/lib/rack/mock.rb +101 -60
  51. data/lib/rack/multipart/generator.rb +11 -12
  52. data/lib/rack/multipart/parser.rb +280 -161
  53. data/lib/rack/multipart/uploaded_file.rb +3 -2
  54. data/lib/rack/multipart.rb +39 -8
  55. data/lib/rack/{nulllogger.rb → null_logger.rb} +3 -1
  56. data/lib/rack/query_parser.rb +218 -0
  57. data/lib/rack/recursive.rb +11 -9
  58. data/lib/rack/reloader.rb +10 -4
  59. data/lib/rack/request.rb +447 -305
  60. data/lib/rack/response.rb +196 -83
  61. data/lib/rack/rewindable_input.rb +5 -14
  62. data/lib/rack/runtime.rb +12 -18
  63. data/lib/rack/sendfile.rb +19 -14
  64. data/lib/rack/server.rb +118 -41
  65. data/lib/rack/session/abstract/id.rb +215 -94
  66. data/lib/rack/session/cookie.rb +45 -28
  67. data/lib/rack/session/memcache.rb +4 -87
  68. data/lib/rack/session/pool.rb +25 -16
  69. data/lib/rack/show_exceptions.rb +392 -0
  70. data/lib/rack/{showstatus.rb → show_status.rb} +7 -5
  71. data/lib/rack/static.rb +41 -11
  72. data/lib/rack/tempfile_reaper.rb +4 -2
  73. data/lib/rack/urlmap.rb +25 -15
  74. data/lib/rack/utils.rb +186 -272
  75. data/lib/rack.rb +76 -24
  76. data/rack.gemspec +25 -14
  77. metadata +62 -182
  78. data/HISTORY.md +0 -375
  79. data/KNOWN-ISSUES +0 -44
  80. data/lib/rack/backports/uri/common_18.rb +0 -56
  81. data/lib/rack/backports/uri/common_192.rb +0 -52
  82. data/lib/rack/backports/uri/common_193.rb +0 -29
  83. data/lib/rack/handler/evented_mongrel.rb +0 -8
  84. data/lib/rack/handler/mongrel.rb +0 -106
  85. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  86. data/lib/rack/showexceptions.rb +0 -387
  87. data/lib/rack/utils/okjson.rb +0 -600
  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/rackup_stub.rb +0 -6
  101. data/test/cgi/sample_rackup.ru +0 -5
  102. data/test/cgi/test +0 -9
  103. data/test/cgi/test+directory/test+file +0 -1
  104. data/test/cgi/test.fcgi +0 -8
  105. data/test/cgi/test.ru +0 -5
  106. data/test/gemloader.rb +0 -10
  107. data/test/multipart/bad_robots +0 -259
  108. data/test/multipart/binary +0 -0
  109. data/test/multipart/content_type_and_no_filename +0 -6
  110. data/test/multipart/empty +0 -10
  111. data/test/multipart/fail_16384_nofile +0 -814
  112. data/test/multipart/file1.txt +0 -1
  113. data/test/multipart/filename_and_modification_param +0 -7
  114. data/test/multipart/filename_and_no_name +0 -6
  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_null_byte +0 -7
  118. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  119. data/test/multipart/filename_with_unescaped_percentages +0 -6
  120. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  121. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  122. data/test/multipart/filename_with_unescaped_quotes +0 -6
  123. data/test/multipart/ie +0 -6
  124. data/test/multipart/invalid_character +0 -6
  125. data/test/multipart/mixed_files +0 -21
  126. data/test/multipart/nested +0 -10
  127. data/test/multipart/none +0 -9
  128. data/test/multipart/semicolon +0 -6
  129. data/test/multipart/text +0 -15
  130. data/test/multipart/three_files_three_fields +0 -31
  131. data/test/multipart/webkit +0 -32
  132. data/test/rackup/config.ru +0 -31
  133. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  134. data/test/spec_auth_basic.rb +0 -81
  135. data/test/spec_auth_digest.rb +0 -259
  136. data/test/spec_body_proxy.rb +0 -85
  137. data/test/spec_builder.rb +0 -223
  138. data/test/spec_cascade.rb +0 -61
  139. data/test/spec_cgi.rb +0 -102
  140. data/test/spec_chunked.rb +0 -101
  141. data/test/spec_commonlogger.rb +0 -93
  142. data/test/spec_conditionalget.rb +0 -102
  143. data/test/spec_config.rb +0 -22
  144. data/test/spec_content_length.rb +0 -85
  145. data/test/spec_content_type.rb +0 -45
  146. data/test/spec_deflater.rb +0 -339
  147. data/test/spec_directory.rb +0 -88
  148. data/test/spec_etag.rb +0 -107
  149. data/test/spec_fastcgi.rb +0 -107
  150. data/test/spec_file.rb +0 -221
  151. data/test/spec_handler.rb +0 -72
  152. data/test/spec_head.rb +0 -45
  153. data/test/spec_lint.rb +0 -550
  154. data/test/spec_lobster.rb +0 -58
  155. data/test/spec_lock.rb +0 -164
  156. data/test/spec_logger.rb +0 -23
  157. data/test/spec_methodoverride.rb +0 -111
  158. data/test/spec_mime.rb +0 -51
  159. data/test/spec_mock.rb +0 -297
  160. data/test/spec_mongrel.rb +0 -182
  161. data/test/spec_multipart.rb +0 -600
  162. data/test/spec_nulllogger.rb +0 -20
  163. data/test/spec_recursive.rb +0 -72
  164. data/test/spec_request.rb +0 -1232
  165. data/test/spec_response.rb +0 -407
  166. data/test/spec_rewindable_input.rb +0 -118
  167. data/test/spec_runtime.rb +0 -49
  168. data/test/spec_sendfile.rb +0 -130
  169. data/test/spec_server.rb +0 -167
  170. data/test/spec_session_abstract_id.rb +0 -53
  171. data/test/spec_session_cookie.rb +0 -410
  172. data/test/spec_session_memcache.rb +0 -321
  173. data/test/spec_session_pool.rb +0 -209
  174. data/test/spec_showexceptions.rb +0 -98
  175. data/test/spec_showstatus.rb +0 -103
  176. data/test/spec_static.rb +0 -145
  177. data/test/spec_tempfile_reaper.rb +0 -63
  178. data/test/spec_thin.rb +0 -91
  179. data/test/spec_urlmap.rb +0 -236
  180. data/test/spec_utils.rb +0 -647
  181. data/test/spec_version.rb +0 -17
  182. data/test/spec_webrick.rb +0 -184
  183. data/test/static/another/index.html +0 -1
  184. data/test/static/index.html +0 -1
  185. data/test/testrequest.rb +0 -78
  186. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  187. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -1,54 +1,91 @@
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
- module Abstract
18
- ENV_SESSION_KEY = 'rack.session'.freeze
19
- ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
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
20
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
+
43
+ module Abstract
21
44
  # SessionHash is responsible to lazily load the session from store.
22
45
 
23
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)
58
+
24
59
  include Enumerable
25
60
  attr_writer :id
26
61
 
27
- def self.find(env)
28
- env[ENV_SESSION_KEY]
62
+ Unspecified = Object.new
63
+
64
+ def self.find(req)
65
+ req.get_header RACK_SESSION
29
66
  end
30
67
 
31
- def self.set(env, session)
32
- env[ENV_SESSION_KEY] = session
68
+ def self.set(req, session)
69
+ req.set_header RACK_SESSION, session
33
70
  end
34
71
 
35
- def self.set_options(env, options)
36
- env[ENV_SESSION_OPTIONS_KEY] = options.dup
72
+ def self.set_options(req, options)
73
+ req.set_header RACK_SESSION_OPTIONS, options.dup
37
74
  end
38
75
 
39
- def initialize(store, env)
76
+ def initialize(store, req)
40
77
  @store = store
41
- @env = env
78
+ @req = req
42
79
  @loaded = false
43
80
  end
44
81
 
45
82
  def id
46
83
  return @id if @loaded or instance_variable_defined?(:@id)
47
- @id = @store.send(:extract_session_id, @env)
84
+ @id = @store.send(:extract_session_id, @req)
48
85
  end
49
86
 
50
87
  def options
51
- @env[ENV_SESSION_OPTIONS_KEY]
88
+ @req.session_options
52
89
  end
53
90
 
54
91
  def each(&block)
@@ -60,7 +97,15 @@ module Rack
60
97
  load_for_read!
61
98
  @data[key.to_s]
62
99
  end
63
- alias :fetch :[]
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
108
+ end
64
109
 
65
110
  def has_key?(key)
66
111
  load_for_read!
@@ -82,7 +127,7 @@ module Rack
82
127
 
83
128
  def destroy
84
129
  clear
85
- @id = @store.send(:destroy_session, @env, id, options)
130
+ @id = @store.send(:delete_session, @req, id, options)
86
131
  end
87
132
 
88
133
  def to_hash
@@ -117,7 +162,7 @@ module Rack
117
162
  def exists?
118
163
  return @exists if instance_variable_defined?(:@exists)
119
164
  @data = {}
120
- @exists = @store.send(:session_exists?, @env)
165
+ @exists = @store.send(:session_exists?, @req)
121
166
  end
122
167
 
123
168
  def loaded?
@@ -130,10 +175,12 @@ module Rack
130
175
  end
131
176
 
132
177
  def keys
178
+ load_for_read!
133
179
  @data.keys
134
180
  end
135
181
 
136
182
  def values
183
+ load_for_read!
137
184
  @data.values
138
185
  end
139
186
 
@@ -148,30 +195,26 @@ module Rack
148
195
  end
149
196
 
150
197
  def load!
151
- @id, session = @store.send(:load_session, @env)
198
+ @id, session = @store.send(:load_session, @req)
152
199
  @data = stringify_keys(session)
153
200
  @loaded = true
154
201
  end
155
202
 
156
203
  def stringify_keys(other)
157
- hash = {}
158
- other.each do |key, value|
159
- hash[key.to_s] = value
160
- end
161
- hash
204
+ other.to_hash.transform_keys(&:to_s)
162
205
  end
163
206
  end
164
207
 
165
208
  # ID sets up a basic framework for implementing an id based sessioning
166
209
  # service. Cookies sent to the client for maintaining sessions will only
167
- # contain an id reference. Only #get_session and #set_session are
210
+ # contain an id reference. Only #find_session and #write_session are
168
211
  # required to be overwritten.
169
212
  #
170
213
  # All parameters are optional.
171
214
  # * :key determines the name of the cookie, by default it is
172
215
  # 'rack.session'
173
216
  # * :path, :domain, :expire_after, :secure, and :httponly set the related
174
- # cookie options as by Rack::Response#add_cookie
217
+ # cookie options as by Rack::Response#set_cookie
175
218
  # * :skip will not a set a cookie in the response nor update the session state
176
219
  # * :defer will not set a cookie in the response but still update the session
177
220
  # state if it is used with a backend
@@ -182,33 +225,33 @@ module Rack
182
225
  # id will be.
183
226
  #
184
227
  # These options can be set on a per request basis, at the location of
185
- # env['rack.session.options']. Additionally the id of the session can be
186
- # found within the options hash at the key :id. It is highly not
187
- # 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.
188
231
  #
189
232
  # Is Rack::Utils::Context compatible.
190
233
  #
191
234
  # Not included by default; you must require 'rack/session/abstract/id'
192
235
  # to use.
193
236
 
194
- class ID
237
+ class Persisted
195
238
  DEFAULT_OPTIONS = {
196
- :key => 'rack.session',
197
- :path => '/',
198
- :domain => nil,
199
- :expire_after => nil,
200
- :secure => false,
201
- :httponly => true,
202
- :defer => false,
203
- :renew => false,
204
- :sidbits => 128,
205
- :cookie_only => true,
206
- :secure_random => (::SecureRandom rescue false)
207
- }
208
-
209
- attr_reader :key, :default_options
210
-
211
- 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 = {})
212
255
  @app = app
213
256
  @default_options = self.class::DEFAULT_OPTIONS.merge(options)
214
257
  @key = @default_options.delete(:key)
@@ -220,14 +263,21 @@ module Rack
220
263
  context(env)
221
264
  end
222
265
 
223
- def context(env, app=@app)
224
- prepare_session(env)
225
- status, headers, body = app.call(env)
226
- 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]
227
273
  end
228
274
 
229
275
  private
230
276
 
277
+ def make_request(env)
278
+ Rack::Request.new env
279
+ end
280
+
231
281
  def initialize_sid
232
282
  @sidbits = @default_options[:sidbits]
233
283
  @sid_secure = @default_options[:secure_random]
@@ -251,26 +301,26 @@ module Rack
251
301
  # Sets the lazy session at 'rack.session' and places options and session
252
302
  # metadata into 'rack.session.options'.
253
303
 
254
- def prepare_session(env)
255
- session_was = env[ENV_SESSION_KEY]
256
- env[ENV_SESSION_KEY] = session_class.new(self, env)
257
- env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
258
- 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
259
310
  end
260
311
 
261
312
  # Extracts the session id from provided cookies and passes it and the
262
- # environment to #get_session.
313
+ # environment to #find_session.
263
314
 
264
- def load_session(env)
265
- sid = current_session_id(env)
266
- sid, session = get_session(env, sid)
315
+ def load_session(req)
316
+ sid = current_session_id(req)
317
+ sid, session = find_session(req, sid)
267
318
  [sid, session || {}]
268
319
  end
269
320
 
270
321
  # Extract session id from request object.
271
322
 
272
- def extract_session_id(env)
273
- request = Rack::Request.new(env)
323
+ def extract_session_id(request)
274
324
  sid = request.cookies[@key]
275
325
  sid ||= request.params[@key] unless @cookie_only
276
326
  sid
@@ -278,26 +328,26 @@ module Rack
278
328
 
279
329
  # Returns the current session id from the SessionHash.
280
330
 
281
- def current_session_id(env)
282
- env[ENV_SESSION_KEY].id
331
+ def current_session_id(req)
332
+ req.get_header(RACK_SESSION).id
283
333
  end
284
334
 
285
335
  # Check if the session exists or not.
286
336
 
287
- def session_exists?(env)
288
- value = current_session_id(env)
337
+ def session_exists?(req)
338
+ value = current_session_id(req)
289
339
  value && !value.empty?
290
340
  end
291
341
 
292
342
  # Session should be committed if it was loaded, any of specific options like :renew, :drop
293
343
  # or :expire_after was given and the security permissions match. Skips if skip is given.
294
344
 
295
- def commit_session?(env, session, options)
345
+ def commit_session?(req, session, options)
296
346
  if options[:skip]
297
347
  false
298
348
  else
299
349
  has_session = loaded_session?(session) || forced_session_update?(session, options)
300
- has_session && security_matches?(env, options)
350
+ has_session && security_matches?(req, options)
301
351
  end
302
352
  end
303
353
 
@@ -313,54 +363,56 @@ module Rack
313
363
  options.values_at(:max_age, :renew, :drop, :defer, :expire_after).any?
314
364
  end
315
365
 
316
- def security_matches?(env, options)
366
+ def security_matches?(request, options)
317
367
  return true unless options[:secure]
318
- request = Rack::Request.new(env)
319
368
  request.ssl?
320
369
  end
321
370
 
322
371
  # Acquires the session from the environment and the session id from
323
- # the session options and passes them to #set_session. If successful
372
+ # the session options and passes them to #write_session. If successful
324
373
  # and the :defer option is not true, a cookie will be added to the
325
374
  # response with the session's id.
326
375
 
327
- def commit_session(env, status, headers, body)
328
- session = env[ENV_SESSION_KEY]
376
+ def commit_session(req, res)
377
+ session = req.get_header RACK_SESSION
329
378
  options = session.options
330
379
 
331
380
  if options[:drop] || options[:renew]
332
- session_id = destroy_session(env, session.id || generate_sid, options)
333
- return [status, headers, body] unless session_id
381
+ session_id = delete_session(req, session.id || generate_sid, options)
382
+ return unless session_id
334
383
  end
335
384
 
336
- return [status, headers, body] unless commit_session?(env, session, options)
385
+ return unless commit_session?(req, session, options)
337
386
 
338
387
  session.send(:load!) unless loaded_session?(session)
339
388
  session_id ||= session.id
340
- session_data = session.to_hash.delete_if { |k,v| v.nil? }
389
+ session_data = session.to_hash.delete_if { |k, v| v.nil? }
341
390
 
342
- if not data = set_session(env, session_id, session_data, options)
343
- 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.")
344
393
  elsif options[:defer] and not options[:renew]
345
- env["rack.errors"].puts("Deferring cookie for #{session_id}") if $VERBOSE
394
+ req.get_header(RACK_ERRORS).puts("Deferring cookie for #{session_id}") if $VERBOSE
346
395
  else
347
396
  cookie = Hash.new
348
- cookie[:value] = data
397
+ cookie[:value] = cookie_value(data)
349
398
  cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
350
399
  cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
351
- set_cookie(env, headers, cookie.merge!(options))
400
+ set_cookie(req, res, cookie.merge!(options))
352
401
  end
402
+ end
403
+ public :commit_session
353
404
 
354
- [status, headers, body]
405
+ def cookie_value(data)
406
+ data
355
407
  end
356
408
 
357
409
  # Sets the cookie back to the client with session id. We skip the cookie
358
410
  # setting if the value didn't change (sid is the same) or expires was given.
359
411
 
360
- def set_cookie(env, headers, cookie)
361
- request = Rack::Request.new(env)
412
+ def set_cookie(request, res, cookie)
362
413
  if request.cookies[@key] != cookie[:value] || cookie[:expires]
363
- Utils.set_cookie_header!(headers, @key, cookie)
414
+ res.set_cookie_header =
415
+ Utils.add_cookie_to_header(res.set_cookie_header, @key, cookie)
364
416
  end
365
417
  end
366
418
 
@@ -375,23 +427,92 @@ module Rack
375
427
  # If nil is provided as the session id, generation of a new valid id
376
428
  # should occur within.
377
429
 
378
- def get_session(env, sid)
379
- raise '#get_session not implemented.'
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)
490
+ end
491
+ super
492
+ end
493
+
494
+ # All thread safety and session retrieval procedures should occur here.
495
+ # Should return [session_id, session].
496
+ # If nil is provided as the session id, generation of a new valid id
497
+ # should occur within.
498
+
499
+ def find_session(req, sid)
500
+ get_session req.env, sid
380
501
  end
381
502
 
382
503
  # All thread safety and session storage procedures should occur here.
383
504
  # Must return the session id if the session was saved successfully, or
384
505
  # false if the session could not be saved.
385
506
 
386
- def set_session(env, sid, session, options)
387
- raise '#set_session not implemented.'
507
+ def write_session(req, sid, session, options)
508
+ set_session req.env, sid, session, options
388
509
  end
389
510
 
390
511
  # All thread safety and session destroy procedures should occur here.
391
512
  # Should return a new session id or nil if options[:drop]
392
513
 
393
- def destroy_session(env, sid, options)
394
- raise '#destroy_session not implemented'
514
+ def delete_session(req, sid, options)
515
+ destroy_session req.env, sid, options
395
516
  end
396
517
  end
397
518
  end
@@ -1,8 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'openssl'
2
4
  require 'zlib'
3
5
  require 'rack/request'
4
6
  require 'rack/response'
5
7
  require 'rack/session/abstract/id'
8
+ require 'json'
9
+ require 'base64'
6
10
 
7
11
  module Rack
8
12
 
@@ -44,15 +48,15 @@ module Rack
44
48
  # })
45
49
  #
46
50
 
47
- class Cookie < Abstract::ID
51
+ class Cookie < Abstract::PersistedSecure
48
52
  # Encode session cookies as Base64
49
53
  class Base64
50
54
  def encode(str)
51
- [str].pack('m')
55
+ ::Base64.strict_encode64(str)
52
56
  end
53
57
 
54
58
  def decode(str)
55
- str.unpack('m').first
59
+ ::Base64.decode64(str)
56
60
  end
57
61
 
58
62
  # Encode session cookies as Marshaled Base64 data
@@ -71,23 +75,23 @@ module Rack
71
75
  # valid JSON composite type, either a Hash or an Array.
72
76
  class JSON < Base64
73
77
  def encode(obj)
74
- super(::Rack::Utils::OkJson.encode(obj))
78
+ super(::JSON.dump(obj))
75
79
  end
76
80
 
77
81
  def decode(str)
78
82
  return unless str
79
- ::Rack::Utils::OkJson.decode(super(str)) rescue nil
83
+ ::JSON.parse(super(str)) rescue nil
80
84
  end
81
85
  end
82
86
 
83
87
  class ZipJSON < Base64
84
88
  def encode(obj)
85
- super(Zlib::Deflate.deflate(::Rack::Utils::OkJson.encode(obj)))
89
+ super(Zlib::Deflate.deflate(::JSON.dump(obj)))
86
90
  end
87
91
 
88
92
  def decode(str)
89
93
  return unless str
90
- ::Rack::Utils::OkJson.decode(Zlib::Inflate.inflate(super(str)))
94
+ ::JSON.parse(Zlib::Inflate.inflate(super(str)))
91
95
  rescue
92
96
  nil
93
97
  end
@@ -102,9 +106,11 @@ module Rack
102
106
 
103
107
  attr_reader :coder
104
108
 
105
- def initialize(app, options={})
109
+ def initialize(app, options = {})
106
110
  @secrets = options.values_at(:secret, :old_secret).compact
107
- warn <<-MSG unless @secrets.size >= 1
111
+ @hmac = options.fetch(:hmac, OpenSSL::Digest::SHA1)
112
+
113
+ warn <<-MSG unless secure?(options)
108
114
  SECURITY WARNING: No secret option provided to Rack::Session::Cookie.
109
115
  This poses a security threat. It is strongly recommended that you
110
116
  provide a secret to prevent exploits that may be possible from crafted
@@ -113,45 +119,51 @@ module Rack
113
119
 
114
120
  Called from: #{caller[0]}.
115
121
  MSG
116
- @coder = options[:coder] ||= Base64::Marshal.new
117
- super(app, options.merge!(:cookie_only => true))
122
+ @coder = options[:coder] ||= Base64::Marshal.new
123
+ super(app, options.merge!(cookie_only: true))
118
124
  end
119
125
 
120
126
  private
121
127
 
122
- def get_session(env, sid)
123
- data = unpacked_cookie_data(env)
128
+ def find_session(req, sid)
129
+ data = unpacked_cookie_data(req)
124
130
  data = persistent_session_id!(data)
125
131
  [data["session_id"], data]
126
132
  end
127
133
 
128
- def extract_session_id(env)
129
- unpacked_cookie_data(env)["session_id"]
134
+ def extract_session_id(request)
135
+ unpacked_cookie_data(request)["session_id"]
130
136
  end
131
137
 
132
- def unpacked_cookie_data(env)
133
- env["rack.session.unpacked_cookie_data"] ||= begin
134
- request = Rack::Request.new(env)
138
+ def unpacked_cookie_data(request)
139
+ request.fetch_header(RACK_SESSION_UNPACKED_COOKIE_DATA) do |k|
135
140
  session_data = request.cookies[@key]
136
141
 
137
142
  if @secrets.size > 0 && session_data
138
- digest, session_data = session_data.reverse.split("--", 2)
139
- digest.reverse! if digest
140
- session_data.reverse! if session_data
143
+ session_data, _, digest = session_data.rpartition('--')
141
144
  session_data = nil unless digest_match?(session_data, digest)
142
145
  end
143
146
 
144
- coder.decode(session_data) || {}
147
+ request.set_header(k, coder.decode(session_data) || {})
145
148
  end
146
149
  end
147
150
 
148
- def persistent_session_id!(data, sid=nil)
151
+ def persistent_session_id!(data, sid = nil)
149
152
  data ||= {}
150
153
  data["session_id"] ||= sid || generate_sid
151
154
  data
152
155
  end
153
156
 
154
- def set_session(env, session_id, session, options)
157
+ class SessionId < DelegateClass(Session::SessionId)
158
+ attr_reader :cookie_value
159
+
160
+ def initialize(session_id, cookie_value)
161
+ super(session_id)
162
+ @cookie_value = cookie_value
163
+ end
164
+ end
165
+
166
+ def write_session(req, session_id, session, options)
155
167
  session = session.merge("session_id" => session_id)
156
168
  session_data = coder.encode(session)
157
169
 
@@ -160,14 +172,14 @@ module Rack
160
172
  end
161
173
 
162
174
  if session_data.size > (4096 - @key.size)
163
- env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
175
+ req.get_header(RACK_ERRORS).puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
164
176
  nil
165
177
  else
166
- session_data
178
+ SessionId.new(session_id, session_data)
167
179
  end
168
180
  end
169
181
 
170
- def destroy_session(env, session_id, options)
182
+ def delete_session(req, session_id, options)
171
183
  # Nothing to do here, data is in the client
172
184
  generate_sid unless options[:drop]
173
185
  end
@@ -180,7 +192,12 @@ module Rack
180
192
  end
181
193
 
182
194
  def generate_hmac(data, secret)
183
- OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data)
195
+ OpenSSL::HMAC.hexdigest(@hmac.new, secret, data)
196
+ end
197
+
198
+ def secure?(options)
199
+ @secrets.size >= 1 ||
200
+ (options[:coder] && options[:let_coder_handle_secure_encoding])
184
201
  end
185
202
 
186
203
  end