summary refs log tree commit
diff options
context:
space:
mode:
authorJames Tucker <jftucker@gmail.com>2014-08-03 14:21:41 -0300
committerJames Tucker <jftucker@gmail.com>2014-08-03 14:21:41 -0300
commit9f52bb1db4b007066c9916881001d15c0a61bf66 (patch)
tree8de03740a1339dc367eb258e3e191abf00a91228
parentd8e2e2af6da57805d2f0906ce925ea150def31a0 (diff)
parent33075a489b85d43fc0be55d3503cc236f349e2f8 (diff)
downloadrack-9f52bb1db4b007066c9916881001d15c0a61bf66.tar.gz
Merge branch 'master' into pr/686
* master: (62 commits)
  build_nested_query includes integer values
  Rack::ETag correctly marks etags as Weak
  Fix yet another body close bug in Rack::Deflater
  Implement full Logger interface on NullLogger
  Revert "support empty string multipart filename"
  support empty string multipart filename
  multipart/form-data with files with no input name
  Fix parent type API regression introduced in #713
  correct weird case regression from #714
  UrlMap: Enable case-insensitive domain matching
  Raise specific exception if the parameters are invalid
  Fix media_type_params when Content-Type parameters contains quoted-strings
  Rack::Multipart::UploadedFile has file extensions
  multipart content-type match now case insensitive
  Undo test that falsely exemplifies production env
  default_middleware_by_environment should always returns empty array for unknown keys
  Remove rbx from Travis' allow_failures
  Fix rbx settings for Travis
  Use latest 2.1 on Travis
  Enable cleanup of Tempfiles from multipart form data by default
  ...

Conflicts:
	lib/rack/request.rb
-rw-r--r--.travis.yml7
-rw-r--r--README.rdoc3
-rw-r--r--SPEC78
-rw-r--r--lib/rack.rb1
-rw-r--r--lib/rack/builder.rb2
-rw-r--r--lib/rack/chunked.rb13
-rw-r--r--lib/rack/conditionalget.rb5
-rw-r--r--lib/rack/deflater.rb43
-rw-r--r--lib/rack/etag.rb2
-rw-r--r--lib/rack/handler/webrick.rb18
-rw-r--r--lib/rack/lint.rb94
-rw-r--r--lib/rack/lobster.rb11
-rw-r--r--lib/rack/methodoverride.rb15
-rw-r--r--lib/rack/mock.rb9
-rw-r--r--lib/rack/multipart.rb2
-rw-r--r--lib/rack/multipart/parser.rb11
-rw-r--r--lib/rack/multipart/uploaded_file.rb4
-rw-r--r--lib/rack/nulllogger.rb23
-rw-r--r--lib/rack/request.rb19
-rw-r--r--lib/rack/response.rb1
-rw-r--r--lib/rack/server.rb48
-rw-r--r--lib/rack/showexceptions.rb31
-rw-r--r--lib/rack/tempfile_reaper.rb22
-rw-r--r--lib/rack/urlmap.rb20
-rw-r--r--lib/rack/utils.rb51
-rw-r--r--test/multipart/filename_and_no_name6
-rw-r--r--test/spec_chunked.rb16
-rw-r--r--test/spec_deflater.rb431
-rw-r--r--test/spec_etag.rb4
-rw-r--r--test/spec_lobster.rb2
-rw-r--r--test/spec_mock.rb8
-rw-r--r--test/spec_multipart.rb36
-rw-r--r--test/spec_request.rb78
-rw-r--r--test/spec_response.rb5
-rw-r--r--test/spec_server.rb20
-rw-r--r--test/spec_showexceptions.rb65
-rw-r--r--test/spec_tempfile_reaper.rb63
-rw-r--r--test/spec_urlmap.rb23
-rw-r--r--test/spec_utils.rb43
-rw-r--r--test/spec_webrick.rb18
40 files changed, 1022 insertions, 329 deletions
diff --git a/.travis.yml b/.travis.yml
index 588bf0ea..aa2eca43 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,14 +9,11 @@ rvm:
   - 1.9.2
   - 1.9.3
   - 2.0.0
-  - 2.1.0
+  - 2.1
   - ruby-head
-  - rbx
+  - rbx-2
   - jruby
   - ree
-matrix:
-  allow_failures:
-    - rvm: rbx
 notifications:
   email: false
   irc: "irc.freenode.org#rack"
diff --git a/README.rdoc b/README.rdoc
index 7a3c8d58..d5bd6d2e 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -1,4 +1,4 @@
-= Rack, a modular Ruby webserver interface {<img src="https://secure.travis-ci.org/rack/rack.png" alt="Build Status" />}[http://travis-ci.org/rack/rack] {<img src="https://gemnasium.com/rack/rack.png" alt="Dependency Status" />}[https://gemnasium.com/rack/rack]
+= Rack, a modular Ruby webserver interface {<img src="https://secure.travis-ci.org/rack/rack.svg" alt="Build Status" />}[http://travis-ci.org/rack/rack] {<img src="https://gemnasium.com/rack/rack.svg" alt="Dependency Status" />}[https://gemnasium.com/rack/rack]
 
 Rack provides a minimal, modular and adaptable interface for developing
 web applications in Ruby.  By wrapping HTTP requests and responses in
@@ -33,6 +33,7 @@ These web servers include Rack handlers in their distributions:
 * Unicorn
 * unixrack
 * uWSGI
+* yahns
 * Zbatery
 
 Any valid Rack app will run the same on all these handlers, without
diff --git a/SPEC b/SPEC
index f7bfb3df..0deb57b5 100644
--- a/SPEC
+++ b/SPEC
@@ -40,7 +40,17 @@ below.
 <tt>QUERY_STRING</tt>:: The portion of the request URL that
                         follows the <tt>?</tt>, if any. May be
                         empty, but is always required!
-<tt>SERVER_NAME</tt>, <tt>SERVER_PORT</tt>:: When combined with <tt>SCRIPT_NAME</tt> and <tt>PATH_INFO</tt>, these variables can be used to complete the URL. Note, however, that <tt>HTTP_HOST</tt>, if present, should be used in preference to <tt>SERVER_NAME</tt> for reconstructing the request URL.  <tt>SERVER_NAME</tt> and <tt>SERVER_PORT</tt> can never be empty strings, and so are always required.
+<tt>SERVER_NAME</tt>, <tt>SERVER_PORT</tt>::
+                       When combined with <tt>SCRIPT_NAME</tt> and
+                       <tt>PATH_INFO</tt>, these variables can be
+                       used to complete the URL. Note, however,
+                       that <tt>HTTP_HOST</tt>, if present,
+                       should be used in preference to
+                       <tt>SERVER_NAME</tt> for reconstructing
+                       the request URL.
+                       <tt>SERVER_NAME</tt> and <tt>SERVER_PORT</tt>
+                       can never be empty strings, and so
+                       are always required.
 <tt>HTTP_</tt> Variables:: Variables corresponding to the
                            client-supplied HTTP request
                            headers (i.e., variables whose
@@ -49,24 +59,47 @@ below.
                            variables should correspond with
                            the presence or absence of the
                            appropriate HTTP header in the
-                           request. See <a href="https://tools.ietf.org/html/rfc3875#section-4.1.18">
-                           RFC3875 section 4.1.18</a> for specific behavior.
+                           request. See
+                           <a href="https://tools.ietf.org/html/rfc3875#section-4.1.18">
+                           RFC3875 section 4.1.18</a> for
+                           specific behavior.
 In addition to this, the Rack environment must include these
 Rack-specific variables:
-<tt>rack.version</tt>:: The Array representing this version of Rack. See Rack::VERSION, that corresponds to the version of this SPEC.
-<tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the request URL.
+<tt>rack.version</tt>:: The Array representing this version of Rack
+                        See Rack::VERSION, that corresponds to
+                        the version of this SPEC.
+<tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the
+                           request URL.
 <tt>rack.input</tt>:: See below, the input stream.
 <tt>rack.errors</tt>:: See below, the error stream.
-<tt>rack.multithread</tt>:: true if the application object may be simultaneously invoked by another thread in the same process, false otherwise.
-<tt>rack.multiprocess</tt>:: true if an equivalent application object may be simultaneously invoked by another process, false otherwise.
-<tt>rack.run_once</tt>:: true if the server expects (but does not guarantee!) that the application will only be invoked this one time during the life of its containing process. Normally, this will only be true for a server based on CGI (or something similar).
-<tt>rack.hijack?</tt>:: present and true if the server supports connection hijacking. See below, hijacking.
-<tt>rack.hijack</tt>:: an object responding to #call that must be called at least once before using rack.hijack_io. It is recommended #call return rack.hijack_io as well as setting it in env if necessary.
-<tt>rack.hijack_io</tt>:: if rack.hijack? is true, and rack.hijack has received #call, this will contain an object resembling an IO. See hijacking.
+<tt>rack.multithread</tt>:: true if the application object may be
+                            simultaneously invoked by another thread
+                            in the same process, false otherwise.
+<tt>rack.multiprocess</tt>:: true if an equivalent application object
+                             may be simultaneously invoked by another
+                             process, false otherwise.
+<tt>rack.run_once</tt>:: true if the server expects
+                         (but does not guarantee!) that the
+                         application will only be invoked this one
+                         time during the life of its containing
+                         process. Normally, this will only be true
+                         for a server based on CGI
+                         (or something similar).
+<tt>rack.hijack?</tt>:: present and true if the server supports
+                        connection hijacking. See below, hijacking.
+<tt>rack.hijack</tt>:: an object responding to #call that must be
+                       called at least once before using
+                       rack.hijack_io.
+                       It is recommended #call return rack.hijack_io
+                       as well as setting it in env if necessary.
+<tt>rack.hijack_io</tt>:: if rack.hijack? is true, and rack.hijack
+                          has received #call, this will contain
+                          an object resembling an IO. See hijacking.
 Additional environment specifications have approved to
 standardized middleware APIs.  None of these are required to
 be implemented by the server.
-<tt>rack.session</tt>:: A hash like interface for storing request session data.
+<tt>rack.session</tt>:: A hash like interface for storing
+                        request session data.
                         The store must implement:
                         store(key, value)         (aliased as []=);
                         fetch(key, default = nil) (aliased as []);
@@ -110,15 +143,18 @@ must be opened in binary mode, for Ruby 1.9 compatibility.
 The input stream must respond to +gets+, +each+, +read+ and +rewind+.
 * +gets+ must be called without arguments and return a string,
   or +nil+ on EOF.
-* +read+ behaves like IO#read. Its signature is <tt>read([length, [buffer]])</tt>.
-  If given, +length+ must be a non-negative Integer (>= 0) or +nil+, and +buffer+ must
-  be a String and may not be nil. If +length+ is given and not nil, then this method
-  reads at most +length+ bytes from the input stream. If +length+ is not given or nil,
-  then this method reads all data until EOF.
-  When EOF is reached, this method returns nil if +length+ is given and not nil, or ""
-  if +length+ is not given or is nil.
-  If +buffer+ is given, then the read data will be placed into +buffer+ instead of a
-  newly created String object.
+* +read+ behaves like IO#read.
+  Its signature is <tt>read([length, [buffer]])</tt>.
+  If given, +length+ must be a non-negative Integer (>= 0) or +nil+,
+  and +buffer+ must be a String and may not be nil.
+  If +length+ is given and not nil, then this method reads at most
+  +length+ bytes from the input stream.
+  If +length+ is not given or nil, then this method reads
+  all data until EOF.
+  When EOF is reached, this method returns nil if +length+ is given
+  and not nil, or "" if +length+ is not given or is nil.
+  If +buffer+ is given, then the read data will be placed
+  into +buffer+ instead of a newly created String object.
 * +each+ must be called without arguments and only yield Strings.
 * +rewind+ must be called without arguments. It rewinds the input
   stream back to the beginning. It must not raise Errno::ESPIPE:
diff --git a/lib/rack.rb b/lib/rack.rb
index 57119df3..341514c5 100644
--- a/lib/rack.rb
+++ b/lib/rack.rb
@@ -53,6 +53,7 @@ module Rack
   autoload :ShowExceptions, "rack/showexceptions"
   autoload :ShowStatus, "rack/showstatus"
   autoload :Static, "rack/static"
+  autoload :TempfileReaper, "rack/tempfile_reaper"
   autoload :URLMap, "rack/urlmap"
   autoload :Utils, "rack/utils"
   autoload :Multipart, "rack/multipart"
diff --git a/lib/rack/builder.rb b/lib/rack/builder.rb
index a4f6c575..bda3be27 100644
--- a/lib/rack/builder.rb
+++ b/lib/rack/builder.rb
@@ -157,7 +157,7 @@ module Rack
 
     def generate_map(default_app, mapping)
       mapped = default_app ? {'/' => default_app} : {}
-      mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b) }
+      mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b).to_app }
       URLMap.new(mapped)
     end
   end
