rack 1.6.11 → 2.2.3

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 +694 -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 +6 -3
  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 +553 -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 +217 -93
  67. data/lib/rack/session/cookie.rb +46 -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} +12 -12
  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 +212 -294
  76. data/lib/rack/version.rb +29 -0
  77. data/lib/rack.rb +76 -33
  78. data/rack.gemspec +43 -30
  79. metadata +65 -186
  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,37 +221,38 @@ 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)
215
254
  @cookie_only = @default_options.delete(:cookie_only)
255
+ @same_site = @default_options.delete(:same_site)
216
256
  initialize_sid
217
257
  end
218
258
 
@@ -220,14 +260,21 @@ module Rack
220
260
  context(env)
221
261
  end
222
262
 
223
- def context(env, app=@app)
224
- prepare_session(env)
225
- status, headers, body = app.call(env)
226
- commit_session(env, status, headers, body)
263
+ def context(env, app = @app)
264
+ req = make_request env
265
+ prepare_session(req)
266
+ status, headers, body = app.call(req.env)
267
+ res = Rack::Response::Raw.new status, headers
268
+ commit_session(req, res)
269
+ [status, headers, body]
227
270
  end
228
271
 
229
272
  private
230
273
 
274
+ def make_request(env)
275
+ Rack::Request.new env
276
+ end
277
+
231
278
  def initialize_sid
232
279
  @sidbits = @default_options[:sidbits]
233
280
  @sid_secure = @default_options[:secure_random]
@@ -251,26 +298,26 @@ module Rack
251
298
  # Sets the lazy session at 'rack.session' and places options and session
252
299
  # metadata into 'rack.session.options'.
253
300
 
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
301
+ def prepare_session(req)
302
+ session_was = req.get_header RACK_SESSION
303
+ session = session_class.new(self, req)
304
+ req.set_header RACK_SESSION, session
305
+ req.set_header RACK_SESSION_OPTIONS, @default_options.dup
306
+ session.merge! session_was if session_was
259
307
  end
260
308
 
261
309
  # Extracts the session id from provided cookies and passes it and the
262
- # environment to #get_session.
310
+ # environment to #find_session.
263
311
 
264
- def load_session(env)
265
- sid = current_session_id(env)
266
- sid, session = get_session(env, sid)
312
+ def load_session(req)
313
+ sid = current_session_id(req)
314
+ sid, session = find_session(req, sid)
267
315
  [sid, session || {}]
268
316
  end
269
317
 
270
318
  # Extract session id from request object.
271
319
 
272
- def extract_session_id(env)
273
- request = Rack::Request.new(env)
320
+ def extract_session_id(request)
274
321
  sid = request.cookies[@key]
275
322
  sid ||= request.params[@key] unless @cookie_only
276
323
  sid
@@ -278,26 +325,26 @@ module Rack
278
325
 
279
326
  # Returns the current session id from the SessionHash.
280
327
 
281
- def current_session_id(env)
282
- env[ENV_SESSION_KEY].id
328
+ def current_session_id(req)
329
+ req.get_header(RACK_SESSION).id
283
330
  end
284
331
 
285
332
  # Check if the session exists or not.
286
333
 
287
- def session_exists?(env)
288
- value = current_session_id(env)
334
+ def session_exists?(req)
335
+ value = current_session_id(req)
289
336
  value && !value.empty?
290
337
  end
291
338
 
292
339
  # Session should be committed if it was loaded, any of specific options like :renew, :drop
293
340
  # or :expire_after was given and the security permissions match. Skips if skip is given.
294
341
 
295
- def commit_session?(env, session, options)
342
+ def commit_session?(req, session, options)
296
343
  if options[:skip]
297
344
  false
298
345
  else
299
346
  has_session = loaded_session?(session) || forced_session_update?(session, options)
300
- has_session && security_matches?(env, options)
347
+ has_session && security_matches?(req, options)
301
348
  end
302
349
  end
303
350
 
@@ -313,54 +360,62 @@ module Rack
313
360
  options.values_at(:max_age, :renew, :drop, :defer, :expire_after).any?
314
361
  end
315
362
 
316
- def security_matches?(env, options)
363
+ def security_matches?(request, options)
317
364
  return true unless options[:secure]
318
- request = Rack::Request.new(env)
319
365
  request.ssl?
320
366
  end
321
367
 
322
368
  # Acquires the session from the environment and the session id from
323
- # the session options and passes them to #set_session. If successful
369
+ # the session options and passes them to #write_session. If successful
324
370
  # and the :defer option is not true, a cookie will be added to the
325
371
  # response with the session's id.
326
372
 
