rack 1.6.0.beta → 1.6.0.beta2

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.

Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/SPEC +3 -3
  3. data/lib/rack.rb +10 -0
  4. data/lib/rack/auth/abstract/handler.rb +4 -4
  5. data/lib/rack/auth/digest/request.rb +1 -1
  6. data/lib/rack/cascade.rb +1 -1
  7. data/lib/rack/chunked.rb +2 -2
  8. data/lib/rack/commonlogger.rb +4 -4
  9. data/lib/rack/conditionalget.rb +3 -3
  10. data/lib/rack/content_length.rb +2 -2
  11. data/lib/rack/content_type.rb +1 -1
  12. data/lib/rack/deflater.rb +4 -5
  13. data/lib/rack/directory.rb +5 -5
  14. data/lib/rack/etag.rb +7 -6
  15. data/lib/rack/file.rb +32 -14
  16. data/lib/rack/handler.rb +1 -1
  17. data/lib/rack/handler/cgi.rb +1 -1
  18. data/lib/rack/handler/fastcgi.rb +1 -1
  19. data/lib/rack/handler/lsws.rb +1 -1
  20. data/lib/rack/handler/mongrel.rb +1 -1
  21. data/lib/rack/handler/scgi.rb +2 -2
  22. data/lib/rack/handler/webrick.rb +6 -4
  23. data/lib/rack/head.rb +1 -1
  24. data/lib/rack/lint.rb +30 -16
  25. data/lib/rack/lobster.rb +2 -2
  26. data/lib/rack/methodoverride.rb +3 -3
  27. data/lib/rack/mock.rb +7 -7
  28. data/lib/rack/multipart/parser.rb +21 -7
  29. data/lib/rack/recursive.rb +6 -5
  30. data/lib/rack/request.rb +6 -6
  31. data/lib/rack/response.rb +8 -6
  32. data/lib/rack/runtime.rb +2 -1
  33. data/lib/rack/sendfile.rb +2 -2
  34. data/lib/rack/server.rb +7 -10
  35. data/lib/rack/showexceptions.rb +2 -2
  36. data/lib/rack/showstatus.rb +3 -3
  37. data/lib/rack/static.rb +1 -1
  38. data/lib/rack/urlmap.rb +2 -2
  39. data/lib/rack/utils.rb +14 -10
  40. data/rack.gemspec +1 -1
  41. data/test/spec_body_proxy.rb +16 -0
  42. data/test/spec_lint.rb +20 -9
  43. data/test/spec_multipart.rb +38 -16
  44. data/test/spec_request.rb +17 -0
  45. data/test/spec_server.rb +22 -13
  46. metadata +2 -2
@@ -39,8 +39,8 @@ module Rack
39
39
  [
40
40
  500,
41
41
  {
42
- "Content-Type" => content_type,
43
- "Content-Length" => Rack::Utils.bytesize(body).to_s,
42
+ CONTENT_TYPE => content_type,
43
+ CONTENT_LENGTH => Rack::Utils.bytesize(body).to_s,
44
44
  },
45
45
  [body],
46
46
  ]
@@ -3,7 +3,7 @@ require 'rack/request'
3
3
  require 'rack/utils'
4
4
 
5
5
  module Rack
6
- # Rack::ShowStatus catches all empty responses and replaces them
6
+ # Rack::ShowStatus catches all empty responses and replaces them
7
7
  # with a site explaining the error.
8
8
  #
9
9
  # Additional details can be put into <tt>rack.showstatus.detail</tt>
@@ -19,7 +19,7 @@ module Rack
19
19
  def call(env)
20
20
  status, headers, body = @app.call(env)
21
21
  headers = Utils::HeaderHash.new(headers)
22
- empty = headers['Content-Length'].to_i <= 0
22
+ empty = headers[CONTENT_LENGTH].to_i <= 0
23
23
 
24
24
  # client or server error, or explicit message
25
25
  if (status.to_i >= 400 && empty) || env["rack.showstatus.detail"]
@@ -35,7 +35,7 @@ module Rack
35
35
 
36
36
  body = @template.result(binding)
37
37
  size = Rack::Utils.bytesize(body)
38
- [status, headers.merge("Content-Type" => "text/html", "Content-Length" => size.to_s), [body]]
38
+ [status, headers.merge(CONTENT_TYPE => "text/html", CONTENT_LENGTH => size.to_s), [body]]
39
39
  else