diff --git a/lib/rack/chunked.rb b/lib/rack/chunked.rb
index a400756a..ea221fa9 100644
--- a/lib/rack/chunked.rb
+++ b/lib/rack/chunked.rb
@@ -39,11 +39,22 @@ module Rack
       @app = app
     end
 
+    # pre-HTTP/1.0 (informally "HTTP/0.9") HTTP requests did not have
+    # a version (nor response headers)
+    def chunkable_version?(ver)
+      case ver
+      when "HTTP/1.0", nil, "HTTP/0.9"
+        false
+      else
+        true
+      end
+    end
+
     def call(env)
       status, headers, body = @app.call(env)
       headers = HeaderHash.new(headers)
 
-      if env['HTTP_VERSION'] == 'HTTP/1.0' ||
+      if ! chunkable_version?(env['HTTP_VERSION']) ||
          STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
          headers['Content-Length'] ||
          headers['Transfer-Encoding']
diff --git a/lib/rack/conditionalget.rb b/lib/rack/conditionalget.rb
index ed87c54e..88573166 100644
--- a/lib/rack/conditionalget.rb
+++ b/lib/rack/conditionalget.rb
@@ -28,7 +28,10 @@ module Rack
           status = 304
           headers.delete('Content-Type')
           headers.delete('Content-Length')
-          body = []
+          original_body = body
+          body = Rack::BodyProxy.new([]) do
+            original_body.close if original_body.respond_to?(:close)
+          end
         end
         [status, headers, body]
       else
diff --git a/lib/rack/deflater.rb b/lib/rack/deflater.rb
index 2e55f97e..9df510bd 100644
--- a/lib/rack/deflater.rb
+++ b/lib/rack/deflater.rb
@@ -17,19 +17,26 @@ module Rack
   # directive of 'no-transform' is present, or when the response status
   # code is one that doesn't allow an entity body.
   class Deflater
-    def initialize(app)
+    ##
+    # Creates Rack::Deflater middleware.
+    #
+    # [app] rack app instance
+    # [options] hash of deflater options, i.e.
+    #           'if' - a lambda enabling / disabling deflation based on returned boolean value
+    #                  e.g use Rack::Deflater, :if => lambda { |env, status, headers, body| body.length > 512 }
+    #           'include' - a list of content types that should be compressed
+    def initialize(app, options = {})
       @app = app
+
+      @condition = options[:if]
+      @compressible_types = options[:include]
     end
 
     def call(env)
       status, headers, body = @app.call(env)
       headers = Utils::HeaderHash.new(headers)
 
-      # Skip compressing empty entity body responses and responses with
-      # no-transform set.
-      if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
-          headers['Cache-Control'].to_s =~ /\bno-transform\b/ ||
-         (headers['Content-Encoding'] && headers['Content-Encoding'] !~ /\bidentity\b/)
+      unless should_deflate?(env, status, headers, body)
         return [status, headers, body]
       end
 
@@ -58,9 +65,9 @@ module Rack
       when "identity"
         [status, headers, body]
       when nil
-        body.close if body.respond_to?(:close)
         message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
-        [406, {"Content-Type" => "text/plain", "Content-Length" => message.length.to_s}, [message]]
+        bp = Rack::BodyProxy.new([message]) { body.close if body.respond_to?(:close) }
+        [406, {"Content-Type" => "text/plain", "Content-Length" => message.length.to_s}, bp]
       end
     end
 
@@ -124,5 +131,25 @@ module Rack
         @body.close if @body.respond_to?(:close)
       end
     end
+
+    private
+
+    def should_deflate?(env, status, headers, body)
+      # Skip compressing empty entity body responses and responses with
+      # no-transform set.
+      if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
+          headers['Cache-Control'].to_s =~ /\bno-transform\b/ ||
+         (headers['Content-Encoding'] && headers['Content-Encoding'] !~ /\bidentity\b/)
+        return false
+      end
+
+      # Skip if @compressible_types are given and does not include request's content type
+      return false if @compressible_types && !(headers.has_key?('Content-Type') && @compressible_types.include?(headers['Content-Type'][/[^;]*/]))
+
+      # Skip if @condition lambda is given and evaluates to false
+      return false if @condition && !@condition.call(env, status, headers, body)
+
+      true
+    end
   end
 end
diff --git a/lib/rack/etag.rb b/lib/rack/etag.rb
index 99a1a4c0..fefe671f 100644
--- a/lib/rack/etag.rb
+++ b/lib/rack/etag.rb
@@ -28,7 +28,7 @@ module Rack
         body = Rack::BodyProxy.new(new_body) do
           original_body.close if original_body.respond_to?(:close)
         end
-        headers['ETag'] = %("#{digest}") if digest
+        headers['ETag'] = %(W/"#{digest}") if digest
       end
 
       unless headers['Cache-Control']
diff --git a/lib/rack/handler/webrick.rb b/lib/rack/handler/webrick.rb
index f76679b4..023d8b27 100644
--- a/lib/rack/handler/webrick.rb
+++ b/lib/rack/handler/webrick.rb
@@ -2,6 +2,23 @@ require 'webrick'
 require 'stringio'
 require 'rack/content_length'
 
+# This monkey patch allows for applications to perform their own chunking
+# through WEBrick::HTTPResponse iff rack is set to true.
+class WEBrick::HTTPResponse
+  attr_accessor :rack
+
+  alias _rack_setup_header setup_header
+  def setup_header
+    app_chunking = rack && @header['transfer-encoding'] == 'chunked'
+
+    @chunked = app_chunking if app_chunking
+
+    _rack_setup_header
+
+    @chunked = false if app_chunking
+  end
+end
+
 module Rack
   module Handler
     class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
@@ -39,6 +56,7 @@ module Rack
       end
 
       def service(req, res)
+        res.rack = true
         env = req.meta_vars
         env.delete_if { |k, v| v.nil? }
 
diff --git a/lib/rack/lint.rb b/lib/rack/lint.rb
index 3978b70a..667c34a6 100644
--- a/lib/rack/lint.rb
+++ b/lib/rack/lint.rb
@@ -102,7 +102,17 @@ module Rack
       ##                         follows the <tt>?</tt>, if any. May be
       ##                         empty, but is always required!
 
-      ## <tt>SERVER_NAME</tt>, <tt>SERVER_PORT</tt>:: When combined with <tt>SCRIPT_NAME</tt> and <tt>PATH_INFO</tt>, these variables can be used to complete the URL. Note, however, that <tt>HTTP_HOST</tt>, if present, should be used in preference to <tt>SERVER_NAME</tt> for reconstructing the request URL.  <tt>SERVER_NAME</tt> and <tt>SERVER_PORT</tt> can never be empty strings, and so are always required.
+      ## <tt>SERVER_NAME</tt>, <tt>SERVER_PORT</tt>::
+      ##                        When combined with <tt>SCRIPT_NAME</tt> and
+      ##                        <tt>PATH_INFO</tt>, these variables can be
+      ##                        used to complete the URL. Note, however,
+      ##                        that <tt>HTTP_HOST</tt>, if present,
+      ##                        should be used in preference to
+      ##                        <tt>SERVER_NAME</tt> for reconstructing
+      ##                        the request URL.
+      ##                        <tt>SERVER_NAME</tt> and <tt>SERVER_PORT</tt>
+      ##                        can never be empty strings, and so
+      ##                        are always required.
 
       ## <tt>HTTP_</tt> Variables:: Variables corresponding to the
       ##                            client-supplied HTTP request
@@ -112,29 +122,60 @@ module Rack
       ##                            variables should correspond with
       ##                            the presence or absence of the
       ##                            appropriate HTTP header in the
-      ##                            request. See <a href="https://tools.ietf.org/html/rfc3875#section-4.1.18">
-      ##                            RFC3875 section 4.1.18</a> for specific behavior.
+      ##                            request. See
+      ##                            <a href="https://tools.ietf.org/html/rfc3875#section-4.1.18">
+      ##                            RFC3875 section 4.1.18</a> for
+      ##                            specific behavior.
 
       ## In addition to this, the Rack environment must include these
       ## Rack-specific variables:
 
-      ## <tt>rack.version</tt>:: The Array representing this version of Rack. See Rack::VERSION, that corresponds to the version of this SPEC.
-      ## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the request URL.
+      ## <tt>rack.version</tt>:: The Array representing this version of Rack
+      ##                         See Rack::VERSION, that corresponds to
+      ##                         the version of this SPEC.
+
+      ## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the
+      ##                            request URL.
+
       ## <tt>rack.input</tt>:: See below, the input stream.
+
       ## <tt>rack.errors</tt>:: See below, the error stream.
-      ## <tt>rack.multithread</tt>:: true if the application object may be simultaneously invoked by another thread in the same process, false otherwise.
-      ## <tt>rack.multiprocess</tt>:: true if an equivalent application object may be simultaneously invoked by another process, false otherwise.
-      ## <tt>rack.run_once</tt>:: true if the server expects (but does not guarantee!) that the application will only be invoked this one time during the life of its containing process. Normally, this will only be true for a server based on CGI (or something similar).
-      ## <tt>rack.hijack?</tt>:: present and true if the server supports connection hijacking. See below, hijacking.
-      ## <tt>rack.hijack</tt>:: an object responding to #call that must be called at least once before using rack.hijack_io. It is recommended #call return rack.hijack_io as well as setting it in env if necessary.
-      ## <tt>rack.hijack_io</tt>:: if rack.hijack? is true, and rack.hijack has received #call, this will contain an object resembling an IO. See hijacking.
-      ##
+
+      ## <tt>rack.multithread</tt>:: true if the application object may be
+      ##                             simultaneously invoked by another thread
+      ##                             in the same process, false otherwise.
+
+      ## <tt>rack.multiprocess</tt>:: true if an equivalent application object
+      ##                              may be simultaneously invoked by another
+      ##                              process, false otherwise.
+
+      ## <tt>rack.run_once</tt>:: true if the server expects
+      ##                          (but does not guarantee!) that the
+      ##                          application will only be invoked this one
+      ##                          time during the life of its containing
+      ##                          process. Normally, this will only be true
+      ##                          for a server based on CGI
+      ##                          (or something similar).
+
+      ## <tt>rack.hijack?</tt>:: present and true if the server supports
+      ##                         connection hijacking. See below, hijacking.
+
+      ## <tt>rack.hijack</tt>:: an object responding to #call that must be
+      ##                        called at least once before using
+      ##                        rack.hijack_io.
+      ##                        It is recommended #call return rack.hijack_io
+      ##                        as well as setting it in env if necessary.
+
+      ## <tt>rack.hijack_io</tt>:: if rack.hijack? is true, and rack.hijack
+      ##                           has received #call, this will contain
+      ##                           an object resembling an IO. See hijacking.
 
       ## Additional environment specifications have approved to
       ## standardized middleware APIs.  None of these are required to
       ## be implemented by the server.
 