327
- def commit_session(env, status, headers, body)
328
- session = env[ENV_SESSION_KEY]
373
+ def commit_session(req, res)
374
+ session = req.get_header RACK_SESSION
329
375
  options = session.options
330
376
 
331
377
  if options[:drop] || options[:renew]
332
- session_id = destroy_session(env, session.id || generate_sid, options)
333
- return [status, headers, body] unless session_id
378
+ session_id = delete_session(req, session.id || generate_sid, options)
379
+ return unless session_id
334
380
  end
335
381
 
336
- return [status, headers, body] unless commit_session?(env, session, options)
382
+ return unless commit_session?(req, session, options)
337
383
 
338
384
  session.send(:load!) unless loaded_session?(session)
339
385
  session_id ||= session.id
340
- session_data = session.to_hash.delete_if { |k,v| v.nil? }
386
+ session_data = session.to_hash.delete_if { |k, v| v.nil? }
341
387
 
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.")
388
+ if not data = write_session(req, session_id, session_data, options)
389
+ req.get_header(RACK_ERRORS).puts("Warning! #{self.class.name} failed to save session. Content dropped.")
344
390
  elsif options[:defer] and not options[:renew]
345
- env["rack.errors"].puts("Deferring cookie for #{session_id}") if $VERBOSE
391
+ req.get_header(RACK_ERRORS).puts("Deferring cookie for #{session_id}") if $VERBOSE
346
392
  else
347
393
  cookie = Hash.new
348
- cookie[:value] = data
394
+ cookie[:value] = cookie_value(data)
349
395
  cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
350
396
  cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
351
- set_cookie(env, headers, cookie.merge!(options))
397
+
398
+ if @same_site.respond_to? :call
399
+ cookie[:same_site] = @same_site.call(req, res)
400
+ else
401
+ cookie[:same_site] = @same_site
402
+ end
403
+ set_cookie(req, res, cookie.merge!(options))
352
404
  end
405
+ end
406
+ public :commit_session
353
407
 
354
- [status, headers, body]
408
+ def cookie_value(data)
409
+ data
355
410
  end
356
411
 
357
412
  # Sets the cookie back to the client with session id. We skip the cookie
358
413
  # setting if the value didn't change (sid is the same) or expires was given.
359
414
 
360
- def set_cookie(env, headers, cookie)
361
- request = Rack::Request.new(env)
415
+ def set_cookie(request, res, cookie)
362
416
  if request.cookies[@key] != cookie[:value] || cookie[:expires]
363
- Utils.set_cookie_header!(headers, @key, cookie)
417
+ res.set_cookie_header =
418
+ Utils.add_cookie_to_header(res.set_cookie_header, @key, cookie)
364
419
  end
365
420
  end
366
421
 
@@ -375,23 +430,92 @@ module Rack
375
430
  # If nil is provided as the session id, generation of a new valid id
376
431
  # should occur within.
377
432
 
378
- def get_session(env, sid)
379
- raise '#get_session not implemented.'
433
+ def find_session(env, sid)
434
+ raise '#find_session not implemented.'
435
+ end
436
+
437
+ # All thread safety and session storage procedures should occur here.
438
+ # Must return the session id if the session was saved successfully, or
439
+ # false if the session could not be saved.
440
+
441
+ def write_session(req, sid, session, options)
442
+ raise '#write_session not implemented.'
443
+ end
444
+
445
+ # All thread safety and session destroy procedures should occur here.
446
+ # Should return a new session id or nil if options[:drop]
447
+
448
+ def delete_session(req, sid, options)
449
+ raise '#delete_session not implemented'
450
+ end
451
+ end
452
+
453
+ class PersistedSecure < Persisted
454
+ class SecureSessionHash < SessionHash
455
+ def [](key)
456
+ if key == "session_id"
457
+ load_for_read!
458
+ id.public_id if id
459
+ else
460
+ super
461
+ end
462
+ end
463
+ end
464
+
465
+ def generate_sid(*)
466
+ public_id = super
467
+
468
+ SessionId.new(public_id)
469
+ end
470
+
471
+ def extract_session_id(*)
472
+ public_id = super
473
+ public_id && SessionId.new(public_id)
474
+ end
475
+
476
+ private
477
+
478
+ def session_class
479
+ SecureSessionHash
480
+ end
481
+
482
+ def cookie_value(data)
483
+ data.cookie_value
484
+ end
485
+ end
486
+
487
+ class ID < Persisted
488
+ def self.inherited(klass)
489
+ k = klass.ancestors.find { |kl| kl.respond_to?(:superclass) && kl.superclass == ID }
490
+ unless k.instance_variable_defined?(:"@_rack_warned")
491
+ warn "#{klass} is inheriting from #{ID}. Inheriting from #{ID} is deprecated, please inherit from #{Persisted} instead" if $VERBOSE
492
+ k.instance_variable_set(:"@_rack_warned", true)
493
+ end
494
+ super
495
+ end
496
+
497
+ # All thread safety and session retrieval procedures should occur here.
498
+ # Should return [session_id, session].
499
+ # If nil is provided as the session id, generation of a new valid id
500
+ # should occur within.
501
+
502
+ def find_session(req, sid)
503
+ get_session req.env, sid
380
504
  end