40
40
  [status, headers, body]
41
41
  end
@@ -107,7 +107,7 @@ module Rack
107
107
  end
108
108
 
109
109
  def call(env)
110
- path = env["PATH_INFO"]
110
+ path = env[PATH_INFO]
111
111
 
112
112
  if can_serve(path)
113
113
  env["PATH_INFO"] = (path =~ /\/$/ ? path + @index : @urls[path]) if overwrite_file_path(path)
@@ -41,7 +41,7 @@ module Rack
41
41
  end
42
42
 
43
43
  def call(env)
44
- path = env["PATH_INFO"]
44
+ path = env[PATH_INFO]
45
45
  script_name = env['SCRIPT_NAME']
46
46
  hHost = env['HTTP_HOST']
47
47
  sName = env['SERVER_NAME']
@@ -66,7 +66,7 @@ module Rack
66
66
  return app.call(env)
67
67
  end
68
68
 
69
- [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
69
+ [404, {CONTENT_TYPE => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
70
70
 
71
71
  ensure
72
72
  env['PATH_INFO'] = path
@@ -61,12 +61,18 @@ module Rack
61
61
 
62
62
  class << self
63
63
  attr_accessor :key_space_limit
64
+ attr_accessor :multipart_part_limit
64
65
  end
65
66
 
66
67
  # The default number of bytes to allow parameter keys to take up.
67
68
  # This helps prevent a rogue client from flooding a Request.
68
69
  self.key_space_limit = 65536
69
70
 
71
+ # The maximum number of parts a request can contain. Accepting to many part
72
+ # can lead to the server running out of file handles.
73
+ # Set to `0` for no limit.
74
+ self.multipart_part_limit = (ENV['RACK_MULTIPART_LIMIT'] || 128).to_i
75
+
70
76
  # Stolen from Mongrel, with some small modifications:
71
77
  # Parses a query string by breaking it up at the '&'
72
78
  # and ';' characters. You can also use this to parse
@@ -418,7 +424,7 @@ module Rack
418
424
  # Constant time string comparison.
419
425
  #
420
426
  # NOTE: the values compared should be of fixed length, such as strings
421
- # that have aready been processed by HMAC. This should not be used
427
+ # that have already been processed by HMAC. This should not be used
422
428
  # on variable length plaintext strings because it could leak length info
423
429
  # via timing attacks.
424
430
  def secure_compare(a, b)
@@ -567,9 +573,9 @@ module Rack
567
573
 
568
574
  # Every standard HTTP code mapped to the appropriate message.
569
575
  # Generated with:
570
- # ruby -ropen-uri -rnokogiri -e "Nokogiri::XML(open(
571
- # 'http://www.iana.org/assignments/http-status-codes/http-status-codes.xml')).css('record').each{|r|
572
- # name = r.css('description').text; puts %Q[#{r.css('value').text} => '#{name}',] unless name == 'Unassigned' }"
576
+ # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
577
+ # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
578
+ # puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
573
579
  HTTP_STATUS_CODES = {
574
580
  100 => 'Continue',
575
581
  101 => 'Switching Protocols',
@@ -590,7 +596,6 @@ module Rack
590
596
  303 => 'See Other',
591
597
  304 => 'Not Modified',
592
598
  305 => 'Use Proxy',
593
- 306 => 'Reserved',
594
599
  307 => 'Temporary Redirect',
595
600
  308 => 'Permanent Redirect',
596
601
  400 => 'Bad Request',
@@ -606,12 +611,11 @@ module Rack
606
611
  410 => 'Gone',
607
612
  411 => 'Length Required',
608
613
  412 => 'Precondition Failed',
609
- 413 => 'Request Entity Too Large',
610
- 414 => 'Request-URI Too Long',
614
+ 413 => 'Payload Too Large',
615
+ 414 => 'URI Too Long',
611
616
  415 => 'Unsupported Media Type',
612
- 416 => 'Requested Range Not Satisfiable',
617
+ 416 => 'Range Not Satisfiable',
613
618
  417 => 'Expectation Failed',
614
- 418 => 'I\'m a teapot',
615
619
  422 => 'Unprocessable Entity',
616
620
  423 => 'Locked',
617
621
  424 => 'Failed Dependency',
@@ -625,7 +629,7 @@ module Rack
625
629
  503 => 'Service Unavailable',
626
630
  504 => 'Gateway Timeout',
627
631
  505 => 'HTTP Version Not Supported',
628
- 506 => 'Variant Also Negotiates (Experimental)',
632
+ 506 => 'Variant Also Negotiates',
629
633
  507 => 'Insufficient Storage',
630
634
  508 => 'Loop Detected',
631
635
  510 => 'Not Extended',
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "rack"
3
- s.version = "1.6.0.beta"
3
+ s.version = "1.6.0.beta2"
4
4
  s.platform = Gem::Platform::RUBY
5
5
  s.summary = "a modular Ruby webserver interface"
6
6
  s.license = "MIT"
@@ -1,5 +1,6 @@
1
1
  require 'rack/body_proxy'
2
2
  require 'stringio'
3
+ require 'ostruct'
3
4
 
4
5
  describe Rack::BodyProxy do
5
6
  should 'call each on the wrapped body' do
@@ -49,6 +50,21 @@ describe Rack::BodyProxy do
49
50
  called.should.equal true
50
51
  end
51
52
 
53
+ should 'allow multiple arguments in respond_to?' do
54
+ body = []
55
+ proxy = Rack::BodyProxy.new(body) { }
56
+ proc { proxy.respond_to?(:foo, false) }.should.not.raise
57
+ end
58
+
59
+ should 'not respond to :to_ary' do
60
+ body = OpenStruct.new(:to_ary => true)
61
+ body.respond_to?(:to_ary).should.equal true
62
+
63
+ proxy = Rack::BodyProxy.new(body) { }
64
+ proxy.respond_to?(:to_ary).should.equal false
65
+ proxy.respond_to?("to_ary").should.equal false
66
+ end
67
+
52
68
  should 'not close more than one time' do
53
69
  count = 0
54
70
  proxy = Rack::BodyProxy.new([]) { count += 1; raise "Block invoked more than 1 time!" if count > 1 }
@@ -1,4 +1,5 @@
1
1
  require 'stringio'
2
+ require 'tempfile'
2
3
  require 'rack/lint'
3
4
  require 'rack/mock'
4
5
 
@@ -74,6 +75,23 @@ describe Rack::Lint do
74
75
  }.should.raise(Rack::Lint::LintError).
75
76
  message.should.equal("logger [] must respond to info")
76
77
 
78
+ lambda {
79
+ Rack::Lint.new(nil).call(env("rack.multipart.buffer_size" => 0))
80
+ }.should.raise(Rack::Lint::LintError).
81
+ message.should.equal("rack.multipart.buffer_size must be an Integer > 0 if specified")
82
+
83
+ lambda {
84
+ Rack::Lint.new(nil).call(env("rack.multipart.tempfile_factory" => Tempfile))
85
+ }.should.raise(Rack::Lint::LintError).
86
+ message.should.equal("rack.multipart.tempfile_factory must respond to #call")
87
+
88
+ lambda {
89
+ Rack::Lint.new(lambda { |env|
90
+ env['rack.multipart.tempfile_factory'].call("testfile", "text/plain")
91
+ }).call(env("rack.multipart.tempfile_factory" => lambda { |filename, content_type| Object.new }))
92
+ }.should.raise(Rack::Lint::LintError).
93
+ message.should.equal("rack.multipart.tempfile_factory return value must respond to #<<")
94
+
77
95
  lambda {
78
96
  Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "FUCKUP?"))
79
97
  }.should.raise(Rack::Lint::LintError).
@@ -191,17 +209,10 @@ describe Rack::Lint do
191
209
 
192
210
  lambda {
193
211
  Rack::Lint.new(lambda { |env|
194
- [200, {"Content-" => "text/plain"}, []]
195
- }).call(env({}))
196
- }.should.raise(Rack::Lint::LintError).
197
- message.should.match(/must not end/)
198
-
199
- lambda {
200
- Rack::Lint.new(lambda { |env|
201
- [200, {"..%%quark%%.." => "text/plain"}, []]
212
+ [200, {"([{<quark>}])?" => "text/plain"}, []]
202
213
  }).call(env({}))
203
214
  }.should.raise(Rack::Lint::LintError).
204
- message.should.equal("invalid header name: ..%%quark%%..")
215
+ message.should.equal("invalid header name: ([{<quark>}])?")
205
216
 
206
217
  lambda {
207
218
  Rack::Lint.new(lambda { |env|
@@ -153,6 +153,12 @@ describe Rack::Multipart do
153
153
  params["files"][:tempfile].read.should.equal "contents"
154
154
  end
155
155
 
156
+ should "preserve extension in the created tempfile" do
157
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:text))
158
+ params = Rack::Multipart.parse_multipart(env)
159
+ File.extname(params["files"][:tempfile].path).should.equal ".txt"
160
+ end
161
+
156
162
  should "parse multipart upload with text file with no name field" do
157
163
  env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_and_no_name))