-      ## <tt>rack.session</tt>:: A hash like interface for storing request session data.
+      ## <tt>rack.session</tt>:: A hash like interface for storing
+      ##                         request session data.
       ##                         The store must implement:
       if session = env['rack.session']
         ##                         store(key, value)         (aliased as []=);
@@ -218,7 +259,6 @@ module Rack
         }
       }
 
-      ##
       ## There are the following restrictions:
 
       ## * <tt>rack.version</tt> must be an array of Integers.
@@ -311,15 +351,23 @@ module Rack
         v
       end
 
-      ## * +read+ behaves like IO#read. Its signature is <tt>read([length, [buffer]])</tt>.
-      ##   If given, +length+ must be a non-negative Integer (>= 0) or +nil+, and +buffer+ must
-      ##   be a String and may not be nil. If +length+ is given and not nil, then this method
-      ##   reads at most +length+ bytes from the input stream. If +length+ is not given or nil,
-      ##   then this method reads all data until EOF.
-      ##   When EOF is reached, this method returns nil if +length+ is given and not nil, or ""
-      ##   if +length+ is not given or is nil.
-      ##   If +buffer+ is given, then the read data will be placed into +buffer+ instead of a
-      ##   newly created String object.
+      ## * +read+ behaves like IO#read.
+      ##   Its signature is <tt>read([length, [buffer]])</tt>.
+      ##
+      ##   If given, +length+ must be a non-negative Integer (>= 0) or +nil+,
+      ##   and +buffer+ must be a String and may not be nil.
+      ##
+      ##   If +length+ is given and not nil, then this method reads at most
+      ##   +length+ bytes from the input stream.
+      ##
+      ##   If +length+ is not given or nil, then this method reads
+      ##   all data until EOF.
+      ##
+      ##   When EOF is reached, this method returns nil if +length+ is given
+      ##   and not nil, or "" if +length+ is not given or is nil.
+      ##
+      ##   If +buffer+ is given, then the read data will be placed
+      ##   into +buffer+ instead of a newly created String object.
       def read(*args)
         assert("rack.input#read called with too many arguments") {
           args.size <= 2
diff --git a/lib/rack/lobster.rb b/lib/rack/lobster.rb
index d1a7f7bc..195bd945 100644
--- a/lib/rack/lobster.rb
+++ b/lib/rack/lobster.rb
@@ -32,9 +32,14 @@ module Rack
     def call(env)
       req = Request.new(env)
       if req.GET["flip"] == "left"
-        lobster = LobsterString.split("\n").
-          map { |line| line.ljust(42).reverse }.
-          join("\n")
+        lobster = LobsterString.split("\n").map do |line|
+          line.ljust(42).reverse.
+            gsub('\\', 'TEMP').
+            gsub('/', '\\').
+            gsub('TEMP', '/').
+            gsub('{','}').
+            gsub('(',')')
+        end.join("\n")
         href = "?flip=right"
       elsif req.GET["flip"] == "crash"
         raise "Lobster crashed"
diff --git a/lib/rack/methodoverride.rb b/lib/rack/methodoverride.rb
index 449961ce..062f3d67 100644
--- a/lib/rack/methodoverride.rb
+++ b/lib/rack/methodoverride.rb
@@ -4,13 +4,14 @@ module Rack
 
     METHOD_OVERRIDE_PARAM_KEY = "_method".freeze
     HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE".freeze
+    ALLOWED_METHODS = ["POST"]
 
     def initialize(app)
       @app = app
     end
 
     def call(env)
-      if env["REQUEST_METHOD"] == "POST"
+      if allowed_methods.include?(env["REQUEST_METHOD"])
         method = method_override(env)
         if HTTP_METHODS.include?(method)
           env["rack.methodoverride.original_method"] = env["REQUEST_METHOD"]
@@ -23,9 +24,19 @@ module Rack
 
     def method_override(env)
       req = Request.new(env)
-      method = req.POST[METHOD_OVERRIDE_PARAM_KEY] ||
+      method = method_override_param(req) ||
         env[HTTP_METHOD_OVERRIDE_HEADER]
       method.to_s.upcase
     end
+
+    private
+
+    def allowed_methods
+      ALLOWED_METHODS
+    end
+
+    def method_override_param(req)
+      req.POST[METHOD_OVERRIDE_PARAM_KEY]
+    end
   end
 end
diff --git a/lib/rack/mock.rb b/lib/rack/mock.rb
index 3ba314e4..3c02c1fe 100644
--- a/lib/rack/mock.rb
+++ b/lib/rack/mock.rb
@@ -77,9 +77,16 @@ module Rack
       body.close if body.respond_to?(:close)
     end
 
+    # For historical reasons, we're pinning to RFC 2396. It's easier for users
+    # and we get support from ruby 1.8 to 2.2 using this method.
+    def self.parse_uri_rfc2396(uri)
+      @parser ||= defined?(URI::RFC2396_Parser) ? URI::RFC2396_Parser.new : URI
+      @parser.parse(uri)
+    end
+
     # Return the Rack environment used for a request to +uri+.
     def self.env_for(uri="", opts={})
-      uri = URI(uri)
+      uri = parse_uri_rfc2396(uri)
       uri.path = "/#{uri.path}" unless uri.path[0] == ?/
 
       env = DEFAULT_ENV.dup
diff --git a/lib/rack/multipart.rb b/lib/rack/multipart.rb
index d67ff051..7a44c4d4 100644
--- a/lib/rack/multipart.rb
+++ b/lib/rack/multipart.rb
@@ -9,7 +9,7 @@ module Rack
 
     EOL = "\r\n"
     MULTIPART_BOUNDARY = "AaB03x"
-    MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
+    MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni
     TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
     CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
     DISPPARM = /;\s*(#{TOKEN})=("(?:\\"|[^"])*"|#{TOKEN})/
diff --git a/lib/rack/multipart/parser.rb b/lib/rack/multipart/parser.rb
index fa47fd16..22f9734b 100644
--- a/lib/rack/multipart/parser.rb
+++ b/lib/rack/multipart/parser.rb
@@ -16,10 +16,10 @@ module Rack
         content_length = env['CONTENT_LENGTH']
         content_length = content_length.to_i if content_length
 
-        new($1, io, content_length)
+        new($1, io, content_length, env)
       end
 
-      def initialize(boundary, io, content_length)
+      def initialize(boundary, io, content_length, env)
         @buf            = ""
 
         if @buf.respond_to? :force_encoding
@@ -31,6 +31,7 @@ module Rack
         @io             = io
         @content_length = content_length
         @boundary_size  = Utils.bytesize(@boundary) + EOL.size
+        @env = env
 
         if @content_length
           @content_length -= @boundary_size
@@ -111,8 +112,12 @@ module Rack
 
             filename = get_filename(head)
 
+            if name.nil? || name.empty? && filename
+              name = filename
+            end
+
             if filename
-              body = Tempfile.new("RackMultipart")
+              (@env['rack.tempfiles'] ||= []) << body = Tempfile.new("RackMultipart")
               body.binmode  if body.respond_to?(:binmode)
             end
 
diff --git a/lib/rack/multipart/uploaded_file.rb b/lib/rack/multipart/uploaded_file.rb
index 11932b17..1b56ad75 100644
--- a/lib/rack/multipart/uploaded_file.rb
+++ b/lib/rack/multipart/uploaded_file.rb
@@ -11,7 +11,7 @@ module Rack
         raise "#{path} file does not exist" unless ::File.exist?(path)
         @content_type = content_type
         @original_filename = ::File.basename(path)
-        @tempfile = Tempfile.new(@original_filename)
+        @tempfile = Tempfile.new([@original_filename, ::File.extname(path)])
         @tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
         @tempfile.binmode if binary
         FileUtils.copy_file(path, @tempfile.path)
@@ -31,4 +31,4 @@ module Rack
       end
     end
   end
-end \ No newline at end of file
+end
diff --git a/lib/rack/nulllogger.rb b/lib/rack/nulllogger.rb
index 77fb637d..2d5a2c97 100644
--- a/lib/rack/nulllogger.rb
+++ b/lib/rack/nulllogger.rb
@@ -9,10 +9,29 @@ module Rack
       @app.call(env)
     end
 
-    def info(progname = nil, &block);  end
+    def info(progname = nil, &block); end
     def debug(progname = nil, &block); end
-    def warn(progname = nil, &block);  end
+    def warn(progname = nil, &block); end
     def error(progname = nil, &block); end
     def fatal(progname = nil, &block); end
+    def unknown(progname = nil, &block); end
+    def info? ;  end
+    def debug? ; end
+    def warn? ;  end
+    def error? ; end
+    def fatal? ; end
+    def level ; end
+    def progname ; end
+    def datetime_format ; end
+    def formatter ; end
+    def sev_threshold ; end
+    def level=(level); end
+    def progname=(progname); end
+    def datetime_format=(datetime_format); end
+    def formatter=(formatter); end
+    def sev_threshold=(sev_threshold); end
+    def close ; end
+    def add(severity, message = nil, progname = nil, &block); end
+    def <<(msg); end
   end
 end
diff --git a/lib/rack/request.rb b/lib/rack/request.rb
index 551f7361..4f038384 100644
--- a/lib/rack/request.rb
+++ b/lib/rack/request.rb
@@ -52,7 +52,7 @@ module Rack
       return {} if content_type.nil?
       Hash[*content_type.split(/\s*[;,]\s*/)[1..-1].
         collect { |s| s.split('=', 2) }.
-        map { |k,v| [k.downcase, v] }.flatten]
+        map { |k,v| [k.downcase, strip_doublequotes(v)] }.flatten]
     end
 
     # The character set of the request body if a "charset" media type
@@ -354,12 +354,6 @@ module Rack
 
       forwarded_ips = split_ip_addresses(@env['HTTP_X_FORWARDED_FOR'])
 
-      if client_ip = @env['HTTP_CLIENT_IP']
-        # If forwarded_ips doesn't include the client_ip, it might be an
-        # ip spoofing attempt, so we ignore HTTP_CLIENT_IP
-        return client_ip if forwarded_ips.include?(client_ip)
-      end
-
       return reject_trusted_ip_addresses(forwarded_ips).last || @env["REMOTE_ADDR"]
     end
 
@@ -377,7 +371,7 @@ module Rack
         when 'application/json'
           (qs && qs != '') ? ::Rack::Utils::OkJson.decode(qs) : {}
         else
-          Utils.parse_nested_query(qs)
+          Utils.parse_nested_query(qs, '&')
         end
       end
 
@@ -395,5 +389,14 @@ module Rack
           [attribute, quality]
         end
       end
+
+  private
+    def strip_doublequotes(s)
+      if s[0] == ?" && s[-1] == ?"
+        s[1..-2]
+      else
+        s
+      end
+    end
   end
 end
diff --git a/lib/rack/response.rb b/lib/rack/response.rb
index bd39da3b..12536710 100644
--- a/lib/rack/response.rb
+++ b/lib/rack/response.rb
@@ -129,6 +129,7 @@ module Rack
       def forbidden?;          status == 403;                        end
       def not_found?;          status == 404;                        end
       def method_not_allowed?; status == 405;                        end
+      def i_m_a_teapot?;       status == 418;                        end
       def unprocessable?;      status == 422;                        end
 
       def redirect?;           [301, 302, 303, 307].include? status; end
diff --git a/lib/rack/server.rb b/lib/rack/server.rb
index be7014c6..d2f0b954 100644
--- a/lib/rack/server.rb
+++ b/lib/rack/server.rb
@@ -1,7 +1,10 @@
 require 'optparse'
 
+
 module Rack
+
   class Server
+
     class Options
       def parse!(args)
         options = {}
@@ -166,7 +169,7 @@ module Rack
     # * :Port
     #     the port to bind to (used by supporting Rack::Handler)
     # * :AccessLog
-    #     webrick acess log options (or supporting Rack::Handler)
+    #     webrick access log options (or supporting Rack::Handler)
     # * :debug
     #     turn on debug output ($DEBUG = true)
     # * :warn
@@ -202,29 +205,44 @@ module Rack
       @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config
     end
 
-    def self.logging_middleware
-      lambda { |server|
-        server.server.name =~ /CGI/ ? nil : [Rack::CommonLogger, $stderr]
-      }
-    end
+    class << self
+      def logging_middleware
+        lambda { |server|
+          server.server.name =~ /CGI/ ? nil : [Rack::CommonLogger, $stderr]
+        }
+      end
 
-    def self.middleware
-      @middleware ||= begin
+      def default_middleware_by_environment
         m = Hash.new {|h,k| h[k] = []}
-        m["deployment"].concat [
+        m["deployment"] = [
           [Rack::ContentLength],
           [Rack::Chunked],
-          logging_middleware
+          logging_middleware,
+          [Rack::TempfileReaper]
         ]
-        m["development"].concat m["deployment"] + [[Rack::ShowExceptions], [Rack::Lint]]
+        m["development"] = [
+          [Rack::ContentLength],
+          [Rack::Chunked],
+          logging_middleware,
+          [Rack::ShowExceptions],
+          [Rack::Lint],
+          [Rack::TempfileReaper]
+        ]
+
         m
       end
+
+      # Aliased for backwards-compatibility
+      alias :middleware :default_middleware_by_environment
     end
 
-    def middleware
-      self.class.middleware
+    def default_middleware_by_environment
+      self.class.default_middleware_by_environment
     end
 
+    # Aliased for backwards-compatibility
+    alias :middleware :default_middleware_by_environment
+
     def start &blk
       if options[:warn]
         $-w = true
@@ -304,7 +322,8 @@ module Rack
       end
 
       def build_app(app)
-        middleware[options[:environment]].reverse_each do |middleware|
+        middlewares = default_middleware_by_environment[options[:environment]]
+        middlewares.reverse_each do |middleware|
           middleware = middleware.call(self) if middleware.respond_to?(:call)
           next unless middleware
           klass, *args = middleware
@@ -364,4 +383,5 @@ module Rack
       end
 
   end
+
 end
diff --git a/lib/rack/showexceptions.rb b/lib/rack/showexceptions.rb
index c91ca07c..731aea49 100644
--- a/lib/rack/showexceptions.rb
+++ b/lib/rack/showexceptions.rb
@@ -28,23 +28,32 @@ module Rack
       env["rack.errors"].puts(exception_string)
       env["rack.errors"].flush
 
-      if prefers_plain_text?(env)
-        content_type = "text/plain"
-        body = [exception_string]
-      else
+      if accepts_html?(env)
         content_type = "text/html"
         body = pretty(env, e)
+      else
+        content_type = "text/plain"
+        body = exception_string
       end
 
-      [500,
-       {"Content-Type" => content_type,
-        "Content-Length" => Rack::Utils.bytesize(body.join).to_s},
-       body]
+      [
+        500,
+        {
+          "Content-Type" => content_type,
+          "Content-Length" => Rack::Utils.bytesize(body).to_s,
+        },
+        [body],
+      ]
+    end
+
+    def prefers_plaintext?(env)
+      !accepts_html(env)
     end
 
-    def prefers_plain_text?(env)
-      env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" && (!env["HTTP_ACCEPT"] || !env["HTTP_ACCEPT"].include?("text/html"))
+    def accepts_html?(env)
+      Rack::Utils.best_q_match(env["HTTP_ACCEPT"], %w[text/html])
     end
+    private :accepts_html?
 
     def dump_exception(exception)
       string = "#{exception.class}: #{exception.message}\n"
@@ -85,7 +94,7 @@ module Rack
         end
       }.compact
 
-      [@template.result(binding)]
+      @template.result(binding)
     end
 
     def h(obj)                  # :nodoc:
diff --git a/lib/rack/tempfile_reaper.rb b/lib/rack/tempfile_reaper.rb
new file mode 100644
index 00000000..1500b06a
--- /dev/null
+++ b/lib/rack/tempfile_reaper.rb
@@ -0,0 +1,22 @@
+require 'rack/body_proxy'
+
+module Rack
+
+  # Middleware tracks and cleans Tempfiles created throughout a request (i.e. Rack::Multipart)
+  # Ideas/strategy based on posts by Eric Wong and Charles Oliver Nutter
+  # https://groups.google.com/forum/#!searchin/rack-devel/temp/rack-devel/brK8eh-MByw/sw61oJJCGRMJ
+  class TempfileReaper
+    def initialize(app)
+      @app = app
+    end
+
+    def call(env)
+      env['rack.tempfiles'] ||= []
+      status, headers, body = @app.call(env)
+      body_proxy = BodyProxy.new(body) do
+        env['rack.tempfiles'].each { |f| f.close! } unless env['rack.tempfiles'].nil?
+      end
+      [status, headers, body_proxy]
+    end
+  end
+end
diff --git a/lib/rack/urlmap.rb b/lib/rack/urlmap.rb
index d301ce9b..df9e7d6d 100644
--- a/lib/rack/urlmap.rb
+++ b/lib/rack/urlmap.rb
@@ -48,9 +48,10 @@ module Rack
       sPort = env['SERVER_PORT']
 
       @mapping.each do |host, location, match, app|
-        unless hHost == host \
-            || sName == host \
-            || (!host && (hHost == sName || hHost == sName+':'+sPort))
+        unless casecmp?(hHost, host) \
+            || casecmp?(sName, host) \
+            || (!host && (casecmp?(hHost, sName) ||
+                          casecmp?(hHost, sName+':'+sPort)))
           next
         end
 
@@ -71,6 +72,19 @@ module Rack
       env['PATH_INFO'] = path
       env['SCRIPT_NAME'] = script_name
     end
+
+    private
+    def casecmp?(v1, v2)
+      # if both nil, or they're the same string
+      return true if v1 == v2
+
+      # if either are nil... (but they're not the same)
+      return false if v1.nil?
+      return false if v2.nil?
+
+      # otherwise check they're not case-insensitive the same
+      v1.casecmp(v2).zero?
+    end
   end
 end
 
diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb
index 6c2bf907..69a96eb9 100644
--- a/lib/rack/utils.rb
+++ b/lib/rack/utils.rb
@@ -22,6 +22,15 @@ module Rack
   # applications adopted from all kinds of Ruby libraries.
 
   module Utils
+    # ParameterTypeError is the error that is raised when incoming structural
+    # parameters (parsed by parse_nested_query) contain conflicting types.
+    class ParameterTypeError < TypeError; end
+
+    # InvalidParameterError is the error that is raised when incoming structural
+    # parameters (parsed by parse_nested_query) contain invalid format or byte
+    # sequence.
+    class InvalidParameterError < ArgumentError; end
+
     # URI escapes. (CGI style space to +)
     def escape(s)
       URI.encode_www_form_component(s)
@@ -87,6 +96,11 @@ module Rack
     end
     module_function :parse_query
 
+    # parse_nested_query expands a query string into structural types. Supported
+    # types are Arrays, Hashes and basic value types. It is possible to supply
+    # query strings with parameters of conflicting types, in this case a
+    # ParameterTypeError is raised. Users are encouraged to return a 400 in this
+    # case.
     def parse_nested_query(qs, d = nil)
       params = KeySpaceConstrainedParams.new
 
@@ -97,9 +111,14 @@ module Rack
       end
 
       return params.to_params_hash
+    rescue ArgumentError => e
+      raise InvalidParameterError, e.message
     end
     module_function :parse_nested_query
 
+    # normalize_params recursively expands parameters into structural types. If
+    # the structural types represented by two different parameter names are in
+    # conflict, a ParameterTypeError is raised.
     def normalize_params(params, name, v = nil)
       name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
       k = $1 || ''
@@ -113,12 +132,12 @@ module Rack
         params[name] = v
       elsif after == "[]"
         params[k] ||= []
-        raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
+        raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
         params[k] << v
       elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
         child_key = $1
         params[k] ||= []
-        raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
+        raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
         if params_hash_type?(params[k].last) && !params[k].last.key?(child_key)
           normalize_params(params[k].last, child_key, v)
         else
@@ -126,7 +145,7 @@ module Rack
         end
       else
         params[k] ||= params.class.new
-        raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
+        raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
         params[k] = normalize_params(params[k], after, v)
       end
 
@@ -159,12 +178,12 @@ module Rack
       when Hash
         value.map { |k, v|
           build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
-        }.join("&")
-      when String
+        }.reject(&:empty?).join('&')
+      when nil
+        prefix
+      else
         raise ArgumentError, "value must be a Hash" if prefix.nil?
         "#{prefix}=#{escape(value)}"