381
505
 
382
506
  # All thread safety and session storage procedures should occur here.
383
507
  # Must return the session id if the session was saved successfully, or
384
508
  # false if the session could not be saved.
385
509
 
386
- def set_session(env, sid, session, options)
387
- raise '#set_session not implemented.'
510
+ def write_session(req, sid, session, options)
511
+ set_session req.env, sid, session, options
388
512
  end
389
513
 
390
514
  # All thread safety and session destroy procedures should occur here.
391
515
  # Should return a new session id or nil if options[:drop]
392
516
 
393
- def destroy_session(env, sid, options)
394
- raise '#destroy_session not implemented'
517
+ def delete_session(req, sid, options)
518
+ destroy_session req.env, sid, options
395
519
  end
396
520
  end
397
521
  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,51 @@ 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
+ super(app, options.merge!(cookie_only: true))
118
122
  end
119
123
 
120
124
  private
121
125
 
122
- def get_session(env, sid)
123
- data = unpacked_cookie_data(env)
126
+ def find_session(req, sid)
127
+ data = unpacked_cookie_data(req)
124
128
  data = persistent_session_id!(data)
125
129
  [data["session_id"], data]
126
130
  end
127
131
 
128
- def extract_session_id(env)
129
- unpacked_cookie_data(env)["session_id"]
132
+ def extract_session_id(request)
133
+ unpacked_cookie_data(request)["session_id"]
130
134
  end
131
135
 
132
- def unpacked_cookie_data(env)
133
- env["rack.session.unpacked_cookie_data"] ||= begin
134
- request = Rack::Request.new(env)
136
+ def unpacked_cookie_data(request)
137
+ request.fetch_header(RACK_SESSION_UNPACKED_COOKIE_DATA) do |k|
135
138
  session_data = request.cookies[@key]
136
139
 
137
140
  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
141
+ session_data, _, digest = session_data.rpartition('--')
141
142
  session_data = nil unless digest_match?(session_data, digest)
142
143
  end
143
144
 
144
- coder.decode(session_data) || {}
145
+ request.set_header(k, coder.decode(session_data) || {})
145
146
  end
146
147
  end
147
148
 
148
- def persistent_session_id!(data, sid=nil)
149
+ def persistent_session_id!(data, sid = nil)
149
150
  data ||= {}
150
151
  data["session_id"] ||= sid || generate_sid
151
152
  data
152
153
  end
153
154
 
154
- def set_session(env, session_id, session, options)
155
+ class SessionId < DelegateClass(Session::SessionId)
156
+ attr_reader :cookie_value
157
+
158
+ def initialize(session_id, cookie_value)
159
+ super(session_id)
160
+ @cookie_value = cookie_value
161
+ end
162
+ end
163
+
164
+ def write_session(req, session_id, session, options)
155
165
  session = session.merge("session_id" => session_id)
156
166
  session_data = coder.encode(session)
157
167
 
@@ -160,14 +170,14 @@ module Rack
160
170
  end
161
171
 
162
172
  if session_data.size > (4096 - @key.size)
163
- env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
173
+ req.get_header(RACK_ERRORS).puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
164
174
  nil
165
175
  else
166
- session_data
176
+ SessionId.new(session_id, session_data)
167
177
  end
168
178
  end
169
179
 
170
- def destroy_session(env, session_id, options)
180
+ def delete_session(req, session_id, options)
171
181
  # Nothing to do here, data is in the client
172
182
  generate_sid unless options[:drop]
173
183
  end
@@ -180,7 +190,12 @@ module Rack
180
190
  end
181
191
 
182
192
  def generate_hmac(data, secret)
183
- OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data)
193
+ OpenSSL::HMAC.hexdigest(@hmac.new, secret, data)
194
+ end
195
+
196
+ def secure?(options)
197
+ @secrets.size >= 1 ||
198
+ (options[:coder] && options[:let_coder_handle_secure_encoding])
184
199
  end
185
200
 
186
201
  end