158
164
  params = Rack::Multipart.parse_multipart(env)
@@ -165,6 +171,15 @@ describe Rack::Multipart do
165
171
  params["file1.txt"][:tempfile].read.should.equal "contents"
166
172
  end
167
173
 
174
+ should "parse multipart upload file using custom tempfile class" do
175
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:text))
176
+ my_tempfile = ""
177
+ env['rack.multipart.tempfile_factory'] = lambda { |filename, content_type| my_tempfile }
178
+ params = Rack::Multipart.parse_multipart(env)
179
+ params["files"][:tempfile].object_id.should.equal my_tempfile.object_id
180
+ my_tempfile.should.equal "contents"
181
+ end
182
+
168
183
  should "parse multipart upload with nested parameters" do
169
184
  env = Rack::MockRequest.env_for("/", multipart_fixture(:nested))
170
185
  params = Rack::Multipart.parse_multipart(env)
@@ -436,22 +451,29 @@ Content-Type: image/jpeg\r
436
451
  end
437
452
 
438
453
  it "builds complete params with the chunk size of 16384 slicing exactly on boundary" do
439
- data = File.open(multipart_file("fail_16384_nofile"), 'rb') { |f| f.read }.gsub(/\n/, "\r\n")
440
- options = {
441
- "CONTENT_TYPE" => "multipart/form-data; boundary=----WebKitFormBoundaryWsY0GnpbI5U7ztzo",
442
- "CONTENT_LENGTH" => data.length.to_s,
443
- :input => StringIO.new(data)
444
- }
445
- env = Rack::MockRequest.env_for("/", options)
446
- params = Rack::Multipart.parse_multipart(env)
447
-
448
- params.should.not.equal nil
449
- params.keys.should.include "AAAAAAAAAAAAAAAAAAA"
450
- params["AAAAAAAAAAAAAAAAAAA"].keys.should.include "PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"
451
- params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"].keys.should.include "new"
452
- params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"].keys.should.include "-2"
453
- params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"].keys.should.include "ba_unit_id"
454
- params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"]["ba_unit_id"].should.equal "1017"
454
+ begin
455
+ previous_limit = Rack::Utils.multipart_part_limit
456
+ Rack::Utils.multipart_part_limit = 256
457
+
458
+ data = File.open(multipart_file("fail_16384_nofile"), 'rb') { |f| f.read }.gsub(/\n/, "\r\n")
459
+ options = {
460
+ "CONTENT_TYPE" => "multipart/form-data; boundary=----WebKitFormBoundaryWsY0GnpbI5U7ztzo",
461
+ "CONTENT_LENGTH" => data.length.to_s,
462
+ :input => StringIO.new(data)
463
+ }
464
+ env = Rack::MockRequest.env_for("/", options)
465
+ params = Rack::Multipart.parse_multipart(env)
466
+
467
+ params.should.not.equal nil
468
+ params.keys.should.include "AAAAAAAAAAAAAAAAAAA"
469
+ params["AAAAAAAAAAAAAAAAAAA"].keys.should.include "PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"
470
+ params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"].keys.should.include "new"
471
+ params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"].keys.should.include "-2"
472
+ params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"].keys.should.include "ba_unit_id"
473
+ params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"]["ba_unit_id"].should.equal "1017"
474
+ ensure
475
+ Rack::Utils.multipart_part_limit = previous_limit
476
+ end
455
477
  end