-      else
-        prefix
       end
     end
     module_function :build_nested_query
@@ -184,13 +203,14 @@ module Rack
     def best_q_match(q_value_header, available_mimes)
       values = q_values(q_value_header)
 
-      values.map do |req_mime, quality|
-        match = available_mimes.first { |am| Rack::Mime.match?(am, req_mime) }
+      matches = values.map do |req_mime, quality|
+        match = available_mimes.find { |am| Rack::Mime.match?(am, req_mime) }
         next unless match
         [match, quality]
       end.compact.sort_by do |match, quality|
         (match.split('/', 2).count('*') * -10) + quality
-      end.last.first
+      end.last
+      matches && matches.first
     end
     module_function :best_q_match
 
@@ -532,7 +552,11 @@ module Rack
         hash.keys.each do |key|
           value = hash[key]
           if value.kind_of?(self.class)
-            hash[key] = value.to_params_hash
+            if value.object_id == self.object_id
+              hash[key] = hash
+            else
+              hash[key] = value.to_params_hash
+            end
           elsif value.kind_of?(Array)
             value.map! {|x| x.kind_of?(self.class) ? x.to_params_hash : x}
           end
@@ -587,6 +611,7 @@ module Rack
       415 => 'Unsupported Media Type',
       416 => 'Requested Range Not Satisfiable',
       417 => 'Expectation Failed',
