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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +694 -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 +6 -3
- 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 +553 -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 +217 -93
- data/lib/rack/session/cookie.rb +46 -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} +12 -12
- 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 +212 -294
- data/lib/rack/version.rb +29 -0
- data/lib/rack.rb +76 -33
- data/rack.gemspec +43 -30
- metadata +65 -186
- 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,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']
|
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)
|
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
|
224
|
-
|
225
|
-
|
226
|
-
|
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(
|
255
|
-
session_was
|
256
|
-
|
257
|
-
|
258
|
-
|
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 #
|
310
|
+
# environment to #find_session.
|
263
311
|
|
264
|
-
def load_session(
|
265
|
-
sid = current_session_id(
|
266
|
-
sid, session =
|
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(
|
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(
|
282
|
-
|
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?(
|
288
|
-
value = current_session_id(
|
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?(
|
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?(
|
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?(
|
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 #
|
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(
|
328
|
-
session =
|
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 =
|
333
|
-
return
|
378
|
+
session_id = delete_session(req, session.id || generate_sid, options)
|
379
|
+
return unless session_id
|
334
380
|
end
|
335
381
|
|
336
|
-
return
|
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 =
|
343
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
361
|
-
request = Rack::Request.new(env)
|
415
|
+
def set_cookie(request, res, cookie)
|
362
416
|
if request.cookies[@key] != cookie[:value] || cookie[:expires]
|
363
|
-
|
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
|
379
|
-
raise '#
|
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
|
387
|
-
|
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
|
394
|
-
|
517
|
+
def delete_session(req, sid, options)
|
518
|
+
destroy_session req.env, sid, options
|
395
519
|
end
|
396
520
|
end
|
397
521
|
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,51 @@ module Rack
|
|
113
117
|
|
114
118
|
Called from: #{caller[0]}.
|
115
119
|
MSG
|
116
|
-
@coder
|
117
|
-
super(app, options.merge!(:
|
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
|
123
|
-
data = unpacked_cookie_data(
|
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(
|
129
|
-
unpacked_cookie_data(
|
132
|
+
def extract_session_id(request)
|
133
|
+
unpacked_cookie_data(request)["session_id"]
|
130
134
|
end
|
131
135
|
|
132
|
-
def unpacked_cookie_data(
|
133
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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(
|
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
|