diff --git a/SPEC.rdoc b/SPEC.rdoc
index ddf474ae1..5c49cc7ef 100644
--- a/SPEC.rdoc
+++ b/SPEC.rdoc
@@ -121,7 +121,7 @@ If the string values for CGI keys contain non-ASCII characters,
they should use ASCII-8BIT encoding.
There are the following restrictions:
* rack.url_scheme must either be +http+ or +https+.
-* There must be a valid input stream in rack.input.
+* There may be a valid input stream in rack.input.
* There must be a valid error stream in rack.errors.
* There may be a valid hijack callback in rack.hijack
* The REQUEST_METHOD must be a valid token.
diff --git a/lib/rack/bad_request.rb b/lib/rack/bad_request.rb
new file mode 100644
index 000000000..67cd5b8d8
--- /dev/null
+++ b/lib/rack/bad_request.rb
@@ -0,0 +1,4 @@
+module Rack
+ module BadRequest
+ end
+end
diff --git a/lib/rack/lint.rb b/lib/rack/lint.rb
index ed3c7f421..abcd58fdd 100644
--- a/lib/rack/lint.rb
+++ b/lib/rack/lint.rb
@@ -56,9 +56,6 @@ def response
raise LintError, "No env given" unless @env
check_environment(@env)
- @env[RACK_INPUT] = InputWrapper.new(@env[RACK_INPUT])
- @env[RACK_ERRORS] = ErrorWrapper.new(@env[RACK_ERRORS])
-
## and returns a non-frozen Array of exactly three values:
@response = @app.call(@env)
raise LintError, "response is not an Array, but #{@response.class}" unless @response.kind_of? Array
@@ -265,11 +262,9 @@ def check_environment(env)
## is reserved for use with the Rack core distribution and other
## accepted specifications and must not be used otherwise.
##
-
- %w[REQUEST_METHOD SERVER_NAME QUERY_STRING SERVER_PROTOCOL
- rack.input rack.errors].each { |header|
+ %w[REQUEST_METHOD SERVER_NAME QUERY_STRING SERVER_PROTOCOL rack.errors].each do |header|
raise LintError, "env missing required key #{header}" unless env.include? header
- }
+ end
## The SERVER_PORT must be an Integer if set.
server_port = env["SERVER_PORT"]
@@ -328,10 +323,20 @@ def check_environment(env)
raise LintError, "rack.url_scheme unknown: #{env[RACK_URL_SCHEME].inspect}"
end
- ## * There must be a valid input stream in rack.input.
- check_input env[RACK_INPUT]
+ ## * There may be a valid input stream in rack.input.
+ if rack_input = env[RACK_INPUT]
+ check_input_stream(rack_input)
+ @env[RACK_INPUT] = InputWrapper.new(rack_input)
+ end
+
## * There must be a valid error stream in rack.errors.
- check_error env[RACK_ERRORS]
+ if rack_errors = env[RACK_ERRORS]
+ check_error_stream(rack_errors)
+ @env[RACK_ERRORS] = ErrorWrapper.new(rack_errors)
+ else
+ raise LintError, "rack.errors is not set"
+ end
+
## * There may be a valid hijack callback in rack.hijack
check_hijack env
@@ -384,7 +389,7 @@ def check_environment(env)
##
## The input stream is an IO-like object which contains the raw HTTP
## POST data.
- def check_input(input)
+ def check_input_stream(input)
## When applicable, its external encoding must be "ASCII-8BIT" and it
## must be opened in binary mode, for Ruby 1.9 compatibility.
if input.respond_to?(:external_encoding) && input.external_encoding != Encoding::ASCII_8BIT
@@ -488,7 +493,7 @@ def close(*args)
##
## === The Error Stream
##
- def check_error(error)
+ def check_error_stream(error)
## The error stream must respond to +puts+, +write+ and +flush+.
[:puts, :write, :flush].each { |method|
unless error.respond_to? method
diff --git a/lib/rack/mock_request.rb b/lib/rack/mock_request.rb
index b6d7ef4fe..3cb7f9f59 100644
--- a/lib/rack/mock_request.rb
+++ b/lib/rack/mock_request.rb
@@ -42,8 +42,7 @@ def string
end
DEFAULT_ENV = {
- RACK_INPUT => StringIO.new,
- RACK_ERRORS => StringIO.new,
+ RACK_ERRORS => StringIO.new,
}.freeze
def initialize(app)
@@ -144,20 +143,25 @@ def self.env_for(uri = "", opts = {})
end
end
- opts[:input] ||= String.new
+ unless opts.key?(:input)
+ opts[:input] = String.new
+ end
+
if String === opts[:input]
rack_input = StringIO.new(opts[:input])
else
rack_input = opts[:input]
end
- rack_input.set_encoding(Encoding::BINARY)
- env[RACK_INPUT] = rack_input
+ if rack_input
+ rack_input.set_encoding(Encoding::BINARY)
+ env[RACK_INPUT] = rack_input
- env["CONTENT_LENGTH"] ||= env[RACK_INPUT].size.to_s if env[RACK_INPUT].respond_to?(:size)
+ env["CONTENT_LENGTH"] ||= env[RACK_INPUT].size.to_s if env[RACK_INPUT].respond_to?(:size)
+ end
opts.each { |field, value|
- env[field] = value if String === field
+ env[field] = value if String === field
}
env
diff --git a/lib/rack/multipart.rb b/lib/rack/multipart.rb
index 3347662ac..165b4db3a 100644
--- a/lib/rack/multipart.rb
+++ b/lib/rack/multipart.rb
@@ -6,6 +6,8 @@
require_relative 'multipart/parser'
require_relative 'multipart/generator'
+require_relative 'bad_request'
+
module Rack
# A multipart form data parser, adapted from IOWA.
#
@@ -13,9 +15,15 @@ module Rack
module Multipart
MULTIPART_BOUNDARY = "AaB03x"
+ class MissingInputError < StandardError
+ include BadRequest
+ end
+
class << self
def parse_multipart(env, params = Rack::Utils.default_query_parser)
- io = env[RACK_INPUT]
+ unless io = env[RACK_INPUT]
+ raise MissingInputError, "Missing input stream!"
+ end
if content_length = env['CONTENT_LENGTH']
content_length = content_length.to_i
diff --git a/lib/rack/request.rb b/lib/rack/request.rb
index 6641b5fcd..8342ae6ab 100644
--- a/lib/rack/request.rb
+++ b/lib/rack/request.rb
@@ -497,7 +497,8 @@ def GET
# multipart/form-data.
def POST
if get_header(RACK_INPUT).nil?
- raise "Missing rack.input"
+ set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
+ set_header(RACK_REQUEST_FORM_HASH, {})
elsif get_header(RACK_REQUEST_FORM_INPUT) == get_header(RACK_INPUT)
get_header(RACK_REQUEST_FORM_HASH)
elsif form_data? || parseable_data?
diff --git a/test/spec_mock_request.rb b/test/spec_mock_request.rb
index b2a16fded..c2438ac9d 100644
--- a/test/spec_mock_request.rb
+++ b/test/spec_mock_request.rb
@@ -46,7 +46,7 @@
end
it "should handle a non-GET request with both :input and :params" do
- env = Rack::MockRequest.env_for("/", method: :post, input: nil, params: {})
+ env = Rack::MockRequest.env_for("/", method: :post, input: "", params: {})
env["PATH_INFO"].must_equal "/"
env.must_be_kind_of Hash
env['rack.input'].read.must_equal ''
diff --git a/test/spec_multipart.rb b/test/spec_multipart.rb
index 43bb90b2d..2aadcb692 100644
--- a/test/spec_multipart.rb
+++ b/test/spec_multipart.rb
@@ -42,6 +42,13 @@ def multipart_file(name)
}.must_raise Rack::Multipart::Error
end
+ it "raises a bad request exception if no body is given" do
+ env = Rack::MockRequest.env_for("/", "CONTENT_TYPE" => 'multipart/form-data; boundary=BurgerBurger', :input => nil)
+ lambda {
+ Rack::Multipart.parse_multipart(env)
+ }.must_raise Rack::BadRequest
+ end
+
it "parse multipart content when content type present but disposition is not" do
env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_no_disposition))
params = Rack::Multipart.parse_multipart(env)
diff --git a/test/spec_request.rb b/test/spec_request.rb
index 750639deb..d026e54aa 100644
--- a/test/spec_request.rb
+++ b/test/spec_request.rb
@@ -696,9 +696,9 @@ def initialize(*)
message.must_equal "invalid %-encoding (a%)"
end
- it "raise if rack.input is missing" do
+ it "return empty POST data if rack.input is missing" do
req = make_request({})
- lambda { req.POST }.must_raise RuntimeError
+ req.POST.must_be_empty
end
it "parse POST data when method is POST and no content-type given" do