+      418 => 'I\'m a teapot',
       422 => 'Unprocessable Entity',
       423 => 'Locked',
       424 => 'Failed Dependency',
@@ -611,7 +636,7 @@ module Rack
     STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 205 << 304)
 
     SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
-      [message.downcase.gsub(/\s|-/, '_').to_sym, code]
+      [message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
     }.flatten]
 
     def status_code(status)
@@ -637,7 +662,7 @@ module Rack
         part == '..' ? clean.pop : clean << part
       end
 
-      clean.unshift '/' if parts.first.empty?
+      clean.unshift '/' if parts.empty? || parts.first.empty?
 
       ::File.join(*clean)
     end
diff --git a/test/multipart/filename_and_no_name b/test/multipart/filename_and_no_name
new file mode 100644
index 00000000..00d58153
--- /dev/null
+++ b/test/multipart/filename_and_no_name
@@ -0,0 +1,6 @@
+--AaB03x
+Content-Disposition: form-data; filename="file1.txt"
+Content-Type: text/plain
+
+contents
+--AaB03x--
diff --git a/test/spec_chunked.rb b/test/spec_chunked.rb
index 12f21581..0a6d9ff1 100644
--- a/test/spec_chunked.rb
+++ b/test/spec_chunked.rb
@@ -64,6 +64,22 @@ describe Rack::Chunked do
     body.join.should.equal 'Hello World!'
   end
 
+  should 'not modify response when client is ancient, pre-HTTP/1.0' do
+    app = lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hello', ' ', 'World!']] }
+    check = lambda do
+      status, headers, body = chunked(app).call(@env.dup)
+      status.should.equal 200
+      headers.should.not.include 'Transfer-Encoding'
+      body.join.should.equal 'Hello World!'
+    end
+
+    @env.delete('HTTP_VERSION') # unicorn will do this on pre-HTTP/1.0 requests
+    check.call
+
+    @env['HTTP_VERSION'] = 'HTTP/0.9' # not sure if this happens in practice
+    check.call
+  end
+
   should 'not modify response when Transfer-Encoding header already present' do
     app = lambda { |env|
       [200, {"Content-Type" => "text/plain", 'Transfer-Encoding' => 'identity'}, ['Hello', ' ', 'World!']]
diff --git a/test/spec_deflater.rb b/test/spec_deflater.rb
index 6f5137ca..1e921eff 100644
--- a/test/spec_deflater.rb
+++ b/test/spec_deflater.rb
@@ -6,199 +6,334 @@ require 'rack/mock'
 require 'zlib'
 
 describe Rack::Deflater do
-  def deflater(app)
-    Rack::Lint.new Rack::Deflater.new(app)
-  end
 
-  def build_response(status, body, accept_encoding, headers = {})
-    body = [body]  if body.respond_to? :to_str
+  def build_response(status, body, accept_encoding, options = {})
+    body = [body] if body.respond_to? :to_str
     app = lambda do |env|
-      res = [status, {}, body]
-      res[1]["Content-Type"] = "text/plain" unless res[0] == 304
+      res = [status, options['response_headers'] || {}, body]
+      res[1]['Content-Type'] = 'text/plain' unless res[0] == 304
       res
     end
-    request = Rack::MockRequest.env_for("", headers.merge("HTTP_ACCEPT_ENCODING" => accept_encoding))
-    response = deflater(app).call(request)
 
-    return response
-  end
+    request = Rack::MockRequest.env_for('', (options['request_headers'] || {}).merge('HTTP_ACCEPT_ENCODING' => accept_encoding))
+    deflater = Rack::Lint.new Rack::Deflater.new(app, options['deflater_options'] || {})
 
-  def inflate(buf)
-    inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
-    inflater.inflate(buf) << inflater.finish
+    deflater.call(request)
   end
 
-  should "be able to deflate bodies that respond to each" do
-    body = Object.new
-    class << body; def each; yield("foo"); yield("bar"); end; end
-
-    response = build_response(200, body, "deflate")
-
-    response[0].should.equal(200)
-    response[1].should.equal({
-      "Content-Encoding" => "deflate",
-      "Vary" => "Accept-Encoding",
-      "Content-Type" => "text/plain"
-    })
-    buf = ''
-    response[2].each { |part| buf << part }
-    inflate(buf).should.equal("foobar")
-  end
-
-  should "flush deflated chunks to the client as they become ready" do
-    body = Object.new
-    class << body; def each; yield("foo"); yield("bar"); end; end
+  ##
+  # Constructs response object and verifies if it yields right results
+  #
+  # [expected_status] expected response status, e.g. 200, 304
+  # [expected_body] expected response body
+  # [accept_encoing] what Accept-Encoding header to send and expect, e.g.
+  #                  'deflate' - accepts and expects deflate encoding in response
+  #                  { 'gzip' => nil } - accepts gzip but expects no encoding in response
+  # [options] hash of request options, i.e.
+  #           'app_status' - what status dummy app should return (may be changed by deflater at some point)
+  #           'app_body' - what body dummy app should return (may be changed by deflater at some point)
+  #           'request_headers' - extra reqest headers to be sent
+  #           'response_headers' - extra response headers to be returned
+  #           'deflater_options' - options passed to deflater middleware
+  # [block] useful for doing some extra verification
+  def verify(expected_status, expected_body, accept_encoding, options = {}, &block)
+    accept_encoding, expected_encoding = if accept_encoding.kind_of?(Hash)
+      [accept_encoding.keys.first, accept_encoding.values.first]
+    else
+      [accept_encoding, accept_encoding.dup]
+    end
 
-    response = build_response(200, body, "deflate")
+    # build response
+    status, headers, body = build_response(
+      options['app_status'] || expected_status,
+      options['app_body'] || expected_body,
+      accept_encoding,
+      options
+    )
+
+    # verify status
+    status.should.equal(expected_status)
+
+    # verify body
+    unless options['skip_body_verify']
+      body_text = ''
+      body.each { |part| body_text << part }
+
+      deflated_body = case expected_encoding
+      when 'deflate'
+        inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
+        inflater.inflate(body_text) << inflater.finish
+      when 'gzip'
+        io = StringIO.new(body_text)
+        gz = Zlib::GzipReader.new(io)
+        tmp = gz.read
+        gz.close
+        tmp
+      else
+        body_text
+      end
+
+      deflated_body.should.equal(expected_body)
+    end
 
-    response[0].should.equal(200)
-    response[1].should.equal({
-      "Content-Encoding" => "deflate",
-      "Vary" => "Accept-Encoding",
-      "Content-Type" => "text/plain"
-    })
-    buf = []
-    inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
-    response[2].each { |part| buf << inflater.inflate(part) }
-    buf << inflater.finish
-    buf.delete_if { |part| part.empty? }
-    buf.join.should.equal("foobar")
+    # yield full response verification
+    yield(status, headers, body) if block_given?
   end
 
-  # TODO: This is really just a special case of the above...
-  should "be able to deflate String bodies" do
-    response = build_response(200, "Hello world!", "deflate")
+  should 'be able to deflate bodies that respond to each' do
+    app_body = Object.new
+    class << app_body; def each; yield('foo'); yield('bar'); end; end
 
-    response[0].should.equal(200)
-    response[1].should.equal({
-      "Content-Encoding" => "deflate",
-      "Vary" => "Accept-Encoding",
-      "Content-Type" => "text/plain"
-    })
-    buf = ''
-    response[2].each { |part| buf << part }
-    inflate(buf).should.equal("Hello world!")
+    verify(200, 'foobar', 'deflate', { 'app_body' => app_body }) do |status, headers, body|
+      headers.should.equal({
+        'Content-Encoding' => 'deflate',
+        'Vary' => 'Accept-Encoding',
+        'Content-Type' => 'text/plain'
+      })
+    end
   end
 
-  should "be able to gzip bodies that respond to each" do
-    body = Object.new
-    class << body; def each; yield("foo"); yield("bar"); end; end
+  should 'flush deflated chunks to the client as they become ready' do
+    app_body = Object.new
+    class << app_body; def each; yield('foo'); yield('bar'); end; end
 
-    response = build_response(200, body, "gzip")
+    verify(200, app_body, 'deflate', { 'skip_body_verify' => true }) do |status, headers, body|
+      headers.should.equal({
+        'Content-Encoding' => 'deflate',
+        'Vary' => 'Accept-Encoding',
+        'Content-Type' => 'text/plain'
+      })
 
-    response[0].should.equal(200)
-    response[1].should.equal({
-      "Content-Encoding" => "gzip",
-      "Vary" => "Accept-Encoding",
-      "Content-Type" => "text/plain"
-    })
+      buf = []
+      inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
+      body.each { |part| buf << inflater.inflate(part) }
+      buf << inflater.finish
 
-    buf = ''
-    response[2].each { |part| buf << part }
-    io = StringIO.new(buf)
-    gz = Zlib::GzipReader.new(io)
-    gz.read.should.equal("foobar")
-    gz.close
+      buf.delete_if { |part| part.empty? }.join.should.equal('foobar')
+    end
   end
 
-  should "flush gzipped chunks to the client as they become ready" do
-    body = Object.new
-    class << body; def each; yield("foo"); yield("bar"); end; end
+  # TODO: This is really just a special case of the above...
+  should 'be able to deflate String bodies' do
+    verify(200, 'Hello world!', 'deflate') do |status, headers, body|
+      headers.should.equal({
+        'Content-Encoding' => 'deflate',
+        'Vary' => 'Accept-Encoding',
+        'Content-Type' => 'text/plain'
+      })
+    end
+  end
 
-    response = build_response(200, body, "gzip")
+  should 'be able to gzip bodies that respond to each' do
+    app_body = Object.new
+    class << app_body; def each; yield('foo'); yield('bar'); end; end
 
-    response[0].should.equal(200)
-    response[1].should.equal({
-      "Content-Encoding" => "gzip",
-      "Vary" => "Accept-Encoding",
-      "Content-Type" => "text/plain"
-    })
-    buf = []
-    inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
-    response[2].each { |part| buf << inflater.inflate(part) }
-    buf << inflater.finish
-    buf.delete_if { |part| part.empty? }
-    buf.join.should.equal("foobar")
+    verify(200, 'foobar', 'gzip', { 'app_body' => app_body }) do |status, headers, body|
+      headers.should.equal({
+        'Content-Encoding' => 'gzip',
+        'Vary' => 'Accept-Encoding',
+        'Content-Type' => 'text/plain'
+      })
+    end
   end
 
-  should "be able to fallback to no deflation" do
-    response = build_response(200, "Hello world!", "superzip")
+  should 'flush gzipped chunks to the client as they become ready' do
+    app_body = Object.new
+    class << app_body; def each; yield('foo'); yield('bar'); end; end
+
+    verify(200, app_body, 'gzip', { 'skip_body_verify' => true }) do |status, headers, body|
+      headers.should.equal({
+        'Content-Encoding' => 'gzip',
+        'Vary' => 'Accept-Encoding',
+        'Content-Type' => 'text/plain'
+      })
+
+      buf = []
+      inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
+      body.each { |part| buf << inflater.inflate(part) }
+      buf << inflater.finish
 
-    response[0].should.equal(200)
-    response[1].should.equal({ "Vary" => "Accept-Encoding", "Content-Type" => "text/plain" })
-    response[2].to_enum.to_a.should.equal(["Hello world!"])
+      buf.delete_if { |part| part.empty? }.join.should.equal('foobar')
+    end
   end
 
-  should "be able to skip when there is no response entity body" do
-    response = build_response(304, [], "gzip")
+  should 'be able to fallback to no deflation' do
+    verify(200, 'Hello world!', 'superzip') do |status, headers, body|
+      headers.should.equal({
+        'Vary' => 'Accept-Encoding',
+        'Content-Type' => 'text/plain'
+      })
+    end
+  end
 
-    response[0].should.equal(304)
-    response[1].should.equal({})
-    response[2].to_enum.to_a.should.equal([])
+  should 'be able to skip when there is no response entity body' do
+    verify(304, '', { 'gzip' => nil }, { 'app_body' => [] }) do |status, headers, body|
+      headers.should.equal({})
+    end
   end
 
-  should "handle the lack of an acceptable encoding" do
-    response1 = build_response(200, "Hello world!", "identity;q=0", "PATH_INFO" => "/")
-    response1[0].should.equal(406)
-    response1[1].should.equal({"Content-Type" => "text/plain", "Content-Length" => "71"})
-    response1[2].to_enum.to_a.should.equal(["An acceptable encoding for the requested resource / could not be found."])
+  should 'handle the lack of an acceptable encoding' do
+    app_body = 'Hello world!'
+    not_found_body1 = 'An acceptable encoding for the requested resource / could not be found.'
+    not_found_body2 = 'An acceptable encoding for the requested resource /foo/bar could not be found.'
+    options1 = {
+      'app_status' => 200,
+      'app_body' => app_body,
+      'request_headers' => {
+        'PATH_INFO' => '/'
+      }
+    }
+    options2 = {
+      'app_status' => 200,
+      'app_body' => app_body,
+      'request_headers' => {
+        'PATH_INFO' => '/foo/bar'
+      }
+    }
+
+    verify(406, not_found_body1, 'identity;q=0', options1) do |status, headers, body|
+      headers.should.equal({
+        'Content-Type' => 'text/plain',
+        'Content-Length' => not_found_body1.length.to_s
+      })
+    end
 
-    response2 = build_response(200, "Hello world!", "identity;q=0", "SCRIPT_NAME" => "/foo", "PATH_INFO" => "/bar")
-    response2[0].should.equal(406)
-    response2[1].should.equal({"Content-Type" => "text/plain", "Content-Length" => "78"})
-    response2[2].to_enum.to_a.should.equal(["An acceptable encoding for the requested resource /foo/bar could not be found."])
+    verify(406, not_found_body2, 'identity;q=0', options2) do |status, headers, body|
+      headers.should.equal({
+        'Content-Type' => 'text/plain',
+        'Content-Length' => not_found_body2.length.to_s
+      })
+    end
   end
 
-  should "handle gzip response with Last-Modified header" do
+  should 'handle gzip response with Last-Modified header' do
     last_modified = Time.now.httpdate
+    options = {
+      'response_headers' => {
+        'Content-Type' => 'text/plain',
+        'Last-Modified' => last_modified
+      }
+    }
+
+    verify(200, 'Hello World!', 'gzip', options) do |status, headers, body|
+      headers.should.equal({
+        'Content-Encoding' => 'gzip',
+        'Vary' => 'Accept-Encoding',
+        'Last-Modified' => last_modified,
+        'Content-Type' => 'text/plain'
+      })
+    end
+  end
 
-    app = lambda { |env| [200, { "Content-Type" => "text/plain", "Last-Modified" => last_modified }, ["Hello World!"]] }
-    request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip")
-    response = deflater(app).call(request)
+  should 'do nothing when no-transform Cache-Control directive present' do
+    options = {
+      'response_headers' => {
+        'Content-Type' => 'text/plain',
+        'Cache-Control' => 'no-transform'
+      }
+    }
+    verify(200, 'Hello World!', { 'gzip' => nil }, options) do |status, headers, body|
+      headers.should.not.include 'Content-Encoding'
+    end
+  end
+
+  should 'do nothing when Content-Encoding already present' do
+    options = {
+      'response_headers' => {
+        'Content-Type' => 'text/plain',
+        'Content-Encoding' => 'gzip'
+      }
+    }
+    verify(200, 'Hello World!', { 'gzip' => nil }, options)
+  end
 
-    response[0].should.equal(200)
-    response[1].should.equal({
-      "Content-Encoding" => "gzip",
-      "Vary" => "Accept-Encoding",
-      "Last-Modified" => last_modified,
-      "Content-Type" => "text/plain"
-    })
+  should 'deflate when Content-Encoding is identity' do
+    options = {
+      'response_headers' => {
+        'Content-Type' => 'text/plain',
+        'Content-Encoding' => 'identity'
+      }
+    }
+    verify(200, 'Hello World!', 'deflate', options)
+  end
 
-    buf = ''
-    response[2].each { |part| buf << part }
-    io = StringIO.new(buf)
-    gz = Zlib::GzipReader.new(io)
-    gz.read.should.equal("Hello World!")
-    gz.close
+  should "deflate if content-type matches :include" do
+    options = {
+      'response_headers' => {
+        'Content-Type' => 'text/plain'
+      },
+      'deflater_options' => {
+        :include => %w(text/plain)
+      }
+    }
+    verify(200, 'Hello World!', 'gzip', options)
   end
 
-  should "do nothing when no-transform Cache-Control directive present" do
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Cache-Control' => 'no-transform'}, ['Hello World!']] }
-    request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip")
-    response = deflater(app).call(request)
+  should "deflate if content-type is included it :include" do
+    options = {
+      'response_headers' => {
+        'Content-Type' => 'text/plain; charset=us-ascii'
+      },
+      'deflater_options' => {
+        :include => %w(text/plain)
+      }
+    }
+    verify(200, 'Hello World!', 'gzip', options)
+  end
 
-    response[0].should.equal(200)
-    response[1].should.not.include "Content-Encoding"
-    response[2].to_enum.to_a.join.should.equal("Hello World!")
+  should "not deflate if content-type is not set but given in :include" do
+    options = {
+      'deflater_options' => {
+        :include => %w(text/plain)
+      }
+    }
+    verify(304, 'Hello World!', { 'gzip' => nil }, options)
   end
 
-  should "do nothing when Content-Encoding already present" do
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Content-Encoding' => 'gzip'}, ['Hello World!']] }
-    request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip")
-    response = deflater(app).call(request)
+  should "not deflate if content-type do not match :include" do
+    options = {
+      'response_headers' => {
+        'Content-Type' => 'text/plain'
+      },
+      'deflater_options' => {
+        :include => %w(text/json)
+      }
+    }
+    verify(200, 'Hello World!', { 'gzip' => nil }, options)
+  end
 
-    response[0].should.equal(200)
-    response[2].to_enum.to_a.join.should.equal("Hello World!")
+  should "deflate response if :if lambda evaluates to true" do
+    options = {
+      'deflater_options' => {
+        :if => lambda { |env, status, headers, body| true }
+      }
+    }
+    verify(200, 'Hello World!', 'deflate', options)
   end
 
-  should "deflate when Content-Encoding is identity" do
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Content-Encoding' => 'identity'}, ['Hello World!']] }
-    request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "deflate")
-    response = deflater(app).call(request)
+  should "not deflate if :if lambda evaluates to false" do
+    options = {
+      'deflater_options' => {
+        :if => lambda { |env, status, headers, body| false }
+      }
+    }
+    verify(200, 'Hello World!', { 'gzip' => nil }, options)
+  end
 