456
478
 
457
479
  should "return nil if no UploadedFiles were used" do
@@ -2,6 +2,7 @@ require 'stringio'
2
2
  require 'cgi'
3
3
  require 'rack/request'
4
4
  require 'rack/mock'
5
+ require 'securerandom'
5
6
 
6
7
  describe Rack::Request do
7
8
  should "wrap the rack variables" do
@@ -744,6 +745,22 @@ EOF
744
745
  f[:tempfile].size.should.equal 76
745
746
  end
746
747
 
748
+ should "MultipartPartLimitError when request has too many multipart parts if limit set" do
749
+ begin
750
+ data = 10000.times.map { "--AaB03x\r\nContent-Type: text/plain\r\nContent-Disposition: attachment; name=#{SecureRandom.hex(10)}; filename=#{SecureRandom.hex(10)}\r\n\r\ncontents\r\n" }.join("\r\n")
751
+ data += "--AaB03x--\r"
752
+
753
+ options = {
754
+ "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
755
+ "CONTENT_LENGTH" => data.length.to_s,
756
+ :input => StringIO.new(data)
757
+ }
758
+
759
+ request = Rack::Request.new Rack::MockRequest.env_for("/", options)
760
+ lambda { request.POST }.should.raise(Rack::Multipart::MultipartPartLimitError)
761
+ end
762
+ end
763
+
747
764
  should "parse big multipart form data" do
