rack 2.2.10 → 3.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +332 -90
- data/CONTRIBUTING.md +63 -55
- data/MIT-LICENSE +1 -1
- data/README.md +328 -0
- data/SPEC.rdoc +204 -131
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +3 -1
- data/lib/rack/auth/basic.rb +1 -3
- data/lib/rack/bad_request.rb +8 -0
- data/lib/rack/body_proxy.rb +21 -3
- data/lib/rack/builder.rb +102 -69
- data/lib/rack/cascade.rb +2 -3
- data/lib/rack/common_logger.rb +23 -18
- data/lib/rack/conditional_get.rb +18 -15
- data/lib/rack/constants.rb +67 -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/files.rb +15 -17
- data/lib/rack/head.rb +9 -8
- data/lib/rack/headers.rb +238 -0
- data/lib/rack/lint.rb +840 -644
- data/lib/rack/lock.rb +2 -5
- data/lib/rack/logger.rb +3 -0
- data/lib/rack/method_override.rb +5 -1
- data/lib/rack/mime.rb +14 -5
- data/lib/rack/mock.rb +1 -271
- data/lib/rack/mock_request.rb +161 -0
- data/lib/rack/mock_response.rb +124 -0
- data/lib/rack/multipart/generator.rb +7 -5
- data/lib/rack/multipart/parser.rb +213 -95
- data/lib/rack/multipart/uploaded_file.rb +4 -0
- data/lib/rack/multipart.rb +53 -40
- data/lib/rack/null_logger.rb +9 -0
- data/lib/rack/query_parser.rb +81 -102
- data/lib/rack/recursive.rb +2 -0
- data/lib/rack/reloader.rb +0 -2
- data/lib/rack/request.rb +260 -123
- data/lib/rack/response.rb +151 -66
- 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 +21 -4
- 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 +3 -1
- data/lib/rack/utils.rb +236 -237
- data/lib/rack/version.rb +1 -9
- data/lib/rack.rb +13 -89
- metadata +13 -39
- data/README.rdoc +0 -320
- data/Rakefile +0 -130
- 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/contrib/rdoc.css +0 -412
- data/example/lobster.ru +0 -6
- data/example/protectedlobster.rb +0 -16
- data/example/protectedlobster.ru +0 -10
- data/lib/rack/auth/digest/md5.rb +0 -131
- data/lib/rack/auth/digest/nonce.rb +0 -53
- data/lib/rack/auth/digest/params.rb +0 -54
- data/lib/rack/auth/digest/request.rb +0 -43
- data/lib/rack/chunked.rb +0 -117
- data/lib/rack/core_ext/regexp.rb +0 -14
- data/lib/rack/file.rb +0 -7
- 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
- data/rack.gemspec +0 -46
@@ -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
|