-    response[0].should.equal(200)
-    buf = ''
-    response[2].each { |part| buf << part }
-    inflate(buf).should.equal("Hello World!")
+  should "check for Content-Length via :if" do
+    body = 'Hello World!'
+    body_len = body.length
+    options = {
+      'response_headers' => {
+        'Content-Length' => body_len.to_s
+      },
+      'deflater_options' => {
+        :if => lambda { |env, status, headers, body|
+          headers['Content-Length'].to_i >= body_len
+        }
+      }
+    }
+
+    verify(200, body, 'gzip', options)
   end
 end
diff --git a/test/spec_etag.rb b/test/spec_etag.rb
index b8b8b637..c075d9d0 100644
--- a/test/spec_etag.rb
+++ b/test/spec_etag.rb
@@ -21,13 +21,13 @@ describe Rack::ETag do
   should "set ETag if none is set if status is 200" do
     app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
     response = etag(app).call(request)
-    response[1]['ETag'].should.equal "\"65a8e27d8879283831b664bd8b7f0ad4\""
+    response[1]['ETag'].should.equal "W/\"65a8e27d8879283831b664bd8b7f0ad4\""
   end
 
   should "set ETag if none is set if status is 201" do
     app = lambda { |env| [201, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
     response = etag(app).call(request)
-    response[1]['ETag'].should.equal "\"65a8e27d8879283831b664bd8b7f0ad4\""
+    response[1]['ETag'].should.equal "W/\"65a8e27d8879283831b664bd8b7f0ad4\""
   end
 
   should "set Cache-Control to 'max-age=0, private, must-revalidate' (default) if none is set" do
diff --git a/test/spec_lobster.rb b/test/spec_lobster.rb
index 56a54795..c6ec2b06 100644
--- a/test/spec_lobster.rb
+++ b/test/spec_lobster.rb
@@ -47,7 +47,7 @@ describe Rack::Lobster do
   should "be flippable" do
     res = lobster.get("/?flip=left")
     res.should.be.ok
-    res.body.should.include "(,,,(,,(,("
+    res.body.should.include "),,,),,),)"
   end
 
   should "provide crashing for testing purposes" do
diff --git a/test/spec_mock.rb b/test/spec_mock.rb
index f49b1961..3ebd7776 100644
--- a/test/spec_mock.rb
+++ b/test/spec_mock.rb
@@ -30,6 +30,14 @@ describe Rack::MockRequest do
     env.should.include "rack.version"
   end
 
+  should "return an environment with a path" do
+    env = Rack::MockRequest.env_for("http://www.example.com/parse?location[]=1&location[]=2&age_group[]=2")
+    env["QUERY_STRING"].should.equal "location[]=1&location[]=2&age_group[]=2"
+    env["PATH_INFO"].should.equal "/parse"
+    env.should.be.kind_of Hash
+    env.should.include "rack.version"
+  end
+
   should "provide sensible defaults" do
     res = Rack::MockRequest.new(app).request
 
diff --git a/test/spec_multipart.rb b/test/spec_multipart.rb
index 069dc4d2..2acb6e0d 100644
--- a/test/spec_multipart.rb
+++ b/test/spec_multipart.rb
@@ -153,6 +153,18 @@ describe Rack::Multipart do
     params["files"][:tempfile].read.should.equal "contents"
   end
 
+  should "parse multipart upload with text file with no name field" do
+    env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_and_no_name))
+    params = Rack::Multipart.parse_multipart(env)
+    params["file1.txt"][:type].should.equal "text/plain"
+    params["file1.txt"][:filename].should.equal "file1.txt"
+    params["file1.txt"][:head].should.equal "Content-Disposition: form-data; " +
+      "filename=\"file1.txt\"\r\n" +
+      "Content-Type: text/plain\r\n"
+    params["file1.txt"][:name].should.equal "file1.txt"
+    params["file1.txt"][:tempfile].read.should.equal "contents"
+  end
+
   should "parse multipart upload with nested parameters" do
     env = Rack::MockRequest.env_for("/", multipart_fixture(:nested))
     params = Rack::Multipart.parse_multipart(env)
