rack 1.6.11 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

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