rack 2.0.7 → 2.0.9.1
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 +5 -5
- data/HISTORY.md +5 -0
- data/lib/rack/common_logger.rb +3 -0
- data/lib/rack/lint.rb +1 -1
- data/lib/rack/multipart/parser.rb +2 -1
- data/lib/rack/multipart.rb +1 -2
- data/lib/rack/session/abstract/id.rb +66 -1
- data/lib/rack/session/cookie.rb +11 -2
- data/lib/rack/session/memcache.rb +20 -14
- data/lib/rack/session/pool.rb +13 -6
- data/lib/rack/utils.rb +2 -0
- data/lib/rack.rb +1 -1
- data/test/cgi/test.gz +0 -0
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +1 -1
- data/test/spec_common_logger.rb +12 -0
- data/test/spec_lint.rb +5 -0
- data/test/spec_multipart.rb +1 -14
- data/test/spec_response.rb +18 -0
- data/test/spec_session_memcache.rb +40 -3
- data/test/spec_session_persisted_secure_secure_session_hash.rb +73 -0
- data/test/spec_session_pool.rb +40 -3
- metadata +45 -44
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3d7540b6cecf9193ad7a6ca8f1be4e4a97cf56b3d9a54d420b21ca164af57b66
|
4
|
+
data.tar.gz: b966e0e74ffe6c9b813bbbb222ec3d5ff5b331878feb57440c23a74ebf93197d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9d484bfb940bb4894a9c4be9cf7c88f5b7d13c55bbd1b7dfc110b6dee577d6aa724614ec7e9f3861f860e4d699933ee49b5e32fca1affdae6541f459176260a
|
7
|
+
data.tar.gz: 4ddec5784e6318979bfcefd89c12882f2da022ddb216e99df6d570871e35b9ba9f693872245f031a3fb8505361b966b41dae0d32b180fcb53a8bdec1f329f57f
|
data/HISTORY.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
Fri May 27 08:27:04 2022 Aaron Patterson <tenderlove@ruby-lang.org>
|
2
|
+
|
3
|
+
* [CVE-2022-30123] Fix shell escaping issue in Common Logger
|
4
|
+
* [CVE-2022-30122] Restrict parsing of broken MIME attachments
|
5
|
+
|
1
6
|
Sun Dec 4 18:48:03 2015 Jeremy Daer <jeremydaer@gmail.com>
|
2
7
|
|
3
8
|
* First-party "SameSite" cookies. Browsers omit SameSite cookies
|
data/lib/rack/common_logger.rb
CHANGED
@@ -54,7 +54,10 @@ module Rack
|
|
54
54
|
length,
|
55
55
|
now - began_at ]
|
56
56
|
|
57
|
+
msg.gsub!(/[^[:print:]\n]/) { |c| "\\x#{c.ord}" }
|
58
|
+
|
57
59
|
logger = @logger || env[RACK_ERRORS]
|
60
|
+
|
58
61
|
# Standard library logger doesn't support write but it supports << which actually
|
59
62
|
# calls to write on the log device without formatting
|
60
63
|
if logger.respond_to?(:write)
|
data/lib/rack/lint.rb
CHANGED
@@ -295,7 +295,7 @@ module Rack
|
|
295
295
|
check_hijack env
|
296
296
|
|
297
297
|
## * The <tt>REQUEST_METHOD</tt> must be a valid token.
|
298
|
-
assert("REQUEST_METHOD unknown: #{env[REQUEST_METHOD]}") {
|
298
|
+
assert("REQUEST_METHOD unknown: #{env[REQUEST_METHOD].dump}") {
|
299
299
|
env[REQUEST_METHOD] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
|
300
300
|
}
|
301
301
|
|
@@ -302,8 +302,9 @@ module Rack
|
|
302
302
|
elsif filename = params['filename*']
|
303
303
|
encoding, _, filename = filename.split("'", 3)
|
304
304
|
end
|
305
|
-
when
|
305
|
+
when BROKEN
|
306
306
|
filename = $1
|
307
|
+
filename = $1 if filename =~ /^"(.*)"$/
|
307
308
|
end
|
308
309
|
|
309
310
|
return unless filename
|
data/lib/rack/multipart.rb
CHANGED
@@ -14,8 +14,7 @@ module Rack
|
|
14
14
|
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
|
15
15
|
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
|
16
16
|
VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
|
17
|
-
|
18
|
-
BROKEN_UNQUOTED = /^#{CONDISP}.*;\sfilename=(#{TOKEN})/i
|
17
|
+
BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
|
19
18
|
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
|
20
19
|
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*\s+name=(#{VALUE})/ni
|
21
20
|
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
|
@@ -6,11 +6,38 @@ require 'time'
|
|
6
6
|
require 'rack/request'
|
7
7
|
require 'rack/response'
|
8
8
|
require 'securerandom'
|
9
|
+
require 'digest/sha2'
|
9
10
|
|
10
11
|
module Rack
|
11
12
|
|
12
13
|
module Session
|
13
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
|
+
|
14
41
|
module Abstract
|
15
42
|
# SessionHash is responsible to lazily load the session from store.
|
16
43
|
|
@@ -357,7 +384,7 @@ module Rack
|
|
357
384
|
req.get_header(RACK_ERRORS).puts("Deferring cookie for #{session_id}") if $VERBOSE
|
358
385
|
else
|
359
386
|
cookie = Hash.new
|
360
|
-
cookie[:value] = data
|
387
|
+
cookie[:value] = cookie_value(data)
|
361
388
|
cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
|
362
389
|
cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
|
363
390
|
set_cookie(req, res, cookie.merge!(options))
|
@@ -365,6 +392,10 @@ module Rack
|
|
365
392
|
end
|
366
393
|
public :commit_session
|
367
394
|
|
395
|
+
def cookie_value(data)
|
396
|
+
data
|
397
|
+
end
|
398
|
+
|
368
399
|
# Sets the cookie back to the client with session id. We skip the cookie
|
369
400
|
# setting if the value didn't change (sid is the same) or expires was given.
|
370
401
|
|
@@ -406,6 +437,40 @@ module Rack
|
|
406
437
|
end
|
407
438
|
end
|
408
439
|
|
440
|
+
class PersistedSecure < Persisted
|
441
|
+
class SecureSessionHash < SessionHash
|
442
|
+
def [](key)
|
443
|
+
if key == "session_id"
|
444
|
+
load_for_read!
|
445
|
+
id.public_id if id
|
446
|
+
else
|
447
|
+
super
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
def generate_sid(*)
|
453
|
+
public_id = super
|
454
|
+
|
455
|
+
SessionId.new(public_id)
|
456
|
+
end
|
457
|
+
|
458
|
+
def extract_session_id(*)
|
459
|
+
public_id = super
|
460
|
+
public_id && SessionId.new(public_id)
|
461
|
+
end
|
462
|
+
|
463
|
+
private
|
464
|
+
|
465
|
+
def session_class
|
466
|
+
SecureSessionHash
|
467
|
+
end
|
468
|
+
|
469
|
+
def cookie_value(data)
|
470
|
+
data.cookie_value
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
409
474
|
class ID < Persisted
|
410
475
|
def self.inherited(klass)
|
411
476
|
k = klass.ancestors.find { |kl| kl.respond_to?(:superclass) && kl.superclass == ID }
|
data/lib/rack/session/cookie.rb
CHANGED
@@ -45,7 +45,7 @@ module Rack
|
|
45
45
|
# })
|
46
46
|
#
|
47
47
|
|
48
|
-
class Cookie < Abstract::
|
48
|
+
class Cookie < Abstract::PersistedSecure
|
49
49
|
# Encode session cookies as Base64
|
50
50
|
class Base64
|
51
51
|
def encode(str)
|
@@ -153,6 +153,15 @@ module Rack
|
|
153
153
|
data
|
154
154
|
end
|
155
155
|
|
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
|
+
|
156
165
|
def write_session(req, session_id, session, options)
|
157
166
|
session = session.merge("session_id" => session_id)
|
158
167
|
session_data = coder.encode(session)
|
@@ -165,7 +174,7 @@ module Rack
|
|
165
174
|
req.get_header(RACK_ERRORS).puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
|
166
175
|
nil
|
167
176
|
else
|
168
|
-
session_data
|
177
|
+
SessionId.new(session_id, session_data)
|
169
178
|
end
|
170
179
|
end
|
171
180
|
|
@@ -19,7 +19,7 @@ module Rack
|
|
19
19
|
# Note that memcache does drop data before it may be listed to expire. For
|
20
20
|
# a full description of behaviour, please see memcache's documentation.
|
21
21
|
|
22
|
-
class Memcache < Abstract::
|
22
|
+
class Memcache < Abstract::PersistedSecure
|
23
23
|
attr_reader :mutex, :pool
|
24
24
|
|
25
25
|
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
|
@@ -42,15 +42,15 @@ module Rack
|
|
42
42
|
def generate_sid
|
43
43
|
loop do
|
44
44
|
sid = super
|
45
|
-
break sid unless @pool.get(sid, true)
|
45
|
+
break sid unless @pool.get(sid.private_id, true)
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
def
|
50
|
-
with_lock(
|
51
|
-
unless sid and session =
|
49
|
+
def find_session(req, sid)
|
50
|
+
with_lock(req) do
|
51
|
+
unless sid and session = get_session_with_fallback(sid)
|
52
52
|
sid, session = generate_sid, {}
|
53
|
-
unless /^STORED/ =~ @pool.add(sid, session)
|
53
|
+
unless /^STORED/ =~ @pool.add(sid.private_id, session)
|
54
54
|
raise "Session collision on '#{sid.inspect}'"
|
55
55
|
end
|
56
56
|
end
|
@@ -58,25 +58,26 @@ module Rack
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
def
|
61
|
+
def write_session(req, session_id, new_session, options)
|
62
62
|
expiry = options[:expire_after]
|
63
63
|
expiry = expiry.nil? ? 0 : expiry + 1
|
64
64
|
|
65
|
-
with_lock(
|
66
|
-
@pool.set session_id, new_session, expiry
|
65
|
+
with_lock(req) do
|
66
|
+
@pool.set session_id.private_id, new_session, expiry
|
67
67
|
session_id
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
-
def
|
72
|
-
with_lock(
|
73
|
-
@pool.delete(session_id)
|
71
|
+
def delete_session(req, session_id, options)
|
72
|
+
with_lock(req) do
|
73
|
+
@pool.delete(session_id.public_id)
|
74
|
+
@pool.delete(session_id.private_id)
|
74
75
|
generate_sid unless options[:drop]
|
75
76
|
end
|
76
77
|
end
|
77
78
|
|
78
|
-
def with_lock(
|
79
|
-
@mutex.lock if
|
79
|
+
def with_lock(req)
|
80
|
+
@mutex.lock if req.multithread?
|
80
81
|
yield
|
81
82
|
rescue MemCache::MemCacheError, Errno::ECONNREFUSED
|
82
83
|
if $VERBOSE
|
@@ -88,6 +89,11 @@ module Rack
|
|
88
89
|
@mutex.unlock if @mutex.locked?
|
89
90
|
end
|
90
91
|
|
92
|
+
private
|
93
|
+
|
94
|
+
def get_session_with_fallback(sid)
|
95
|
+
@pool.get(sid.private_id) || @pool.get(sid.public_id)
|
96
|
+
end
|
91
97
|
end
|
92
98
|
end
|
93
99
|
end
|
data/lib/rack/session/pool.rb
CHANGED
@@ -24,7 +24,7 @@ module Rack
|
|
24
24
|
# )
|
25
25
|
# Rack::Handler::WEBrick.run sessioned
|
26
26
|
|
27
|
-
class Pool < Abstract::
|
27
|
+
class Pool < Abstract::PersistedSecure
|
28
28
|
attr_reader :mutex, :pool
|
29
29
|
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :drop => false
|
30
30
|
|
@@ -37,15 +37,15 @@ module Rack
|
|
37
37
|
def generate_sid
|
38
38
|
loop do
|
39
39
|
sid = super
|
40
|
-
break sid unless @pool.key? sid
|
40
|
+
break sid unless @pool.key? sid.private_id
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
44
|
def find_session(req, sid)
|
45
45
|
with_lock(req) do
|
46
|
-
unless sid and session =
|
46
|
+
unless sid and session = get_session_with_fallback(sid)
|
47
47
|
sid, session = generate_sid, {}
|
48
|
-
@pool.store sid, session
|
48
|
+
@pool.store sid.private_id, session
|
49
49
|
end
|
50
50
|
[sid, session]
|
51
51
|
end
|
@@ -53,14 +53,15 @@ module Rack
|
|
53
53
|
|
54
54
|
def write_session(req, session_id, new_session, options)
|
55
55
|
with_lock(req) do
|
56
|
-
@pool.store session_id, new_session
|
56
|
+
@pool.store session_id.private_id, new_session
|
57
57
|
session_id
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
61
|
def delete_session(req, session_id, options)
|
62
62
|
with_lock(req) do
|
63
|
-
@pool.delete(session_id)
|
63
|
+
@pool.delete(session_id.public_id)
|
64
|
+
@pool.delete(session_id.private_id)
|
64
65
|
generate_sid unless options[:drop]
|
65
66
|
end
|
66
67
|
end
|
@@ -71,6 +72,12 @@ module Rack
|
|
71
72
|
ensure
|
72
73
|
@mutex.unlock if @mutex.locked?
|
73
74
|
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def get_session_with_fallback(sid)
|
79
|
+
@pool[sid.private_id] || @pool[sid.public_id]
|
80
|
+
end
|
74
81
|
end
|
75
82
|
end
|
76
83
|
end
|
data/lib/rack/utils.rb
CHANGED
data/lib/rack.rb
CHANGED
data/test/cgi/test.gz
CHANGED
File without changes
|
@@ -1,6 +1,6 @@
|
|
1
1
|
--AaB03x
|
2
2
|
Content-Type: image/jpeg
|
3
|
-
Content-Disposition: attachment; name="files"; filename=""human" genome.jpeg"; modification-date="Wed, 12 Feb 1997 16:29:51 -0500";
|
3
|
+
Content-Disposition: attachment; name="files"; filename="\"human\" genome.jpeg"; modification-date="Wed, 12 Feb 1997 16:29:51 -0500";
|
4
4
|
Content-Description: a complete map of the human genome
|
5
5
|
|
6
6
|
contents
|
data/test/spec_common_logger.rb
CHANGED
@@ -21,6 +21,10 @@ describe Rack::CommonLogger do
|
|
21
21
|
[200,
|
22
22
|
{"Content-Type" => "text/html", "Content-Length" => "0"},
|
23
23
|
[]]}
|
24
|
+
app_without_lint = lambda { |env|
|
25
|
+
[200,
|
26
|
+
{ "content-type" => "text/html", "content-length" => length.to_s },
|
27
|
+
[obj]]}
|
24
28
|
|
25
29
|
it "log to rack.errors by default" do
|
26
30
|
res = Rack::MockRequest.new(Rack::CommonLogger.new(app)).get("/")
|
@@ -85,6 +89,14 @@ describe Rack::CommonLogger do
|
|
85
89
|
(0..1).must_include duration.to_f
|
86
90
|
end
|
87
91
|
|
92
|
+
it "escapes non printable characters except newline" do
|
93
|
+
logdev = StringIO.new
|
94
|
+
log = Logger.new(logdev)
|
95
|
+
Rack::MockRequest.new(Rack::CommonLogger.new(app_without_lint, log)).request("GET\b", "/hello")
|
96
|
+
|
97
|
+
logdev.string.must_match(/GET\\x8 \/hello/)
|
98
|
+
end
|
99
|
+
|
88
100
|
def length
|
89
101
|
123
|
90
102
|
end
|
data/test/spec_lint.rb
CHANGED
@@ -96,6 +96,11 @@ describe Rack::Lint do
|
|
96
96
|
}.must_raise(Rack::Lint::LintError).
|
97
97
|
message.must_match(/REQUEST_METHOD/)
|
98
98
|
|
99
|
+
lambda {
|
100
|
+
Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "OOPS?\b!"))
|
101
|
+
}.must_raise(Rack::Lint::LintError).
|
102
|
+
message.must_match(/OOPS\?\\/)
|
103
|
+
|
99
104
|
lambda {
|
100
105
|
Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "howdy"))
|
101
106
|
}.must_raise(Rack::Lint::LintError).
|
data/test/spec_multipart.rb
CHANGED
@@ -381,19 +381,6 @@ describe Rack::Multipart do
|
|
381
381
|
params["files"][:tempfile].read.must_equal "contents"
|
382
382
|
end
|
383
383
|
|
384
|
-
it "parse filename with unescaped quotes" do
|
385
|
-
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_quotes))
|
386
|
-
params = Rack::Multipart.parse_multipart(env)
|
387
|
-
params["files"][:type].must_equal "application/octet-stream"
|
388
|
-
params["files"][:filename].must_equal "escape \"quotes"
|
389
|
-
params["files"][:head].must_equal "Content-Disposition: form-data; " +
|
390
|
-
"name=\"files\"; " +
|
391
|
-
"filename=\"escape \"quotes\"\r\n" +
|
392
|
-
"Content-Type: application/octet-stream\r\n"
|
393
|
-
params["files"][:name].must_equal "files"
|
394
|
-
params["files"][:tempfile].read.must_equal "contents"
|
395
|
-
end
|
396
|
-
|
397
384
|
it "parse filename with escaped quotes and modification param" do
|
398
385
|
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_escaped_quotes_and_modification_param))
|
399
386
|
params = Rack::Multipart.parse_multipart(env)
|
@@ -402,7 +389,7 @@ describe Rack::Multipart do
|
|
402
389
|
params["files"][:head].must_equal "Content-Type: image/jpeg\r\n" +
|
403
390
|
"Content-Disposition: attachment; " +
|
404
391
|
"name=\"files\"; " +
|
405
|
-
"filename=\"
|
392
|
+
"filename=\"\\\"human\\\" genome.jpeg\"; " +
|
406
393
|
"modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";\r\n" +
|
407
394
|
"Content-Description: a complete map of the human genome\r\n"
|
408
395
|
params["files"][:name].must_equal "files"
|
data/test/spec_response.rb
CHANGED
@@ -115,6 +115,24 @@ describe Rack::Response do
|
|
115
115
|
response["Set-Cookie"].must_equal "foo=bar"
|
116
116
|
end
|
117
117
|
|
118
|
+
it "can set SameSite cookies with symbol value :none" do
|
119
|
+
response = Rack::Response.new
|
120
|
+
response.set_cookie "foo", { value: "bar", same_site: :none }
|
121
|
+
response["Set-Cookie"].must_equal "foo=bar; SameSite=None"
|
122
|
+
end
|
123
|
+
|
124
|
+
it "can set SameSite cookies with symbol value :None" do
|
125
|
+
response = Rack::Response.new
|
126
|
+
response.set_cookie "foo", { value: "bar", same_site: :None }
|
127
|
+
response["Set-Cookie"].must_equal "foo=bar; SameSite=None"
|
128
|
+
end
|
129
|
+
|
130
|
+
it "can set SameSite cookies with string value 'None'" do
|
131
|
+
response = Rack::Response.new
|
132
|
+
response.set_cookie "foo", { value: "bar", same_site: "None" }
|
133
|
+
response["Set-Cookie"].must_equal "foo=bar; SameSite=None"
|
134
|
+
end
|
135
|
+
|
118
136
|
it "can set SameSite cookies with symbol value :lax" do
|
119
137
|
response = Rack::Response.new
|
120
138
|
response.set_cookie "foo", {:value => "bar", :same_site => :lax}
|
@@ -226,15 +226,52 @@ begin
|
|
226
226
|
req = Rack::MockRequest.new(pool)
|
227
227
|
|
228
228
|
res0 = req.get("/")
|
229
|
-
session_id = (cookie = res0["Set-Cookie"])[session_match, 1]
|
230
|
-
ses0 = pool.pool.get(session_id, true)
|
229
|
+
session_id = Rack::Session::SessionId.new (cookie = res0["Set-Cookie"])[session_match, 1]
|
230
|
+
ses0 = pool.pool.get(session_id.private_id, true)
|
231
231
|
|
232
232
|
req.get("/", "HTTP_COOKIE" => cookie)
|
233
|
-
ses1 = pool.pool.get(session_id, true)
|
233
|
+
ses1 = pool.pool.get(session_id.private_id, true)
|
234
234
|
|
235
235
|
ses1.wont_equal ses0
|
236
236
|
end
|
237
237
|
|
238
|
+
it "can read the session with the legacy id" do
|
239
|
+
pool = Rack::Session::Memcache.new(incrementor)
|
240
|
+
req = Rack::MockRequest.new(pool)
|
241
|
+
|
242
|
+
res0 = req.get("/")
|
243
|
+
cookie = res0["Set-Cookie"]
|
244
|
+
session_id = Rack::Session::SessionId.new cookie[session_match, 1]
|
245
|
+
ses0 = pool.pool.get(session_id.private_id, true)
|
246
|
+
pool.pool.set(session_id.public_id, ses0, 0, true)
|
247
|
+
pool.pool.delete(session_id.private_id)
|
248
|
+
|
249
|
+
res1 = req.get("/", "HTTP_COOKIE" => cookie)
|
250
|
+
res1["Set-Cookie"].must_be_nil
|
251
|
+
res1.body.must_equal '{"counter"=>2}'
|
252
|
+
pool.pool.get(session_id.private_id, true).wont_be_nil
|
253
|
+
end
|
254
|
+
|
255
|
+
it "drops the session in the legacy id as well" do
|
256
|
+
pool = Rack::Session::Memcache.new(incrementor)
|
257
|
+
req = Rack::MockRequest.new(pool)
|
258
|
+
drop = Rack::Utils::Context.new(pool, drop_session)
|
259
|
+
dreq = Rack::MockRequest.new(drop)
|
260
|
+
|
261
|
+
res0 = req.get("/")
|
262
|
+
cookie = res0["Set-Cookie"]
|
263
|
+
session_id = Rack::Session::SessionId.new cookie[session_match, 1]
|
264
|
+
ses0 = pool.pool.get(session_id.private_id, true)
|
265
|
+
pool.pool.set(session_id.public_id, ses0, 0, true)
|
266
|
+
pool.pool.delete(session_id.private_id)
|
267
|
+
|
268
|
+
res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
|
269
|
+
res2["Set-Cookie"].must_be_nil
|
270
|
+
res2.body.must_equal '{"counter"=>2}'
|
271
|
+
pool.pool.get(session_id.private_id, true).must_be_nil
|
272
|
+
pool.pool.get(session_id.public_id, true).must_be_nil
|
273
|
+
end
|
274
|
+
|
238
275
|
# anyone know how to do this better?
|
239
276
|
it "cleanly merges sessions when multithreaded" do
|
240
277
|
skip unless $DEBUG
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'minitest/global_expectations/autorun'
|
4
|
+
require 'rack/session/abstract/id'
|
5
|
+
|
6
|
+
describe Rack::Session::Abstract::PersistedSecure::SecureSessionHash do
|
7
|
+
attr_reader :hash
|
8
|
+
|
9
|
+
def setup
|
10
|
+
super
|
11
|
+
@store = Class.new do
|
12
|
+
def load_session(req)
|
13
|
+
[Rack::Session::SessionId.new("id"), { foo: :bar, baz: :qux }]
|
14
|
+
end
|
15
|
+
def session_exists?(req)
|
16
|
+
true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
@hash = Rack::Session::Abstract::PersistedSecure::SecureSessionHash.new(@store.new, nil)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns keys" do
|
23
|
+
assert_equal ["foo", "baz"], hash.keys
|
24
|
+
end
|
25
|
+
|
26
|
+
it "returns values" do
|
27
|
+
assert_equal [:bar, :qux], hash.values
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#[]" do
|
31
|
+
it "returns value for a matching key" do
|
32
|
+
assert_equal :bar, hash[:foo]
|
33
|
+
end
|
34
|
+
|
35
|
+
it "returns value for a 'session_id' key" do
|
36
|
+
assert_equal "id", hash['session_id']
|
37
|
+
end
|
38
|
+
|
39
|
+
it "returns nil value for missing 'session_id' key" do
|
40
|
+
store = @store.new
|
41
|
+
def store.load_session(req)
|
42
|
+
[nil, {}]
|
43
|
+
end
|
44
|
+
@hash = Rack::Session::Abstract::PersistedSecure::SecureSessionHash.new(store, nil)
|
45
|
+
assert_nil hash['session_id']
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#fetch" do
|
50
|
+
it "returns value for a matching key" do
|
51
|
+
assert_equal :bar, hash.fetch(:foo)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "works with a default value" do
|
55
|
+
assert_equal :default, hash.fetch(:unknown, :default)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "works with a block" do
|
59
|
+
assert_equal :default, hash.fetch(:unkown) { :default }
|
60
|
+
end
|
61
|
+
|
62
|
+
it "it raises when fetching unknown keys without defaults" do
|
63
|
+
lambda { hash.fetch(:unknown) }.must_raise KeyError
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "#stringify_keys" do
|
68
|
+
it "returns hash or session hash with keys stringified" do
|
69
|
+
assert_equal({ "foo" => :bar, "baz" => :qux }, hash.send(:stringify_keys, hash).to_h)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
data/test/spec_session_pool.rb
CHANGED
@@ -6,7 +6,7 @@ require 'rack/session/pool'
|
|
6
6
|
|
7
7
|
describe Rack::Session::Pool do
|
8
8
|
session_key = Rack::Session::Pool::DEFAULT_OPTIONS[:key]
|
9
|
-
session_match = /#{session_key}=[0-9a-fA-F]
|
9
|
+
session_match = /#{session_key}=([0-9a-fA-F]+);/
|
10
10
|
|
11
11
|
incrementor = lambda do |env|
|
12
12
|
env["rack.session"]["counter"] ||= 0
|
@@ -14,7 +14,7 @@ describe Rack::Session::Pool do
|
|
14
14
|
Rack::Response.new(env["rack.session"].inspect).to_a
|
15
15
|
end
|
16
16
|
|
17
|
-
|
17
|
+
get_session_id = Rack::Lint.new(lambda do |env|
|
18
18
|
Rack::Response.new(env["rack.session"].inspect).to_a
|
19
19
|
end)
|
20
20
|
|
@@ -143,6 +143,43 @@ describe Rack::Session::Pool do
|
|
143
143
|
pool.pool.size.must_equal 1
|
144
144
|
end
|
145
145
|
|
146
|
+
it "can read the session with the legacy id" do
|
147
|
+
pool = Rack::Session::Pool.new(incrementor)
|
148
|
+
req = Rack::MockRequest.new(pool)
|
149
|
+
|
150
|
+
res0 = req.get("/")
|
151
|
+
cookie = res0["Set-Cookie"]
|
152
|
+
session_id = Rack::Session::SessionId.new cookie[session_match, 1]
|
153
|
+
ses0 = pool.pool[session_id.private_id]
|
154
|
+
pool.pool[session_id.public_id] = ses0
|
155
|
+
pool.pool.delete(session_id.private_id)
|
156
|
+
|
157
|
+
res1 = req.get("/", "HTTP_COOKIE" => cookie)
|
158
|
+
res1["Set-Cookie"].must_be_nil
|
159
|
+
res1.body.must_equal '{"counter"=>2}'
|
160
|
+
pool.pool[session_id.private_id].wont_be_nil
|
161
|
+
end
|
162
|
+
|
163
|
+
it "drops the session in the legacy id as well" do
|
164
|
+
pool = Rack::Session::Pool.new(incrementor)
|
165
|
+
req = Rack::MockRequest.new(pool)
|
166
|
+
drop = Rack::Utils::Context.new(pool, drop_session)
|
167
|
+
dreq = Rack::MockRequest.new(drop)
|
168
|
+
|
169
|
+
res0 = req.get("/")
|
170
|
+
cookie = res0["Set-Cookie"]
|
171
|
+
session_id = Rack::Session::SessionId.new cookie[session_match, 1]
|
172
|
+
ses0 = pool.pool[session_id.private_id]
|
173
|
+
pool.pool[session_id.public_id] = ses0
|
174
|
+
pool.pool.delete(session_id.private_id)
|
175
|
+
|
176
|
+
res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
|
177
|
+
res2["Set-Cookie"].must_be_nil
|
178
|
+
res2.body.must_equal '{"counter"=>2}'
|
179
|
+
pool.pool[session_id.private_id].must_be_nil
|
180
|
+
pool.pool[session_id.public_id].must_be_nil
|
181
|
+
end
|
182
|
+
|
146
183
|
# anyone know how to do this better?
|
147
184
|
it "should merge sessions when multithreaded" do
|
148
185
|
unless $DEBUG
|
@@ -191,7 +228,7 @@ describe Rack::Session::Pool do
|
|
191
228
|
end
|
192
229
|
|
193
230
|
it "does not return a cookie if cookie was not written (only read)" do
|
194
|
-
app = Rack::Session::Pool.new(
|
231
|
+
app = Rack::Session::Pool.new(get_session_id)
|
195
232
|
res = Rack::MockRequest.new(app).get("/")
|
196
233
|
res["Set-Cookie"].must_be_nil
|
197
234
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.9.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Leah Neukirchen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-05-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -239,6 +239,7 @@ files:
|
|
239
239
|
- test/spec_session_abstract_session_hash.rb
|
240
240
|
- test/spec_session_cookie.rb
|
241
241
|
- test/spec_session_memcache.rb
|
242
|
+
- test/spec_session_persisted_secure_secure_session_hash.rb
|
242
243
|
- test/spec_session_pool.rb
|
243
244
|
- test/spec_show_exceptions.rb
|
244
245
|
- test/spec_show_status.rb
|
@@ -274,60 +275,60 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
274
275
|
- !ruby/object:Gem::Version
|
275
276
|
version: '0'
|
276
277
|
requirements: []
|
277
|
-
|
278
|
-
rubygems_version: 2.6.13
|
278
|
+
rubygems_version: 3.0.3.1
|
279
279
|
signing_key:
|
280
280
|
specification_version: 4
|
281
281
|
summary: a modular Ruby webserver interface
|
282
282
|
test_files:
|
283
|
-
- test/
|
284
|
-
- test/spec_auth_digest.rb
|
285
|
-
- test/spec_body_proxy.rb
|
286
|
-
- test/spec_builder.rb
|
287
|
-
- test/spec_cascade.rb
|
288
|
-
- test/spec_cgi.rb
|
289
|
-
- test/spec_chunked.rb
|
290
|
-
- test/spec_common_logger.rb
|
291
|
-
- test/spec_conditional_get.rb
|
292
|
-
- test/spec_config.rb
|
293
|
-
- test/spec_content_length.rb
|
294
|
-
- test/spec_content_type.rb
|
283
|
+
- test/spec_multipart.rb
|
295
284
|
- test/spec_deflater.rb
|
296
|
-
- test/
|
285
|
+
- test/spec_static.rb
|
286
|
+
- test/spec_session_cookie.rb
|
287
|
+
- test/spec_session_pool.rb
|
297
288
|
- test/spec_etag.rb
|
298
|
-
- test/
|
299
|
-
- test/spec_fastcgi.rb
|
300
|
-
- test/spec_file.rb
|
289
|
+
- test/spec_version.rb
|
301
290
|
- test/spec_handler.rb
|
302
|
-
- test/
|
303
|
-
- test/
|
304
|
-
- test/spec_lobster.rb
|
305
|
-
- test/spec_lock.rb
|
306
|
-
- test/spec_logger.rb
|
307
|
-
- test/spec_media_type.rb
|
308
|
-
- test/spec_method_override.rb
|
291
|
+
- test/spec_thin.rb
|
292
|
+
- test/spec_session_abstract_id.rb
|
309
293
|
- test/spec_mime.rb
|
310
|
-
- test/spec_mock.rb
|
311
|
-
- test/spec_multipart.rb
|
312
|
-
- test/spec_null_logger.rb
|
313
294
|
- test/spec_recursive.rb
|
295
|
+
- test/spec_null_logger.rb
|
296
|
+
- test/spec_media_type.rb
|
297
|
+
- test/spec_cgi.rb
|
298
|
+
- test/spec_method_override.rb
|
299
|
+
- test/spec_content_type.rb
|
300
|
+
- test/spec_session_abstract_session_hash.rb
|
314
301
|
- test/spec_request.rb
|
315
|
-
- test/
|
316
|
-
- test/
|
302
|
+
- test/spec_chunked.rb
|
303
|
+
- test/spec_show_exceptions.rb
|
317
304
|
- test/spec_runtime.rb
|
305
|
+
- test/spec_session_persisted_secure_secure_session_hash.rb
|
306
|
+
- test/spec_fastcgi.rb
|
307
|
+
- test/spec_common_logger.rb
|
308
|
+
- test/spec_builder.rb
|
309
|
+
- test/spec_config.rb
|
310
|
+
- test/spec_utils.rb
|
318
311
|
- test/spec_sendfile.rb
|
312
|
+
- test/spec_lobster.rb
|
313
|
+
- test/spec_lint.rb
|
314
|
+
- test/spec_conditional_get.rb
|
315
|
+
- test/spec_tempfile_reaper.rb
|
316
|
+
- test/spec_mock.rb
|
319
317
|
- test/spec_server.rb
|
320
|
-
- test/
|
321
|
-
- test/
|
322
|
-
- test/
|
323
|
-
- test/
|
324
|
-
- test/spec_session_pool.rb
|
325
|
-
- test/spec_show_exceptions.rb
|
318
|
+
- test/spec_directory.rb
|
319
|
+
- test/spec_webrick.rb
|
320
|
+
- test/spec_response.rb
|
321
|
+
- test/spec_file.rb
|
326
322
|
- test/spec_show_status.rb
|
327
|
-
- test/
|
328
|
-
- test/
|
329
|
-
- test/
|
323
|
+
- test/spec_body_proxy.rb
|
324
|
+
- test/spec_logger.rb
|
325
|
+
- test/spec_auth_digest.rb
|
330
326
|
- test/spec_urlmap.rb
|
331
|
-
- test/
|
332
|
-
- test/
|
333
|
-
- test/
|
327
|
+
- test/spec_events.rb
|
328
|
+
- test/spec_cascade.rb
|
329
|
+
- test/spec_auth_basic.rb
|
330
|
+
- test/spec_head.rb
|
331
|
+
- test/spec_lock.rb
|
332
|
+
- test/spec_rewindable_input.rb
|
333
|
+
- test/spec_session_memcache.rb
|
334
|
+
- test/spec_content_length.rb
|