@@ -502,4 +514,28 @@ contents\r
     params["file"][:filename].should.equal('long' * 100)
   end
 
+  should "support mixed case metadata" do
+    file = multipart_file(:text)
+    data = File.open(file, 'rb') { |io| io.read }
+
+    type = "Multipart/Form-Data; Boundary=AaB03x"
+    length = data.respond_to?(:bytesize) ? data.bytesize : data.size
+
+    e = { "CONTENT_TYPE" => type,
+      "CONTENT_LENGTH" => length.to_s,
+      :input => StringIO.new(data) }
+
+    env = Rack::MockRequest.env_for("/", e)
+    params = Rack::Multipart.parse_multipart(env)
+    params["submit-name"].should.equal "Larry"
+    params["submit-name-with-content"].should.equal "Berry"
+    params["files"][:type].should.equal "text/plain"
+    params["files"][:filename].should.equal "file1.txt"
+    params["files"][:head].should.equal "Content-Disposition: form-data; " +
+      "name=\"files\"; filename=\"file1.txt\"\r\n" +
+      "Content-Type: text/plain\r\n"
+    params["files"][:name].should.equal "files"
+    params["files"][:tempfile].read.should.equal "contents"
+  end
+
 end
diff --git a/test/spec_request.rb b/test/spec_request.rb
index 7a6aa744..c4a7400a 100644
--- a/test/spec_request.rb
+++ b/test/spec_request.rb
@@ -130,6 +130,14 @@ describe Rack::Request do
     req.params.should.equal "foo" => "bar", "quux" => "bla"
   end
 
+  should "not truncate query strings containing semi-colons #543" do
+    req = Rack::Request.new(Rack::MockRequest.env_for("/?foo=bar&quux=b;la"))
+    req.query_string.should.equal "foo=bar&quux=b;la"
+    req.GET.should.equal "foo" => "bar", "quux" => "b;la"
+    req.POST.should.be.empty
+    req.params.should.equal "foo" => "bar", "quux" => "b;la"
+  end
+
   should "limit the keys from the GET query string" do
     env = Rack::MockRequest.env_for("/?foo=bar")
 
@@ -143,7 +151,7 @@ describe Rack::Request do
   end
 
   should "limit the key size per nested params hash" do
-    nested_query = Rack::MockRequest.env_for("/?foo[bar][baz][qux]=1")
+    nested_query = Rack::MockRequest.env_for("/?foo%5Bbar%5D%5Bbaz%5D%5Bqux%5D=1")
     plain_query  = Rack::MockRequest.env_for("/?foo_bar__baz__qux_=1")
 
     old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 3
@@ -169,6 +177,18 @@ describe Rack::Request do
     req.params.should.equal req.GET.merge(req.POST)
   end
 
+  should "raise if input params has invalid %-encoding" do
+    mr = Rack::MockRequest.env_for("/?foo=quux",
+      "REQUEST_METHOD" => 'POST',
+      :input => "a%=1"
+    )
+    req = Rack::Request.new mr
+
+    lambda { req.POST }.
+      should.raise(Rack::Utils::InvalidParameterError).
+      message.should.equal "invalid %-encoding (a%)"
+  end
+
   should "raise if rack.input is missing" do
     req = Rack::Request.new({})
     lambda { req.POST }.should.raise(RuntimeError)
@@ -613,7 +633,7 @@ describe Rack::Request do
   should "handle multiple media type parameters" do
     req = Rack::Request.new \
       Rack::MockRequest.env_for("/",
-        "CONTENT_TYPE" => 'text/plain; foo=BAR,baz=bizzle dizzle;BLING=bam')
+        "CONTENT_TYPE" => 'text/plain; foo=BAR,baz=bizzle dizzle;BLING=bam;blong="boo";zump="zoo\"o";weird=lol"')
       req.should.not.be.form_data
       req.media_type_params.should.include 'foo'
       req.media_type_params['foo'].should.equal 'BAR'
@@ -622,6 +642,9 @@ describe Rack::Request do
       req.media_type_params.should.not.include 'BLING'
       req.media_type_params.should.include 'bling'
       req.media_type_params['bling'].should.equal 'bam'
+      req.media_type_params['blong'].should.equal 'boo'
+      req.media_type_params['zump'].should.equal 'zoo\"o'
+      req.media_type_params['weird'].should.equal 'lol"'
   end
 
   should "parse with junk before boundry" do
@@ -750,6 +773,31 @@ EOF
     req.POST["mean"][:tempfile].read.should.equal "--AaB03xha"
   end
 
+  should "record tempfiles from multipart form data in env[rack.tempfiles]" do
+    input = <<EOF
+--AaB03x\r
+content-disposition: form-data; name="fileupload"; filename="foo.jpg"\r
+Content-Type: image/jpeg\r
+Content-Transfer-Encoding: base64\r
+\r
+/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg\r
+--AaB03x\r
+content-disposition: form-data; name="fileupload"; filename="bar.jpg"\r
+Content-Type: image/jpeg\r
+Content-Transfer-Encoding: base64\r
+\r
+/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg\r
+--AaB03x--\r
+EOF
+    env = Rack::MockRequest.env_for("/",
+                          "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
+                          "CONTENT_LENGTH" => input.size,
+                          :input => input)
+    req = Rack::Request.new(env)
+    req.params
+    env['rack.tempfiles'].size.should.equal(2)
+  end
+
   should "detect invalid multipart form data" do
     input = <<EOF
 --AaB03x\r
@@ -1047,12 +1095,6 @@ EOF
       'HTTP_CLIENT_IP' => '1.1.1.1'
     res.body.should.equal '1.1.1.1'
 
-    # Spoofing attempt
-    res = mock.get '/',
-      'HTTP_X_FORWARDED_FOR' => '1.1.1.1',
-      'HTTP_CLIENT_IP' => '2.2.2.2'
-    res.body.should.equal '1.1.1.1'
-
     res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, 9.9.9.9'
     res.body.should.equal '9.9.9.9'
 
@@ -1071,6 +1113,24 @@ EOF
     res.body.should.equal '3.4.5.6'
   end
 
+  should "not allow IP spoofing via Client-IP and X-Forwarded-For headers" do
+    mock = Rack::MockRequest.new(Rack::Lint.new(ip_app))
+
+    # IP Spoofing attempt:
+    # Client sends          X-Forwarded-For: 6.6.6.6
+    #                       Client-IP: 6.6.6.6
+    # Load balancer adds    X-Forwarded-For: 2.2.2.3, 192.168.0.7
+    # App receives:         X-Forwarded-For: 6.6.6.6
+    #                       X-Forwarded-For: 2.2.2.3, 192.168.0.7
+    #                       Client-IP: 6.6.6.6
+    # Rack env:             HTTP_X_FORWARDED_FOR: '6.6.6.6, 2.2.2.3, 192.168.0.7'
+    #                       HTTP_CLIENT_IP: '6.6.6.6'
+    res = mock.get '/',
+      'HTTP_X_FORWARDED_FOR' => '6.6.6.6, 2.2.2.3, 192.168.0.7',
+      'HTTP_CLIENT_IP' => '6.6.6.6'
+    res.body.should.equal '2.2.2.3'
+  end
+
   should "regard local addresses as proxies" do
     req = Rack::Request.new(Rack::MockRequest.env_for("/"))
     req.trusted_proxy?('127.0.0.1').should.equal 0
@@ -1126,7 +1186,7 @@ EOF
   end
 
   should "raise TypeError every time if request parameters are broken" do
-    broken_query = Rack::MockRequest.env_for("/?foo[]=0&foo[bar]=1")
+    broken_query = Rack::MockRequest.env_for("/?foo%5B%5D=0&foo%5Bbar%5D=1")
     req = Rack::Request.new(broken_query)
     lambda{req.GET}.should.raise(TypeError)
     lambda{req.params}.should.raise(TypeError)
diff --git a/test/spec_response.rb b/test/spec_response.rb
index 031488bb..6b13c0c9 100644
--- a/test/spec_response.rb
+++ b/test/spec_response.rb
@@ -251,6 +251,11 @@ describe Rack::Response do
     res.should.be.client_error
     res.should.be.method_not_allowed
 
+    res.status = 418
+    res.should.not.be.successful
+    res.should.be.client_error
+    res.should.be.i_m_a_teapot
+
     res.status = 422
     res.should.not.be.successful
     res.should.be.client_error
diff --git a/test/spec_server.rb b/test/spec_server.rb
index 44d4bcbb..01b4f562 100644
--- a/test/spec_server.rb
+++ b/test/spec_server.rb
@@ -30,14 +30,24 @@ describe Rack::Server do
 
   should "not include Rack::Lint in deployment or none environments" do
     server = Rack::Server.new(:app => 'foo')
-    server.middleware['deployment'].flatten.should.not.include(Rack::Lint)
-    server.middleware['none'].flatten.should.not.include(Rack::Lint)
+    server.default_middleware_by_environment['deployment'].flatten.should.not.include(Rack::Lint)
+    server.default_middleware_by_environment['none'].flatten.should.not.include(Rack::Lint)
   end
 
   should "not include Rack::ShowExceptions in deployment or none environments" do
     server = Rack::Server.new(:app => 'foo')
-    server.middleware['deployment'].flatten.should.not.include(Rack::ShowExceptions)
-    server.middleware['none'].flatten.should.not.include(Rack::ShowExceptions)
+    server.default_middleware_by_environment['deployment'].flatten.should.not.include(Rack::ShowExceptions)
+    server.default_middleware_by_environment['none'].flatten.should.not.include(Rack::ShowExceptions)
+  end
+
+  should "always return an empty array for unknown environments" do
+    server = Rack::Server.new(:app => 'foo')
+    server.default_middleware_by_environment['production'].should.equal []
+  end
+
+  should "include Rack::TempfileReaper in deployment environment" do
+    server = Rack::Server.new(:app => 'foo')
+    server.middleware['deployment'].flatten.should.include(Rack::TempfileReaper)
   end
 
   should "support CGI" do
@@ -53,7 +63,7 @@ describe Rack::Server do
 
   should "not force any middleware under the none configuration" do
     server = Rack::Server.new(:app => 'foo')
-    server.middleware['none'].should.be.empty
+    server.default_middleware_by_environment['none'].should.be.empty
   end
 
   should "use a full path to the pidfile" do
diff --git a/test/spec_showexceptions.rb b/test/spec_showexceptions.rb
index bdd5ce5b..7d50c59f 100644
--- a/test/spec_showexceptions.rb
+++ b/test/spec_showexceptions.rb
@@ -16,7 +16,7 @@ describe Rack::ShowExceptions do
     ))
 
     lambda{
-      res = req.get("/")
+      res = req.get("/", "HTTP_ACCEPT" => "text/html")
     }.should.not.raise
 
     res.should.be.a.server_error
@@ -26,7 +26,7 @@ describe Rack::ShowExceptions do
     res.should =~ /ShowExceptions/
   end
 
