rack 2.0.3 → 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
- SHA1:
3
- metadata.gz: 17cc155727caff77770fb98d752892acbb5c3a77
4
- data.tar.gz: 60a9aec0727551bc33323e06b30e930027f2d5c1
2
+ SHA256:
3
+ metadata.gz: e9142cfb8ba777286d8118d2b094a23c1fe4698b302c99966cb80670041c67f5
4
+ data.tar.gz: 341991ef42232bfecf702d98a17e671ffbbf6a95a8ff70bcca40f7c9aa9f5e85
5
5
  SHA512:
6
- metadata.gz: 84cc54271f0d00b51ef4be8c1f3e35a90ada7d88918653ed35276db93c2cac66f004fa4c6c092ac92a0fced6da86ebc65b11e83a0aa6be6143bdcc82d938b27b
7
- data.tar.gz: c6cabd254ed040468806288a9765002a5d029ab35239280cfd2877f773b543b6d32489945cc78e8a0b09f6b494790da2963b73d5d1f61be5c8a4f2b3563ed2c0
6
+ metadata.gz: 012e3ac8b25a2fa3c75e7cfbed5f5f4875010ecf96ee887aa9d4ed844badc614fa7decf04bfb889983004d0c310780763e8f9328c4dfb5243388a086f05ef059
7
+ data.tar.gz: 312252b7e153667c49c11fea9bdceaf679813d6e3176c6e8599fbe4741c326abc43510c3c0a48136a36d2027393e8286f11771a5c353c68269088a6f606b8372
@@ -228,7 +228,7 @@ You are also welcome to join the #rack channel on irc.freenode.net.
228
228
 
229
229
  The Rack Core Team, consisting of
230
230
 
