rack 2.2.10 → 3.0.0.beta1
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 +138 -105
- data/CONTRIBUTING.md +53 -47
- data/MIT-LICENSE +1 -1
- data/README.md +287 -0
- data/Rakefile +40 -7
- data/SPEC.rdoc +166 -125
- data/contrib/LICENSE.md +7 -0
- data/contrib/logo.webp +0 -0
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +3 -1
- data/lib/rack/auth/basic.rb +2 -1
- data/lib/rack/auth/digest/md5.rb +1 -131
- data/lib/rack/auth/digest/nonce.rb +1 -53
- data/lib/rack/auth/digest/params.rb +1 -54
- data/lib/rack/auth/digest/request.rb +1 -43
- data/lib/rack/auth/digest.rb +256 -0
- data/lib/rack/body_proxy.rb +3 -1
- data/lib/rack/builder.rb +60 -42
- data/lib/rack/cascade.rb +2 -0
- data/lib/rack/chunked.rb +16 -13
- data/lib/rack/common_logger.rb +23 -18
- data/lib/rack/conditional_get.rb +18 -15
- data/lib/rack/constants.rb +62 -0
- data/lib/rack/content_length.rb +12 -16
- data/lib/rack/content_type.rb +8 -5
- data/lib/rack/deflater.rb +40 -26
- data/lib/rack/directory.rb +9 -3
- data/lib/rack/etag.rb +14 -23
- data/lib/rack/events.rb +4 -0
- data/lib/rack/file.rb +2 -0
- data/lib/rack/files.rb +15 -17
- data/lib/rack/head.rb +9 -8
- data/lib/rack/headers.rb +154 -0
- data/lib/rack/lint.rb +740 -649
- data/lib/rack/lock.rb +2 -5
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +4 -9
- data/lib/rack/method_override.rb +5 -1
- data/lib/rack/mime.rb +8 -0
- data/lib/rack/mock.rb +1 -271
- data/lib/rack/mock_request.rb +166 -0
- data/lib/rack/mock_response.rb +124 -0
- data/lib/rack/multipart/generator.rb +7 -5
- data/lib/rack/multipart/parser.rb +123 -85
- data/lib/rack/multipart/uploaded_file.rb +4 -0
- data/lib/rack/multipart.rb +20 -40
- data/lib/rack/null_logger.rb +9 -0
- data/lib/rack/query_parser.rb +76 -44
- data/lib/rack/recursive.rb +2 -0
- data/lib/rack/reloader.rb +0 -2
- data/lib/rack/request.rb +189 -91
- data/lib/rack/response.rb +131 -61
- data/lib/rack/rewindable_input.rb +24 -5
- data/lib/rack/runtime.rb +7 -6
- data/lib/rack/sendfile.rb +30 -25
- data/lib/rack/show_exceptions.rb +15 -2
- data/lib/rack/show_status.rb +17 -7
- data/lib/rack/static.rb +8 -8
- data/lib/rack/tempfile_reaper.rb +15 -4
- data/lib/rack/urlmap.rb +4 -2
- data/lib/rack/utils.rb +212 -202
- data/lib/rack/version.rb +9 -4
- data/lib/rack.rb +5 -76
- data/rack.gemspec +6 -6
- metadata +19 -31
- data/README.rdoc +0 -320
- data/bin/rackup +0 -5
- data/contrib/rack.png +0 -0
- data/contrib/rack.svg +0 -150
- data/contrib/rack_logo.svg +0 -164
- data/lib/rack/core_ext/regexp.rb +0 -14
- data/lib/rack/handler/cgi.rb +0 -59
- data/lib/rack/handler/fastcgi.rb +0 -100
- data/lib/rack/handler/lsws.rb +0 -61
- data/lib/rack/handler/scgi.rb +0 -71
- data/lib/rack/handler/thin.rb +0 -36
- data/lib/rack/handler/webrick.rb +0 -129
- data/lib/rack/handler.rb +0 -104
- data/lib/rack/lobster.rb +0 -70
- data/lib/rack/server.rb +0 -466
- data/lib/rack/session/abstract/id.rb +0 -523
- data/lib/rack/session/cookie.rb +0 -203
- data/lib/rack/session/memcache.rb +0 -10
- data/lib/rack/session/pool.rb +0 -85
@@ -1,523 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
|
4
|
-
# bugrep: Andreas Zehnder
|
5
|
-
|
6
|
-
require_relative '../../../rack'
|
7
|
-
require 'time'
|
8
|
-
require 'securerandom'
|
9
|
-
require 'digest/sha2'
|
10
|
-
|
11
|
-
module Rack
|
12
|
-
|
13
|
-
module Session
|
14
|
-
|
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
|
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
|
42
|
-
# SessionHash is responsible to lazily load the session from store.
|
43
|
-
|
44
|
-
class SessionHash
|
45
|
-
include Enumerable
|
46
|
-
attr_writer :id
|
47
|
-
|
48
|
-
Unspecified = Object.new
|
49
|
-
|
50
|
-
def self.find(req)
|
51
|
-
req.get_header RACK_SESSION
|
52
|
-
end
|
53
|
-
|
54
|
-
def self.set(req, session)
|
55
|
-
req.set_header RACK_SESSION, session
|
56
|
-
end
|
57
|
-
|
58
|
-
def self.set_options(req, options)
|
59
|
-
req.set_header RACK_SESSION_OPTIONS, options.dup
|
60
|
-
end
|
61
|
-
|
62
|
-
def initialize(store, req)
|
63
|
-
@store = store
|
64
|
-
@req = req
|
65
|
-
@loaded = false
|
66
|
-
end
|
67
|
-
|
68
|
-
def id
|
69
|
-
return @id if @loaded or instance_variable_defined?(:@id)
|
70
|
-
@id = @store.send(:extract_session_id, @req)
|
71
|
-
end
|
72
|
-
|
73
|
-
def options
|
74
|
-
@req.session_options
|
75
|
-
end
|
76
|
-
|
77
|
-
def each(&block)
|
78
|
-
load_for_read!
|
79
|
-
@data.each(&block)
|
80
|
-
end
|
81
|
-
|
82
|
-
def [](key)
|
83
|
-
load_for_read!
|
84
|
-
@data[key.to_s]
|
85
|
-
end
|
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
|
100
|
-
|
101
|
-
def has_key?(key)
|
102
|
-
load_for_read!
|
103
|
-
@data.has_key?(key.to_s)
|
104
|
-
end
|
105
|
-
alias :key? :has_key?
|
106
|
-
alias :include? :has_key?
|
107
|
-
|
108
|
-
def []=(key, value)
|
109
|
-
load_for_write!
|
110
|
-
@data[key.to_s] = value
|
111
|
-
end
|
112
|
-
alias :store :[]=
|
113
|
-
|
114
|
-
def clear
|
115
|
-
load_for_write!
|
116
|
-
@data.clear
|
117
|
-
end
|
118
|
-
|
119
|
-
def destroy
|
120
|
-
clear
|
121
|
-
@id = @store.send(:delete_session, @req, id, options)
|
122
|
-
end
|
123
|
-
|
124
|
-
def to_hash
|
125
|
-
load_for_read!
|
126
|
-
@data.dup
|
127
|
-
end
|
128
|
-
|
129
|
-
def update(hash)
|
130
|
-
load_for_write!
|
131
|
-
@data.update(stringify_keys(hash))
|
132
|
-
end
|
133
|
-
alias :merge! :update
|
134
|
-
|
135
|
-
def replace(hash)
|
136
|
-
load_for_write!
|
137
|
-
@data.replace(stringify_keys(hash))
|
138
|
-
end
|
139
|
-
|
140
|
-
def delete(key)
|
141
|
-
load_for_write!
|
142
|
-
@data.delete(key.to_s)
|
143
|
-
end
|
144
|
-
|
145
|
-
def inspect
|
146
|
-
if loaded?
|
147
|
-
@data.inspect
|
148
|
-
else
|
149
|
-
"#<#{self.class}:0x#{self.object_id.to_s(16)} not yet loaded>"
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
def exists?
|
154
|
-
return @exists if instance_variable_defined?(:@exists)
|
155
|
-
@data = {}
|
156
|
-
@exists = @store.send(:session_exists?, @req)
|
157
|
-
end
|
158
|
-
|
159
|
-
def loaded?
|
160
|
-
@loaded
|
161
|
-
end
|
162
|
-
|
163
|
-
def empty?
|
164
|
-
load_for_read!
|
165
|
-
@data.empty?
|
166
|
-
end
|
167
|
-
|
168
|
-
def keys
|
169
|
-
load_for_read!
|
170
|
-
@data.keys
|
171
|
-
end
|
172
|
-
|
173
|
-
def values
|
174
|
-
load_for_read!
|
175
|
-
@data.values
|
176
|
-
end
|
177
|
-
|
178
|
-
private
|
179
|
-
|
180
|
-
def load_for_read!
|
181
|
-
load! if !loaded? && exists?
|
182
|
-
end
|
183
|
-
|
184
|
-
def load_for_write!
|
185
|
-
load! unless loaded?
|
186
|
-
end
|
187
|
-
|
188
|
-
def load!
|
189
|
-
@id, session = @store.send(:load_session, @req)
|
190
|
-
@data = stringify_keys(session)
|
191
|
-
@loaded = true
|
192
|
-
end
|
193
|
-
|
194
|
-
def stringify_keys(other)
|
195
|
-
# Use transform_keys after dropping Ruby 2.4 support
|
196
|
-
hash = {}
|
197
|
-
other.to_hash.each do |key, value|
|
198
|
-
hash[key.to_s] = value
|
199
|
-
end
|
200
|
-
hash
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
# ID sets up a basic framework for implementing an id based sessioning
|
205
|
-
# service. Cookies sent to the client for maintaining sessions will only
|
206
|
-
# contain an id reference. Only #find_session, #write_session and
|
207
|
-
# #delete_session are required to be overwritten.
|
208
|
-
#
|
209
|
-
# All parameters are optional.
|
210
|
-
# * :key determines the name of the cookie, by default it is
|
211
|
-
# 'rack.session'
|
212
|
-
# * :path, :domain, :expire_after, :secure, and :httponly set the related
|
213
|
-
# cookie options as by Rack::Response#set_cookie
|
214
|
-
# * :skip will not a set a cookie in the response nor update the session state
|
215
|
-
# * :defer will not set a cookie in the response but still update the session
|
216
|
-
# state if it is used with a backend
|
217
|
-
# * :renew (implementation dependent) will prompt the generation of a new
|
218
|
-
# session id, and migration of data to be referenced at the new id. If
|
219
|
-
# :defer is set, it will be overridden and the cookie will be set.
|
220
|
-
# * :sidbits sets the number of bits in length that a generated session
|
221
|
-
# id will be.
|
222
|
-
#
|
223
|
-
# These options can be set on a per request basis, at the location of
|
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.
|
227
|
-
#
|
228
|
-
# Is Rack::Utils::Context compatible.
|
229
|
-
#
|
230
|
-
# Not included by default; you must require 'rack/session/abstract/id'
|
231
|
-
# to use.
|
232
|
-
|
233
|
-
class Persisted
|
234
|
-
DEFAULT_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 = {})
|
251
|
-
@app = app
|
252
|
-
@default_options = self.class::DEFAULT_OPTIONS.merge(options)
|
253
|
-
@key = @default_options.delete(:key)
|
254
|
-
@cookie_only = @default_options.delete(:cookie_only)
|
255
|
-
@same_site = @default_options.delete(:same_site)
|
256
|
-
initialize_sid
|
257
|
-
end
|
258
|
-
|
259
|
-
def call(env)
|
260
|
-
context(env)
|
261
|
-
end
|
262
|
-
|
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]
|
270
|
-
end
|
271
|
-
|
272
|
-
private
|
273
|
-
|
274
|
-
def make_request(env)
|
275
|
-
Rack::Request.new env
|
276
|
-
end
|
277
|
-
|
278
|
-
def initialize_sid
|
279
|
-
@sidbits = @default_options[:sidbits]
|
280
|
-
@sid_secure = @default_options[:secure_random]
|
281
|
-
@sid_length = @sidbits / 4
|
282
|
-
end
|
283
|
-
|
284
|
-
# Generate a new session id using Ruby #rand. The size of the
|
285
|
-
# session id is controlled by the :sidbits option.
|
286
|
-
# Monkey patch this to use custom methods for session id generation.
|
287
|
-
|
288
|
-
def generate_sid(secure = @sid_secure)
|
289
|
-
if secure
|
290
|
-
secure.hex(@sid_length)
|
291
|
-
else
|
292
|
-
"%0#{@sid_length}x" % Kernel.rand(2**@sidbits - 1)
|
293
|
-
end
|
294
|
-
rescue NotImplementedError
|
295
|
-
generate_sid(false)
|
296
|
-
end
|
297
|
-
|
298
|
-
# Sets the lazy session at 'rack.session' and places options and session
|
299
|
-
# metadata into 'rack.session.options'.
|
300
|
-
|
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
|
307
|
-
end
|
308
|
-
|
309
|
-
# Extracts the session id from provided cookies and passes it and the
|
310
|
-
# environment to #find_session.
|
311
|
-
|
312
|
-
def load_session(req)
|
313
|
-
sid = current_session_id(req)
|
314
|
-
sid, session = find_session(req, sid)
|
315
|
-
[sid, session || {}]
|
316
|
-
end
|
317
|
-
|
318
|
-
# Extract session id from request object.
|
319
|
-
|
320
|
-
def extract_session_id(request)
|
321
|
-
sid = request.cookies[@key]
|
322
|
-
sid ||= request.params[@key] unless @cookie_only
|
323
|
-
sid
|
324
|
-
end
|
325
|
-
|
326
|
-
# Returns the current session id from the SessionHash.
|
327
|
-
|
328
|
-
def current_session_id(req)
|
329
|
-
req.get_header(RACK_SESSION).id
|
330
|
-
end
|
331
|
-
|
332
|
-
# Check if the session exists or not.
|
333
|
-
|
334
|
-
def session_exists?(req)
|
335
|
-
value = current_session_id(req)
|
336
|
-
value && !value.empty?
|
337
|
-
end
|
338
|
-
|
339
|
-
# Session should be committed if it was loaded, any of specific options like :renew, :drop
|
340
|
-
# or :expire_after was given and the security permissions match. Skips if skip is given.
|
341
|
-
|
342
|
-
def commit_session?(req, session, options)
|
343
|
-
if options[:skip]
|
344
|
-
false
|
345
|
-
else
|
346
|
-
has_session = loaded_session?(session) || forced_session_update?(session, options)
|
347
|
-
has_session && security_matches?(req, options)
|
348
|
-
end
|
349
|
-
end
|
350
|
-
|
351
|
-
def loaded_session?(session)
|
352
|
-
!session.is_a?(session_class) || session.loaded?
|
353
|
-
end
|
354
|
-
|
355
|
-
def forced_session_update?(session, options)
|
356
|
-
force_options?(options) && session && !session.empty?
|
357
|
-
end
|
358
|
-
|
359
|
-
def force_options?(options)
|
360
|
-
options.values_at(:max_age, :renew, :drop, :defer, :expire_after).any?
|
361
|
-
end
|
362
|
-
|
363
|
-
def security_matches?(request, options)
|
364
|
-
return true unless options[:secure]
|
365
|
-
request.ssl?
|
366
|
-
end
|
367
|
-
|
368
|
-
# Acquires the session from the environment and the session id from
|
369
|
-
# the session options and passes them to #write_session. If successful
|
370
|
-
# and the :defer option is not true, a cookie will be added to the
|
371
|
-
# response with the session's id.
|
372
|
-
|
373
|
-
def commit_session(req, res)
|
374
|
-
session = req.get_header RACK_SESSION
|
375
|
-
options = session.options
|
376
|
-
|
377
|
-
if options[:drop] || options[:renew]
|
378
|
-
session_id = delete_session(req, session.id || generate_sid, options)
|
379
|
-
return unless session_id
|
380
|
-
end
|
381
|
-
|
382
|
-
return unless commit_session?(req, session, options)
|
383
|
-
|
384
|
-
session.send(:load!) unless loaded_session?(session)
|
385
|
-
session_id ||= session.id
|
386
|
-
session_data = session.to_hash.delete_if { |k, v| v.nil? }
|
387
|
-
|
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.")
|
390
|
-
elsif options[:defer] and not options[:renew]
|
391
|
-
req.get_header(RACK_ERRORS).puts("Deferring cookie for #{session_id}") if $VERBOSE
|
392
|
-
else
|
393
|
-
cookie = Hash.new
|
394
|
-
cookie[:value] = cookie_value(data)
|
395
|
-
cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
|
396
|
-
cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
|
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))
|
404
|
-
end
|
405
|
-
end
|
406
|
-
public :commit_session
|
407
|
-
|
408
|
-
def cookie_value(data)
|
409
|
-
data
|
410
|
-
end
|
411
|
-
|
412
|
-
# Sets the cookie back to the client with session id. We skip the cookie
|
413
|
-
# setting if the value didn't change (sid is the same) or expires was given.
|
414
|
-
|
415
|
-
def set_cookie(request, res, cookie)
|
416
|
-
if request.cookies[@key] != cookie[:value] || cookie[:expires]
|
417
|
-
res.set_cookie_header =
|
418
|
-
Utils.add_cookie_to_header(res.set_cookie_header, @key, cookie)
|
419
|
-
end
|
420
|
-
end
|
421
|
-
|
422
|
-
# Allow subclasses to prepare_session for different Session classes
|
423
|
-
|
424
|
-
def session_class
|
425
|
-
SessionHash
|
426
|
-
end
|
427
|
-
|
428
|
-
# All thread safety and session retrieval procedures should occur here.
|
429
|
-
# Should return [session_id, session].
|
430
|
-
# If nil is provided as the session id, generation of a new valid id
|
431
|
-
# should occur within.
|
432
|
-
|
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
|
504
|
-
end
|
505
|
-
|
506
|
-
# All thread safety and session storage procedures should occur here.
|
507
|
-
# Must return the session id if the session was saved successfully, or
|
508
|
-
# false if the session could not be saved.
|
509
|
-
|
510
|
-
def write_session(req, sid, session, options)
|
511
|
-
set_session req.env, sid, session, options
|
512
|
-
end
|
513
|
-
|
514
|
-
# All thread safety and session destroy procedures should occur here.
|
515
|
-
# Should return a new session id or nil if options[:drop]
|
516
|
-
|
517
|
-
def delete_session(req, sid, options)
|
518
|
-
destroy_session req.env, sid, options
|
519
|
-
end
|
520
|
-
end
|
521
|
-
end
|
522
|
-
end
|
523
|
-
end
|
data/lib/rack/session/cookie.rb
DELETED
@@ -1,203 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'openssl'
|
4
|
-
require 'zlib'
|
5
|
-
require_relative 'abstract/id'
|
6
|
-
require 'json'
|
7
|
-
require 'delegate'
|
8
|
-
|
9
|
-
module Rack
|
10
|
-
|
11
|
-
module Session
|
12
|
-
|
13
|
-
# Rack::Session::Cookie provides simple cookie based session management.
|
14
|
-
# By default, the session is a Ruby Hash stored as base64 encoded marshalled
|
15
|
-
# data set to :key (default: rack.session). The object that encodes the
|
16
|
-
# session data is configurable and must respond to +encode+ and +decode+.
|
17
|
-
# Both methods must take a string and return a string.
|
18
|
-
#
|
19
|
-
# When the secret key is set, cookie data is checked for data integrity.
|
20
|
-
# The old secret key is also accepted and allows graceful secret rotation.
|
21
|
-
#
|
22
|
-
# Example:
|
23
|
-
#
|
24
|
-
# use Rack::Session::Cookie, :key => 'rack.session',
|
25
|
-
# :domain => 'foo.com',
|
26
|
-
# :path => '/',
|
27
|
-
# :expire_after => 2592000,
|
28
|
-
# :secret => 'change_me',
|
29
|
-
# :old_secret => 'also_change_me'
|
30
|
-
#
|
31
|
-
# All parameters are optional.
|
32
|
-
#
|
33
|
-
# Example of a cookie with no encoding:
|
34
|
-
#
|
35
|
-
# Rack::Session::Cookie.new(application, {
|
36
|
-
# :coder => Rack::Session::Cookie::Identity.new
|
37
|
-
# })
|
38
|
-
#
|
39
|
-
# Example of a cookie with custom encoding:
|
40
|
-
#
|
41
|
-
# Rack::Session::Cookie.new(application, {
|
42
|
-
# :coder => Class.new {
|
43
|
-
# def encode(str); str.reverse; end
|
44
|
-
# def decode(str); str.reverse; end
|
45
|
-
# }.new
|
46
|
-
# })
|
47
|
-
#
|
48
|
-
|
49
|
-
class Cookie < Abstract::PersistedSecure
|
50
|
-
# Encode session cookies as Base64
|
51
|
-
class Base64
|
52
|
-
def encode(str)
|
53
|
-
[str].pack("m0")
|
54
|
-
end
|
55
|
-
|
56
|
-
def decode(str)
|
57
|
-
str.unpack("m").first
|
58
|
-
end
|
59
|
-
|
60
|
-
# Encode session cookies as Marshaled Base64 data
|
61
|
-
class Marshal < Base64
|
62
|
-
def encode(str)
|
63
|
-
super(::Marshal.dump(str))
|
64
|
-
end
|
65
|
-
|
66
|
-
def decode(str)
|
67
|
-
return unless str
|
68
|
-
::Marshal.load(super(str)) rescue nil
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
# N.B. Unlike other encoding methods, the contained objects must be a
|
73
|
-
# valid JSON composite type, either a Hash or an Array.
|
74
|
-
class JSON < Base64
|
75
|
-
def encode(obj)
|
76
|
-
super(::JSON.dump(obj))
|
77
|
-
end
|
78
|
-
|
79
|
-
def decode(str)
|
80
|
-
return unless str
|
81
|
-
::JSON.parse(super(str)) rescue nil
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
class ZipJSON < Base64
|
86
|
-
def encode(obj)
|
87
|
-
super(Zlib::Deflate.deflate(::JSON.dump(obj)))
|
88
|
-
end
|
89
|
-
|
90
|
-
def decode(str)
|
91
|
-
return unless str
|
92
|
-
::JSON.parse(Zlib::Inflate.inflate(super(str)))
|
93
|
-
rescue
|
94
|
-
nil
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# Use no encoding for session cookies
|
100
|
-
class Identity
|
101
|
-
def encode(str); str; end
|
102
|
-
def decode(str); str; end
|
103
|
-
end
|
104
|
-
|
105
|
-
attr_reader :coder
|
106
|
-
|
107
|
-
def initialize(app, options = {})
|
108
|
-
@secrets = options.values_at(:secret, :old_secret).compact
|
109
|
-
@hmac = options.fetch(:hmac, OpenSSL::Digest::SHA1)
|
110
|
-
|
111
|
-
warn <<-MSG unless secure?(options)
|
112
|
-
SECURITY WARNING: No secret option provided to Rack::Session::Cookie.
|
113
|
-
This poses a security threat. It is strongly recommended that you
|
114
|
-
provide a secret to prevent exploits that may be possible from crafted
|
115
|
-
cookies. This will not be supported in future versions of Rack, and
|
116
|
-
future versions will even invalidate your existing user cookies.
|
117
|
-
|
118
|
-
Called from: #{caller[0]}.
|
119
|
-
MSG
|
120
|
-
@coder = options[:coder] ||= Base64::Marshal.new
|
121
|
-
super(app, options.merge!(cookie_only: true))
|
122
|
-
end
|
123
|
-
|
124
|
-
private
|
125
|
-
|
126
|
-
def find_session(req, sid)
|
127
|
-
data = unpacked_cookie_data(req)
|
128
|
-
data = persistent_session_id!(data)
|
129
|
-
[data["session_id"], data]
|
130
|
-
end
|
131
|
-
|
132
|
-
def extract_session_id(request)
|
133
|
-
unpacked_cookie_data(request)["session_id"]
|
134
|
-
end
|
135
|
-
|
136
|
-
def unpacked_cookie_data(request)
|
137
|
-
request.fetch_header(RACK_SESSION_UNPACKED_COOKIE_DATA) do |k|
|
138
|
-
session_data = request.cookies[@key]
|
139
|
-
|
140
|
-
if @secrets.size > 0 && session_data
|
141
|
-
session_data, _, digest = session_data.rpartition('--')
|
142
|
-
session_data = nil unless digest_match?(session_data, digest)
|
143
|
-
end
|
144
|
-
|
145
|
-
request.set_header(k, coder.decode(session_data) || {})
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
def persistent_session_id!(data, sid = nil)
|
150
|
-
data ||= {}
|
151
|
-
data["session_id"] ||= sid || generate_sid
|
152
|
-
data
|
153
|
-
end
|
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)
|
165
|
-
session = session.merge("session_id" => session_id)
|
166
|
-
session_data = coder.encode(session)
|
167
|
-
|
168
|
-
if @secrets.first
|
169
|
-
session_data << "--#{generate_hmac(session_data, @secrets.first)}"
|
170
|
-
end
|
171
|
-
|
172
|
-
if session_data.size > (4096 - @key.size)
|
173
|
-
req.get_header(RACK_ERRORS).puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
|
174
|
-
nil
|
175
|
-
else
|
176
|
-
SessionId.new(session_id, session_data)
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
def delete_session(req, session_id, options)
|
181
|
-
# Nothing to do here, data is in the client
|
182
|
-
generate_sid unless options[:drop]
|
183
|
-
end
|
184
|
-
|
185
|
-
def digest_match?(data, digest)
|
186
|
-
return unless data && digest
|
187
|
-
@secrets.any? do |secret|
|
188
|
-
Rack::Utils.secure_compare(digest, generate_hmac(data, secret))
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
def generate_hmac(data, secret)
|
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])
|
199
|
-
end
|
200
|
-
|
201
|
-
end
|
202
|
-
end
|
203
|
-
end
|