748
765
  input = <<EOF
749
766
  --AaB03x\r
@@ -28,21 +28,35 @@ describe Rack::Server do
28
28
  Rack::MockRequest.new(server.app).get("/").body.to_s.should.equal 'success'
29
29
  end
30
30
 
31
- should "not include Rack::Lint in deployment or none environments" do
31
+ should "allow subclasses to override middleware" do
32
+ server = Class.new(Rack::Server).class_eval { def middleware; Hash.new [] end; self }
33
+ server.middleware['deployment'].should.not.equal []
34
+ server.new(:app => 'foo').middleware['deployment'].should.equal []
35
+ end
36
+
37
+ should "allow subclasses to override default middleware" do
38
+ server = Class.new(Rack::Server).instance_eval { def default_middleware_by_environment; Hash.new [] end; self }
39
+ server.middleware['deployment'].should.equal []
40
+ server.new(:app => 'foo').middleware['deployment'].should.equal []
41
+ end
42
+
43
+ should "only provide default middleware for development and deployment environments" do
44
+ Rack::Server.default_middleware_by_environment.keys.sort.should.equal %w(deployment development)
45
+ end
46
+
47
+ should "always return an empty array for unknown environments" do
32
48
  server = Rack::Server.new(:app => 'foo')
33
- server.default_middleware_by_environment['deployment'].flatten.should.not.include(Rack::Lint)
34
- server.default_middleware_by_environment['none'].flatten.should.not.include(Rack::Lint)
49
+ server.middleware['production'].should.equal []
35
50
  end
36
51
 
37
- should "not include Rack::ShowExceptions in deployment or none environments" do
52
+ should "not include Rack::Lint in deployment environment" do
38
53
  server = Rack::Server.new(:app => 'foo')
39
- server.default_middleware_by_environment['deployment'].flatten.should.not.include(Rack::ShowExceptions)
40
- server.default_middleware_by_environment['none'].flatten.should.not.include(Rack::ShowExceptions)
54
+ server.middleware['deployment'].flatten.should.not.include(Rack::Lint)
41
55
  end
42
56
 
43
- should "always return an empty array for unknown environments" do
57
+ should "not include Rack::ShowExceptions in deployment environment" do
44
58
  server = Rack::Server.new(:app => 'foo')
45
- server.default_middleware_by_environment['production'].should.equal []
59
+ server.middleware['deployment'].flatten.should.not.include(Rack::ShowExceptions)
46
60
  end
47
61
 
48
62
  should "include Rack::TempfileReaper in deployment environment" do
@@ -66,11 +80,6 @@ describe Rack::Server do
66
80
  Rack::Server.logging_middleware.call(server).should.eql(nil)
67
81
  end
68
82
 
69
- should "not force any middleware under the none configuration" do
70
- server = Rack::Server.new(:app => 'foo')
71
- server.default_middleware_by_environment['none'].should.be.empty
72
- end
73
-
74
83
  should "use a full path to the pidfile" do
75
84
  # avoids issues with daemonize chdir
76
85
  opts = Rack::Server.new.send(:parse_options, %w[--pid testing.pid])
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: 1.6.0.beta
4
+ version: 1.6.0.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christian Neukirchen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-18 00:00:00.000000000 Z
11
+ date: 2014-11-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bacon