rack 2.0.6 → 2.0.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c26f21f48fd630f4f95eead2d2845f51c160bcd084824b305d81d95bad08b6d9
4
- data.tar.gz: 312598a6017f9dd516214a2390f828008e6f30a6f6b4acc570c79133b975c8a4
3
+ metadata.gz: e9142cfb8ba777286d8118d2b094a23c1fe4698b302c99966cb80670041c67f5
4
+ data.tar.gz: 341991ef42232bfecf702d98a17e671ffbbf6a95a8ff70bcca40f7c9aa9f5e85
5
5
  SHA512:
6
- metadata.gz: bf760ff4d0077492ddd2760a8b9d9a16a45560dd55612c29b31331ce70f0308defa5edacbcd74bac2a8f0fb26a41330ff3bb995cc6367822deea2246c474e7aa
7
- data.tar.gz: d4e666fcdbdc9c09b6175b4b3ec96f20f1638c12133ebc2666428be6cb98d54097ef8a4351e92f9ba9637ed7b0bc64ea45b755081ae07f24264bbb6ecd10c270
6
+ metadata.gz: 012e3ac8b25a2fa3c75e7cfbed5f5f4875010ecf96ee887aa9d4ed844badc614fa7decf04bfb889983004d0c310780763e8f9328c4dfb5243388a086f05ef059
7
+ data.tar.gz: 312252b7e153667c49c11fea9bdceaf679813d6e3176c6e8599fbe4741c326abc43510c3c0a48136a36d2027393e8286f11771a5c353c68269088a6f606b8372
@@ -18,7 +18,7 @@ module Rack
18
18
  VERSION.join(".")
19
19
  end
20
20
 
21
- RELEASE = "2.0.6"
21
+ RELEASE = "2.0.8"
22
22
 
23
23
  # Return the Rack release as a dotted string.
24
24
  def self.release
@@ -39,8 +39,6 @@ module Rack
39
39
  str
40
40
  end
41
41
 
42
- def eof?; @content_length == @cursor; end
43
-
44
42
  def rewind
45
43
  @io.rewind
46
44
  end
@@ -65,11 +63,11 @@ module Rack
65
63
  io = BoundedIO.new(io, content_length) if content_length
66
64
 
67
65
  parser = new(boundary, tmpfile, bufsize, qp)
68
- parser.on_read io.read(bufsize), io.eof?
66
+ parser.on_read io.read(bufsize)
69
67
 
70
68
  loop do
71
69
  break if parser.state == :DONE
72
- parser.on_read io.read(bufsize), io.eof?
70
+ parser.on_read io.read(bufsize)
73
71
  end
74
72
 
75
73
  io.rewind
@@ -181,8 +179,8 @@ module Rack
181
179
  @collector = Collector.new tempfile
182
180
  end
183
181
 
184
- def on_read content, eof
185
- handle_empty_content!(content, eof)
182
+ def on_read content
183
+ handle_empty_content!(content)
186
184
  @buf << content
187
185
  run_parser
188
186
  end
@@ -358,10 +356,9 @@ module Rack
358
356
  end
359
357
 
360
358
 
361
- def handle_empty_content!(content, eof)
359
+ def handle_empty_content!(content)
362
360
  if content.nil? || content.empty?
363
- raise EOFError if eof
364
- return true
361
+ raise EOFError
365
362
  end
366
363
  end
367
364
  end
@@ -261,7 +261,7 @@ module Rack
261
261
 
262
262
  forwarded_ips = split_ip_addresses(get_header('HTTP_X_FORWARDED_FOR'))
263
263
 
264
- return reject_trusted_ip_addresses(forwarded_ips).last || get_header("REMOTE_ADDR")
264
+ return reject_trusted_ip_addresses(forwarded_ips).last || forwarded_ips.first || get_header("REMOTE_ADDR")
265
265
  end
266
266
 
267
267
  # The media type (type/subtype) portion of the CONTENT_TYPE header
@@ -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
+
30
+ def empty?; false; end
31
+ def to_s; raise; 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
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 }
@@ -45,7 +45,7 @@ module Rack
45
45
  # })
46
46
  #
47
47
 
48
- class Cookie < Abstract::Persisted
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::ID
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 get_session(env, sid)
50
- with_lock(env) do
51
- unless sid and session = @pool.get(sid)
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 set_session(env, session_id, new_session, options)
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(env) do
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 destroy_session(env, session_id, options)
72
- with_lock(env) do
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(env)
79
- @mutex.lock if env[RACK_MULTITHREAD]
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
@@ -24,7 +24,7 @@ module Rack
24
24
  # )
25
25
  # Rack::Handler::WEBrick.run sessioned
26
26
 
27
- class Pool < Abstract::Persisted
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 = @pool[sid]
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
@@ -1286,7 +1286,16 @@ EOF
1286
1286
  res.body.must_equal '2.2.2.3'
1287
1287
  end
1288
1288
 
1289
- it "regard local addresses as proxies" do
1289
+ it "preserves ip for trusted proxy chain" do
1290
+ mock = Rack::MockRequest.new(Rack::Lint.new(ip_app))
1291
+ res = mock.get '/',
1292
+ 'HTTP_X_FORWARDED_FOR' => '192.168.0.11, 192.168.0.7',
1293
+ 'HTTP_CLIENT_IP' => '127.0.0.1'
1294
+ res.body.must_equal '192.168.0.11'
1295
+
1296
+ end
1297
+
1298
+ it "regards local addresses as proxies" do
1290
1299
  req = make_request(Rack::MockRequest.env_for("/"))
1291
1300
  req.trusted_proxy?('127.0.0.1').must_equal 0
1292
1301
  req.trusted_proxy?('10.0.0.1').must_equal 0
@@ -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
@@ -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
- session_id = Rack::Lint.new(lambda do |env|
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(session_id)
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.6
4
+ version: 2.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leah Neukirchen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-05 00:00:00.000000000 Z
11
+ date: 2019-12-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -274,8 +274,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
274
274
  - !ruby/object:Gem::Version
275
275
  version: '0'
276
276
  requirements: []
277
- rubyforge_project:
278
- rubygems_version: 2.7.6
277
+ rubygems_version: 3.0.3
279
278
  signing_key:
280
279
  specification_version: 4
281
280
  summary: a modular Ruby webserver interface