231
- * Christian Neukirchen (chneukirchen[https://github.com/chneukirchen])
231
+ * Leah Neukirchen (chneukirchen[https://github.com/chneukirchen])
232
232
  * James Tucker (raggi[https://github.com/raggi])
233
233
  * Josh Peek (josh[https://github.com/josh])
234
234
  * José Valim (josevalim[https://github.com/josevalim])
@@ -295,10 +295,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
295
295
 
296
296
  == Links
297
297
 
298
- Rack:: <http://rack.github.io/>
298
+ Rack:: <https://rack.github.io/>
299
299
  Official Rack repositories:: <https://github.com/rack>
300
300
  Rack Bug Tracking:: <https://github.com/rack/rack/issues>
301
301
  rack-devel mailing list:: <https://groups.google.com/group/rack-devel>
302
302
  Rack's Rubyforge project:: <http://rubyforge.org/projects/rack>
303
-
304
- Christian Neukirchen:: <http://chneukirchen.org/>
data/SPEC CHANGED
@@ -60,8 +60,8 @@ below.
60
60
  the presence or absence of the
61
61
  appropriate HTTP header in the
62
62
  request. See
63
- {https://tools.ietf.org/html/rfc3875#section-4.1.18
64
- RFC3875 section 4.1.18} for
63
+ <a href="https://tools.ietf.org/html/rfc3875#section-4.1.18">
64
+ RFC3875 section 4.1.18</a> for
65
65
  specific behavior.
66
66
  In addition to this, the Rack environment must include these
67
67
  Rack-specific variables:
@@ -98,12 +98,13 @@ Rack-specific variables:
98
98
  Additional environment specifications have approved to
99
99
  standardized middleware APIs. None of these are required to
100
100
  be implemented by the server.
101
- <tt>rack.session</tt>:: A hash like interface for storing request session data.
101
+ <tt>rack.session</tt>:: A hash like interface for storing
102
+ request session data.
102
103
  The store must implement:
103
- store(key, value) (aliased as []=);
104
- fetch(key, default = nil) (aliased as []);
105
- delete(key);
106
- clear;
104
+ store(key, value) (aliased as []=);
105
+ fetch(key, default = nil) (aliased as []);
106
+ delete(key);
107
+ clear;
107
108
  <tt>rack.logger</tt>:: A common object interface for logging messages.
108
109
  The object must implement:
109
110
  info(message, &block)
@@ -18,7 +18,7 @@ module Rack
18
18
  VERSION.join(".")
19
19
  end
20
20
 
21
- RELEASE = "2.0.3"
21
+ RELEASE = "2.0.8"
22
22
 
23
23
  # Return the Rack release as a dotted string.
24
24
  def self.release
@@ -24,7 +24,7 @@ module Rack
24
24
  size = chunk.bytesize
25
25
  next if size == 0
26
26
 
27
- chunk = chunk.dup.force_encoding(Encoding::BINARY)
27
+ chunk = chunk.b
28
28
  yield [size.to_s(16), term, chunk, term].join
29
29
  end
30
30
  yield TAIL
@@ -1,5 +1,5 @@
1
1
  require 'rack'
2
- require 'digest/md5'
2
+ require 'digest/sha2'
3
3
 
4
4
  module Rack
5
5
  # Automatically sets the ETag header on all String bodies.
@@ -11,12 +11,21 @@ module Rack
11
11
 
12
12
  def call(env)
13
13
  @mutex.lock
14
+ @env = env
15
+ @old_rack_multithread = env[RACK_MULTITHREAD]
14
16
  begin
15
- response = @app.call(env.merge(RACK_MULTITHREAD => false))
16
- returned = response << BodyProxy.new(response.pop) { @mutex.unlock }
17
+ response = @app.call(env.merge!(RACK_MULTITHREAD => false))
18
+ returned = response << BodyProxy.new(response.pop) { unlock }
17
19
  ensure
18
- @mutex.unlock unless returned
20
+ unlock unless returned
19
21
  end
20
22
  end
23
+
24
+ private
25
+
26
+ def unlock
27
+ @mutex.unlock
28
+ @env[RACK_MULTITHREAD] = @old_rack_multithread
29
+ end
21
30
  end
22
31
  end
@@ -26,7 +26,11 @@ module Rack
26
26
  req = Request.new(env)
27
27
  method = method_override_param(req) ||
28
28
  env[HTTP_METHOD_OVERRIDE_HEADER]
29
- method.to_s.upcase
29
+ begin
30
+ method.to_s.upcase
31
+ rescue ArgumentError
32
+ env[RACK_ERRORS].puts "Invalid string for method"
33
+ end
30
34
  end
31
35
 
32
36
  private
@@ -128,7 +128,7 @@ module Rack
128
128
  end
129
129
  end
130
130
 
131
- empty_str = String.new.force_encoding(Encoding::ASCII_8BIT)
131
+ empty_str = String.new
132
132
  opts[:input] ||= empty_str
133
133
  if String === opts[:input]
134
134
  rack_input = StringIO.new(opts[:input])
@@ -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
@@ -135,7 +133,7 @@ module Rack
135
133
  klass = TempfilePart
136
134
  @open_files += 1
137
135
  else
138
- body = ''.force_encoding(Encoding::ASCII_8BIT)
136
+ body = String.new
139
137
  klass = BufferPart
140
138
  end
141
139
 
@@ -165,15 +163,15 @@ module Rack
165
163
  attr_reader :state
166
164
 
167
165
  def initialize(boundary, tempfile, bufsize, query_parser)
168
- @buf = "".force_encoding(Encoding::ASCII_8BIT)
166
+ @buf = String.new
169
167
 
170
168
  @query_parser = query_parser
171
169
  @params = query_parser.make_params
172
170
  @boundary = "--#{boundary}"
173
- @boundary_size = @boundary.bytesize + EOL.size
174
171
  @bufsize = bufsize
175
172
 
176
173
  @rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
174
+ @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
177
175
  @full_boundary = @boundary
178
176
  @end_boundary = @boundary + '--'
179
177
  @state = :FAST_FORWARD
@@ -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
@@ -263,15 +261,17 @@ module Rack
263
261
  end
264
262
 
265
263
  def handle_mime_body
266
- if @buf =~ rx
264
+ if i = @buf.index(rx)
267
265
  # Save the rest.
268
- if i = @buf.index(rx)
269
- @collector.on_mime_body @mime_index, @buf.slice!(0, i)
270
- @buf.slice!(0, 2) # Remove \r\n after the content
271
- end
266
+ @collector.on_mime_body @mime_index, @buf.slice!(0, i)
267
+ @buf.slice!(0, 2) # Remove \r\n after the content
272
268
  @state = :CONSUME_TOKEN
273
269
  @mime_index += 1
274
270
  else
271
+ # Save the read body part.
272
+ if @rx_max_size < @buf.size
273
+ @collector.on_mime_body @mime_index, @buf.slice!(0, @buf.size - @rx_max_size)
274
+ end
275
275
  :want_read
276
276
  end
277
277
  end
@@ -356,10 +356,9 @@ module Rack
356
356
  end
357
357
 
358
358
 
359
- def handle_empty_content!(content, eof)
359
+ def handle_empty_content!(content)
360
360
  if content.nil? || content.empty?
361
- raise EOFError if eof
362
- return true
361
+ raise EOFError
363
362
  end
364
363
  end
365
364
  end
@@ -11,6 +11,8 @@ module Rack
11
11
  # req.params["data"]
12
12
 
13
13
  class Request
14
+ SCHEME_WHITELIST = %w(https http).freeze
15
+
14
16
  def initialize(env)
15
17
  @params = nil
16
18
  super(env)
@@ -188,10 +190,8 @@ module Rack
188
190
  'https'
189
191
  elsif get_header(HTTP_X_FORWARDED_SSL) == 'on'
190
192
  'https'
191
- elsif get_header(HTTP_X_FORWARDED_SCHEME)
192
- get_header(HTTP_X_FORWARDED_SCHEME)
193
- elsif get_header(HTTP_X_FORWARDED_PROTO)
194
- get_header(HTTP_X_FORWARDED_PROTO).split(',')[0]
193
+ elsif forwarded_scheme
194
+ forwarded_scheme
195
195
  else
196
196
  get_header(RACK_URL_SCHEME)
197
197
  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
@@ -479,6 +479,19 @@ module Rack
479
479
  def reject_trusted_ip_addresses(ip_addresses)
480
480
  ip_addresses.reject { |ip| trusted_proxy?(ip) }
481
481
  end
482
+
483
+ def forwarded_scheme
484
+ scheme_headers = [
485
+ get_header(HTTP_X_FORWARDED_SCHEME),
486
+ get_header(HTTP_X_FORWARDED_PROTO).to_s.split(',')[0]
487
+ ]
488
+
489
+ scheme_headers.each do |header|
490
+ return header if SCHEME_WHITELIST.include?(header)
491
+ end
492
+
493
+ nil
494
+ end
482
495
  end
483
496
 
484
497
  include Env
@@ -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
@@ -46,7 +46,7 @@ module Rack
46
46
  end
47
47
 
48
48
  def prefers_plaintext?(env)
49
- !accepts_html(env)
49
+ !accepts_html?(env)
50
50
  end
51
51
 
52
52
  def accepts_html?(env)
@@ -12,7 +12,7 @@ the simplest way possible, it unifies and distills the API for web
12
12
  servers, web frameworks, and software in between (the so-called
13
13
  middleware) into a single method call.
14
14
 
15
- Also see http://rack.github.io/.
15
+ Also see https://rack.github.io/.
16
16
  EOF
17
17
 
18
18
  s.files = Dir['{bin/*,contrib/*,example/*,lib/**/*,test/**/*}'] +
@@ -23,13 +23,12 @@ EOF
23
23
  s.extra_rdoc_files = ['README.rdoc', 'HISTORY.md']
24
24
  s.test_files = Dir['test/spec_*.rb']
25
25
 
26
- s.author = 'Christian Neukirchen'
27
- s.email = 'chneukirchen@gmail.com'
28
- s.homepage = 'http://rack.github.io/'
26
+ s.author = 'Leah Neukirchen'
27
+ s.email = 'leah@vuxu.org'
28
+ s.homepage = 'https://rack.github.io/'
29
29
  s.required_ruby_version = '>= 2.2.2'
30
30
 
31
31
  s.add_development_dependency 'minitest', "~> 5.0"
32
32
  s.add_development_dependency 'minitest-sprint'
33
- s.add_development_dependency 'concurrent-ruby'
34
33
  s.add_development_dependency 'rake'
35
34
  end
@@ -147,7 +147,8 @@ describe Rack::Lock do
147
147
  }, false)
148
148
  env = Rack::MockRequest.env_for("/")
149
149
  env['rack.multithread'].must_equal true
150
- app.call(env)
150
+ _, _, body = app.call(env)
151
+ body.close
151
152
  env['rack.multithread'].must_equal true
152
153
  end
153
154
 
@@ -191,4 +192,13 @@ describe Rack::Lock do
191
192
  lambda { app.call(env) }.must_raise Exception
192
193
  lock.synchronized.must_equal false
193
194
  end
195
+
196
+ it "not replace the environment" do
197
+ env = Rack::MockRequest.env_for("/")
198
+ app = lock_app(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, [inner_env.object_id.to_s]] })
199
+
200
+ _, _, body = app.call(env)
201
+
202
+ body.to_enum.to_a.must_equal [env.object_id.to_s]
203
+ end
194
204
  end
@@ -17,6 +17,20 @@ describe Rack::MethodOverride do
17
17
  env["REQUEST_METHOD"].must_equal "GET"
18
18
  end
19
19
 
20
+ it "sets rack.errors for invalid UTF8 _method values" do
21
+ errors = StringIO.new
22
+ env = Rack::MockRequest.env_for("/",
23
+ :method => "POST",
24
+ :input => "_method=\xBF".b,
25
+ Rack::RACK_ERRORS => errors)
26
+
27
+ app.call env
28
+
29
+ errors.rewind
30
+ errors.read.must_equal "Invalid string for method\n"
31
+ env["REQUEST_METHOD"].must_equal "POST"
32
+ end
33
+
20
34
  it "modify REQUEST_METHOD for POST requests when _method parameter is set" do
21
35
  env = Rack::MockRequest.env_for("/", :method => "POST", :input => "_method=put")
22
36
  app.call env
@@ -572,6 +572,11 @@ class RackRequestTest < Minitest::Spec
572
572
  request.must_be :ssl?
573
573
  end
574
574
 
575
+ it "prevents scheme abuse" do
576
+ request = make_request(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_SCHEME' => 'a."><script>alert(1)</script>'))
577
+ request.scheme.must_equal 'http'
578
+ end
579
+
575
580
  it "parse cookies" do
576
581
  req = make_request \
577
582
  Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;quux=h&m")
@@ -1281,7 +1286,16 @@ EOF
1281
1286
  res.body.must_equal '2.2.2.3'
1282
1287
  end
1283
1288
 
1284
- 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
1285
1299
  req = make_request(Rack::MockRequest.env_for("/"))
1286
1300
  req.trusted_proxy?('127.0.0.1').must_equal 0
1287
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
@@ -77,4 +77,17 @@ describe Rack::ShowExceptions do
77
77
  assert_match(res, /ShowExceptions/)
78
78
  assert_match(res, /unknown location/)
79
79
  end
80
+
81
+ it "knows to prefer plaintext for non-html" do
82
+ # We don't need an app for this
83
+ exc = Rack::ShowExceptions.new(nil)
84
+
85
+ [
86
+ [{ "HTTP_ACCEPT" => "text/plain" }, true],
87
+ [{ "HTTP_ACCEPT" => "text/foo" }, true],
88
+ [{ "HTTP_ACCEPT" => "text/html" }, false]
89
+ ].each do |env, expected|
90
+ assert_equal(expected, exc.prefers_plaintext?(env))
91
+ end
92
+ end
80
93
  end
@@ -1,7 +1,6 @@
1
1
  require 'minitest/autorun'
2
2
  require 'rack/mock'
3
- require 'concurrent/utility/native_integer'
4
- require 'concurrent/atomic/count_down_latch'
3
+ require 'thread'
5
4
  require File.expand_path('../testrequest', __FILE__)
6
5
 
7
6
  Thread.abort_on_exception = true
@@ -120,8 +119,7 @@ describe Rack::Handler::WEBrick do
120
119
  end
121
120
 
122
121
  it "provide a .run" do
123
- block_ran = false
124
- latch = Concurrent::CountDownLatch.new 1
122
+ queue = Queue.new
125
123
 
126
124
  t = Thread.new do
127
125
  Rack::Handler::WEBrick.run(lambda {},
@@ -132,13 +130,12 @@ describe Rack::Handler::WEBrick do
132
130
  :AccessLog => []}) { |server|
133
131
  block_ran = true
134
132
  assert_kind_of WEBrick::HTTPServer, server
135
- @s = server
136
- latch.count_down
133
+ queue.push(server)
137
134
  }
138
135
  end
139
136
 
140
- latch.wait
141
- @s.shutdown
137
+ server = queue.pop
138
+ server.shutdown
142
139
  t.join
143
140
  end
144
141
 
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.3
4
+ version: 2.0.8
5
5
  platform: ruby
6
6
  authors:
7
- - Christian Neukirchen
7
+ - Leah Neukirchen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-15 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
@@ -38,20 +38,6 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: concurrent-ruby
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: rake
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -73,8 +59,8 @@ description: |
73
59
  servers, web frameworks, and software in between (the so-called
74
60
  middleware) into a single method call.
75
61
 
76
- Also see http://rack.github.io/.
77
- email: chneukirchen@gmail.com
62
+ Also see https://rack.github.io/.
63
+ email: leah@vuxu.org
78
64
  executables:
79
65
  - rackup
80
66
  extensions: []
@@ -269,7 +255,7 @@ files:
269
255
  - test/testrequest.rb
270
256
  - test/unregistered_handler/rack/handler/unregistered.rb
271
257
  - test/unregistered_handler/rack/handler/unregistered_long_one.rb
272
- homepage: http://rack.github.io/
258
+ homepage: https://rack.github.io/
273
259
  licenses:
274
260
  - MIT
275
261
  metadata: {}
@@ -288,60 +274,59 @@ required_rubygems_version: !ruby/object:Gem::Requirement
288
274
  - !ruby/object:Gem::Version
289
275
  version: '0'
290
276
  requirements: []
291
- rubyforge_project:
292
- rubygems_version: 2.6.8
277
+ rubygems_version: 3.0.3
293
278
  signing_key:
294
279
  specification_version: 4
295
280
  summary: a modular Ruby webserver interface
296
281
  test_files:
297
- - test/spec_auth_basic.rb
298
- - test/spec_auth_digest.rb
299
- - test/spec_body_proxy.rb
300
- - test/spec_builder.rb
301
- - test/spec_cascade.rb
302
- - test/spec_cgi.rb
303
- - test/spec_chunked.rb
304
- - test/spec_common_logger.rb
305
- - test/spec_conditional_get.rb
306
- - test/spec_config.rb
307
- - test/spec_content_length.rb
308
- - test/spec_content_type.rb
282
+ - test/spec_multipart.rb
309
283
  - test/spec_deflater.rb
310
- - test/spec_directory.rb
284
+ - test/spec_static.rb
285
+ - test/spec_session_cookie.rb
286
+ - test/spec_session_pool.rb
311
287
  - test/spec_etag.rb
312
- - test/spec_events.rb
313
- - test/spec_fastcgi.rb
314
- - test/spec_file.rb
288
+ - test/spec_version.rb
315
289
  - test/spec_handler.rb
316
- - test/spec_head.rb
317
- - test/spec_lint.rb
318
- - test/spec_lobster.rb
319
- - test/spec_lock.rb
320
- - test/spec_logger.rb
321
- - test/spec_media_type.rb
322
- - test/spec_method_override.rb
290
+ - test/spec_thin.rb
291
+ - test/spec_session_abstract_id.rb
323
292
  - test/spec_mime.rb
324
- - test/spec_mock.rb
325
- - test/spec_multipart.rb
326
- - test/spec_null_logger.rb
327
293
  - test/spec_recursive.rb
294
+ - test/spec_null_logger.rb
295
+ - test/spec_media_type.rb
296
+ - test/spec_cgi.rb
297
+ - test/spec_method_override.rb
298
+ - test/spec_content_type.rb
299
+ - test/spec_session_abstract_session_hash.rb
328
300
  - test/spec_request.rb
329
- - test/spec_response.rb
330
- - test/spec_rewindable_input.rb
301
+ - test/spec_chunked.rb
302
+ - test/spec_show_exceptions.rb
331
303
  - test/spec_runtime.rb
304
+ - test/spec_fastcgi.rb
305
+ - test/spec_common_logger.rb
306
+ - test/spec_builder.rb
307
+ - test/spec_config.rb
308
+ - test/spec_utils.rb
332
309
  - test/spec_sendfile.rb
310
+ - test/spec_lobster.rb
311
+ - test/spec_lint.rb
312
+ - test/spec_conditional_get.rb
313
+ - test/spec_tempfile_reaper.rb
314
+ - test/spec_mock.rb
333
315
  - test/spec_server.rb
334
- - test/spec_session_abstract_id.rb
335
- - test/spec_session_abstract_session_hash.rb
336
- - test/spec_session_cookie.rb
337
- - test/spec_session_memcache.rb
338
- - test/spec_session_pool.rb
339
- - test/spec_show_exceptions.rb
316
+ - test/spec_directory.rb
317
+ - test/spec_webrick.rb
318
+ - test/spec_response.rb
319
+ - test/spec_file.rb
340
320
  - test/spec_show_status.rb
341
- - test/spec_static.rb
342
- - test/spec_tempfile_reaper.rb
343
- - test/spec_thin.rb
321
+ - test/spec_body_proxy.rb
322
+ - test/spec_logger.rb
323
+ - test/spec_auth_digest.rb
344
324
  - test/spec_urlmap.rb
345
- - test/spec_utils.rb
346
- - test/spec_version.rb
347
- - test/spec_webrick.rb
325
+ - test/spec_events.rb
326
+ - test/spec_cascade.rb
327
+ - test/spec_auth_basic.rb
328
+ - test/spec_head.rb
329
+ - test/spec_lock.rb
330
+ - test/spec_rewindable_input.rb
331
+ - test/spec_session_memcache.rb
332
+ - test/spec_content_length.rb