-  it "responds with plain text on AJAX requests accepting anything but HTML" do
+  it "responds with HTML only to requests accepting HTML" do
     res = nil
 
     req = Rack::MockRequest.new(
@@ -34,39 +34,32 @@ describe Rack::ShowExceptions do
         lambda{|env| raise RuntimeError, "It was never supposed to work" }
     ))
 
-    lambda{
-      res = req.get("/", "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest")
-    }.should.not.raise
-
-    res.should.be.a.server_error
-    res.status.should.equal 500
-
-    res.content_type.should.equal "text/plain"
-
-    res.body.should.include "RuntimeError: It was never supposed to work\n"
-    res.body.should.include __FILE__
-  end
-
-  it "responds with HTML on AJAX requests accepting HTML" do
-    res = nil
-
-    req = Rack::MockRequest.new(
-      show_exceptions(
-        lambda{|env| raise RuntimeError, "It was never supposed to work" }
-    ))
-
-    lambda{
-      res = req.get("/", "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", "HTTP_ACCEPT" => "text/html")
-    }.should.not.raise
-
-    res.should.be.a.server_error
-    res.status.should.equal 500
-
-    res.content_type.should.equal "text/html"
-
-    res.body.should.include "RuntimeError"
-    res.body.should.include "It was never supposed to work"
-    res.body.should.include Rack::Utils.escape_html(__FILE__)
+    [
+      # Serve text/html when the client accepts text/html
+      ["text/html", ["/", {"HTTP_ACCEPT" => "text/html"}]],
+      ["text/html", ["/", {"HTTP_ACCEPT" => "*/*"}]],
+      # Serve text/plain when the client does not accept text/html
+      ["text/plain", ["/"]],
+      ["text/plain", ["/", {"HTTP_ACCEPT" => "application/json"}]]
+    ].each do |exmime, rargs|
+      lambda{
+        res = req.get(*rargs)
+      }.should.not.raise
+
+      res.should.be.a.server_error
+      res.status.should.equal 500
+
+      res.content_type.should.equal exmime
+
+      res.body.should.include "RuntimeError"
+      res.body.should.include "It was never supposed to work"
+
+      if exmime == "text/html"
+        res.body.should.include '</html>'
+      else
+        res.body.should.not.include '</html>'
+      end
+    end
   end
 
   it "handles exceptions without a backtrace" do
@@ -79,7 +72,7 @@ describe Rack::ShowExceptions do
     )
 
     lambda{
-      res = req.get("/")
+      res = req.get("/", "HTTP_ACCEPT" => "text/html")
     }.should.not.raise
 
     res.should.be.a.server_error
diff --git a/test/spec_tempfile_reaper.rb b/test/spec_tempfile_reaper.rb
new file mode 100644
index 00000000..ac39d878
--- /dev/null
+++ b/test/spec_tempfile_reaper.rb
@@ -0,0 +1,63 @@
+require 'rack/tempfile_reaper'
+require 'rack/lint'
+require 'rack/mock'
+
+describe Rack::TempfileReaper do
+  class MockTempfile
+    attr_reader :closed
+
+    def initialize
+      @closed = false
+    end
+
+    def close!
+      @closed = true
+    end
+  end
+
+  before do
+    @env = Rack::MockRequest.env_for
+  end
+
+  def call(app)
+    Rack::Lint.new(Rack::TempfileReaper.new(app)).call(@env)
+  end
+
+  should 'do nothing (i.e. not bomb out) without env[rack.tempfiles]' do
+    app = lambda { |_| [200, {}, ['Hello, World!']] }
+    response = call(app)
+    response[2].close
+    response[0].should.equal(200)
+  end
+
+  should 'close env[rack.tempfiles] when body is closed' do
+    tempfile1, tempfile2 = MockTempfile.new, MockTempfile.new
+    @env['rack.tempfiles'] = [ tempfile1, tempfile2 ]
+    app = lambda { |_| [200, {}, ['Hello, World!']] }
+    call(app)[2].close
+    tempfile1.closed.should.equal true
+    tempfile2.closed.should.equal true
+  end
+
+  should 'initialize env[rack.tempfiles] when not already present' do
+    tempfile = MockTempfile.new
+    app = lambda do |env|
+      env['rack.tempfiles'] << tempfile
+      [200, {}, ['Hello, World!']]
+    end
+    call(app)[2].close
+    tempfile.closed.should.equal true
+  end
+
+  should 'append env[rack.tempfiles] when already present' do
+    tempfile1, tempfile2 = MockTempfile.new, MockTempfile.new
+    @env['rack.tempfiles'] = [ tempfile1 ]
+    app = lambda do |env|
+      env['rack.tempfiles'] << tempfile2
+      [200, {}, ['Hello, World!']]
+    end
+    call(app)[2].close
+    tempfile1.closed.should.equal true
+    tempfile2.closed.should.equal true
+  end
+end
diff --git a/test/spec_urlmap.rb b/test/spec_urlmap.rb
index 316c7254..2ef41cdc 100644
--- a/test/spec_urlmap.rb
+++ b/test/spec_urlmap.rb
@@ -210,4 +210,27 @@ describe Rack::URLMap do
     res["X-PathInfo"].should.equal "/http://example.org/bar"
     res["X-ScriptName"].should.equal ""
   end
+
+  should "not be case sensitive with hosts" do
+    map = Rack::Lint.new(Rack::URLMap.new("http://example.org/" => lambda { |env|
+                             [200,
+                              { "Content-Type" => "text/plain",
+                                "X-Position" => "root",
+                                "X-PathInfo" => env["PATH_INFO"],
+                                "X-ScriptName" => env["SCRIPT_NAME"]
+                              }, [""]]}
+                           ))
+
+    res = Rack::MockRequest.new(map).get("http://example.org/")
+    res.should.be.ok
+    res["X-Position"].should.equal "root"
+    res["X-PathInfo"].should.equal "/"
+    res["X-ScriptName"].should.equal ""
+
+    res = Rack::MockRequest.new(map).get("http://EXAMPLE.ORG/")
+    res.should.be.ok
+    res["X-Position"].should.equal "root"
+    res["X-PathInfo"].should.equal "/"
+    res["X-ScriptName"].should.equal ""
+  end
 end
diff --git a/test/spec_utils.rb b/test/spec_utils.rb
index c3867965..06ed5636 100644
--- a/test/spec_utils.rb
+++ b/test/spec_utils.rb
@@ -123,6 +123,17 @@ describe Rack::Utils do
     Rack::Utils.parse_query(",foo=bar;,", ";,").should.equal "foo" => "bar"
   end
 
+  should "not create infinite loops with cycle structures" do
+    ex = { "foo" => nil }
+    ex["foo"] = ex
+
+    params = Rack::Utils::KeySpaceConstrainedParams.new
+    params['foo'] = params
+    lambda {
+      params.to_params_hash.to_s.should.equal ex.to_s
+    }.should.not.raise
+  end
+
   should "parse nested query strings correctly" do
     Rack::Utils.parse_nested_query("foo").
       should.equal "foo" => nil
@@ -202,16 +213,22 @@ describe Rack::Utils do
       should.equal "x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}
 
     lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y]z=2") }.
-      should.raise(TypeError).
+      should.raise(Rack::Utils::ParameterTypeError).
       message.should.equal "expected Hash (got String) for param `y'"
 
     lambda { Rack::Utils.parse_nested_query("x[y]=1&x[]=1") }.
-      should.raise(TypeError).
+      should.raise(Rack::Utils::ParameterTypeError).
       message.should.match(/expected Array \(got [^)]*\) for param `x'/)
 
     lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y][][w]=2") }.
-      should.raise(TypeError).
+      should.raise(Rack::Utils::ParameterTypeError).
       message.should.equal "expected Array (got String) for param `y'"
+
+    if RUBY_VERSION.to_f > 1.9
+      lambda { Rack::Utils.parse_nested_query("foo%81E=1") }.
+        should.raise(Rack::Utils::InvalidParameterError).
+        message.should.equal "invalid byte sequence in UTF-8"
+    end
   end
 
   should "build query strings correctly" do
@@ -231,6 +248,8 @@ describe Rack::Utils do
 
     Rack::Utils.build_nested_query("foo" => "1", "bar" => "2").
       should.be equal_query_to("foo=1&bar=2")
+    Rack::Utils.build_nested_query("foo" => 1, "bar" => 2).
+      should.be equal_query_to("foo=1&bar=2")
     Rack::Utils.build_nested_query("my weird field" => "q1!2\"'w$5&7/z8)?").
       should.be equal_query_to("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F")
 
@@ -240,6 +259,14 @@ describe Rack::Utils do
       should.equal "foo[]="
     Rack::Utils.build_nested_query("foo" => ["bar"]).
       should.equal "foo[]=bar"
+    Rack::Utils.build_nested_query('foo' => []).
+      should.equal ''
+    Rack::Utils.build_nested_query('foo' => {}).
+      should.equal ''
+    Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => []).
+      should.equal 'foo=bar'
+    Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => {}).
+      should.equal 'foo=bar'
 
     # The ordering of the output query string is unpredictable with 1.8's
     # unordered hash. Test that build_nested_query performs the inverse
@@ -300,9 +327,15 @@ describe Rack::Utils do
     # Higher quality matches are preferred
     Rack::Utils.best_q_match("text/*;q=0.5,text/plain;q=1.0", %w[text/plain text/html]).should.equal "text/plain"
 
+    # Respect requested content type
+    Rack::Utils.best_q_match("application/json", %w[application/vnd.lotus-1-2-3 application/json]).should.equal "application/json"
+
     # All else equal, the available mimes are preferred in order
     Rack::Utils.best_q_match("text/*", %w[text/html text/plain]).should.equal "text/html"
     Rack::Utils.best_q_match("text/plain,text/html", %w[text/html text/plain]).should.equal "text/html"
+
+    # When there are no matches, return nil:
+    Rack::Utils.best_q_match("application/json", %w[text/html text/plain]).should.equal nil
   end
 
   should "escape html entities [&><'\"/]" do
@@ -403,6 +436,10 @@ describe Rack::Utils do
   should "not clean directory traversal with encoded periods" do
     Rack::Utils.clean_path_info("/%2E%2E/README").should.equal "/%2E%2E/README"
   end
+
+  should "clean slash only paths" do
+    Rack::Utils.clean_path_info("/").should.equal "/"
+  end
 end
 
 describe Rack::Utils, "byte_range" do
diff --git a/test/spec_webrick.rb b/test/spec_webrick.rb
index b29a82d5..497bfe20 100644
--- a/test/spec_webrick.rb
+++ b/test/spec_webrick.rb
@@ -162,5 +162,23 @@ describe Rack::Handler::WEBrick do
     }
   end
 
+  should "produce correct HTTP semantics with and without app chunking" do
+    @server.mount "/chunked", Rack::Handler::WEBrick,
+    Rack::Lint.new(lambda{ |req|
+      [
+        200,
+        {"Transfer-Encoding" => "chunked"},
+        ["7\r\nchunked\r\n0\r\n\r\n"]
+      ]
+    })
+
+    Net::HTTP.start(@host, @port){ |http|
+      res = http.get("/chunked")
+      res["Transfer-Encoding"].should.equal "chunked"
+      res["Content-Length"].should.equal nil
+      res.body.should.equal "chunked"
+    }
+  end
+
   @server.shutdown
 end