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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +675 -0
- data/CONTRIBUTING.md +136 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +157 -163
- data/Rakefile +38 -32
- data/{SPEC → SPEC.rdoc} +41 -13
- data/bin/rackup +1 -0
- data/contrib/rack_logo.svg +164 -111
- data/example/lobster.ru +2 -0
- data/example/protectedlobster.rb +4 -2
- data/example/protectedlobster.ru +3 -1
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +6 -2
- data/lib/rack/auth/basic.rb +7 -4
- data/lib/rack/auth/digest/md5.rb +13 -11
- data/lib/rack/auth/digest/nonce.rb +6 -3
- data/lib/rack/auth/digest/params.rb +5 -4
- data/lib/rack/auth/digest/request.rb +6 -4
- data/lib/rack/body_proxy.rb +21 -15
- data/lib/rack/builder.rb +119 -26
- data/lib/rack/cascade.rb +28 -12
- data/lib/rack/chunked.rb +70 -22
- data/lib/rack/common_logger.rb +80 -0
- data/lib/rack/{conditionalget.rb → conditional_get.rb} +20 -16
- data/lib/rack/config.rb +2 -0
- data/lib/rack/content_length.rb +9 -8
- data/lib/rack/content_type.rb +5 -4
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +60 -70
- data/lib/rack/directory.rb +117 -85
- data/lib/rack/etag.rb +9 -7
- data/lib/rack/events.rb +153 -0
- data/lib/rack/file.rb +4 -149
- data/lib/rack/files.rb +218 -0
- data/lib/rack/handler/cgi.rb +17 -19
- data/lib/rack/handler/fastcgi.rb +17 -18
- data/lib/rack/handler/lsws.rb +14 -14
- data/lib/rack/handler/scgi.rb +22 -21
- data/lib/rack/handler/thin.rb +20 -11
- data/lib/rack/handler/webrick.rb +39 -32
- data/lib/rack/handler.rb +9 -26
- data/lib/rack/head.rb +16 -18
- data/lib/rack/lint.rb +110 -64
- data/lib/rack/lobster.rb +10 -10
- data/lib/rack/lock.rb +17 -11
- data/lib/rack/logger.rb +4 -2
- data/lib/rack/media_type.rb +43 -0
- data/lib/rack/{methodoverride.rb → method_override.rb} +10 -8
- data/lib/rack/mime.rb +27 -6
- data/lib/rack/mock.rb +124 -65
- data/lib/rack/multipart/generator.rb +20 -16
- data/lib/rack/multipart/parser.rb +273 -162
- data/lib/rack/multipart/uploaded_file.rb +15 -8
- data/lib/rack/multipart.rb +39 -8
- data/lib/rack/{nulllogger.rb → null_logger.rb} +3 -1
- data/lib/rack/query_parser.rb +217 -0
- data/lib/rack/recursive.rb +11 -9
- data/lib/rack/reloader.rb +8 -4
- data/lib/rack/request.rb +543 -305
- data/lib/rack/response.rb +244 -88
- data/lib/rack/rewindable_input.rb +5 -15
- data/lib/rack/runtime.rb +12 -18
- data/lib/rack/sendfile.rb +17 -15
- data/lib/rack/server.rb +125 -47
- data/lib/rack/session/abstract/id.rb +216 -93
- data/lib/rack/session/cookie.rb +47 -31
- data/lib/rack/session/memcache.rb +4 -87
- data/lib/rack/session/pool.rb +26 -17
- data/lib/rack/show_exceptions.rb +390 -0
- data/lib/rack/{showstatus.rb → show_status.rb} +8 -8
- data/lib/rack/static.rb +48 -11
- data/lib/rack/tempfile_reaper.rb +3 -3
- data/lib/rack/urlmap.rb +26 -19
- data/lib/rack/utils.rb +208 -294
- data/lib/rack/version.rb +29 -0
- data/lib/rack.rb +76 -33
- data/rack.gemspec +43 -30
- metadata +62 -183
- data/HISTORY.md +0 -375
- data/KNOWN-ISSUES +0 -44
- data/lib/rack/backports/uri/common_18.rb +0 -56
- data/lib/rack/backports/uri/common_192.rb +0 -52
- data/lib/rack/backports/uri/common_193.rb +0 -29
- data/lib/rack/commonlogger.rb +0 -72
- data/lib/rack/handler/evented_mongrel.rb +0 -8
- data/lib/rack/handler/mongrel.rb +0 -106
- data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
- data/lib/rack/showexceptions.rb +0 -387
- data/lib/rack/utils/okjson.rb +0 -600
- data/test/builder/anything.rb +0 -5
- data/test/builder/comment.ru +0 -4
- data/test/builder/end.ru +0 -5
- data/test/builder/line.ru +0 -1
- data/test/builder/options.ru +0 -2
- data/test/cgi/assets/folder/test.js +0 -1
- data/test/cgi/assets/fonts/font.eot +0 -1
- data/test/cgi/assets/images/image.png +0 -1
- data/test/cgi/assets/index.html +0 -1
- data/test/cgi/assets/javascripts/app.js +0 -1
- data/test/cgi/assets/stylesheets/app.css +0 -1
- data/test/cgi/lighttpd.conf +0 -26
- data/test/cgi/rackup_stub.rb +0 -6
- data/test/cgi/sample_rackup.ru +0 -5
- data/test/cgi/test +0 -9
- data/test/cgi/test+directory/test+file +0 -1
- data/test/cgi/test.fcgi +0 -8
- data/test/cgi/test.ru +0 -5
- data/test/gemloader.rb +0 -10
- data/test/multipart/bad_robots +0 -259
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +0 -6
- data/test/multipart/empty +0 -10
- data/test/multipart/fail_16384_nofile +0 -814
- data/test/multipart/file1.txt +0 -1
- data/test/multipart/filename_and_modification_param +0 -7
- data/test/multipart/filename_and_no_name +0 -6
- data/test/multipart/filename_with_escaped_quotes +0 -6
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
- data/test/multipart/filename_with_null_byte +0 -7
- data/test/multipart/filename_with_percent_escaped_quotes +0 -6
- data/test/multipart/filename_with_unescaped_percentages +0 -6
- data/test/multipart/filename_with_unescaped_percentages2 +0 -6
- data/test/multipart/filename_with_unescaped_percentages3 +0 -6
- data/test/multipart/filename_with_unescaped_quotes +0 -6
- data/test/multipart/ie +0 -6
- data/test/multipart/invalid_character +0 -6
- data/test/multipart/mixed_files +0 -21
- data/test/multipart/nested +0 -10
- data/test/multipart/none +0 -9
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -15
- data/test/multipart/three_files_three_fields +0 -31
- data/test/multipart/webkit +0 -32
- data/test/rackup/config.ru +0 -31
- data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
- data/test/spec_auth_basic.rb +0 -81
- data/test/spec_auth_digest.rb +0 -259
- data/test/spec_body_proxy.rb +0 -85
- data/test/spec_builder.rb +0 -223
- data/test/spec_cascade.rb +0 -61
- data/test/spec_cgi.rb +0 -102
- data/test/spec_chunked.rb +0 -101
- data/test/spec_commonlogger.rb +0 -93
- data/test/spec_conditionalget.rb +0 -102
- data/test/spec_config.rb +0 -22
- data/test/spec_content_length.rb +0 -85
- data/test/spec_content_type.rb +0 -45
- data/test/spec_deflater.rb +0 -339
- data/test/spec_directory.rb +0 -88
- data/test/spec_etag.rb +0 -107
- data/test/spec_fastcgi.rb +0 -107
- data/test/spec_file.rb +0 -221
- data/test/spec_handler.rb +0 -72
- data/test/spec_head.rb +0 -45
- data/test/spec_lint.rb +0 -550
- data/test/spec_lobster.rb +0 -58
- data/test/spec_lock.rb +0 -164
- data/test/spec_logger.rb +0 -23
- data/test/spec_methodoverride.rb +0 -111
- data/test/spec_mime.rb +0 -51
- data/test/spec_mock.rb +0 -297
- data/test/spec_mongrel.rb +0 -182
- data/test/spec_multipart.rb +0 -600
- data/test/spec_nulllogger.rb +0 -20
- data/test/spec_recursive.rb +0 -72
- data/test/spec_request.rb +0 -1232
- data/test/spec_response.rb +0 -407
- data/test/spec_rewindable_input.rb +0 -118
- data/test/spec_runtime.rb +0 -49
- data/test/spec_sendfile.rb +0 -130
- data/test/spec_server.rb +0 -167
- data/test/spec_session_abstract_id.rb +0 -53
- data/test/spec_session_cookie.rb +0 -410
- data/test/spec_session_memcache.rb +0 -321
- data/test/spec_session_pool.rb +0 -209
- data/test/spec_showexceptions.rb +0 -98
- data/test/spec_showstatus.rb +0 -103
- data/test/spec_static.rb +0 -145
- data/test/spec_tempfile_reaper.rb +0 -63
- data/test/spec_thin.rb +0 -91
- data/test/spec_urlmap.rb +0 -236
- data/test/spec_utils.rb +0 -647
- data/test/spec_version.rb +0 -17
- data/test/spec_webrick.rb +0 -184
- data/test/static/another/index.html +0 -1
- data/test/static/index.html +0 -1
- data/test/testrequest.rb +0 -78
- data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
- 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 '
|
6
|
-
require '
|
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
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
28
|
-
|
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(
|
32
|
-
|
54
|
+
def self.set(req, session)
|
55
|
+
req.set_header RACK_SESSION, session
|
33
56
|
end
|
34
57
|
|
35
|
-
def self.set_options(
|
36
|
-
|
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,
|
62
|
+
def initialize(store, req)
|
40
63
|
@store = store
|
41
|
-
@
|
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, @
|
70
|
+
@id = @store.send(:extract_session_id, @req)
|
48
71
|
end
|
49
72
|
|
50
73
|
def options
|
51
|
-
@
|
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
|
-
|
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(:
|
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?, @
|
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, @
|
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 #
|
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#
|
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']
|
186
|
-
# found within the options hash at the key :id. It is
|
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
|
233
|
+
class Persisted
|
195
234
|
DEFAULT_OPTIONS = {
|
196
|
-
:
|
197
|
-
:
|
198
|
-
:
|
199
|
-
:
|
200
|
-
:
|
201
|
-
:
|
202
|
-
:
|
203
|
-
:
|
204
|
-
:
|
205
|
-
:
|
206
|
-
:
|
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
|
224
|
-
|
225
|
-
|
226
|
-
|
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(
|
255
|
-
session_was
|
256
|
-
|
257
|
-
|
258
|
-
|
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 #
|
309
|
+
# environment to #find_session.
|
263
310
|
|
264
|
-
def load_session(
|
265
|
-
sid = current_session_id(
|
266
|
-
sid, session =
|
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(
|
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(
|
282
|
-
|
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?(
|
288
|
-
value = current_session_id(
|
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?(
|
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?(
|
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?(
|
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 #
|
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(
|
328
|
-
session =
|
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 =
|
333
|
-
return
|
377
|
+
session_id = delete_session(req, session.id || generate_sid, options)
|
378
|
+
return unless session_id
|
334
379
|
end
|
335
380
|
|
336
|
-
return
|
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 =
|
343
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
361
|
-
request = Rack::Request.new(env)
|
414
|
+
def set_cookie(request, res, cookie)
|
362
415
|
if request.cookies[@key] != cookie[:value] || cookie[:expires]
|
363
|
-
|
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
|
379
|
-
raise '#
|
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
|
387
|
-
|
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
|
394
|
-
|
516
|
+
def delete_session(req, sid, options)
|
517
|
+
destroy_session req.env, sid, options
|
395
518
|
end
|
396
519
|
end
|
397
520
|
end
|
data/lib/rack/session/cookie.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'openssl'
|
2
4
|
require 'zlib'
|
3
|
-
|
4
|
-
require '
|
5
|
-
require '
|
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::
|
49
|
+
class Cookie < Abstract::PersistedSecure
|
48
50
|
# Encode session cookies as Base64
|
49
51
|
class Base64
|
50
52
|
def encode(str)
|
51
|
-
|
53
|
+
::Base64.strict_encode64(str)
|
52
54
|
end
|
53
55
|
|
54
56
|
def decode(str)
|
55
|
-
|
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(::
|
76
|
+
super(::JSON.dump(obj))
|
75
77
|
end
|
76
78
|
|
77
79
|
def decode(str)
|
78
80
|
return unless str
|
79
|
-
::
|
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(::
|
87
|
+
super(Zlib::Deflate.deflate(::JSON.dump(obj)))
|
86
88
|
end
|
87
89
|
|
88
90
|
def decode(str)
|
89
91
|
return unless str
|
90
|
-
::
|
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
|
-
|
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
|
117
|
-
|
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
|
123
|
-
data = unpacked_cookie_data(
|
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(
|
129
|
-
unpacked_cookie_data(
|
133
|
+
def extract_session_id(request)
|
134
|
+
unpacked_cookie_data(request)["session_id"]
|
130
135
|
end
|
131
136
|
|
132
|
-
def unpacked_cookie_data(
|
133
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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(
|
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
|