[DRE-commits] [SCM] ruby-rack.git branch, master, updated. debian/1.3.1-1-8-ge579ca8

Paul van Tilburg paul at luon.net
Tue Jan 3 21:55:33 UTC 2012


The following commit has been merged in the master branch:
commit e159324894bae9405eecb6464f86364b5750e779
Author: Paul van Tilburg <paul at luon.net>
Date:   Tue Jan 3 11:07:20 2012 +0100

    Imported Upstream version 1.4.0

diff --git a/README.rdoc b/README.rdoc
index 558f1c9..664f021 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -352,6 +352,46 @@ run on port 11211) and memcache-client installed.
   * Rack::MockResponse calls close on the body object
   * Fix a DOS vector from MRI stdlib backport
 
+* July 16, 2011: Sixteenth public release 1.3.2
+  * Fix for Rails and rack-test, Rack::Utils#escape calls to_s
+
+* Not Yet Released: Seventeenth public release 1.3.3
+  * Fix bug with broken query parameters in Rack::ShowExceptions
+  * Rack::Request#cookies no longer swallows exceptions on broken input
+  * Prevents XSS attacks enabled by bug in Ruby 1.8's regexp engine
+  * Rack::ConditionalGet handles broken If-Modified-Since helpers
+
+* September 16, 2011: Eighteenth public release 1.2.4
+  * Fix a bug with MRI regex engine to prevent XSS by malformed unicode
+
+* October 1, 2011: Nineteenth public release 1.3.4
+  * Backport security fix from 1.9.3, also fixes some roundtrip issues in URI
+  * Small documentation update
+  * Fix an issue where BodyProxy could cause an infinite recursion
+  * Add some supporting files for travis-ci
+
+* October 17, 2011: Twentieth public release 1.3.5
+  * Fix annoying warnings caused by the backport in 1.3.4
+
+* December 28th, 2011: Twenty fourth public release 1.4.0
+  * Ruby 1.8.6 support has officially been dropped. Not all tests pass.
+  * Raise sane error messages for broken config.ru
+  * Allow combining run and map in a config.ru
+  * Rack::ContentType will not set Content-Type for responses without a body
+  * Status code 205 does not send a response body
+  * Rack::Response::Helpers will not rely on instance variables
+  * Rack::Utils.build_query no longer outputs '=' for nil query values
+  * Various mime types added
+  * Rack::MockRequest now supports HEAD
+  * Rack::Directory now supports files that contain RFC3986 reserved chars
+  * Rack::File now only supports GET and HEAD requests
+  * Rack::Server#start now passes the block to Rack::Handler::<h>#run
+  * Rack::Static now supports an index option
+  * Added the Teapot status code
+  * rackup now defaults to Thin instead of Mongrel (if installed)
+  * Support added for HTTP_X_FORWARDED_SCHEME
+  * Numerous bug fixes, including many fixes for new and alternate rubies
+
 == Contact
 
 Please post bugs, suggestions and patches to
diff --git a/Rakefile b/Rakefile
index 8899877..562b767 100644
--- a/Rakefile
+++ b/Rakefile
@@ -3,6 +3,18 @@
 desc "Run all the tests"
 task :default => [:test]
 
+desc "Install gem dependencies"
+task :deps do
+  require 'rubygems'
+  spec = Gem::Specification.load('rack.gemspec')
+  spec.dependencies.each do |dep|
+    reqs = dep.requirements_list
+    reqs = (["-v"] * reqs.size).zip(reqs).flatten
+    # Use system over sh, because we want to ignore errors!
+    system "gem", "install", '--conservative', dep.name, *reqs
+  end
+end
+
 desc "Make an archive as .tar.gz"
 task :dist => [:chmod, :changelog, :rdoc, "SPEC"] do
   sh "git archive --format=tar --prefix=#{release}/ HEAD^{tree} >#{release}.tar"
@@ -64,15 +76,18 @@ file "SPEC" => 'lib/rack/lint.rb' do
   }
 end
 
-desc "Run all the fast tests"
+desc "Run all the fast + platform agnostic tests"
 task :test => 'SPEC' do
   opts     = ENV['TEST'] || '-a'
   specopts = ENV['TESTOPTS'] ||
-    "-q -t '^(?!Rack::Adapter|Rack::Session::Memcache|Rack::Server)'"
+    "-q -t '^(?!Rack::Adapter|Rack::Session::Memcache|Rack::Server|Rack::Handler)'"
 
-  sh "bacon -I./lib:./test -w #{opts} #{specopts}"
+  sh "bacon -I./lib:./test #{opts} #{specopts}"
 end
 
+desc "Run all the tests we run on CI"
+task :ci => :test
+
 desc "Run all the tests"
 task :fulltest => %w[SPEC chmod] do
   opts     = ENV['TEST'] || '-a'
diff --git a/SPEC b/SPEC
index 525b045..d802e75 100644
--- a/SPEC
+++ b/SPEC
@@ -146,11 +146,11 @@ consisting of lines (for multiple header values, e.g. multiple
 The lines must not contain characters below 037.
 === The Content-Type
 There must be a <tt>Content-Type</tt>, except when the
-+Status+ is 1xx, 204 or 304, in which case there must be none
++Status+ is 1xx, 204, 205 or 304, in which case there must be none
 given.
 === The Content-Length
 There must not be a <tt>Content-Length</tt> header when the
-+Status+ is 1xx, 204 or 304.
++Status+ is 1xx, 204, 205 or 304.
 === The Body
 The Body must respond to +each+
 and must only yield String values.
diff --git a/lib/rack/backports/uri/common.rb b/lib/rack/backports/uri/common_18.rb
similarity index 94%
rename from lib/rack/backports/uri/common.rb
rename to lib/rack/backports/uri/common_18.rb
index aaa7904..4a4d6be 100644
--- a/lib/rack/backports/uri/common.rb
+++ b/lib/rack/backports/uri/common_18.rb
@@ -10,7 +10,7 @@ module URI
   TBLENCWWWCOMP_ = {} # :nodoc:
   TBLDECWWWCOMP_ = {} # :nodoc:
 
-  # Encode given +str+ to URL-encoded form data.
+  # Encode given +s+ to URL-encoded form data.
   #
   # This method doesn't convert *, -, ., 0-9, A-Z, _, a-z, but does convert SP
   # (ASCII space) to + and converts others to %XX.
@@ -19,7 +19,8 @@ module URI
   # http://www.w3.org/TR/html5/forms.html#url-encoded-form-data
   #
   # See URI.decode_www_form_component, URI.encode_www_form
-  def self.encode_www_form_component(str)
+  def self.encode_www_form_component(s)
+    str = s.to_s
     if RUBY_VERSION < "1.9" && $KCODE =~ /u/i
       str.gsub(/([^ a-zA-Z0-9_.-]+)/) do
         '%' + $1.unpack('H2' * Rack::Utils.bytesize($1)).join('%').upcase
@@ -37,7 +38,6 @@ module URI
         rescue
         end
       end
-      str = str.to_s
       str.gsub(/[^*\-.0-9A-Z_a-z]/) {|m| TBLENCWWWCOMP_[m]}
     end
   end
diff --git a/lib/rack/backports/uri/common_192.rb b/lib/rack/backports/uri/common_192.rb
new file mode 100644
index 0000000..4adeb90
--- /dev/null
+++ b/lib/rack/backports/uri/common_192.rb
@@ -0,0 +1,55 @@
+# :stopdoc:
+
+# Stolen from ruby core's uri/common.rb @32618ba to fix DoS issues in 1.9.2
+#
+# https://github.com/ruby/ruby/blob/32618ba7438a2247042bba9b5d85b5d49070f5e5/lib/uri/common.rb
+#
+# Issue:
+# http://redmine.ruby-lang.org/issues/5149
+#
+# Relevant Fixes:
+# https://github.com/ruby/ruby/commit/b5f91deee04aa6ccbe07c23c8222b937c22a799b
+# https://github.com/ruby/ruby/commit/93177c1e5c3906abf14472ae0b905d8b5c72ce1b
+#
+# This should probably be removed once there is a Ruby 1.9.2 patch level that
+# includes this fix.
+
+require 'uri/common'
+
+module URI
+  def self.decode_www_form(str, enc=Encoding::UTF_8)
+    return [] if str.empty?
+    unless /\A#{WFKV_}=#{WFKV_}(?:[;&]#{WFKV_}=#{WFKV_})*\z/o =~ str
+      raise ArgumentError, "invalid data of application/x-www-form-urlencoded (#{str})"
+    end
+    ary = []
+    $&.scan(/([^=;&]+)=([^;&]*)/) do
+      ary << [decode_www_form_component($1, enc), decode_www_form_component($2, enc)]
+    end
+    ary
+  end
+
+  def self.decode_www_form_component(str, enc=Encoding::UTF_8)
+    if TBLDECWWWCOMP_.empty?
+      tbl = {}
+      256.times do |i|
+        h, l = i>>4, i&15
+        tbl['%%%X%X' % [h, l]] = i.chr
+        tbl['%%%x%X' % [h, l]] = i.chr
+        tbl['%%%X%x' % [h, l]] = i.chr
+        tbl['%%%x%x' % [h, l]] = i.chr
+      end
+      tbl['+'] = ' '
+      begin
+        TBLDECWWWCOMP_.replace(tbl)
+        TBLDECWWWCOMP_.freeze
+      rescue
+      end
+    end
+    raise ArgumentError, "invalid %-encoding (#{str})" unless /\A[^%]*(?:%\h\h[^%]*)*\z/ =~ str
+    str.gsub(/\+|%\h\h/, TBLDECWWWCOMP_).force_encoding(enc)
+  end
+
+  remove_const :WFKV_
+  WFKV_ = '(?:[^%#=;&]*(?:%\h\h[^%#=;&]*)*)' # :nodoc:
+end
diff --git a/lib/rack/body_proxy.rb b/lib/rack/body_proxy.rb
index 921af6b..6362c5f 100644
--- a/lib/rack/body_proxy.rb
+++ b/lib/rack/body_proxy.rb
@@ -9,13 +9,10 @@ module Rack
     end
 
     def close
-      raise IOError, "closed stream" if @closed
-      begin
-        @body.close if @body.respond_to? :close
-      ensure
-        @block.call
-        @closed = true
-      end
+      return if @closed
+      @closed = true
+      @body.close if @body.respond_to? :close
+      @block.call
     end
 
     def closed?
diff --git a/lib/rack/builder.rb b/lib/rack/builder.rb
index db8e2d8..be1970e 100644
--- a/lib/rack/builder.rb
+++ b/lib/rack/builder.rb
@@ -20,7 +20,7 @@ module Rack
   #
   #  app = Rack::Builder.app do
   #    use Rack::CommonLogger
-  #    lambda { |env| [200, {'Content-Type' => 'text/plain'}, 'OK'] }
+  #    run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
   #  end
   #
   #  run app
@@ -36,7 +36,7 @@ module Rack
         if cfgfile[/^#\\(.*)/] && opts
           options = opts.parse! $1.split(/\s+/)
         end
-        cfgfile.sub!(/^__END__\n.*/, '')
+        cfgfile.sub!(/^__END__\n.*\Z/m, '')
         app = eval "Rack::Builder.new {\n" + cfgfile + "\n}.to_app",
           TOPLEVEL_BINDING, config
       else
@@ -46,13 +46,13 @@ module Rack
       return app, options
     end
 
-    def initialize(&block)
-      @ins = []
+    def initialize(default_app = nil,&block)
+      @use, @map, @run = [], nil, default_app
       instance_eval(&block) if block_given?
     end
 
-    def self.app(&block)
-      self.new(&block).to_app
+    def self.app(default_app = nil, &block)
+      self.new(default_app, &block).to_app
     end
 
     # Specifies a middleware to use in a stack.
@@ -75,7 +75,11 @@ module Rack
     # The +call+ method in this example sets an additional environment key which then can be
     # referenced in the application if required.
     def use(middleware, *args, &block)
-      @ins << lambda { |app| middleware.new(app, *args, &block) }
+      if @map
+        mapping, @map = @map, nil
+        @use << proc { |app| generate_map app, mapping }
+      end
+      @use << proc { |app| middleware.new(app, *args, &block) }
     end
 
     # Takes an argument that is an object that responds to #call and returns a Rack response.
@@ -93,7 +97,7 @@ module Rack
     #
     #   run Heartbeat
     def run(app)
-      @ins << app #lambda { |nothing| app }
+      @run = app
     end
 
     # Creates a route within the application.
@@ -116,22 +120,26 @@ module Rack
     # This example includes a piece of middleware which will run before requests hit +Heartbeat+.
     #
     def map(path, &block)
-      if @ins.last.kind_of? Hash
-        @ins.last[path] = self.class.new(&block).to_app
-      else
-        @ins << {}
-        map(path, &block)
-      end
+      @map ||= {}
+      @map[path] = block
     end
 
     def to_app
-      @ins[-1] = Rack::URLMap.new(@ins.last)  if Hash === @ins.last
-      inner_app = @ins.last
-      @ins[0...-1].reverse.inject(inner_app) { |a, e| e.call(a) }
+      app = @map ? generate_map(@run, @map) : @run
+      fail "missing run or map statement" unless app
+      @use.reverse.inject(app) { |a,e| e[a] }
     end
 
     def call(env)
       to_app.call(env)
     end
+
+    private
+
+    def generate_map(default_app, mapping)
+      mapped = default_app ? {'/' => default_app} : {}
+      mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b) }
+      URLMap.new(mapped)
+    end
   end
 end
diff --git a/lib/rack/cascade.rb b/lib/rack/cascade.rb
index 14c3e54..4b9ba35 100644
--- a/lib/rack/cascade.rb
+++ b/lib/rack/cascade.rb
@@ -4,7 +4,7 @@ module Rack
   # status codes).
 
   class Cascade
-    NotFound = [404, {}, []]
+    NotFound = [404, {"Content-Type" => "text/plain"}, []]
 
     attr_reader :apps
 
diff --git a/lib/rack/chunked.rb b/lib/rack/chunked.rb
index 60acf25..a400756 100644
--- a/lib/rack/chunked.rb
+++ b/lib/rack/chunked.rb
@@ -23,6 +23,8 @@ module Rack
         @body.each do |chunk|
           size = bytesize(chunk)
           next if size == 0
+
+          chunk = chunk.dup.force_encoding(Encoding::BINARY) if chunk.respond_to?(:force_encoding)
           yield [size.to_s(16), term, chunk, term].join
         end
         yield TAIL
diff --git a/lib/rack/conditionalget.rb b/lib/rack/conditionalget.rb
index 136d187..dc580eb 100644
--- a/lib/rack/conditionalget.rb
+++ b/lib/rack/conditionalget.rb
@@ -56,6 +56,7 @@ module Rack
 
     def modified_since?(modified_since, headers)
       last_modified = to_rfc2822(headers['Last-Modified']) and
+        modified_since and
         modified_since >= last_modified
     end
 
diff --git a/lib/rack/content_type.rb b/lib/rack/content_type.rb
index 874c28c..dd96e95 100644
--- a/lib/rack/content_type.rb
+++ b/lib/rack/content_type.rb
@@ -9,6 +9,8 @@ module Rack
   #
   # When no content type argument is provided, "text/html" is assumed.
   class ContentType
+    include Rack::Utils
+
     def initialize(app, content_type = "text/html")
       @app, @content_type = app, content_type
     end
@@ -16,7 +18,11 @@ module Rack
     def call(env)
       status, headers, body = @app.call(env)
       headers = Utils::HeaderHash.new(headers)
-      headers['Content-Type'] ||= @content_type
+
+      unless STATUS_WITH_NO_ENTITY_BODY.include?(status)
+        headers['Content-Type'] ||= @content_type
+      end
+
       [status, headers, body]
     end
   end
diff --git a/lib/rack/directory.rb b/lib/rack/directory.rb
index 927ac0c..d57652a 100644
--- a/lib/rack/directory.rb
+++ b/lib/rack/directory.rb
@@ -80,13 +80,17 @@ table { width:100%%; }
       @files = [['../','Parent Directory','','','']]
       glob = F.join(@path, '*')
 
+      url_head = ([@script_name] + @path_info.split('/')).map do |part|
+        Rack::Utils.escape part
+      end
+
       Dir[glob].sort.each do |node|
         stat = stat(node)
         next  unless stat
         basename = F.basename(node)
         ext = F.extname(node)
 
-        url = F.join(@script_name, @path_info, basename)
+        url = F.join(*url_head + [Rack::Utils.escape(basename)])
         size = stat.size
         type = stat.directory? ? 'directory' : Mime.mime_type(ext)
         size = stat.directory? ? '-' : filesize_format(size)
diff --git a/lib/rack/file.rb b/lib/rack/file.rb
index a034ea9..8973195 100644
--- a/lib/rack/file.rb
+++ b/lib/rack/file.rb
@@ -13,6 +13,7 @@ module Rack
 
   class File
     SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
+    ALLOWED_VERBS = %w[GET HEAD]
 
     attr_accessor :root
     attr_accessor :path
@@ -32,10 +33,24 @@ module Rack
     F = ::File
 
     def _call(env)
+      unless ALLOWED_VERBS.include? env["REQUEST_METHOD"]
+        return fail(403, "Forbidden")
+      end
+
       @path_info = Utils.unescape(env["PATH_INFO"])
       parts = @path_info.split SEPS
 
-      return fail(403, "Forbidden")  if parts.include? ".."
+      parts.inject(0) do |depth, part|
+        case part
+        when '', '.'
+          depth
+        when '..'
+          return fail(403, "Forbidden") if depth - 1 < 0
+          depth - 1
+        else
+          depth + 1
+        end
+      end
 
       @path = F.join(@root, *parts)
 
@@ -53,22 +68,24 @@ module Rack
     end
 
     def serving(env)
-      # NOTE:
-      #   We check via File::size? whether this file provides size info
-      #   via stat (e.g. /proc files often don't), otherwise we have to
-      #   figure it out by reading the whole file into memory.
-      size = F.size?(@path) || Utils.bytesize(F.read(@path))
-
+      last_modified = F.mtime(@path).httpdate
+      return [304, {}, []] if env['HTTP_IF_MODIFIED_SINCE'] == last_modified
       response = [
         200,
         {
-          "Last-Modified"  => F.mtime(@path).httpdate,
+          "Last-Modified"  => last_modified,
           "Content-Type"   => Mime.mime_type(F.extname(@path), 'text/plain')
         },
-        self
+        env["REQUEST_METHOD"] == "HEAD" ? [] : self
       ]
       response[1].merge! 'Cache-Control' => @cache_control if @cache_control
 
+      # NOTE:
+      #   We check via File::size? whether this file provides size info
+      #   via stat (e.g. /proc files often don't), otherwise we have to
+      #   figure it out by reading the whole file into memory.
+      size = F.size?(@path) || Utils.bytesize(F.read(@path))
+
       ranges = Rack::Utils.byte_ranges(env, size)
       if ranges.nil? || ranges.length > 1
         # No ranges, or multiple ranges (which we don't support):
diff --git a/lib/rack/handler.rb b/lib/rack/handler.rb
index a2649df..70316be 100644
--- a/lib/rack/handler.rb
+++ b/lib/rack/handler.rb
@@ -1,7 +1,7 @@
 module Rack
   # *Handlers* connect web servers with Rack.
   #
-  # Rack includes Handlers for Mongrel, WEBrick, FastCGI, CGI, SCGI
+  # Rack includes Handlers for Thin, WEBrick, FastCGI, CGI, SCGI
   # and LiteSpeed.
   #
   # Handlers usually are activated by calling <tt>MyHandler.run(myapp)</tt>.
@@ -38,7 +38,7 @@ module Rack
         Rack::Handler::CGI
       else
         begin
-          Rack::Handler::Mongrel
+          Rack::Handler::Thin
         rescue LoadError
           Rack::Handler::WEBrick
         end
diff --git a/lib/rack/lint.rb b/lib/rack/lint.rb
index d21ea21..2448f22 100644
--- a/lib/rack/lint.rb
+++ b/lib/rack/lint.rb
@@ -464,7 +464,7 @@ module Rack
     def check_content_type(status, headers)
       headers.each { |key, value|
         ## There must be a <tt>Content-Type</tt>, except when the
-        ## +Status+ is 1xx, 204 or 304, in which case there must be none
+        ## +Status+ is 1xx, 204, 205 or 304, in which case there must be none
         ## given.
         if key.downcase == "content-type"
           assert("Content-Type header found in #{status} response, not allowed") {
@@ -483,7 +483,7 @@ module Rack
       headers.each { |key, value|
         if key.downcase == 'content-length'
           ## There must not be a <tt>Content-Length</tt> header when the
-          ## +Status+ is 1xx, 204 or 304.
+          ## +Status+ is 1xx, 204, 205 or 304.
           assert("Content-Length header found in #{status} response, not allowed") {
             not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
           }
diff --git a/lib/rack/methodoverride.rb b/lib/rack/methodoverride.rb
index e1bca56..48d6afd 100644
--- a/lib/rack/methodoverride.rb
+++ b/lib/rack/methodoverride.rb
@@ -11,10 +11,7 @@ module Rack
 
     def call(env)
       if env["REQUEST_METHOD"] == "POST"
-        req = Request.new(env)
-        method = req.POST[METHOD_OVERRIDE_PARAM_KEY] ||
-          env[HTTP_METHOD_OVERRIDE_HEADER]
-        method = method.to_s.upcase
+        method = method_override(env)
         if HTTP_METHODS.include?(method)
           env["rack.methodoverride.original_method"] = env["REQUEST_METHOD"]
           env["REQUEST_METHOD"] = method
@@ -23,5 +20,14 @@ module Rack
 
       @app.call(env)
     end
+
+    def method_override(env)
+      req = Request.new(env)
+      method = req.POST[METHOD_OVERRIDE_PARAM_KEY] ||
+        env[HTTP_METHOD_OVERRIDE_HEADER]
+      method.to_s.upcase
+    rescue EOFError
+      ""
+    end
   end
 end
diff --git a/lib/rack/mime.rb b/lib/rack/mime.rb
index 38c58f9..d2e908c 100644
--- a/lib/rack/mime.rb
+++ b/lib/rack/mime.rb
@@ -28,183 +28,621 @@ module Rack
     #     list = WEBrick::HTTPUtils.load_mime_types('/etc/mime.types')
     #     Rack::Mime::MIME_TYPES.merge!(list)
     #
+    # N.B. On Ubuntu the mime.types file does not include the leading period, so
+    # users may need to modify the data before merging into the hash.
+    #
     # To add the list mongrel provides, use:
     #
     #     require 'mongrel/handlers'
     #     Rack::Mime::MIME_TYPES.merge!(Mongrel::DirHandler::MIME_TYPES)
 
     MIME_TYPES = {
-      ".3gp"     => "video/3gpp",
-      ".a"       => "application/octet-stream",
-      ".ai"      => "application/postscript",
-      ".aif"     => "audio/x-aiff",
-      ".aiff"    => "audio/x-aiff",
-      ".asc"     => "application/pgp-signature",
-      ".asf"     => "video/x-ms-asf",
-      ".asm"     => "text/x-asm",
-      ".asx"     => "video/x-ms-asf",
-      ".atom"    => "application/atom+xml",
-      ".au"      => "audio/basic",
-      ".avi"     => "video/x-msvideo",
-      ".bat"     => "application/x-msdownload",
-      ".bin"     => "application/octet-stream",
-      ".bmp"     => "image/bmp",
-      ".bz2"     => "application/x-bzip2",
-      ".c"       => "text/x-c",
-      ".cab"     => "application/vnd.ms-cab-compressed",
-      ".cc"      => "text/x-c",
-      ".chm"     => "application/vnd.ms-htmlhelp",
-      ".class"   => "application/octet-stream",
-      ".com"     => "application/x-msdownload",
-      ".conf"    => "text/plain",
-      ".cpp"     => "text/x-c",
-      ".crt"     => "application/x-x509-ca-cert",
-      ".css"     => "text/css",
-      ".csv"     => "text/csv",
-      ".cxx"     => "text/x-c",
-      ".deb"     => "application/x-debian-package",
-      ".der"     => "application/x-x509-ca-cert",
-      ".diff"    => "text/x-diff",
-      ".djv"     => "image/vnd.djvu",
-      ".djvu"    => "image/vnd.djvu",
-      ".dll"     => "application/x-msdownload",
-      ".dmg"     => "application/octet-stream",
-      ".doc"     => "application/msword",
-      ".dot"     => "application/msword",
-      ".dtd"     => "application/xml-dtd",
-      ".dvi"     => "application/x-dvi",
-      ".ear"     => "application/java-archive",
-      ".eml"     => "message/rfc822",
-      ".eps"     => "application/postscript",
-      ".exe"     => "application/x-msdownload",
-      ".f"       => "text/x-fortran",
-      ".f77"     => "text/x-fortran",
-      ".f90"     => "text/x-fortran",
-      ".flv"     => "video/x-flv",
-      ".for"     => "text/x-fortran",
-      ".gem"     => "application/octet-stream",
-      ".gemspec" => "text/x-script.ruby",
-      ".gif"     => "image/gif",
-      ".gz"      => "application/x-gzip",
-      ".h"       => "text/x-c",
-      ".htc"     => "text/x-component",
-      ".hh"      => "text/x-c",
-      ".htm"     => "text/html",
-      ".html"    => "text/html",
-      ".ico"     => "image/vnd.microsoft.icon",
-      ".ics"     => "text/calendar",
-      ".ifb"     => "text/calendar",
-      ".iso"     => "application/octet-stream",
-      ".jar"     => "application/java-archive",
-      ".java"    => "text/x-java-source",
-      ".jnlp"    => "application/x-java-jnlp-file",
-      ".jpeg"    => "image/jpeg",
-      ".jpg"     => "image/jpeg",
-      ".js"      => "application/javascript",
-      ".json"    => "application/json",
-      ".log"     => "text/plain",
-      ".m3u"     => "audio/x-mpegurl",
-      ".m4v"     => "video/mp4",
-      ".man"     => "text/troff",
-      ".manifest"=> "text/cache-manifest",
-      ".mathml"  => "application/mathml+xml",
-      ".mbox"    => "application/mbox",
-      ".mdoc"    => "text/troff",
-      ".me"      => "text/troff",
-      ".mid"     => "audio/midi",
-      ".midi"    => "audio/midi",
-      ".mime"    => "message/rfc822",
-      ".mml"     => "application/mathml+xml",
-      ".mng"     => "video/x-mng",
-      ".mov"     => "video/quicktime",
-      ".mp3"     => "audio/mpeg",
-      ".mp4"     => "video/mp4",
-      ".mp4v"    => "video/mp4",
-      ".mpeg"    => "video/mpeg",
-      ".mpg"     => "video/mpeg",
-      ".ms"      => "text/troff",
-      ".msi"     => "application/x-msdownload",
-      ".odp"     => "application/vnd.oasis.opendocument.presentation",
-      ".ods"     => "application/vnd.oasis.opendocument.spreadsheet",
-      ".odt"     => "application/vnd.oasis.opendocument.text",
-      ".ogg"     => "application/ogg",
-      ".ogv"     => "video/ogg",
-      ".p"       => "text/x-pascal",
-      ".pas"     => "text/x-pascal",
-      ".pbm"     => "image/x-portable-bitmap",
-      ".pdf"     => "application/pdf",
-      ".pem"     => "application/x-x509-ca-cert",
-      ".pgm"     => "image/x-portable-graymap",
-      ".pgp"     => "application/pgp-encrypted",
-      ".pkg"     => "application/octet-stream",
-      ".pl"      => "text/x-script.perl",
-      ".pm"      => "text/x-script.perl-module",
-      ".png"     => "image/png",
-      ".pnm"     => "image/x-portable-anymap",
-      ".ppm"     => "image/x-portable-pixmap",
-      ".pps"     => "application/vnd.ms-powerpoint",
-      ".ppt"     => "application/vnd.ms-powerpoint",
-      ".ps"      => "application/postscript",
-      ".psd"     => "image/vnd.adobe.photoshop",
-      ".py"      => "text/x-script.python",
-      ".qt"      => "video/quicktime",
-      ".ra"      => "audio/x-pn-realaudio",
-      ".rake"    => "text/x-script.ruby",
-      ".ram"     => "audio/x-pn-realaudio",
-      ".rar"     => "application/x-rar-compressed",
-      ".rb"      => "text/x-script.ruby",
-      ".rdf"     => "application/rdf+xml",
-      ".roff"    => "text/troff",
-      ".rpm"     => "application/x-redhat-package-manager",
-      ".rss"     => "application/rss+xml",
-      ".rtf"     => "application/rtf",
-      ".ru"      => "text/x-script.ruby",
-      ".s"       => "text/x-asm",
-      ".sgm"     => "text/sgml",
-      ".sgml"    => "text/sgml",
-      ".sh"      => "application/x-sh",
-      ".sig"     => "application/pgp-signature",
-      ".snd"     => "audio/basic",
-      ".so"      => "application/octet-stream",
-      ".svg"     => "image/svg+xml",
-      ".svgz"    => "image/svg+xml",
-      ".swf"     => "application/x-shockwave-flash",
-      ".t"       => "text/troff",
-      ".tar"     => "application/x-tar",
-      ".tbz"     => "application/x-bzip-compressed-tar",
-      ".tcl"     => "application/x-tcl",
-      ".tex"     => "application/x-tex",
-      ".texi"    => "application/x-texinfo",
-      ".texinfo" => "application/x-texinfo",
-      ".text"    => "text/plain",
-      ".tif"     => "image/tiff",
-      ".tiff"    => "image/tiff",
-      ".torrent" => "application/x-bittorrent",
-      ".tr"      => "text/troff",
-      ".ttf"     => "application/octet-stream",
-      ".txt"     => "text/plain",
-      ".vcf"     => "text/x-vcard",
-      ".vcs"     => "text/x-vcalendar",
-      ".vrml"    => "model/vrml",
-      ".war"     => "application/java-archive",
-      ".wav"     => "audio/x-wav",
-      ".webm"    => "video/webm",
-      ".wma"     => "audio/x-ms-wma",
-      ".wmv"     => "video/x-ms-wmv",
-      ".wmx"     => "video/x-ms-wmx",
-      ".woff"    => "application/octet-stream",
-      ".wrl"     => "model/vrml",
-      ".wsdl"    => "application/wsdl+xml",
-      ".xbm"     => "image/x-xbitmap",
-      ".xhtml"   => "application/xhtml+xml",
-      ".xls"     => "application/vnd.ms-excel",
-      ".xml"     => "application/xml",
-      ".xpm"     => "image/x-xpixmap",
-      ".xsl"     => "application/xml",
-      ".xslt"    => "application/xslt+xml",
-      ".yaml"    => "text/yaml",
-      ".yml"     => "text/yaml",
-      ".zip"     => "application/zip",
+      ".123"       => "application/vnd.lotus-1-2-3",
+      ".3dml"      => "text/vnd.in3d.3dml",
+      ".3g2"       => "video/3gpp2",
+      ".3gp"       => "video/3gpp",
+      ".a"         => "application/octet-stream",
+      ".acc"       => "application/vnd.americandynamics.acc",
+      ".ace"       => "application/x-ace-compressed",
+      ".acu"       => "application/vnd.acucobol",
+      ".aep"       => "application/vnd.audiograph",
+      ".afp"       => "application/vnd.ibm.modcap",
+      ".ai"        => "application/postscript",
+      ".aif"       => "audio/x-aiff",
+      ".aiff"      => "audio/x-aiff",
+      ".ami"       => "application/vnd.amiga.ami",
+      ".appcache"  => "text/cache-manifest",
+      ".apr"       => "application/vnd.lotus-approach",
+      ".asc"       => "application/pgp-signature",
+      ".asf"       => "video/x-ms-asf",
+      ".asm"       => "text/x-asm",
+      ".aso"       => "application/vnd.accpac.simply.aso",
+      ".asx"       => "video/x-ms-asf",
+      ".atc"       => "application/vnd.acucorp",
+      ".atom"      => "application/atom+xml",
+      ".atomcat"   => "application/atomcat+xml",
+      ".atomsvc"   => "application/atomsvc+xml",
+      ".atx"       => "application/vnd.antix.game-component",
+      ".au"        => "audio/basic",
+      ".avi"       => "video/x-msvideo",
+      ".bat"       => "application/x-msdownload",
+      ".bcpio"     => "application/x-bcpio",
+      ".bdm"       => "application/vnd.syncml.dm+wbxml",
+      ".bh2"       => "application/vnd.fujitsu.oasysprs",
+      ".bin"       => "application/octet-stream",
+      ".bmi"       => "application/vnd.bmi",
+      ".bmp"       => "image/bmp",
+      ".box"       => "application/vnd.previewsystems.box",
+      ".btif"      => "image/prs.btif",
+      ".bz"        => "application/x-bzip",
+      ".bz2"       => "application/x-bzip2",
+      ".c"         => "text/x-c",
+      ".c4g"       => "application/vnd.clonk.c4group",
+      ".cab"       => "application/vnd.ms-cab-compressed",
+      ".cc"        => "text/x-c",
+      ".ccxml"     => "application/ccxml+xml",
+      ".cdbcmsg"   => "application/vnd.contact.cmsg",
+      ".cdkey"     => "application/vnd.mediastation.cdkey",
+      ".cdx"       => "chemical/x-cdx",
+      ".cdxml"     => "application/vnd.chemdraw+xml",
+      ".cdy"       => "application/vnd.cinderella",
+      ".cer"       => "application/pkix-cert",
+      ".cgm"       => "image/cgm",
+      ".chat"      => "application/x-chat",
+      ".chm"       => "application/vnd.ms-htmlhelp",
+      ".chrt"      => "application/vnd.kde.kchart",
+      ".cif"       => "chemical/x-cif",
+      ".cii"       => "application/vnd.anser-web-certificate-issue-initiation",
+      ".cil"       => "application/vnd.ms-artgalry",
+      ".cla"       => "application/vnd.claymore",
+      ".class"     => "application/octet-stream",
+      ".clkk"      => "application/vnd.crick.clicker.keyboard",
+      ".clkp"      => "application/vnd.crick.clicker.palette",
+      ".clkt"      => "application/vnd.crick.clicker.template",
+      ".clkw"      => "application/vnd.crick.clicker.wordbank",
+      ".clkx"      => "application/vnd.crick.clicker",
+      ".clp"       => "application/x-msclip",
+      ".cmc"       => "application/vnd.cosmocaller",
+      ".cmdf"      => "chemical/x-cmdf",
+      ".cml"       => "chemical/x-cml",
+      ".cmp"       => "application/vnd.yellowriver-custom-menu",
+      ".cmx"       => "image/x-cmx",
+      ".com"       => "application/x-msdownload",
+      ".conf"      => "text/plain",
+      ".cpio"      => "application/x-cpio",
+      ".cpp"       => "text/x-c",
+      ".cpt"       => "application/mac-compactpro",
+      ".crd"       => "application/x-mscardfile",
+      ".crl"       => "application/pkix-crl",
+      ".crt"       => "application/x-x509-ca-cert",
+      ".csh"       => "application/x-csh",
+      ".csml"      => "chemical/x-csml",
+      ".csp"       => "application/vnd.commonspace",
+      ".css"       => "text/css",
+      ".csv"       => "text/csv",
+      ".curl"      => "application/vnd.curl",
+      ".cww"       => "application/prs.cww",
+      ".cxx"       => "text/x-c",
+      ".daf"       => "application/vnd.mobius.daf",
+      ".davmount"  => "application/davmount+xml",
+      ".dcr"       => "application/x-director",
+      ".dd2"       => "application/vnd.oma.dd2+xml",
+      ".ddd"       => "application/vnd.fujixerox.ddd",
+      ".deb"       => "application/x-debian-package",
+      ".der"       => "application/x-x509-ca-cert",
+      ".dfac"      => "application/vnd.dreamfactory",
+      ".diff"      => "text/x-diff",
+      ".dis"       => "application/vnd.mobius.dis",
+      ".djv"       => "image/vnd.djvu",
+      ".djvu"      => "image/vnd.djvu",
+      ".dll"       => "application/x-msdownload",
+      ".dmg"       => "application/octet-stream",
+      ".dna"       => "application/vnd.dna",
+      ".doc"       => "application/msword",
+      ".docx"      => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+      ".dot"       => "application/msword",
+      ".dp"        => "application/vnd.osgi.dp",
+      ".dpg"       => "application/vnd.dpgraph",
+      ".dsc"       => "text/prs.lines.tag",
+      ".dtd"       => "application/xml-dtd",
+      ".dts"       => "audio/vnd.dts",
+      ".dtshd"     => "audio/vnd.dts.hd",
+      ".dv"        => "video/x-dv",
+      ".dvi"       => "application/x-dvi",
+      ".dwf"       => "model/vnd.dwf",
+      ".dwg"       => "image/vnd.dwg",
+      ".dxf"       => "image/vnd.dxf",
+      ".dxp"       => "application/vnd.spotfire.dxp",
+      ".ear"       => "application/java-archive",
+      ".ecelp4800" => "audio/vnd.nuera.ecelp4800",
+      ".ecelp7470" => "audio/vnd.nuera.ecelp7470",
+      ".ecelp9600" => "audio/vnd.nuera.ecelp9600",
+      ".ecma"      => "application/ecmascript",
+      ".edm"       => "application/vnd.novadigm.edm",
+      ".edx"       => "application/vnd.novadigm.edx",
+      ".efif"      => "application/vnd.picsel",
+      ".ei6"       => "application/vnd.pg.osasli",
+      ".eml"       => "message/rfc822",
+      ".eol"       => "audio/vnd.digital-winds",
+      ".eot"       => "application/vnd.ms-fontobject",
+      ".eps"       => "application/postscript",
+      ".es3"       => "application/vnd.eszigno3+xml",
+      ".esf"       => "application/vnd.epson.esf",
+      ".etx"       => "text/x-setext",
+      ".exe"       => "application/x-msdownload",
+      ".ext"       => "application/vnd.novadigm.ext",
+      ".ez"        => "application/andrew-inset",
+      ".ez2"       => "application/vnd.ezpix-album",
+      ".ez3"       => "application/vnd.ezpix-package",
+      ".f"         => "text/x-fortran",
+      ".f77"       => "text/x-fortran",
+      ".f90"       => "text/x-fortran",
+      ".fbs"       => "image/vnd.fastbidsheet",
+      ".fdf"       => "application/vnd.fdf",
+      ".fe_launch" => "application/vnd.denovo.fcselayout-link",
+      ".fg5"       => "application/vnd.fujitsu.oasysgp",
+      ".fli"       => "video/x-fli",
+      ".flo"       => "application/vnd.micrografx.flo",
+      ".flv"       => "video/x-flv",
+      ".flw"       => "application/vnd.kde.kivio",
+      ".flx"       => "text/vnd.fmi.flexstor",
+      ".fly"       => "text/vnd.fly",
+      ".fm"        => "application/vnd.framemaker",
+      ".fnc"       => "application/vnd.frogans.fnc",
+      ".for"       => "text/x-fortran",
+      ".fpx"       => "image/vnd.fpx",
+      ".fsc"       => "application/vnd.fsc.weblaunch",
+      ".fst"       => "image/vnd.fst",
+      ".ftc"       => "application/vnd.fluxtime.clip",
+      ".fti"       => "application/vnd.anser-web-funds-transfer-initiation",
+      ".fvt"       => "video/vnd.fvt",
+      ".fzs"       => "application/vnd.fuzzysheet",
+      ".g3"        => "image/g3fax",
+      ".gac"       => "application/vnd.groove-account",
+      ".gdl"       => "model/vnd.gdl",
+      ".gem"       => "application/octet-stream",
+      ".gemspec"   => "text/x-script.ruby",
+      ".ghf"       => "application/vnd.groove-help",
+      ".gif"       => "image/gif",
+      ".gim"       => "application/vnd.groove-identity-message",
+      ".gmx"       => "application/vnd.gmx",
+      ".gph"       => "application/vnd.flographit",
+      ".gqf"       => "application/vnd.grafeq",
+      ".gram"      => "application/srgs",
+      ".grv"       => "application/vnd.groove-injector",
+      ".grxml"     => "application/srgs+xml",
+      ".gtar"      => "application/x-gtar",
+      ".gtm"       => "application/vnd.groove-tool-message",
+      ".gtw"       => "model/vnd.gtw",
+      ".gv"        => "text/vnd.graphviz",
+      ".gz"        => "application/x-gzip",
+      ".h"         => "text/x-c",
+      ".h261"      => "video/h261",
+      ".h263"      => "video/h263",
+      ".h264"      => "video/h264",
+      ".hbci"      => "application/vnd.hbci",
+      ".hdf"       => "application/x-hdf",
+      ".hh"        => "text/x-c",
+      ".hlp"       => "application/winhlp",
+      ".hpgl"      => "application/vnd.hp-hpgl",
+      ".hpid"      => "application/vnd.hp-hpid",
+      ".hps"       => "application/vnd.hp-hps",
+      ".hqx"       => "application/mac-binhex40",
+      ".htc"       => "text/x-component",
+      ".htke"      => "application/vnd.kenameaapp",
+      ".htm"       => "text/html",
+      ".html"      => "text/html",
+      ".hvd"       => "application/vnd.yamaha.hv-dic",
+      ".hvp"       => "application/vnd.yamaha.hv-voice",
+      ".hvs"       => "application/vnd.yamaha.hv-script",
+      ".icc"       => "application/vnd.iccprofile",
+      ".ice"       => "x-conference/x-cooltalk",
+      ".ico"       => "image/vnd.microsoft.icon",
+      ".ics"       => "text/calendar",
+      ".ief"       => "image/ief",
+      ".ifb"       => "text/calendar",
+      ".ifm"       => "application/vnd.shana.informed.formdata",
+      ".igl"       => "application/vnd.igloader",
+      ".igs"       => "model/iges",
+      ".igx"       => "application/vnd.micrografx.igx",
+      ".iif"       => "application/vnd.shana.informed.interchange",
+      ".imp"       => "application/vnd.accpac.simply.imp",
+      ".ims"       => "application/vnd.ms-ims",
+      ".ipk"       => "application/vnd.shana.informed.package",
+      ".irm"       => "application/vnd.ibm.rights-management",
+      ".irp"       => "application/vnd.irepository.package+xml",
+      ".iso"       => "application/octet-stream",
+      ".itp"       => "application/vnd.shana.informed.formtemplate",
+      ".ivp"       => "application/vnd.immervision-ivp",
+      ".ivu"       => "application/vnd.immervision-ivu",
+      ".jad"       => "text/vnd.sun.j2me.app-descriptor",
+      ".jam"       => "application/vnd.jam",
+      ".jar"       => "application/java-archive",
+      ".java"      => "text/x-java-source",
+      ".jisp"      => "application/vnd.jisp",
+      ".jlt"       => "application/vnd.hp-jlyt",
+      ".jnlp"      => "application/x-java-jnlp-file",
+      ".joda"      => "application/vnd.joost.joda-archive",
+      ".jp2"       => "image/jp2",
+      ".jpeg"      => "image/jpeg",
+      ".jpg"       => "image/jpeg",
+      ".jpgv"      => "video/jpeg",
+      ".jpm"       => "video/jpm",
+      ".js"        => "application/javascript",
+      ".json"      => "application/json",
+      ".karbon"    => "application/vnd.kde.karbon",
+      ".kfo"       => "application/vnd.kde.kformula",
+      ".kia"       => "application/vnd.kidspiration",
+      ".kml"       => "application/vnd.google-earth.kml+xml",
+      ".kmz"       => "application/vnd.google-earth.kmz",
+      ".kne"       => "application/vnd.kinar",
+      ".kon"       => "application/vnd.kde.kontour",
+      ".kpr"       => "application/vnd.kde.kpresenter",
+      ".ksp"       => "application/vnd.kde.kspread",
+      ".ktz"       => "application/vnd.kahootz",
+      ".kwd"       => "application/vnd.kde.kword",
+      ".latex"     => "application/x-latex",
+      ".lbd"       => "application/vnd.llamagraphics.life-balance.desktop",
+      ".lbe"       => "application/vnd.llamagraphics.life-balance.exchange+xml",
+      ".les"       => "application/vnd.hhe.lesson-player",
+      ".link66"    => "application/vnd.route66.link66+xml",
+      ".log"       => "text/plain",
+      ".lostxml"   => "application/lost+xml",
+      ".lrm"       => "application/vnd.ms-lrm",
+      ".ltf"       => "application/vnd.frogans.ltf",
+      ".lvp"       => "audio/vnd.lucent.voice",
+      ".lwp"       => "application/vnd.lotus-wordpro",
+      ".m3u"       => "audio/x-mpegurl",
+      ".m4a"       => "audio/mp4a-latm",
+      ".m4v"       => "video/mp4",
+      ".ma"        => "application/mathematica",
+      ".mag"       => "application/vnd.ecowin.chart",
+      ".man"       => "text/troff",
+      ".manifest"  => "text/cache-manifest",
+      ".mathml"    => "application/mathml+xml",
+      ".mbk"       => "application/vnd.mobius.mbk",
+      ".mbox"      => "application/mbox",
+      ".mc1"       => "application/vnd.medcalcdata",
+      ".mcd"       => "application/vnd.mcd",
+      ".mdb"       => "application/x-msaccess",
+      ".mdi"       => "image/vnd.ms-modi",
+      ".mdoc"      => "text/troff",
+      ".me"        => "text/troff",
+      ".mfm"       => "application/vnd.mfmp",
+      ".mgz"       => "application/vnd.proteus.magazine",
+      ".mid"       => "audio/midi",
+      ".midi"      => "audio/midi",
+      ".mif"       => "application/vnd.mif",
+      ".mime"      => "message/rfc822",
+      ".mj2"       => "video/mj2",
+      ".mlp"       => "application/vnd.dolby.mlp",
+      ".mmd"       => "application/vnd.chipnuts.karaoke-mmd",
+      ".mmf"       => "application/vnd.smaf",
+      ".mml"       => "application/mathml+xml",
+      ".mmr"       => "image/vnd.fujixerox.edmics-mmr",
+      ".mng"       => "video/x-mng",
+      ".mny"       => "application/x-msmoney",
+      ".mov"       => "video/quicktime",
+      ".movie"     => "video/x-sgi-movie",
+      ".mp3"       => "audio/mpeg",
+      ".mp4"       => "video/mp4",
+      ".mp4a"      => "audio/mp4",
+      ".mp4s"      => "application/mp4",
+      ".mp4v"      => "video/mp4",
+      ".mpc"       => "application/vnd.mophun.certificate",
+      ".mpeg"      => "video/mpeg",
+      ".mpg"       => "video/mpeg",
+      ".mpga"      => "audio/mpeg",
+      ".mpkg"      => "application/vnd.apple.installer+xml",
+      ".mpm"       => "application/vnd.blueice.multipass",
+      ".mpn"       => "application/vnd.mophun.application",
+      ".mpp"       => "application/vnd.ms-project",
+      ".mpy"       => "application/vnd.ibm.minipay",
+      ".mqy"       => "application/vnd.mobius.mqy",
+      ".mrc"       => "application/marc",
+      ".ms"        => "text/troff",
+      ".mscml"     => "application/mediaservercontrol+xml",
+      ".mseq"      => "application/vnd.mseq",
+      ".msf"       => "application/vnd.epson.msf",
+      ".msh"       => "model/mesh",
+      ".msi"       => "application/x-msdownload",
+      ".msl"       => "application/vnd.mobius.msl",
+      ".msty"      => "application/vnd.muvee.style",
+      ".mts"       => "model/vnd.mts",
+      ".mus"       => "application/vnd.musician",
+      ".mvb"       => "application/x-msmediaview",
+      ".mwf"       => "application/vnd.mfer",
+      ".mxf"       => "application/mxf",
+      ".mxl"       => "application/vnd.recordare.musicxml",
+      ".mxml"      => "application/xv+xml",
+      ".mxs"       => "application/vnd.triscape.mxs",
+      ".mxu"       => "video/vnd.mpegurl",
+      ".n"         => "application/vnd.nokia.n-gage.symbian.install",
+      ".nc"        => "application/x-netcdf",
+      ".ngdat"     => "application/vnd.nokia.n-gage.data",
+      ".nlu"       => "application/vnd.neurolanguage.nlu",
+      ".nml"       => "application/vnd.enliven",
+      ".nnd"       => "application/vnd.noblenet-directory",
+      ".nns"       => "application/vnd.noblenet-sealer",
+      ".nnw"       => "application/vnd.noblenet-web",
+      ".npx"       => "image/vnd.net-fpx",
+      ".nsf"       => "application/vnd.lotus-notes",
+      ".oa2"       => "application/vnd.fujitsu.oasys2",
+      ".oa3"       => "application/vnd.fujitsu.oasys3",
+      ".oas"       => "application/vnd.fujitsu.oasys",
+      ".obd"       => "application/x-msbinder",
+      ".oda"       => "application/oda",
+      ".odc"       => "application/vnd.oasis.opendocument.chart",
+      ".odf"       => "application/vnd.oasis.opendocument.formula",
+      ".odg"       => "application/vnd.oasis.opendocument.graphics",
+      ".odi"       => "application/vnd.oasis.opendocument.image",
+      ".odp"       => "application/vnd.oasis.opendocument.presentation",
+      ".ods"       => "application/vnd.oasis.opendocument.spreadsheet",
+      ".odt"       => "application/vnd.oasis.opendocument.text",
+      ".oga"       => "audio/ogg",
+      ".ogg"       => "application/ogg",
+      ".ogv"       => "video/ogg",
+      ".ogx"       => "application/ogg",
+      ".org"       => "application/vnd.lotus-organizer",
+      ".otc"       => "application/vnd.oasis.opendocument.chart-template",
+      ".otf"       => "application/vnd.oasis.opendocument.formula-template",
+      ".otg"       => "application/vnd.oasis.opendocument.graphics-template",
+      ".oth"       => "application/vnd.oasis.opendocument.text-web",
+      ".oti"       => "application/vnd.oasis.opendocument.image-template",
+      ".otm"       => "application/vnd.oasis.opendocument.text-master",
+      ".ots"       => "application/vnd.oasis.opendocument.spreadsheet-template",
+      ".ott"       => "application/vnd.oasis.opendocument.text-template",
+      ".oxt"       => "application/vnd.openofficeorg.extension",
+      ".p"         => "text/x-pascal",
+      ".p10"       => "application/pkcs10",
+      ".p12"       => "application/x-pkcs12",
+      ".p7b"       => "application/x-pkcs7-certificates",
+      ".p7m"       => "application/pkcs7-mime",
+      ".p7r"       => "application/x-pkcs7-certreqresp",
+      ".p7s"       => "application/pkcs7-signature",
+      ".pas"       => "text/x-pascal",
+      ".pbd"       => "application/vnd.powerbuilder6",
+      ".pbm"       => "image/x-portable-bitmap",
+      ".pcl"       => "application/vnd.hp-pcl",
+      ".pclxl"     => "application/vnd.hp-pclxl",
+      ".pcx"       => "image/x-pcx",
+      ".pdb"       => "chemical/x-pdb",
+      ".pdf"       => "application/pdf",
+      ".pem"       => "application/x-x509-ca-cert",
+      ".pfr"       => "application/font-tdpfr",
+      ".pgm"       => "image/x-portable-graymap",
+      ".pgn"       => "application/x-chess-pgn",
+      ".pgp"       => "application/pgp-encrypted",
+      ".pic"       => "image/x-pict",
+      ".pict"      => "image/pict",
+      ".pkg"       => "application/octet-stream",
+      ".pki"       => "application/pkixcmp",
+      ".pkipath"   => "application/pkix-pkipath",
+      ".pl"        => "text/x-script.perl",
+      ".plb"       => "application/vnd.3gpp.pic-bw-large",
+      ".plc"       => "application/vnd.mobius.plc",
+      ".plf"       => "application/vnd.pocketlearn",
+      ".pls"       => "application/pls+xml",
+      ".pm"        => "text/x-script.perl-module",
+      ".pml"       => "application/vnd.ctc-posml",
+      ".png"       => "image/png",
+      ".pnm"       => "image/x-portable-anymap",
+      ".pntg"      => "image/x-macpaint",
+      ".portpkg"   => "application/vnd.macports.portpkg",
+      ".ppd"       => "application/vnd.cups-ppd",
+      ".ppm"       => "image/x-portable-pixmap",
+      ".pps"       => "application/vnd.ms-powerpoint",
+      ".ppt"       => "application/vnd.ms-powerpoint",
+      ".prc"       => "application/vnd.palm",
+      ".pre"       => "application/vnd.lotus-freelance",
+      ".prf"       => "application/pics-rules",
+      ".ps"        => "application/postscript",
+      ".psb"       => "application/vnd.3gpp.pic-bw-small",
+      ".psd"       => "image/vnd.adobe.photoshop",
+      ".ptid"      => "application/vnd.pvi.ptid1",
+      ".pub"       => "application/x-mspublisher",
+      ".pvb"       => "application/vnd.3gpp.pic-bw-var",
+      ".pwn"       => "application/vnd.3m.post-it-notes",
+      ".py"        => "text/x-script.python",
+      ".pya"       => "audio/vnd.ms-playready.media.pya",
+      ".pyv"       => "video/vnd.ms-playready.media.pyv",
+      ".qam"       => "application/vnd.epson.quickanime",
+      ".qbo"       => "application/vnd.intu.qbo",
+      ".qfx"       => "application/vnd.intu.qfx",
+      ".qps"       => "application/vnd.publishare-delta-tree",
+      ".qt"        => "video/quicktime",
+      ".qtif"      => "image/x-quicktime",
+      ".qxd"       => "application/vnd.quark.quarkxpress",
+      ".ra"        => "audio/x-pn-realaudio",
+      ".rake"      => "text/x-script.ruby",
+      ".ram"       => "audio/x-pn-realaudio",
+      ".rar"       => "application/x-rar-compressed",
+      ".ras"       => "image/x-cmu-raster",
+      ".rb"        => "text/x-script.ruby",
+      ".rcprofile" => "application/vnd.ipunplugged.rcprofile",
+      ".rdf"       => "application/rdf+xml",
+      ".rdz"       => "application/vnd.data-vision.rdz",
+      ".rep"       => "application/vnd.businessobjects",
+      ".rgb"       => "image/x-rgb",
+      ".rif"       => "application/reginfo+xml",
+      ".rl"        => "application/resource-lists+xml",
+      ".rlc"       => "image/vnd.fujixerox.edmics-rlc",
+      ".rld"       => "application/resource-lists-diff+xml",
+      ".rm"        => "application/vnd.rn-realmedia",
+      ".rmp"       => "audio/x-pn-realaudio-plugin",
+      ".rms"       => "application/vnd.jcp.javame.midlet-rms",
+      ".rnc"       => "application/relax-ng-compact-syntax",
+      ".roff"      => "text/troff",
+      ".rpm"       => "application/x-redhat-package-manager",
+      ".rpss"      => "application/vnd.nokia.radio-presets",
+      ".rpst"      => "application/vnd.nokia.radio-preset",
+      ".rq"        => "application/sparql-query",
+      ".rs"        => "application/rls-services+xml",
+      ".rsd"       => "application/rsd+xml",
+      ".rss"       => "application/rss+xml",
+      ".rtf"       => "application/rtf",
+      ".rtx"       => "text/richtext",
+      ".ru"        => "text/x-script.ruby",
+      ".s"         => "text/x-asm",
+      ".saf"       => "application/vnd.yamaha.smaf-audio",
+      ".sbml"      => "application/sbml+xml",
+      ".sc"        => "application/vnd.ibm.secure-container",
+      ".scd"       => "application/x-msschedule",
+      ".scm"       => "application/vnd.lotus-screencam",
+      ".scq"       => "application/scvp-cv-request",
+      ".scs"       => "application/scvp-cv-response",
+      ".sdkm"      => "application/vnd.solent.sdkm+xml",
+      ".sdp"       => "application/sdp",
+      ".see"       => "application/vnd.seemail",
+      ".sema"      => "application/vnd.sema",
+      ".semd"      => "application/vnd.semd",
+      ".semf"      => "application/vnd.semf",
+      ".setpay"    => "application/set-payment-initiation",
+      ".setreg"    => "application/set-registration-initiation",
+      ".sfd"       => "application/vnd.hydrostatix.sof-data",
+      ".sfs"       => "application/vnd.spotfire.sfs",
+      ".sgm"       => "text/sgml",
+      ".sgml"      => "text/sgml",
+      ".sh"        => "application/x-sh",
+      ".shar"      => "application/x-shar",
+      ".shf"       => "application/shf+xml",
+      ".sig"       => "application/pgp-signature",
+      ".sit"       => "application/x-stuffit",
+      ".sitx"      => "application/x-stuffitx",
+      ".skp"       => "application/vnd.koan",
+      ".slt"       => "application/vnd.epson.salt",
+      ".smi"       => "application/smil+xml",
+      ".snd"       => "audio/basic",
+      ".so"        => "application/octet-stream",
+      ".spf"       => "application/vnd.yamaha.smaf-phrase",
+      ".spl"       => "application/x-futuresplash",
+      ".spot"      => "text/vnd.in3d.spot",
+      ".spp"       => "application/scvp-vp-response",
+      ".spq"       => "application/scvp-vp-request",
+      ".src"       => "application/x-wais-source",
+      ".srx"       => "application/sparql-results+xml",
+      ".sse"       => "application/vnd.kodak-descriptor",
+      ".ssf"       => "application/vnd.epson.ssf",
+      ".ssml"      => "application/ssml+xml",
+      ".stf"       => "application/vnd.wt.stf",
+      ".stk"       => "application/hyperstudio",
+      ".str"       => "application/vnd.pg.format",
+      ".sus"       => "application/vnd.sus-calendar",
+      ".sv4cpio"   => "application/x-sv4cpio",
+      ".sv4crc"    => "application/x-sv4crc",
+      ".svd"       => "application/vnd.svd",
+      ".svg"       => "image/svg+xml",
+      ".svgz"      => "image/svg+xml",
+      ".swf"       => "application/x-shockwave-flash",
+      ".swi"       => "application/vnd.arastra.swi",
+      ".t"         => "text/troff",
+      ".tao"       => "application/vnd.tao.intent-module-archive",
+      ".tar"       => "application/x-tar",
+      ".tbz"       => "application/x-bzip-compressed-tar",
+      ".tcap"      => "application/vnd.3gpp2.tcap",
+      ".tcl"       => "application/x-tcl",
+      ".tex"       => "application/x-tex",
+      ".texi"      => "application/x-texinfo",
+      ".texinfo"   => "application/x-texinfo",
+      ".text"      => "text/plain",
+      ".tif"       => "image/tiff",
+      ".tiff"      => "image/tiff",
+      ".tmo"       => "application/vnd.tmobile-livetv",
+      ".torrent"   => "application/x-bittorrent",
+      ".tpl"       => "application/vnd.groove-tool-template",
+      ".tpt"       => "application/vnd.trid.tpt",
+      ".tr"        => "text/troff",
+      ".tra"       => "application/vnd.trueapp",
+      ".trm"       => "application/x-msterminal",
+      ".tsv"       => "text/tab-separated-values",
+      ".ttf"       => "application/octet-stream",
+      ".twd"       => "application/vnd.simtech-mindmapper",
+      ".txd"       => "application/vnd.genomatix.tuxedo",
+      ".txf"       => "application/vnd.mobius.txf",
+      ".txt"       => "text/plain",
+      ".ufd"       => "application/vnd.ufdl",
+      ".umj"       => "application/vnd.umajin",
+      ".unityweb"  => "application/vnd.unity",
+      ".uoml"      => "application/vnd.uoml+xml",
+      ".uri"       => "text/uri-list",
+      ".ustar"     => "application/x-ustar",
+      ".utz"       => "application/vnd.uiq.theme",
+      ".uu"        => "text/x-uuencode",
+      ".vcd"       => "application/x-cdlink",
+      ".vcf"       => "text/x-vcard",
+      ".vcg"       => "application/vnd.groove-vcard",
+      ".vcs"       => "text/x-vcalendar",
+      ".vcx"       => "application/vnd.vcx",
+      ".vis"       => "application/vnd.visionary",
+      ".viv"       => "video/vnd.vivo",
+      ".vrml"      => "model/vrml",
+      ".vsd"       => "application/vnd.visio",
+      ".vsf"       => "application/vnd.vsf",
+      ".vtu"       => "model/vnd.vtu",
+      ".vxml"      => "application/voicexml+xml",
+      ".war"       => "application/java-archive",
+      ".wav"       => "audio/x-wav",
+      ".wax"       => "audio/x-ms-wax",
+      ".wbmp"      => "image/vnd.wap.wbmp",
+      ".wbs"       => "application/vnd.criticaltools.wbs+xml",
+      ".wbxml"     => "application/vnd.wap.wbxml",
+      ".webm"      => "video/webm",
+      ".wm"        => "video/x-ms-wm",
+      ".wma"       => "audio/x-ms-wma",
+      ".wmd"       => "application/x-ms-wmd",
+      ".wmf"       => "application/x-msmetafile",
+      ".wml"       => "text/vnd.wap.wml",
+      ".wmlc"      => "application/vnd.wap.wmlc",
+      ".wmls"      => "text/vnd.wap.wmlscript",
+      ".wmlsc"     => "application/vnd.wap.wmlscriptc",
+      ".wmv"       => "video/x-ms-wmv",
+      ".wmx"       => "video/x-ms-wmx",
+      ".wmz"       => "application/x-ms-wmz",
+      ".woff"      => "application/octet-stream",
+      ".wpd"       => "application/vnd.wordperfect",
+      ".wpl"       => "application/vnd.ms-wpl",
+      ".wps"       => "application/vnd.ms-works",
+      ".wqd"       => "application/vnd.wqd",
+      ".wri"       => "application/x-mswrite",
+      ".wrl"       => "model/vrml",
+      ".wsdl"      => "application/wsdl+xml",
+      ".wspolicy"  => "application/wspolicy+xml",
+      ".wtb"       => "application/vnd.webturbo",
+      ".wvx"       => "video/x-ms-wvx",
+      ".x3d"       => "application/vnd.hzn-3d-crossword",
+      ".xar"       => "application/vnd.xara",
+      ".xbd"       => "application/vnd.fujixerox.docuworks.binder",
+      ".xbm"       => "image/x-xbitmap",
+      ".xdm"       => "application/vnd.syncml.dm+xml",
+      ".xdp"       => "application/vnd.adobe.xdp+xml",
+      ".xdw"       => "application/vnd.fujixerox.docuworks",
+      ".xenc"      => "application/xenc+xml",
+      ".xer"       => "application/patch-ops-error+xml",
+      ".xfdf"      => "application/vnd.adobe.xfdf",
+      ".xfdl"      => "application/vnd.xfdl",
+      ".xhtml"     => "application/xhtml+xml",
+      ".xif"       => "image/vnd.xiff",
+      ".xls"       => "application/vnd.ms-excel",
+      ".xlsx"      => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+      ".xml"       => "application/xml",
+      ".xo"        => "application/vnd.olpc-sugar",
+      ".xop"       => "application/xop+xml",
+      ".xpm"       => "image/x-xpixmap",
+      ".xpr"       => "application/vnd.is-xpr",
+      ".xps"       => "application/vnd.ms-xpsdocument",
+      ".xpw"       => "application/vnd.intercon.formnet",
+      ".xsl"       => "application/xml",
+      ".xslt"      => "application/xslt+xml",
+      ".xsm"       => "application/vnd.syncml+xml",
+      ".xspf"      => "application/xspf+xml",
+      ".xul"       => "application/vnd.mozilla.xul+xml",
+      ".xwd"       => "image/x-xwindowdump",
+      ".xyz"       => "chemical/x-xyz",
+      ".yaml"      => "text/yaml",
+      ".yml"       => "text/yaml",
+      ".zaz"       => "application/vnd.zzazz.deck+xml",
+      ".zip"       => "application/zip",
+      ".zmm"       => "application/vnd.handheld-entertainment+xml",
     }
   end
 end
diff --git a/lib/rack/mock.rb b/lib/rack/mock.rb
index 008d9c4..75cf364 100644
--- a/lib/rack/mock.rb
+++ b/lib/rack/mock.rb
@@ -57,6 +57,7 @@ module Rack
     def post(uri, opts={})   request("POST", uri, opts)   end
     def put(uri, opts={})    request("PUT", uri, opts)    end
     def delete(uri, opts={}) request("DELETE", uri, opts) end
+    def head(uri, opts={})   request("HEAD", uri, opts)   end
 
     def request(method="GET", uri="", opts={})
       env = self.class.env_for(uri, opts.merge(:method => method))
@@ -182,7 +183,7 @@ module Rack
     end
 
     def empty?
-      [201, 204, 304].include? status
+      [201, 204, 205, 304].include? status
     end
   end
 end
diff --git a/lib/rack/multipart/parser.rb b/lib/rack/multipart/parser.rb
index 6eee64e..2b55cf9 100644
--- a/lib/rack/multipart/parser.rb
+++ b/lib/rack/multipart/parser.rb
@@ -14,6 +14,9 @@ module Rack
 
         fast_forward_to_first_boundary
 
+        max_key_space = Utils.key_space_limit
+        bytes = 0
+
         loop do
           head, filename, content_type, name, body =
             get_current_head_and_filename_and_content_type_and_name_and_body
@@ -28,6 +31,13 @@ module Rack
 
           filename, data = get_data(filename, body, content_type, name, head)
 
+          if name
+            bytes += name.size
+            if bytes > max_key_space
+              raise RangeError, "exceeded available parameter key space"
+            end
+          end
+
           Utils.normalize_params(@params, name, data) unless data.nil?
 
           # break if we're at the end of a buffer, but not if it is the end of a field
diff --git a/lib/rack/request.rb b/lib/rack/request.rb
index 59987bf..fbbf00b 100644
--- a/lib/rack/request.rb
+++ b/lib/rack/request.rb
@@ -72,6 +72,8 @@ module Rack
         'https'
       elsif @env['HTTP_X_FORWARDED_SSL'] == 'on'
         'https'
+      elsif @env['HTTP_X_FORWARDED_SCHEME']
+        @env['HTTP_X_FORWARDED_SCHEME']
       elsif @env['HTTP_X_FORWARDED_PROTO']
         @env['HTTP_X_FORWARDED_PROTO'].split(',')[0]
       else
@@ -113,15 +115,32 @@ module Rack
     def script_name=(s); @env["SCRIPT_NAME"] = s.to_s             end
     def path_info=(s);   @env["PATH_INFO"] = s.to_s               end
 
+
+    # Checks the HTTP request method (or verb) to see if it was of type DELETE
     def delete?;  request_method == "DELETE"  end
+    
+    # Checks the HTTP request method (or verb) to see if it was of type GET
     def get?;     request_method == "GET"     end
+    
+    # Checks the HTTP request method (or verb) to see if it was of type HEAD
     def head?;    request_method == "HEAD"    end
+    
+    # Checks the HTTP request method (or verb) to see if it was of type OPTIONS
     def options?; request_method == "OPTIONS" end
+    
+    # Checks the HTTP request method (or verb) to see if it was of type PATCH
     def patch?;   request_method == "PATCH"   end
+    
+    # Checks the HTTP request method (or verb) to see if it was of type POST
     def post?;    request_method == "POST"    end
+    
+    # Checks the HTTP request method (or verb) to see if it was of type PUT
     def put?;     request_method == "PUT"     end
+    
+    # Checks the HTTP request method (or verb) to see if it was of type TRACE
     def trace?;   request_method == "TRACE"   end
 
+
     # The set of form-data media-types. Requests that do not indicate
     # one of the media types presents in this list will not be eligible
     # for form-data / param parsing.
@@ -230,22 +249,23 @@ module Rack
     end
 
     def cookies
-      return {}  unless @env["HTTP_COOKIE"]
-
-      if @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
-        @env["rack.request.cookie_hash"]
-      else
-        @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
-        # According to RFC 2109:
-        #   If multiple cookies satisfy the criteria above, they are ordered in
-        #   the Cookie header such that those with more specific Path attributes
-        #   precede those with less specific.  Ordering with respect to other
-        #   attributes (e.g., Domain) is unspecified.
-        @env["rack.request.cookie_hash"] =
-          Hash[*Utils.parse_query(@env["rack.request.cookie_string"], ';,').map {|k,v|
-            [k, Array === v ? v.first : v]
-          }.flatten]
-      end
+      hash   = @env["rack.request.cookie_hash"] ||= {}
+      string = @env["HTTP_COOKIE"]
+
+      return hash if string == @env["rack.request.cookie_string"]
+      hash.clear
+
+      # According to RFC 2109:
+      #   If multiple cookies satisfy the criteria above, they are ordered in
+      #   the Cookie header such that those with more specific Path attributes
+      #   precede those with less specific.  Ordering with respect to other
+      #   attributes (e.g., Domain) is unspecified.
+      Utils.parse_query(string, ';,').each { |k,v| hash[k] = Array === v ? v.first : v }
+      @env["rack.request.cookie_string"] = string
+      hash
+    rescue => error
+      error.message.replace "cannot parse Cookie header: #{error.message}"
+      raise
     end
 
     def xhr?
@@ -278,23 +298,35 @@ module Rack
     end
 
     def accept_encoding
-      @env["HTTP_ACCEPT_ENCODING"].to_s.split(/,\s*/).map do |part|
-        m = /^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$/.match(part) # From WEBrick
-
-        if m
-          [m[1], (m[2] || 1.0).to_f]
-        else
-          raise "Invalid value for Accept-Encoding: #{part.inspect}"
+      @env["HTTP_ACCEPT_ENCODING"].to_s.split(/\s*,\s*/).map do |part|
+        encoding, parameters = part.split(/\s*;\s*/, 2)
+        quality = 1.0
+        if parameters and /\Aq=([\d.]+)/ =~ parameters
+          quality = $1.to_f
         end
+        [encoding, quality]
       end
     end
 
+    def trusted_proxy?(ip)
+      ip =~ /^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|^::1$|^fd[0-9a-f]{2}:.+|^localhost$/i
+    end
+
     def ip
-      if addr = @env['HTTP_X_FORWARDED_FOR']
-        (addr.split(',').grep(/\d\./).first || @env['REMOTE_ADDR']).to_s.strip
-      else
-        @env['REMOTE_ADDR']
+      remote_addrs = @env['REMOTE_ADDR'] ? @env['REMOTE_ADDR'].split(/[,\s]+/) : []
+      remote_addrs.reject! { |addr| trusted_proxy?(addr) }
+      
+      return remote_addrs.first if remote_addrs.any?
+
+      forwarded_ips = @env['HTTP_X_FORWARDED_FOR'] ? @env['HTTP_X_FORWARDED_FOR'].strip.split(/[,\s]+/) : []
+
+      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 forwarded_ips.reject { |ip| trusted_proxy?(ip) }.last || @env["REMOTE_ADDR"]
     end
 
     protected
diff --git a/lib/rack/response.rb b/lib/rack/response.rb
index bcc31e3..12180d8 100644
--- a/lib/rack/response.rb
+++ b/lib/rack/response.rb
@@ -19,7 +19,7 @@ module Rack
   class Response
     attr_accessor :length
 
-    def initialize(body=[], status=200, header={}, &block)
+    def initialize(body=[], status=200, header={})
       @status = status.to_i
       @header = Utils::HeaderHash.new("Content-Type" => "text/html").
                                       merge(header)
@@ -71,7 +71,7 @@ module Rack
     def finish(&block)
       @block = block
 
-      if [204, 304].include?(status.to_i)
+      if [204, 205, 304].include?(status.to_i)
         header.delete "Content-Type"
         header.delete "Content-Length"
         [status.to_i, header, []]
@@ -112,19 +112,21 @@ module Rack
     alias headers header
 
     module Helpers
-      def invalid?;       @status < 100 || @status >= 600;       end
+      def invalid?;       status < 100 || status >= 600;        end
 
-      def informational?; @status >= 100 && @status < 200;       end
-      def successful?;    @status >= 200 && @status < 300;       end
-      def redirection?;   @status >= 300 && @status < 400;       end
-      def client_error?;  @status >= 400 && @status < 500;       end
-      def server_error?;  @status >= 500 && @status < 600;       end
+      def informational?; status >= 100 && status < 200;        end
+      def successful?;    status >= 200 && status < 300;        end
+      def redirection?;   status >= 300 && status < 400;        end
+      def client_error?;  status >= 400 && status < 500;        end
+      def server_error?;  status >= 500 && status < 600;        end
 
-      def ok?;            @status == 200;                        end
-      def forbidden?;     @status == 403;                        end
-      def not_found?;     @status == 404;                        end
+      def ok?;            status == 200;                        end
+      def bad_request?;   status == 400;                        end
+      def forbidden?;     status == 403;                        end
+      def not_found?;     status == 404;                        end
+      def unprocessable?; status == 422;                        end
 
-      def redirect?;      [301, 302, 303, 307].include? @status; end
+      def redirect?;      [301, 302, 303, 307].include? status; end
 
       # Headers
       attr_reader :headers, :original_headers
diff --git a/lib/rack/sendfile.rb b/lib/rack/sendfile.rb
index 367d689..c82145a 100644
--- a/lib/rack/sendfile.rb
+++ b/lib/rack/sendfile.rb
@@ -46,10 +46,11 @@ module Rack
   #     proxy_pass         http://127.0.0.1:8080/;
   #   }
   #
-  # Note that the X-Sendfile-Type header must be set exactly as shown above. The
-  # X-Accel-Mapping header should specify the internal URI path, followed by an
-  # equals sign (=), followed name of the location in the file system that it maps
-  # to. The middleware performs a simple substitution on the resulting path.
+  # Note that the X-Sendfile-Type header must be set exactly as shown above.
+  # The X-Accel-Mapping header should specify the location on the file system,
+  # followed by an equals sign (=), followed name of the private URL pattern
+  # that it maps to. The middleware performs a simple substitution on the
+  # resulting path.
   #
   # See Also: http://wiki.codemongers.com/NginxXSendfile
   #
@@ -80,7 +81,7 @@ module Rack
   #
   # X-Sendfile is supported under Apache 2.x using a separate module:
   #
-  # http://tn123.ath.cx/mod_xsendfile/
+  # https://tn123.org/mod_xsendfile/
   #
   # Once the module is compiled and installed, you can enable it using
   # XSendFile config directive:
@@ -104,10 +105,11 @@ module Rack
         when 'X-Accel-Redirect'
           path = F.expand_path(body.to_path)
           if url = map_accel_path(env, path)
+            headers['Content-Length'] = '0'
             headers[type] = url
             body = []
           else
-            env['rack.errors'] << "X-Accel-Mapping header missing"
+            env['rack.errors'].puts "X-Accel-Mapping header missing"
           end
         when 'X-Sendfile', 'X-Lighttpd-Send-File'
           path = F.expand_path(body.to_path)
@@ -116,7 +118,7 @@ module Rack
           body = []
         when '', nil
         else
-          env['rack.errors'] << "Unknown x-sendfile variation: '#{variation}'.\n"
+          env['rack.errors'].puts "Unknown x-sendfile variation: '#{variation}'.\n"
         end
       end
       [status, headers, body]
diff --git a/lib/rack/server.rb b/lib/rack/server.rb
index 3c6584d..a8546bf 100644
--- a/lib/rack/server.rb
+++ b/lib/rack/server.rb
@@ -226,7 +226,7 @@ module Rack
       self.class.middleware
     end
 
-    def start
+    def start &blk
       if options[:warn]
         $-w = true
       end
@@ -262,7 +262,7 @@ module Rack
         end
       end
 
-      server.run wrapped_app, options
+      server.run wrapped_app, options, &blk
     end
 
     def server
diff --git a/lib/rack/session/abstract/id.rb b/lib/rack/session/abstract/id.rb
index 16481ab..641e3c0 100644
--- a/lib/rack/session/abstract/id.rb
+++ b/lib/rack/session/abstract/id.rb
@@ -95,8 +95,11 @@ module Rack
         end
 
         def inspect
-          load_for_read!
-          super
+          if loaded?
+            super
+          else
+            "#<#{self.class}:0x#{self.object_id.to_s(16)} not yet loaded>"
+          end
         end
 
         def exists?
@@ -108,6 +111,11 @@ module Rack
           @loaded
         end
 
+        def empty?
+          load_for_read!
+          super
+        end
+
       private
 
         def load_for_read!
@@ -144,7 +152,9 @@ module Rack
       #   'rack.session'
       # * :path, :domain, :expire_after, :secure, and :httponly set the related
       #   cookie options as by Rack::Response#add_cookie
-      # * :defer will not set a cookie in the response.
+      # * :skip will not a set a cookie in the response nor update the session state
+      # * :defer will not set a cookie in the response but still update the session
+      #   state if it is used with a backend
       # * :renew (implementation dependent) will prompt the generation of a new
       #   session id, and migration of data to be referenced at the new id. If
       #   :defer is set, it will be overridden and the cookie will be set.
@@ -260,21 +270,30 @@ module Rack
         end
 
         # Session should be commited if it was loaded, any of specific options like :renew, :drop
-        # or :expire_after was given and the security permissions match.
+        # or :expire_after was given and the security permissions match. Skips if skip is given.
 
         def commit_session?(env, session, options)
-          (loaded_session?(session) || force_options?(options)) && secure_session?(env, options)
+          if options[:skip]
+            false
+          else
+            has_session = loaded_session?(session) || forced_session_update?(session, options)
+            has_session && security_matches?(env, options)
+          end
         end
 
         def loaded_session?(session)
           !session.is_a?(SessionHash) || session.loaded?
         end
 
+        def forced_session_update?(session, options)
+          force_options?(options) && session && !session.empty?
+        end
+
         def force_options?(options)
           options.values_at(:renew, :drop, :defer, :expire_after).any?
         end
 
-        def secure_session?(env, options)
+        def security_matches?(env, options)
           return true unless options[:secure]
           request = Rack::Request.new(env)
           request.ssl?
diff --git a/lib/rack/session/cookie.rb b/lib/rack/session/cookie.rb
index 0cf9b8d..1d87384 100644
--- a/lib/rack/session/cookie.rb
+++ b/lib/rack/session/cookie.rb
@@ -14,6 +14,7 @@ module Rack
     # Both methods must take a string and return a string.
     #
     # When the secret key is set, cookie data is checked for data integrity.
+    # The old secret key is also accepted and allows graceful secret rotation.
     #
     # Example:
     #
@@ -21,14 +22,15 @@ module Rack
     #                                :domain => 'foo.com',
     #                                :path => '/',
     #                                :expire_after => 2592000,
-    #                                :secret => 'change_me'
+    #                                :secret => 'change_me',
+    #                                :old_secret => 'also_change_me'
     #
     #     All parameters are optional.
     #
     # Example of a cookie with no encoding:
     #
     #   Rack::Session::Cookie.new(application, {
-    #     :coder => Racke::Session::Cookie::Identity.new
+    #     :coder => Rack::Session::Cookie::Identity.new
     #   })
     #
     # Example of a cookie with custom encoding:
@@ -80,6 +82,7 @@ module Rack
 
       def initialize(app, options={})
         @secret = options[:secret]
+        @old_secret = options[:old_secret]
         @coder  = options[:coder] ||= Base64::Marshal.new
         super(app, options.merge!(:cookie_only => true))
       end
@@ -101,9 +104,11 @@ module Rack
           request = Rack::Request.new(env)
           session_data = request.cookies[@key]
 
-          if @secret && session_data
+          if (@secret || @old_secret) && session_data
             session_data, digest = session_data.split("--")
-            session_data = nil  unless digest == generate_hmac(session_data)
+            if (digest != generate_hmac(session_data, @secret)) && (digest != generate_hmac(session_data, @old_secret))
+              session_data = nil
+            end
           end
 
           coder.decode(session_data) || {}
@@ -127,7 +132,7 @@ module Rack
         session_data = coder.encode(session)
 
         if @secret
-          session_data = "#{session_data}--#{generate_hmac(session_data)}"
+          session_data = "#{session_data}--#{generate_hmac(session_data, @secret)}"
         end
 
         if session_data.size > (4096 - @key.size)
@@ -143,8 +148,8 @@ module Rack
         generate_sid unless options[:drop]
       end
 
-      def generate_hmac(data)
-        OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, @secret, data)
+      def generate_hmac(data, secret)
+        OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data)
       end
 
     end
diff --git a/lib/rack/showexceptions.rb b/lib/rack/showexceptions.rb
index 00ddf45..c91ca07 100644
--- a/lib/rack/showexceptions.rb
+++ b/lib/rack/showexceptions.rb
@@ -277,7 +277,7 @@ TEMPLATE = <<'HTML'
   <h2>Request information</h2>
 
   <h3 id="get-info">GET</h3>
-  <% unless req.GET.empty? %>
+  <% if req.GET and not req.GET.empty? %>
     <table class="req">
       <thead>
         <tr>
@@ -299,7 +299,7 @@ TEMPLATE = <<'HTML'
   <% end %>
 
   <h3 id="post-info">POST</h3>
-  <% unless req.POST.empty? %>
+  <% if req.POST and not req.POST.empty? %>
     <table class="req">
       <thead>
         <tr>
diff --git a/lib/rack/static.rb b/lib/rack/static.rb
index f3cac7b..1b843a0 100644
--- a/lib/rack/static.rb
+++ b/lib/rack/static.rb
@@ -22,6 +22,12 @@ module Rack
   #
   #     use Rack::Static, :urls => {"/" => 'index.html'}, :root => 'public'
   #
+  # Serve all requests normally from the folder "public" in the current
+  # directory but uses index.html as default route for "/"
+  #
+  #     use Rack::Static, :urls => [""], :root => 'public', :index =>
+  #     'public/index.html'
+  #
   # Set a fixed Cache-Control header for all served files:
   #
   #     use Rack::Static, :root => 'public', :cache_control => 'public'
@@ -32,22 +38,29 @@ module Rack
     def initialize(app, options={})
       @app = app
       @urls = options[:urls] || ["/favicon.ico"]
+      @index = options[:index] || "index.html"
       root = options[:root] || Dir.pwd
       cache_control = options[:cache_control]
       @file_server = Rack::File.new(root, cache_control)
     end
 
+    def overwrite_file_path(path)
+      @urls.kind_of?(Hash) && @urls.key?(path) || @index && path == '/'
+    end
+
+    def route_file(path)
+      @urls.kind_of?(Array) && @urls.any? { |url| path.index(url) == 0 }
+    end
+
+    def can_serve(path)
+      route_file(path) || overwrite_file_path(path)
+    end
+
     def call(env)
       path = env["PATH_INFO"]
 
-      unless @urls.kind_of? Hash
-        can_serve = @urls.any? { |url| path.index(url) == 0 }
-      else
-        can_serve = @urls.key? path
-      end
-
-      if can_serve
-        env["PATH_INFO"] = @urls[path] if @urls.kind_of? Hash
+      if can_serve(path)
+        env["PATH_INFO"] = (path == '/' ? @index : @urls[path]) if overwrite_file_path(path)
         @file_server.call(env)
       else
         @app.call(env)
diff --git a/lib/rack/urlmap.rb b/lib/rack/urlmap.rb
index 4d6b8c6..d3b95a5 100644
--- a/lib/rack/urlmap.rb
+++ b/lib/rack/urlmap.rb
@@ -19,9 +19,6 @@ module Rack
     end
 
     def remap(map)
-      longest_path_first = lambda do |(host, location, _, _)|
-        [host ? -host.size : NEGATIVE_INFINITY, -location.size]
-      end
       @mapping = map.map { |location, app|
         if location =~ %r{\Ahttps?://(.*?)(/.*)}
           host, location = $1, $2
@@ -32,28 +29,46 @@ module Rack
         unless location[0] == ?/
           raise ArgumentError, "paths need to start with /"
         end
+
         location = location.chomp('/')
         match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
 
         [host, location, match, app]
-      }.sort_by(&longest_path_first)
+      }.sort_by do |(host, location, _, _)|
+        [host ? -host.size : NEGATIVE_INFINITY, -location.size]
+      end
     end
 
     def call(env)
       path = env["PATH_INFO"]
       script_name = env['SCRIPT_NAME']
-      hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT')
-      @mapping.each { |host, location, match, app|
-        next unless (hHost == host || sName == host \
-          || (host.nil? && (hHost == sName || hHost == sName+':'+sPort)))
-        next unless path.to_s =~ match && rest = $1
-        next unless rest.empty? || rest[0] == ?/
-        env.merge!('SCRIPT_NAME' => (script_name + location), 'PATH_INFO' => rest)
+      hHost = env['HTTP_HOST']
+      sName = env['SERVER_NAME']
+      sPort = env['SERVER_PORT']
+
+      @mapping.each do |host, location, match, app|
+        unless hHost == host \
+            || sName == host \
+            || (!host && (hHost == sName || hHost == sName+':'+sPort))
+          next
+        end
+
+        next unless m = match.match(path.to_s)
+
+        rest = m[1]
+        next unless !rest || rest.empty? || rest[0] == ?/
+
+        env['SCRIPT_NAME'] = (script_name + location)
+        env['PATH_INFO'] = rest
+
         return app.call(env)
-      }
+      end
+
       [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
+
     ensure
-      env.merge! 'PATH_INFO' => path, 'SCRIPT_NAME' => script_name
+      env['PATH_INFO'] = path
+      env['SCRIPT_NAME'] = script_name
     end
   end
 end
diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb
index 9e726c8..7bceb45 100644
--- a/lib/rack/utils.rb
+++ b/lib/rack/utils.rb
@@ -6,9 +6,10 @@ require 'rack/multipart'
 
 major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i }
 
-if (major == 1 && minor < 9) || (major == 1 && minor == 9 && patch < 2)
-  # pull in backports
-  require 'rack/backports/uri/common'
+if major == 1 && minor < 9
+  require 'rack/backports/uri/common_18'
+elsif major == 1 && minor == 9 && patch < 3
+  require 'rack/backports/uri/common_192'
 else
   require 'uri/common'
 end
@@ -18,7 +19,7 @@ module Rack
   # applications adopted from all kinds of Ruby libraries.
 
   module Utils
-    # URI escapes a string. (CGI style space to +)
+    # URI escapes. (CGI style space to +)
     def escape(s)
       URI.encode_www_form_component(s)
     end
@@ -31,14 +32,29 @@ module Rack
     end
     module_function :escape_path
 
-    # Unescapes a URI escaped string.
-    def unescape(s)
-      URI.decode_www_form_component(s)
+    # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
+    # target encoding of the string returned, and it defaults to UTF-8
+    if defined?(::Encoding)
+      def unescape(s, encoding = Encoding::UTF_8)
+        URI.decode_www_form_component(s, encoding)
+      end
+    else
+      def unescape(s, encoding = nil)
+        URI.decode_www_form_component(s, encoding)
+      end
     end
     module_function :unescape
 
     DEFAULT_SEP = /[&;] */n
 
+    class << self
+      attr_accessor :key_space_limit
+    end
+
+    # The default number of bytes to allow parameter keys to take up.
+    # This helps prevent a rogue client from flooding a Request.
+    self.key_space_limit = 65536
+
     # Stolen from Mongrel, with some small modifications:
     # Parses a query string by breaking it up at the '&'
     # and ';' characters.  You can also use this to parse
@@ -47,8 +63,19 @@ module Rack
     def parse_query(qs, d = nil)
       params = {}
 
+      max_key_space = Utils.key_space_limit
+      bytes = 0
+
       (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
         k, v = p.split('=', 2).map { |x| unescape(x) }
+
+        if k
+          bytes += k.size
+          if bytes > max_key_space
+            raise RangeError, "exceeded available parameter key space"
+          end
+        end
+
         if cur = params[k]
           if cur.class == Array
             params[k] << v
@@ -67,8 +94,19 @@ module Rack
     def parse_nested_query(qs, d = nil)
       params = {}
 
+      max_key_space = Utils.key_space_limit
+      bytes = 0
+
       (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
         k, v = p.split('=', 2).map { |s| unescape(s) }
+
+        if k
+          bytes += k.size
+          if bytes > max_key_space
+            raise RangeError, "exceeded available parameter key space"
+          end
+        end
+
         normalize_params(params, k, v)
       end
 
@@ -113,7 +151,7 @@ module Rack
         if v.class == Array
           build_query(v.map { |x| [k, x] })
         else
-          "#{escape(k)}=#{escape(v)}"
+          v.nil? ? escape(k) : "#{escape(k)}=#{escape(v)}"
         end
       }.join("&")
     end
@@ -146,7 +184,13 @@ module Rack
       '"' => """,
       "/" => "&#x2F;"
     }
-    ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
+    if //.respond_to?(:encoding)
+      ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
+    else
+      # On 1.8, there is a kcode = 'u' bug that allows for XSS otherwhise
+      # TODO doesn't apply to jruby, so a better condition above might be preferable?
+      ESCAPE_HTML_PATTERN = /#{Regexp.union(*ESCAPE_HTML.keys)}/n
+    end
 
     # Escape ampersands, brackets and quotes to their HTML/XML entities.
     def escape_html(string)
@@ -228,6 +272,8 @@ module Rack
       cookies.reject! { |cookie|
         if value[:domain]
           cookie =~ /\A#{escape(key)}=.*domain=#{value[:domain]}/
+        elsif value[:path]
+          cookie =~ /\A#{escape(key)}=.*path=#{value[:path]}/
         else
           cookie =~ /\A#{escape(key)}=/
         end
@@ -443,6 +489,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',
@@ -459,7 +506,7 @@ module Rack
     }
 
     # Responses with HTTP status codes that should not have an entity body
-    STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 304)
+    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]
diff --git a/metadata.yml b/metadata.yml
index 2261efc..3234b9b 100644
--- a/metadata.yml
+++ b/metadata.yml
@@ -1,13 +1,13 @@
 --- !ruby/object:Gem::Specification 
 name: rack
 version: !ruby/object:Gem::Version 
-  hash: 25
+  hash: 7
   prerelease: 
   segments: 
   - 1
-  - 3
-  - 1
-  version: 1.3.1
+  - 4
+  - 0
+  version: 1.4.0
 platform: ruby
 authors: 
 - Christian Neukirchen
@@ -15,7 +15,7 @@ autorequire:
 bindir: bin
 cert_chain: []
 
-date: 2011-07-13 00:00:00 Z
+date: 2011-12-28 00:00:00 Z
 dependencies: 
 - !ruby/object:Gem::Dependency 
   name: bacon
@@ -46,7 +46,7 @@ dependencies:
   type: :development
   version_requirements: *id002
 - !ruby/object:Gem::Dependency 
-  name: fcgi
+  name: ruby-fcgi
   prerelease: false
   requirement: &id003 !ruby/object:Gem::Requirement 
     none: false
@@ -81,10 +81,14 @@ dependencies:
     requirements: 
     - - ">="
       - !ruby/object:Gem::Version 
-        hash: 3
+        hash: 1923831981
         segments: 
+        - 1
+        - 2
         - 0
-        version: "0"
+        - pre
+        - 2
+        version: 1.2.0.pre2
   type: :development
   version_requirements: *id005
 - !ruby/object:Gem::Dependency 
@@ -102,7 +106,7 @@ dependencies:
   type: :development
   version_requirements: *id006
 description: |
-  Rack provides minimal, modular and adaptable interface for developing
+  Rack provides a minimal, modular and adaptable interface for developing
   web applications in Ruby.  By wrapping HTTP requests and responses in
   the simplest way possible, it unifies and distills the API for web
   servers, web frameworks, and software in between (the so-called
@@ -131,7 +135,8 @@ files:
 - lib/rack/auth/digest/nonce.rb
 - lib/rack/auth/digest/params.rb
 - lib/rack/auth/digest/request.rb
-- lib/rack/backports/uri/common.rb
+- lib/rack/backports/uri/common_18.rb
+- lib/rack/backports/uri/common_192.rb
 - lib/rack/body_proxy.rb
 - lib/rack/builder.rb
 - lib/rack/cascade.rb
@@ -194,6 +199,7 @@ files:
 - test/cgi/rackup_stub.rb
 - test/cgi/sample_rackup.ru
 - test/cgi/test
+- test/cgi/test+directory/test+file
 - test/cgi/test.fcgi
 - test/cgi/test.ru
 - test/gemloader.rb
@@ -263,6 +269,7 @@ files:
 - test/spec_urlmap.rb
 - test/spec_utils.rb
 - test/spec_webrick.rb
+- test/static/index.html
 - test/testrequest.rb
 - test/unregistered_handler/rack/handler/unregistered.rb
 - test/unregistered_handler/rack/handler/unregistered_long_one.rb
@@ -301,7 +308,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
 requirements: []
 
 rubyforge_project: rack
-rubygems_version: 1.8.5
+rubygems_version: 1.8.12
 signing_key: 
 specification_version: 3
 summary: a modular Ruby webserver interface
diff --git a/rack.gemspec b/rack.gemspec
index 971a99c..0d6fa07 100644
--- a/rack.gemspec
+++ b/rack.gemspec
@@ -1,11 +1,11 @@
 Gem::Specification.new do |s|
   s.name            = "rack"
-  s.version         = "1.3.1"
+  s.version         = "1.4.0"
   s.platform        = Gem::Platform::RUBY
   s.summary         = "a modular Ruby webserver interface"
 
   s.description = <<-EOF
-Rack provides minimal, modular and adaptable interface for developing
+Rack provides a minimal, modular and adaptable interface for developing
 web applications in Ruby.  By wrapping HTTP requests and responses in
 the simplest way possible, it unifies and distills the API for web
 servers, web frameworks, and software in between (the so-called
@@ -30,8 +30,8 @@ EOF
   s.add_development_dependency 'bacon'
   s.add_development_dependency 'rake'
 
-  s.add_development_dependency 'fcgi'
+  s.add_development_dependency 'ruby-fcgi'
   s.add_development_dependency 'memcache-client'
-  s.add_development_dependency 'mongrel'
+  s.add_development_dependency 'mongrel', '>= 1.2.0.pre2'
   s.add_development_dependency 'thin'
 end
diff --git a/test/builder/end.ru b/test/builder/end.ru
index 8eea56e..7f36d8c 100644
--- a/test/builder/end.ru
+++ b/test/builder/end.ru
@@ -1,3 +1,5 @@
 run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
 __END__
 Should not be evaluated
+Neither should
+This
diff --git a/test/cgi/lighttpd.conf b/test/cgi/lighttpd.conf
index 721b76a..c195f78 100755
--- a/test/cgi/lighttpd.conf
+++ b/test/cgi/lighttpd.conf
@@ -2,6 +2,7 @@ server.modules = ("mod_fastcgi", "mod_cgi")
 server.document-root = "."
 server.errorlog = var.CWD + "/lighttpd.errors"
 server.port = 9203
+server.bind = "127.0.0.1"
 
 server.event-handler = "select"
 
diff --git a/test/cgi/sample_rackup.ru b/test/cgi/sample_rackup.ru
index 86d99e6..a73df81 100755
--- a/test/cgi/sample_rackup.ru
+++ b/test/cgi/sample_rackup.ru
@@ -2,4 +2,4 @@
 
 require '../testrequest'
 
-run TestRequest.new
+run Rack::Lint.new(TestRequest.new)
diff --git a/test/cgi/test+directory/test+file b/test/cgi/test+directory/test+file
new file mode 100644
index 0000000..f4273fb
--- /dev/null
+++ b/test/cgi/test+directory/test+file
@@ -0,0 +1 @@
+this file has plusses!
diff --git a/test/cgi/test.ru b/test/cgi/test.ru
index bd3ee72..7913ef7 100755
--- a/test/cgi/test.ru
+++ b/test/cgi/test.ru
@@ -2,4 +2,4 @@
 # -*- ruby -*-
 
 require '../testrequest'
-run TestRequest.new
+run Rack::Lint.new(TestRequest.new)
diff --git a/test/gemloader.rb b/test/gemloader.rb
index 382e8b8..22be697 100644
--- a/test/gemloader.rb
+++ b/test/gemloader.rb
@@ -2,5 +2,9 @@ require 'rubygems'
 project = 'rack'
 gemspec = File.expand_path("#{project}.gemspec", Dir.pwd)
 Gem::Specification.load(gemspec).dependencies.each do |dep|
-  gem dep.name, *dep.requirement.as_list
-end
\ No newline at end of file
+  begin
+    gem dep.name, *dep.requirement.as_list
+  rescue Gem::LoadError
+    warn "Cannot load #{dep.name} #{dep.requirement.to_s}"
+  end
+end
diff --git a/test/spec_auth_basic.rb b/test/spec_auth_basic.rb
index 5ef2643..145f6b9 100644
--- a/test/spec_auth_basic.rb
+++ b/test/spec_auth_basic.rb
@@ -1,4 +1,5 @@
 require 'rack/auth/basic'
+require 'rack/lint'
 require 'rack/mock'
 
 describe Rack::Auth::Basic do
@@ -7,7 +8,9 @@ describe Rack::Auth::Basic do
   end
 
   def unprotected_app
-    lambda { |env| [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}"] ] }
+    Rack::Lint.new lambda { |env|
+      [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}"] ]
+    }
   end
 
   def protected_app
diff --git a/test/spec_auth_digest.rb b/test/spec_auth_digest.rb
index c85d717..1828923 100644
--- a/test/spec_auth_digest.rb
+++ b/test/spec_auth_digest.rb
@@ -1,4 +1,5 @@
 require 'rack/auth/digest/md5'
+require 'rack/lint'
 require 'rack/mock'
 
 describe Rack::Auth::Digest::MD5 do
@@ -7,10 +8,10 @@ describe Rack::Auth::Digest::MD5 do
   end
 
   def unprotected_app
-    lambda do |env|
+    Rack::Lint.new lambda { |env|
       friend = Rack::Utils.parse_query(env["QUERY_STRING"])["friend"]
       [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}#{friend ? " and #{friend}" : ''}"] ]
-    end
+    }
   end
 
   def protected_app
diff --git a/test/spec_body_proxy.rb b/test/spec_body_proxy.rb
index 5c5a080..64bd65f 100644
--- a/test/spec_body_proxy.rb
+++ b/test/spec_body_proxy.rb
@@ -35,9 +35,14 @@ describe Rack::BodyProxy do
   should 'not close more than one time' do
     count = 0
     proxy = Rack::BodyProxy.new([]) { count += 1; raise "Block invoked more than 1 time!" if count > 1 }
+    2.times { proxy.close }
+    count.should.equal 1
+  end
+
+  should 'be closed when the callback is triggered' do
+    closed = false
+    proxy = Rack::BodyProxy.new([]) { closed = proxy.closed? }
     proxy.close
-    lambda {
-      proxy.close
-    }.should.raise(IOError)
+    closed.should.equal true
   end
 end
diff --git a/test/spec_builder.rb b/test/spec_builder.rb
index f7501ae..eb497df 100644
--- a/test/spec_builder.rb
+++ b/test/spec_builder.rb
@@ -1,4 +1,5 @@
 require 'rack/builder'
+require 'rack/lint'
 require 'rack/mock'
 require 'rack/showexceptions'
 require 'rack/urlmap'
@@ -18,38 +19,46 @@ class NothingMiddleware
 end
 
 describe Rack::Builder do
+  def builder(&block)
+    Rack::Lint.new Rack::Builder.new(&block)
+  end
+  
+  def builder_to_app(&block)
+    Rack::Lint.new Rack::Builder.new(&block).to_app
+  end
+  
   it "supports mapping" do
-    app = Rack::Builder.new do
+    app = builder_to_app do
       map '/' do |outer_env|
-        run lambda { |inner_env| [200, {}, ['root']] }
+        run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['root']] }
       end
       map '/sub' do
-        run lambda { |inner_env| [200, {}, ['sub']] }
+        run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['sub']] }
       end
-    end.to_app
+    end
     Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'root'
     Rack::MockRequest.new(app).get("/sub").body.to_s.should.equal 'sub'
   end
 
   it "doesn't dupe env even when mapping" do
-    app = Rack::Builder.new do
+    app = builder_to_app do
       use NothingMiddleware
       map '/' do |outer_env|
         run lambda { |inner_env|
           inner_env['new_key'] = 'new_value'
-          [200, {}, ['root']]
+          [200, {"Content-Type" => "text/plain"}, ['root']]
         }
       end
-    end.to_app
+    end
     Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'root'
     NothingMiddleware.env['new_key'].should.equal 'new_value'
   end
 
   it "chains apps by default" do
-    app = Rack::Builder.new do
+    app = builder_to_app do
       use Rack::ShowExceptions
       run lambda { |env| raise "bzzzt" }
-    end.to_app
+    end
 
     Rack::MockRequest.new(app).get("/").should.be.server_error
     Rack::MockRequest.new(app).get("/").should.be.server_error
@@ -57,7 +66,7 @@ describe Rack::Builder do
   end
 
   it "has implicit #to_app" do
-    app = Rack::Builder.new do
+    app = builder do
       use Rack::ShowExceptions
       run lambda { |env| raise "bzzzt" }
     end
@@ -68,13 +77,13 @@ describe Rack::Builder do
   end
 
   it "supports blocks on use" do
-    app = Rack::Builder.new do
+    app = builder do
       use Rack::ShowExceptions
       use Rack::Auth::Basic do |username, password|
         'secret' == password
       end
 
-      run lambda { |env| [200, {}, ['Hi Boss']] }
+      run lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hi Boss']] }
     end
 
     response = Rack::MockRequest.new(app).get("/")
@@ -89,7 +98,7 @@ describe Rack::Builder do
   end
 
   it "has explicit #to_app" do
-    app = Rack::Builder.app do
+    app = builder do
       use Rack::ShowExceptions
       run lambda { |env| raise "bzzzt" }
     end
@@ -99,8 +108,30 @@ describe Rack::Builder do
     Rack::MockRequest.new(app).get("/").should.be.server_error
   end
 
+  it "can mix map and run for endpoints" do
+    app = builder do
+      map '/sub' do
+        run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['sub']] }
+      end
+      run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['root']] }
+    end
+
+    Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'root'
+    Rack::MockRequest.new(app).get("/sub").body.to_s.should.equal 'sub'
+  end
+
+  it "accepts middleware-only map blocks" do
+    app = builder do
+      map('/foo') { use Rack::ShowExceptions }
+      run lambda { |env| raise "bzzzt" }
+    end
+
+    proc { Rack::MockRequest.new(app).get("/") }.should.raise(RuntimeError)
+    Rack::MockRequest.new(app).get("/foo").should.be.server_error
+  end
+
   should "initialize apps once" do
-    app = Rack::Builder.new do
+    app = builder do
       class AppClass
         def initialize
           @called = 0
@@ -120,6 +151,23 @@ describe Rack::Builder do
     Rack::MockRequest.new(app).get("/").should.be.server_error
   end
 
+  it "allows use after run" do
+    app = builder do
+      run lambda { |env| raise "bzzzt" }
+      use Rack::ShowExceptions
+    end
+
+    Rack::MockRequest.new(app).get("/").should.be.server_error
+    Rack::MockRequest.new(app).get("/").should.be.server_error
+    Rack::MockRequest.new(app).get("/").should.be.server_error
+  end
+
+  it 'complains about a missing run' do
+    proc do
+      Rack::Lint.new Rack::Builder.app { use Rack::ShowExceptions }
+    end.should.raise(RuntimeError)
+  end
+
   describe "parse_file" do
     def config_file(name)
       File.join(File.dirname(__FILE__), 'builder', name)
@@ -133,6 +181,7 @@ describe Rack::Builder do
 
     it "removes __END__ before evaluating app" do
       app, options = Rack::Builder.parse_file config_file('end.ru')
+      options = nil # ignored, prevents warning
       Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'OK'
     end
 
diff --git a/test/spec_cascade.rb b/test/spec_cascade.rb
index c2c4920..cfd1164 100644
--- a/test/spec_cascade.rb
+++ b/test/spec_cascade.rb
@@ -1,9 +1,14 @@
 require 'rack/cascade'
 require 'rack/file'
+require 'rack/lint'
 require 'rack/urlmap'
 require 'rack/mock'
 
 describe Rack::Cascade do
+  def cascade(*args)
+    Rack::Lint.new Rack::Cascade.new(*args)
+  end
+  
   docroot = File.expand_path(File.dirname(__FILE__))
   app1 = Rack::File.new(docroot)
 
@@ -13,20 +18,20 @@ describe Rack::Cascade do
                             [200, { "Content-Type" => "text/plain"}, [""]]})
 
   should "dispatch onward on 404 by default" do
-    cascade = Rack::Cascade.new([app1, app2, app3])
+    cascade = cascade([app1, app2, app3])
     Rack::MockRequest.new(cascade).get("/cgi/test").should.be.ok
     Rack::MockRequest.new(cascade).get("/foo").should.be.ok
     Rack::MockRequest.new(cascade).get("/toobad").should.be.not_found
-    Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.forbidden
+    Rack::MockRequest.new(cascade).get("/cgi/../..").should.be.forbidden
   end
 
   should "dispatch onward on whatever is passed" do
-    cascade = Rack::Cascade.new([app1, app2, app3], [404, 403])
+    cascade = cascade([app1, app2, app3], [404, 403])
     Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.not_found
   end
 
   should "return 404 if empty" do
-    Rack::MockRequest.new(Rack::Cascade.new([])).get('/').should.be.not_found
+    Rack::MockRequest.new(cascade([])).get('/').should.be.not_found
   end
 
   should "append new app" do
@@ -37,7 +42,7 @@ describe Rack::Cascade do
     Rack::MockRequest.new(cascade).get('/cgi/../bla').should.be.not_found
     cascade << app1
     Rack::MockRequest.new(cascade).get('/cgi/test').should.be.ok
-    Rack::MockRequest.new(cascade).get('/cgi/../bla').should.be.forbidden
+    Rack::MockRequest.new(cascade).get('/cgi/../..').should.be.forbidden
     Rack::MockRequest.new(cascade).get('/foo').should.be.not_found
     cascade << app3
     Rack::MockRequest.new(cascade).get('/foo').should.be.ok
diff --git a/test/spec_cgi.rb b/test/spec_cgi.rb
index 345a49a..ebfee02 100644
--- a/test/spec_cgi.rb
+++ b/test/spec_cgi.rb
@@ -5,7 +5,7 @@ require 'rack/handler/cgi'
 describe Rack::Handler::CGI do
   extend TestRequest::Helpers
 
-  @host = '0.0.0.0'
+  @host = '127.0.0.1'
   @port = 9203
 
   if `which lighttpd` && !$?.success?
diff --git a/test/spec_chunked.rb b/test/spec_chunked.rb
index 04c22fd..1aa5130 100644
--- a/test/spec_chunked.rb
+++ b/test/spec_chunked.rb
@@ -1,31 +1,56 @@
 require 'rack/chunked'
+require 'rack/lint'
 require 'rack/mock'
 
 describe Rack::Chunked do
+  Enumerator = ::Enumerable::Enumerator unless defined?(Enumerator)
+
+  def chunked(app)
+    proc do |env|
+      app = Rack::Chunked.new(app)
+      Rack::Lint.new(app).call(env).tap do |response|
+        # we want to use body like an array, but it only has #each
+        response[2] = Enumerator.new(response[2]).to_a
+      end
+    end
+  end
+  
   before do
     @env = Rack::MockRequest.
       env_for('/', 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => 'GET')
   end
 
   should 'chunk responses with no Content-Length' do
-    app = lambda { |env| [200, {}, ['Hello', ' ', 'World!']] }
-    response = Rack::MockResponse.new(*Rack::Chunked.new(app).call(@env))
+    app = lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hello', ' ', 'World!']] }
+    response = Rack::MockResponse.new(*chunked(app).call(@env))
     response.headers.should.not.include 'Content-Length'
     response.headers['Transfer-Encoding'].should.equal 'chunked'
     response.body.should.equal "5\r\nHello\r\n1\r\n \r\n6\r\nWorld!\r\n0\r\n\r\n"
   end
 
   should 'chunks empty bodies properly' do
-    app = lambda { |env| [200, {}, []] }
-    response = Rack::MockResponse.new(*Rack::Chunked.new(app).call(@env))
+    app = lambda { |env| [200, {"Content-Type" => "text/plain"}, []] }
+    response = Rack::MockResponse.new(*chunked(app).call(@env))
     response.headers.should.not.include 'Content-Length'
     response.headers['Transfer-Encoding'].should.equal 'chunked'
     response.body.should.equal "0\r\n\r\n"
   end
 
+  should 'chunks encoded bodies properly' do
+    body = ["\uFFFEHello", " ", "World"].map {|t| t.encode("UTF-16LE") }
+    app  = lambda { |env| [200, {"Content-Type" => "text/plain"}, body] }
+    response = Rack::MockResponse.new(*chunked(app).call(@env))
+    response.headers.should.not.include 'Content-Length'
+    response.headers['Transfer-Encoding'].should.equal 'chunked'
+    response.body.encoding.to_s.should == "ASCII-8BIT"
+    response.body.should.equal "c\r\n\xFE\xFFH\x00e\x00l\x00l\x00o\x00\r\n2\r\n \x00\r\na\r\nW\x00o\x00r\x00l\x00d\x00\r\n0\r\n\r\n"
+  end if RUBY_VERSION >= "1.9"
+
   should 'not modify response when Content-Length header present' do
-    app = lambda { |env| [200, {'Content-Length'=>'12'}, ['Hello', ' ', 'World!']] }
-    status, headers, body = Rack::Chunked.new(app).call(@env)
+    app = lambda { |env|
+      [200, {"Content-Type" => "text/plain", 'Content-Length'=>'12'}, ['Hello', ' ', 'World!']]
+    }
+    status, headers, body = chunked(app).call(@env)
     status.should.equal 200
     headers.should.not.include 'Transfer-Encoding'
     headers.should.include 'Content-Length'
@@ -33,26 +58,28 @@ describe Rack::Chunked do
   end
 
   should 'not modify response when client is HTTP/1.0' do
-    app = lambda { |env| [200, {}, ['Hello', ' ', 'World!']] }
+    app = lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hello', ' ', 'World!']] }
     @env['HTTP_VERSION'] = 'HTTP/1.0'
-    status, headers, body = Rack::Chunked.new(app).call(@env)
+    status, headers, body = chunked(app).call(@env)
     status.should.equal 200
     headers.should.not.include 'Transfer-Encoding'
     body.join.should.equal 'Hello World!'
   end
 
   should 'not modify response when Transfer-Encoding header already present' do
-    app = lambda { |env| [200, {'Transfer-Encoding' => 'identity'}, ['Hello', ' ', 'World!']] }
-    status, headers, body = Rack::Chunked.new(app).call(@env)
+    app = lambda { |env|
+      [200, {"Content-Type" => "text/plain", 'Transfer-Encoding' => 'identity'}, ['Hello', ' ', 'World!']]
+    }
+    status, headers, body = chunked(app).call(@env)
     status.should.equal 200
     headers['Transfer-Encoding'].should.equal 'identity'
     body.join.should.equal 'Hello World!'
   end
 
-  [100, 204, 304].each do |status_code|
+  [100, 204, 205, 304].each do |status_code|
     should "not modify response when status code is #{status_code}" do
       app = lambda { |env| [status_code, {}, []] }
-      status, headers, _ = Rack::Chunked.new(app).call(@env)
+      status, headers, _ = chunked(app).call(@env)
       status.should.equal status_code
       headers.should.not.include 'Transfer-Encoding'
     end
diff --git a/test/spec_commonlogger.rb b/test/spec_commonlogger.rb
index 9172750..d88e19c 100644
--- a/test/spec_commonlogger.rb
+++ b/test/spec_commonlogger.rb
@@ -1,19 +1,20 @@
 require 'rack/commonlogger'
+require 'rack/lint'
 require 'rack/mock'
 
 describe Rack::CommonLogger do
   obj = 'foobar'
   length = obj.size
 
-  app = lambda { |env|
+  app = Rack::Lint.new lambda { |env|
     [200,
      {"Content-Type" => "text/html", "Content-Length" => length.to_s},
      [obj]]}
-  app_without_length = lambda { |env|
+  app_without_length = Rack::Lint.new lambda { |env|
     [200,
      {"Content-Type" => "text/html"},
      []]}
-  app_with_zero_length = lambda { |env|
+  app_with_zero_length = Rack::Lint.new lambda { |env|
     [200,
      {"Content-Type" => "text/html", "Content-Length" => "0"},
      []]}
diff --git a/test/spec_conditionalget.rb b/test/spec_conditionalget.rb
index f9e9cec..8b365eb 100644
--- a/test/spec_conditionalget.rb
+++ b/test/spec_conditionalget.rb
@@ -3,9 +3,13 @@ require 'rack/conditionalget'
 require 'rack/mock'
 
 describe Rack::ConditionalGet do
+  def conditional_get(app)
+    Rack::Lint.new Rack::ConditionalGet.new(app)
+  end
+  
   should "set a 304 status and truncate body when If-Modified-Since hits" do
     timestamp = Time.now.httpdate
-    app = Rack::ConditionalGet.new(lambda { |env|
+    app = conditional_get(lambda { |env|
       [200, {'Last-Modified'=>timestamp}, ['TEST']] })
 
     response = Rack::MockRequest.new(app).
@@ -16,7 +20,7 @@ describe Rack::ConditionalGet do
   end
 
   should "set a 304 status and truncate body when If-Modified-Since hits and is higher than current time" do
-    app = Rack::ConditionalGet.new(lambda { |env|
+    app = conditional_get(lambda { |env|
       [200, {'Last-Modified'=>(Time.now - 3600).httpdate}, ['TEST']] })
 
     response = Rack::MockRequest.new(app).
@@ -27,7 +31,7 @@ describe Rack::ConditionalGet do
   end
 
   should "set a 304 status and truncate body when If-None-Match hits" do
-    app = Rack::ConditionalGet.new(lambda { |env|
+    app = conditional_get(lambda { |env|
       [200, {'Etag'=>'1234'}, ['TEST']] })
 
     response = Rack::MockRequest.new(app).
@@ -39,8 +43,8 @@ describe Rack::ConditionalGet do
 
   should "not set a 304 status if If-Modified-Since hits but Etag does not" do
     timestamp = Time.now.httpdate
-    app = Rack::ConditionalGet.new(lambda { |env|
-      [200, {'Last-Modified'=>timestamp, 'Etag'=>'1234'}, ['TEST']] })
+    app = conditional_get(lambda { |env|
+      [200, {'Last-Modified'=>timestamp, 'Etag'=>'1234', 'Content-Type' => 'text/plain'}, ['TEST']] })
 
     response = Rack::MockRequest.new(app).
       get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp, 'HTTP_IF_NONE_MATCH' => '4321')
@@ -51,7 +55,7 @@ describe Rack::ConditionalGet do
 
   should "set a 304 status and truncate body when both If-None-Match and If-Modified-Since hits" do
     timestamp = Time.now.httpdate
-    app = Rack::ConditionalGet.new(lambda { |env|
+    app = conditional_get(lambda { |env|
       [200, {'Last-Modified'=>timestamp, 'Etag'=>'1234'}, ['TEST']] })
 
     response = Rack::MockRequest.new(app).
@@ -62,8 +66,8 @@ describe Rack::ConditionalGet do
   end
 
   should "not affect non-GET/HEAD requests" do
-    app = Rack::ConditionalGet.new(lambda { |env|
-      [200, {'Etag'=>'1234'}, ['TEST']] })
+    app = conditional_get(lambda { |env|
+      [200, {'Etag'=>'1234', 'Content-Type' => 'text/plain'}, ['TEST']] })
 
     response = Rack::MockRequest.new(app).
       post("/", 'HTTP_IF_NONE_MATCH' => '1234')
@@ -73,8 +77,8 @@ describe Rack::ConditionalGet do
   end
 
   should "not affect non-200 requests" do
-    app = Rack::ConditionalGet.new(lambda { |env|
-      [302, {'Etag'=>'1234'}, ['TEST']] })
+    app = conditional_get(lambda { |env|
+      [302, {'Etag'=>'1234', 'Content-Type' => 'text/plain'}, ['TEST']] })
 
     response = Rack::MockRequest.new(app).
       get("/", 'HTTP_IF_NONE_MATCH' => '1234')
@@ -83,4 +87,16 @@ describe Rack::ConditionalGet do
     response.body.should.equal 'TEST'
   end
 
+  should "not affect requests with malformed HTTP_IF_NONE_MATCH" do
+    bad_timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S %z')
+    app = conditional_get(lambda { |env|
+      [200,{'Last-Modified'=>(Time.now - 3600).httpdate, 'Content-Type' => 'text/plain'}, ['TEST']] })
+
+    response = Rack::MockRequest.new(app).
+      get("/", 'HTTP_IF_MODIFIED_SINCE' => bad_timestamp)
+
+    response.status.should.equal 200
+    response.body.should.equal 'TEST'
+  end
+
 end
diff --git a/test/spec_content_length.rb b/test/spec_content_length.rb
index 5e480ae..db7944f 100644
--- a/test/spec_content_length.rb
+++ b/test/spec_content_length.rb
@@ -52,7 +52,7 @@ describe Rack::ContentLength do
     end.new(%w[one two three])
 
     app = lambda { |env| [200, {}, body] }
-    response = Rack::ContentLength.new(app).call({})
+    Rack::ContentLength.new(app).call({})
     body.closed.should.equal true
   end
 
diff --git a/test/spec_content_type.rb b/test/spec_content_type.rb
index 44b0ed1..2ba1927 100644
--- a/test/spec_content_type.rb
+++ b/test/spec_content_type.rb
@@ -26,4 +26,10 @@ describe Rack::ContentType do
     headers.to_a.select { |k,v| k.downcase == "content-type" }.
       should.equal [["CONTENT-Type","foo/bar"]]
   end
+
+  should "not set Content-Type on 304 responses" do
+    app = lambda { |env| [304, {}, []] }
+    response = Rack::ContentType.new(app, "text/html").call({})
+    response[1]['Content-Type'].should.equal nil
+  end
 end
diff --git a/test/spec_deflater.rb b/test/spec_deflater.rb
index 0c9d060..5747560 100644
--- a/test/spec_deflater.rb
+++ b/test/spec_deflater.rb
@@ -51,7 +51,7 @@ describe Rack::Deflater do
     response[2].each { |part| buf << inflater.inflate(part) }
     buf << inflater.finish
     buf.delete_if { |part| part.empty? }
-    buf.should.equal(%w(foo bar))
+    buf.join.should.equal("foobar")
   end
 
   # TODO: This is really just a special case of the above...
@@ -104,7 +104,7 @@ describe Rack::Deflater do
     response[2].each { |part| buf << inflater.inflate(part) }
     buf << inflater.finish
     buf.delete_if { |part| part.empty? }
-    buf.should.equal(%w(foo bar))
+    buf.join.should.equal("foobar")
   end
 
   should "be able to fallback to no deflation" do
diff --git a/test/spec_directory.rb b/test/spec_directory.rb
index a45ba23..81b8c55 100644
--- a/test/spec_directory.rb
+++ b/test/spec_directory.rb
@@ -54,4 +54,16 @@ describe Rack::Directory do
 
     res.should.be.not_found
   end
+
+  should "uri escape path parts" do # #265, properly escape file names
+    mr = Rack::MockRequest.new(Rack::Lint.new(app))
+
+    res = mr.get("/cgi/test%2bdirectory")
+
+    res.should.be.ok
+    res.body.should =~ %r[/cgi/test%2Bdirectory/test%2Bfile]
+
+    res = mr.get("/cgi/test%2bdirectory/test%2bfile")
+    res.should.be.ok
+  end
 end
diff --git a/test/spec_fastcgi.rb b/test/spec_fastcgi.rb
index 93df4d1..5897f35 100644
--- a/test/spec_fastcgi.rb
+++ b/test/spec_fastcgi.rb
@@ -5,7 +5,7 @@ require 'rack/handler/fastcgi'
 describe Rack::Handler::FastCGI do
   extend TestRequest::Helpers
 
-  @host = '0.0.0.0'
+  @host = '127.0.0.1'
   @port = 9203
 
   if `which lighttpd` && !$?.success?
diff --git a/test/spec_file.rb b/test/spec_file.rb
index 121e5a2..2a4d214 100644
--- a/test/spec_file.rb
+++ b/test/spec_file.rb
@@ -22,6 +22,23 @@ describe Rack::File do
     res["Last-Modified"].should.equal File.mtime(path).httpdate
   end
 
+  should "return 304 if file isn't modified since last serve" do
+    path = File.join(DOCROOT, "/cgi/test")
+    res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
+      get("/cgi/test", 'HTTP_IF_MODIFIED_SINCE' => File.mtime(path).httpdate)
+
+    res.status.should.equal 304
+    res.body.should.be.empty
+  end
+
+  should "return the file if it's modified since last serve" do
+    path = File.join(DOCROOT, "/cgi/test")
+    res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
+      get("/cgi/test", 'HTTP_IF_MODIFIED_SINCE' => (File.mtime(path) - 100).httpdate)
+
+    res.should.be.ok
+  end
+
   should "serve files with URL encoded filenames" do
     res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
       get("/cgi/%74%65%73%74") # "/cgi/test"
@@ -30,9 +47,23 @@ describe Rack::File do
     res.should =~ /ruby/
   end
 
-  should "not allow directory traversal" do
+  should "allow safe directory traversal" do
+    req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
+
+    res = req.get('/cgi/../cgi/test')
+    res.should.be.successful
+
+    res = req.get('.')
+    res.should.be.not_found
+
+    res = req.get("test/..")
+    res.should.be.not_found
+  end
+
+  should "not allow unsafe directory traversal" do
     req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
-    res = req.get("/cgi/../test")
+
+    res = req.get("/../README")
     res.should.be.forbidden
 
     res = req.get("../test")
@@ -40,9 +71,6 @@ describe Rack::File do
 
     res = req.get("..")
     res.should.be.forbidden
-
-    res = req.get("test/..")
-    res.should.be.forbidden
   end
 
   should "allow files with .. in their name" do
@@ -57,13 +85,20 @@ describe Rack::File do
     res.should.be.not_found
   end
 
-  should "not allow directory traversal with encoded periods" do
+  should "not allow unsafe directory traversal with encoded periods" do
     res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
       get("/%2E%2E/README")
 
     res.should.be.forbidden
   end
 
+  should "allow safe directory traversal with encoded periods" do
+    res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
+      get("/cgi/%2E%2E/cgi/test")
+
+    res.should.be.successful
+  end
+
   should "404 if it can't find the file" do
     res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
       get("/cgi/blubb")
@@ -113,10 +148,25 @@ describe Rack::File do
     env = Rack::MockRequest.env_for("/cgi/test")
     status, heads, _ = Rack::File.new(DOCROOT, 'public, max-age=38').call(env)
 
-    path = File.join(DOCROOT, "/cgi/test")
-
     status.should.equal 200
     heads['Cache-Control'].should.equal 'public, max-age=38'
   end
 
+  should "only support GET and HEAD requests" do
+    req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
+
+    forbidden = %w[post put delete]
+    forbidden.each do |method|
+
+      res = req.send(method, "/cgi/test")
+      res.should.be.forbidden
+    end
+
+    allowed = %w[get head]
+    allowed.each do |method|
+      res = req.send(method, "/cgi/test")
+      res.should.be.successful
+    end
+  end
+
 end
diff --git a/test/spec_lint.rb b/test/spec_lint.rb
index eda0dfc..6eda182 100644
--- a/test/spec_lint.rb
+++ b/test/spec_lint.rb
@@ -241,7 +241,7 @@ describe Rack::Lint do
     }.should.raise(Rack::Lint::LintError).
       message.should.match(/No Content-Type/)
 
-    [100, 101, 204, 304].each do |status|
+    [100, 101, 204, 205, 304].each do |status|
       lambda {
         Rack::Lint.new(lambda { |env|
                          [status, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
@@ -252,7 +252,7 @@ describe Rack::Lint do
   end
 
   should "notice content-length errors" do
-    [100, 101, 204, 304].each do |status|
+    [100, 101, 204, 205, 304].each do |status|
       lambda {
         Rack::Lint.new(lambda { |env|
                          [status, {"Content-length" => "0"}, []]
diff --git a/test/spec_methodoverride.rb b/test/spec_methodoverride.rb
index 82c6cbd..171025a 100644
--- a/test/spec_methodoverride.rb
+++ b/test/spec_methodoverride.rb
@@ -55,4 +55,19 @@ describe Rack::MethodOverride do
 
     req.env["rack.methodoverride.original_method"].should.equal "POST"
   end
+
+  should "not modify REQUEST_METHOD when given invalid multipart form data" do
+    input = <<EOF
+--AaB03x\r
+content-disposition: form-data; name="huge"; filename="huge"\r
+EOF
+    env = Rack::MockRequest.env_for("/",
+                      "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
+                      "CONTENT_LENGTH" => input.size,
+                      :method => "POST", :input => input)
+    app = Rack::MethodOverride.new(lambda{|envx| Rack::Request.new(envx) })
+    req = app.call(env)
+
+    req.env["REQUEST_METHOD"].should.equal "POST"
+  end
 end
diff --git a/test/spec_mock.rb b/test/spec_mock.rb
index c8191a4..cd144c6 100644
--- a/test/spec_mock.rb
+++ b/test/spec_mock.rb
@@ -42,7 +42,7 @@ describe Rack::MockRequest do
     env["mock.postdata"].should.be.empty
   end
 
-  should "allow GET/POST/PUT/DELETE" do
+  should "allow GET/POST/PUT/DELETE/HEAD" do
     res = Rack::MockRequest.new(app).get("", :input => "foo")
     env = YAML.load(res.body)
     env["REQUEST_METHOD"].should.equal "GET"
@@ -59,6 +59,10 @@ describe Rack::MockRequest do
     env = YAML.load(res.body)
     env["REQUEST_METHOD"].should.equal "DELETE"
 
+    res = Rack::MockRequest.new(app).head("", :input => "foo")
+    env = YAML.load(res.body)
+    env["REQUEST_METHOD"].should.equal "HEAD"
+
     Rack::MockRequest.env_for("/", :method => "OPTIONS")["REQUEST_METHOD"].
       should.equal "OPTIONS"
   end
@@ -175,7 +179,8 @@ describe Rack::MockRequest do
     env["QUERY_STRING"].should.equal ""
     env["PATH_INFO"].should.equal "/foo"
     env["CONTENT_TYPE"].should.equal "multipart/form-data; boundary=AaB03x"
-    env["mock.postdata"].length.should.equal 206
+    # The gsub accounts for differences in YAMLs affect on the data.
+    env["mock.postdata"].gsub("\r", "").length.should.equal 206
   end
 
   should "behave valid according to the Rack spec" do
@@ -188,7 +193,7 @@ describe Rack::MockRequest do
   should "call close on the original body object" do
     called = false
     body   = Rack::BodyProxy.new(['hi']) { called = true }
-    capp   = proc { |e| [200, {'Content-Type' => 'text/plain '}, body] }
+    capp   = proc { |e| [200, {'Content-Type' => 'text/plain'}, body] }
     called.should.equal false
     Rack::MockRequest.new(capp).get('/', :lint => true)
     called.should.equal true
diff --git a/test/spec_mongrel.rb b/test/spec_mongrel.rb
index 5361b5b..6160327 100644
--- a/test/spec_mongrel.rb
+++ b/test/spec_mongrel.rb
@@ -11,7 +11,7 @@ $tcp_cork_opts = nil
 describe Rack::Handler::Mongrel do
   extend TestRequest::Helpers
 
-  @server = Mongrel::HttpServer.new(@host='0.0.0.0', @port=9201)
+  @server = Mongrel::HttpServer.new(@host='127.0.0.1', @port=9201)
   @server.register('/test',
                   Rack::Handler::Mongrel.new(Rack::Lint.new(TestRequest.new)))
   @server.register('/stream',
@@ -31,7 +31,7 @@ describe Rack::Handler::Mongrel do
     response["HTTP_VERSION"].should.equal "HTTP/1.1"
     response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
     response["SERVER_PORT"].should.equal "9201"
-    response["SERVER_NAME"].should.equal "0.0.0.0"
+    response["SERVER_NAME"].should.equal "127.0.0.1"
   end
 
   should "have rack headers" do
@@ -84,7 +84,7 @@ describe Rack::Handler::Mongrel do
   should "provide a .run" do
     block_ran = false
     Thread.new {
-      Rack::Handler::Mongrel.run(lambda {}, {:Port => 9211}) { |server|
+      Rack::Handler::Mongrel.run(lambda {}, {:Host => '127.0.0.1', :Port => 9211}) { |server|
         server.should.be.kind_of Mongrel::HttpServer
         block_ran = true
       }
@@ -97,7 +97,7 @@ describe Rack::Handler::Mongrel do
     block_ran = false
     Thread.new {
       map = {'/'=>lambda{},'/foo'=>lambda{}}
-      Rack::Handler::Mongrel.run(map, :map => true, :Port => 9221) { |server|
+      Rack::Handler::Mongrel.run(map, :map => true, :Host => '127.0.0.1', :Port => 9221) { |server|
         server.should.be.kind_of Mongrel::HttpServer
         server.classifier.uris.size.should.equal 2
         server.classifier.uris.should.not.include '/arf'
@@ -114,7 +114,7 @@ describe Rack::Handler::Mongrel do
     block_ran = false
     Thread.new {
       map = Rack::URLMap.new({'/'=>lambda{},'/bar'=>lambda{}})
-      Rack::Handler::Mongrel.run(map, {:map => true, :Port => 9231}) { |server|
+      Rack::Handler::Mongrel.run(map, {:map => true, :Host => '127.0.0.1', :Port => 9231}) { |server|
         server.should.be.kind_of Mongrel::HttpServer
         server.classifier.uris.size.should.equal 2
         server.classifier.uris.should.not.include '/arf'
@@ -134,12 +134,12 @@ describe Rack::Handler::Mongrel do
         '/' => lambda{},
         '/foo' => lambda{},
         '/bar' => lambda{},
-        'http://localhost/' => lambda{},
-        'http://localhost/bar' => lambda{},
+        'http://127.0.0.1/' => lambda{},
+        'http://127.0.0.1/bar' => lambda{},
         'http://falsehost/arf' => lambda{},
         'http://falsehost/qux' => lambda{}
       })
-      opt = {:map => true, :Port => 9241, :Host => 'localhost'}
+      opt = {:map => true, :Port => 9241, :Host => '127.0.0.1'}
       Rack::Handler::Mongrel.run(map, opt) { |server|
         server.should.be.kind_of Mongrel::HttpServer
         server.classifier.uris.should.include '/'
diff --git a/test/spec_multipart.rb b/test/spec_multipart.rb
index fcd825c..1dc2f4d 100644
--- a/test/spec_multipart.rb
+++ b/test/spec_multipart.rb
@@ -30,6 +30,17 @@ describe Rack::Multipart do
     params["text"].should.equal "contents"
   end
 
+  should "raise RangeError if the key space is exhausted" do
+    env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_no_filename))
+
+    old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 1
+    begin
+      lambda { Rack::Multipart.parse_multipart(env) }.should.raise(RangeError)
+    ensure
+      Rack::Utils.key_space_limit = old
+    end
+  end
+
   should "parse multipart form webkit style" do
     env = Rack::MockRequest.env_for '/', multipart_fixture(:webkit)
     env['CONTENT_TYPE'] = "multipart/form-data; boundary=----WebKitFormBoundaryWLHCs9qmcJJoyjKR"
@@ -284,4 +295,24 @@ describe Rack::Multipart do
       message.should.equal "value must be a Hash"
   end
 
+  it "can parse fields with a content type" do
+    data = <<-EOF
+--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon\r
+Content-Disposition: form-data; name="description"\r
+Content-Type: text/plain"\r
+\r
+Very very blue\r
+--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon--\r
+EOF
+    options = {
+      "CONTENT_TYPE" => "multipart/form-data; boundary=1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon",
+      "CONTENT_LENGTH" => data.length.to_s,
+      :input => StringIO.new(data)
+    }
+    env = Rack::MockRequest.env_for("/", options)
+    params = Rack::Utils::Multipart.parse_multipart(env)
+
+    params.should.equal({"description"=>"Very very blue"})
+  end
+
 end
diff --git a/test/spec_request.rb b/test/spec_request.rb
index 5c089b5..d20585c 100644
--- a/test/spec_request.rb
+++ b/test/spec_request.rb
@@ -125,6 +125,18 @@ describe Rack::Request do
     req.params.should.equal "foo" => "bar", "quux" => "bla"
   end
 
+  should "limit the keys from the GET query string" do
+    env = Rack::MockRequest.env_for("/?foo=bar")
+
+    old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 1
+    begin
+      req = Rack::Request.new(env)
+      lambda { req.GET }.should.raise(RangeError)
+    ensure
+      Rack::Utils.key_space_limit = old
+    end
+  end
+
   should "not unify GET and POST when calling params" do
     mr = Rack::MockRequest.env_for("/?foo=quux",
       "REQUEST_METHOD" => 'POST',
@@ -157,6 +169,20 @@ describe Rack::Request do
     req.params.should.equal "foo" => "bar", "quux" => "bla"
   end
 
+  should "limit the keys from the POST form data" do
+    env = Rack::MockRequest.env_for("",
+            "REQUEST_METHOD" => 'POST',
+            :input => "foo=bar&quux=bla")
+
+    old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 1
+    begin
+      req = Rack::Request.new(env)
+      lambda { req.POST }.should.raise(RangeError)
+    ensure
+      Rack::Utils.key_space_limit = old
+    end
+  end
+
   should "parse POST data with explicit content type regardless of method" do
     req = Rack::Request.new \
       Rack::MockRequest.env_for("/",
@@ -333,13 +359,17 @@ describe Rack::Request do
     request.scheme.should.equal "https"
     request.should.be.ssl?
 
+    request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_SCHEME' => 'https'))
+    request.scheme.should.equal "https"
+    request.should.be.ssl?
+
     request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_PROTO' => 'https'))
     request.scheme.should.equal "https"
     request.should.be.ssl?
 
     request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_PROTO' => 'https, http, http'))
     request.scheme.should.equal "https"
-    request.should.be.ssl
+    request.should.be.ssl?
   end
 
   should "parse cookies" do
@@ -351,6 +381,28 @@ describe Rack::Request do
     req.cookies.should.equal({})
   end
 
+  should "always return the same hash object" do
+    req = Rack::Request.new \
+      Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;quux=h&m")
+    hash = req.cookies
+    req.env.delete("HTTP_COOKIE")
+    req.cookies.should.equal(hash)
+    req.env["HTTP_COOKIE"] = "zoo=m"
+    req.cookies.should.equal(hash)
+  end
+
+  should "modify the cookies hash in place" do
+    req = Rack::Request.new(Rack::MockRequest.env_for(""))
+    req.cookies.should.equal({})
+    req.cookies['foo'] = 'bar'
+    req.cookies.should.equal 'foo' => 'bar'
+  end
+
+  should "raise any errors on every request" do
+    req = Rack::Request.new Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=%")
+    2.times { proc { req.cookies }.should.raise(ArgumentError) }
+  end
+
   should "parse cookies according to RFC 2109" do
     req = Rack::Request.new \
       Rack::MockRequest.env_for('', 'HTTP_COOKIE' => 'foo=bar;foo=car')
@@ -735,38 +787,98 @@ EOF
     parser.call("compress;q=0.5, gzip;q=1.0").should.equal([["compress", 0.5], ["gzip", 1.0]])
     parser.call("gzip;q=1.0, identity; q=0.5, *;q=0").should.equal([["gzip", 1.0], ["identity", 0.5], ["*", 0] ])
 
-    lambda { parser.call("gzip ; q=1.0") }.should.raise(RuntimeError)
+    parser.call("gzip ; q=0.9").should.equal([["gzip", 0.9]])
+    parser.call("gzip ; deflate").should.equal([["gzip", 1.0]])
   end
 
+  ip_app = lambda { |env|
+    request = Rack::Request.new(env)
+    response = Rack::Response.new
+    response.write request.ip
+    response.finish
+  }
+
   should 'provide ip information' do
-    app = lambda { |env|
-      request = Rack::Request.new(env)
-      response = Rack::Response.new
-      response.write request.ip
-      response.finish
-    }
+    mock = Rack::MockRequest.new(Rack::Lint.new(ip_app))
 
-    mock = Rack::MockRequest.new(Rack::Lint.new(app))
-    res = mock.get '/', 'REMOTE_ADDR' => '123.123.123.123'
-    res.body.should.equal '123.123.123.123'
+    res = mock.get '/', 'REMOTE_ADDR' => '1.2.3.4'
+    res.body.should.equal '1.2.3.4'
 
-    res = mock.get '/',
-      'REMOTE_ADDR' => '123.123.123.123',
-      'HTTP_X_FORWARDED_FOR' => '234.234.234.234'
+    res = mock.get '/', 'REMOTE_ADDR' => 'fe80::202:b3ff:fe1e:8329'
+    res.body.should.equal 'fe80::202:b3ff:fe1e:8329'
+
+    res = mock.get '/', 'REMOTE_ADDR' => '1.2.3.4,3.4.5.6'
+    res.body.should.equal '1.2.3.4'
+  end
+
+  should 'deals with proxies' do
+    mock = Rack::MockRequest.new(Rack::Lint.new(ip_app))
 
-    res.body.should.equal '234.234.234.234'
+    res = mock.get '/',
+      'REMOTE_ADDR' => '1.2.3.4',
+      'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+    res.body.should.equal '1.2.3.4'
 
     res = mock.get '/',
-      'REMOTE_ADDR' => '123.123.123.123',
-      'HTTP_X_FORWARDED_FOR' => '234.234.234.234,212.212.212.212'
+      'REMOTE_ADDR' => '127.0.0.1',
+      'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+    res.body.should.equal '3.4.5.6'
+
+    res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,3.4.5.6'
+    res.body.should.equal '3.4.5.6'
+
+    res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '192.168.0.1,3.4.5.6'
+    res.body.should.equal '3.4.5.6'
+
+    res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '10.0.0.1,3.4.5.6'
+    res.body.should.equal '3.4.5.6'
 
-    res.body.should.equal '234.234.234.234'
+    res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 10.0.0.1, 3.4.5.6'
+    res.body.should.equal '3.4.5.6'
+
+    res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '127.0.0.1, 3.4.5.6'
+    res.body.should.equal '3.4.5.6'
+
+    res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1'
+    res.body.should.equal 'unknown'
+
+    res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'other,unknown,192.168.0.1'
+    res.body.should.equal 'unknown'
+
+    res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,localhost,192.168.0.1'
+    res.body.should.equal 'unknown'
+
+    res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4'
+    res.body.should.equal '3.4.5.6'
+
+    res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '::1,2620:0:1c00:0:812c:9583:754b:ca11'
+    res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11'
+
+    res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '2620:0:1c00:0:812c:9583:754b:ca11,::1'
+    res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11'
+
+    res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'fd5b:982e:9130:247f:0000:0000:0000:0000,2620:0:1c00:0:812c:9583:754b:ca11'
+    res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11'
+
+    res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '2620:0:1c00:0:812c:9583:754b:ca11,fd5b:982e:9130:247f:0000:0000:0000:0000'
+    res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11'
 
     res = mock.get '/',
-      'REMOTE_ADDR' => '123.123.123.123',
-      'HTTP_X_FORWARDED_FOR' => 'unknown,234.234.234.234,212.212.212.212'
+      'HTTP_X_FORWARDED_FOR' => '1.1.1.1, 127.0.0.1',
+      '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'
 
-    res.body.should.equal '234.234.234.234'
+    res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, fe80::202:b3ff:fe1e:8329'
+    res.body.should.equal 'fe80::202:b3ff:fe1e:8329'
   end
 
   class MyRequest < Rack::Request
diff --git a/test/spec_response.rb b/test/spec_response.rb
index c92fa5b..07dd012 100644
--- a/test/spec_response.rb
+++ b/test/spec_response.rb
@@ -109,6 +109,18 @@ describe Rack::Response do
                                          "foo=; domain=sample.example.com; expires=Thu, 01-Jan-1970 00:00:00 GMT"].join("\n")
   end
 
+  it "can delete cookies with the same name with different paths" do
+    response = Rack::Response.new
+    response.set_cookie "foo", {:value => "bar", :path => "/"}
+    response.set_cookie "foo", {:value => "bar", :path => "/path"}
+    response["Set-Cookie"].should.equal ["foo=bar; path=/",
+                                         "foo=bar; path=/path"].join("\n")
+
+    response.delete_cookie "foo", :path => "/path"
+    response["Set-Cookie"].should.equal ["foo=bar; path=/",
+                                         "foo=; path=/path; expires=Thu, 01-Jan-1970 00:00:00 GMT"].join("\n")
+  end
+
   it "can do redirects" do
     response = Rack::Response.new
     response.redirect "/foo"
@@ -196,11 +208,21 @@ describe Rack::Response do
     res.should.be.successful
     res.should.be.ok
 
+    res.status = 400
+    res.should.not.be.successful
+    res.should.be.client_error
+    res.should.be.bad_request
+
     res.status = 404
     res.should.not.be.successful
     res.should.be.client_error
     res.should.be.not_found
 
+    res.status = 422
+    res.should.not.be.successful
+    res.should.be.client_error
+    res.should.be.unprocessable
+
     res.status = 501
     res.should.not.be.successful
     res.should.be.server_error
diff --git a/test/spec_sendfile.rb b/test/spec_sendfile.rb
index 84517e7..de4ae7a 100644
--- a/test/spec_sendfile.rb
+++ b/test/spec_sendfile.rb
@@ -40,6 +40,7 @@ describe Rack::Sendfile do
     request 'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile' do |response|
       response.should.be.ok
       response.body.should.be.empty
+      response.headers['Content-Length'].should == '0'
       response.headers['X-Sendfile'].should.equal '/tmp/hello.txt'
     end
   end
@@ -48,6 +49,7 @@ describe Rack::Sendfile do
     request 'HTTP_X_SENDFILE_TYPE' => 'X-Lighttpd-Send-File' do |response|
       response.should.be.ok
       response.body.should.be.empty
+      response.headers['Content-Length'].should == '0'
       response.headers['X-Lighttpd-Send-File'].should.equal '/tmp/hello.txt'
     end
   end
@@ -60,6 +62,7 @@ describe Rack::Sendfile do
     request headers do |response|
       response.should.be.ok
       response.body.should.be.empty
+      response.headers['Content-Length'].should == '0'
       response.headers['X-Accel-Redirect'].should.equal '/foo/bar/hello.txt'
     end
   end
diff --git a/test/spec_server.rb b/test/spec_server.rb
index 9befacb..a1a51d9 100644
--- a/test/spec_server.rb
+++ b/test/spec_server.rb
@@ -61,8 +61,8 @@ describe Rack::Server do
       :daemonize   => false,
       :server      => 'webrick'
     )
-    t = Thread.new { server.start }
-    until t.status == 'sleep'; t.join(0.01) end
+    t = Thread.new { server.start { |s| Thread.current[:server] = s } }
+    t.join(0.01) until t[:server] && t[:server].status != :Stop
     body = open("http://127.0.0.1:#{server.options[:Port]}/") { |f| f.read }
     body.should.eql('success')
 
diff --git a/test/spec_session_cookie.rb b/test/spec_session_cookie.rb
index 9fa8b49..514bb3b 100644
--- a/test/spec_session_cookie.rb
+++ b/test/spec_session_cookie.rb
@@ -11,7 +11,7 @@ describe Rack::Session::Cookie do
   end
 
   session_id = lambda do |env|
-    Rack::Response.new(env["rack.session"].inspect).to_a
+    Rack::Response.new(env["rack.session"].to_hash.inspect).to_a
   end
 
   session_option = lambda do |opt|
@@ -99,18 +99,23 @@ describe Rack::Session::Cookie do
   end
 
   only_session_id = lambda do |env|
-    Rack::Response.new(env["rack.session"]["session_id"]).to_a
+    Rack::Response.new(env["rack.session"]["session_id"].to_s).to_a
   end
 
   it "renew session id" do
     res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/")
     res = Rack::MockRequest.new(Rack::Session::Cookie.new(only_session_id)).
       get("/", "HTTP_COOKIE" => res["Set-Cookie"])
+
+    res.body.should.not.equal ""
     old_session_id = res.body
+
     res = Rack::MockRequest.new(Rack::Session::Cookie.new(renewer)).
       get("/", "HTTP_COOKIE" => res["Set-Cookie"])
     res = Rack::MockRequest.new(Rack::Session::Cookie.new(only_session_id)).
       get("/", "HTTP_COOKIE" => res["Set-Cookie"])
+
+    res.body.should.not.equal ""
     res.body.should.not.equal old_session_id
   end
 
@@ -144,6 +149,18 @@ describe Rack::Session::Cookie do
     res.body.should.equal '{"counter"=>3}'
   end
 
+  it "loads from a cookie wih accept-only integrity hash for graceful key rotation" do
+    res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).get("/")
+    cookie = res["Set-Cookie"]
+    res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test2', :old_secret => 'test')).
+      get("/", "HTTP_COOKIE" => cookie)
+    res.body.should.equal '{"counter"=>2}'
+    cookie = res["Set-Cookie"]
+    res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test3', :old_secret => 'test2')).
+      get("/", "HTTP_COOKIE" => cookie)
+    res.body.should.equal '{"counter"=>3}'
+  end
+
   it "ignores tampered with session cookies" do
     app = Rack::Session::Cookie.new(incrementor, :secret => 'test')
     response1 = Rack::MockRequest.new(app).get("/")
@@ -158,6 +175,38 @@ describe Rack::Session::Cookie do
     response2.body.should.equal '{"counter"=>1}'
   end
 
+  describe "1.9 bugs relating to inspecting yet-to-be-loaded from cookie data: Rack::Session::Abstract::SessionHash" do
+
+    it "can handle Rack::Lint middleware" do
+      app = Rack::Session::Cookie.new(incrementor)
+      res = Rack::MockRequest.new(app).get("/")
+
+      app = Rack::Session::Cookie.new(Rack::Lint.new(session_id))
+      res = Rack::MockRequest.new(app).get("/", "HTTP_COOKIE" => res["Set-Cookie"])
+      res.body.should.not.be.nil
+    end
+
+    it "can handle a middleware that inspects the env" do
+      class TestEnvInspector
+        def initialize(app)
+          @app = app
+        end
+        def call(env)
+          env.inspect
+          @app.call(env)
+        end
+      end
+
+      app = Rack::Session::Cookie.new(incrementor)
+      res = Rack::MockRequest.new(app).get("/")
+
+      app = Rack::Session::Cookie.new(TestEnvInspector.new(session_id))
+      res = Rack::MockRequest.new(app).get("/", "HTTP_COOKIE" => res["Set-Cookie"])
+      res.body.should.not.be.nil
+    end
+
+  end
+
   it "returns the session id in the session hash" do
     app = Rack::Session::Cookie.new(incrementor)
     res = Rack::MockRequest.new(app).get("/")
@@ -192,10 +241,16 @@ describe Rack::Session::Cookie do
 
   it "returns even if not read/written if :expire_after is set" do
     app = Rack::Session::Cookie.new(nothing, :expire_after => 3600)
-    res = Rack::MockRequest.new(app).get("/")
+    res = Rack::MockRequest.new(app).get("/", 'rack.session' => {'not' => 'empty'})
     res["Set-Cookie"].should.not.be.nil
   end
 
+  it "returns no cookie if no data was written and no session was created previously, even if :expire_after is set" do
+    app = Rack::Session::Cookie.new(nothing, :expire_after => 3600)
+    res = Rack::MockRequest.new(app).get("/")
+    res["Set-Cookie"].should.be.nil
+  end
+
   it "exposes :secret in env['rack.session.option']" do
     app = Rack::Session::Cookie.new(session_option[:secret], :secret => "foo")
     res = Rack::MockRequest.new(app).get("/")
diff --git a/test/spec_session_memcache.rb b/test/spec_session_memcache.rb
index 7c81b9a..0ae4bdf 100644
--- a/test/spec_session_memcache.rb
+++ b/test/spec_session_memcache.rb
@@ -23,6 +23,10 @@ begin
       env['rack.session.options'][:defer] = true
       incrementor.call(env)
     end
+    skip_session = proc do |env|
+      env['rack.session.options'][:skip] = true
+      incrementor.call(env)
+    end
 
     # test memcache connection
     Rack::Session::Memcache.new(incrementor)
@@ -168,14 +172,40 @@ begin
       res4.body.should.equal '{"counter"=>1}'
     end
 
-    it "omits cookie with :defer option" do
+    it "omits cookie with :defer option but still updates the state" do
       pool = Rack::Session::Memcache.new(incrementor)
+      count = Rack::Utils::Context.new(pool, incrementor)
       defer = Rack::Utils::Context.new(pool, defer_session)
       dreq = Rack::MockRequest.new(defer)
+      creq = Rack::MockRequest.new(count)
 
       res0 = dreq.get("/")
       res0["Set-Cookie"].should.equal nil
       res0.body.should.equal '{"counter"=>1}'
+
+      res0 = creq.get("/")
+      res1 = dreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"])
+      res1.body.should.equal '{"counter"=>2}'
+      res2 = dreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"])
+      res2.body.should.equal '{"counter"=>3}'
+    end
+
+    it "omits cookie and state update with :skip option" do
+      pool = Rack::Session::Memcache.new(incrementor)
+      count = Rack::Utils::Context.new(pool, incrementor)
+      skip = Rack::Utils::Context.new(pool, skip_session)
+      sreq = Rack::MockRequest.new(skip)
+      creq = Rack::MockRequest.new(count)
+
+      res0 = sreq.get("/")
+      res0["Set-Cookie"].should.equal nil
+      res0.body.should.equal '{"counter"=>1}'
+
+      res0 = creq.get("/")
+      res1 = sreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"])
+      res1.body.should.equal '{"counter"=>2}'
+      res2 = sreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"])
+      res2.body.should.equal '{"counter"=>2}'
     end
 
     it "updates deep hashes correctly" do
diff --git a/test/spec_session_pool.rb b/test/spec_session_pool.rb
index b498806..8c34236 100644
--- a/test/spec_session_pool.rb
+++ b/test/spec_session_pool.rb
@@ -181,20 +181,26 @@ describe Rack::Session::Pool do
   end
 
   it "does not return a cookie if cookie was not read/written" do
-    app = Rack::Session::Cookie.new(nothing)
+    app = Rack::Session::Pool.new(nothing)
     res = Rack::MockRequest.new(app).get("/")
     res["Set-Cookie"].should.be.nil
   end
 
   it "does not return a cookie if cookie was not written (only read)" do
-    app = Rack::Session::Cookie.new(session_id)
+    app = Rack::Session::Pool.new(session_id)
     res = Rack::MockRequest.new(app).get("/")
     res["Set-Cookie"].should.be.nil
   end
 
   it "returns even if not read/written if :expire_after is set" do
-    app = Rack::Session::Cookie.new(nothing, :expire_after => 3600)
-    res = Rack::MockRequest.new(app).get("/")
+    app = Rack::Session::Pool.new(nothing, :expire_after => 3600)
+    res = Rack::MockRequest.new(app).get("/", 'rack.session' => {'not' => 'empty'})
     res["Set-Cookie"].should.not.be.nil
   end
+
+  it "returns no cookie if no data was written and no session was created previously, even if :expire_after is set" do
+    app = Rack::Session::Pool.new(nothing, :expire_after => 3600)
+    res = Rack::MockRequest.new(app).get("/")
+    res["Set-Cookie"].should.be.nil
+  end
 end
diff --git a/test/spec_static.rb b/test/spec_static.rb
index 651c9f4..21b2b6a 100644
--- a/test/spec_static.rb
+++ b/test/spec_static.rb
@@ -11,9 +11,11 @@ describe Rack::Static do
   root = File.expand_path(File.dirname(__FILE__))
 
   OPTIONS = {:urls => ["/cgi"], :root => root}
+  STATIC_OPTIONS = {:urls => [""], :root => root, :index => 'static/index.html'}
   HASH_OPTIONS = {:urls => {"/cgi/sekret" => 'cgi/test'}, :root => root}
 
   @request = Rack::MockRequest.new(Rack::Static.new(DummyApp.new, OPTIONS))
+  @static_request = Rack::MockRequest.new(Rack::Static.new(DummyApp.new, STATIC_OPTIONS))
   @hash_request = Rack::MockRequest.new(Rack::Static.new(DummyApp.new, HASH_OPTIONS))
 
   it "serves files" do
@@ -33,6 +35,12 @@ describe Rack::Static do
     res.body.should == "Hello World"
   end
 
+  it "calls index file when requesting root" do
+    res = @static_request.get("/")
+    res.should.be.ok
+    res.body.should =~ /index!/
+  end
+
   it "serves hidden files" do
     res = @hash_request.get("/cgi/sekret")
     res.should.be.ok
diff --git a/test/spec_thin.rb b/test/spec_thin.rb
index 9403964..f762d11 100644
--- a/test/spec_thin.rb
+++ b/test/spec_thin.rb
@@ -11,7 +11,7 @@ describe Rack::Handler::Thin do
   Thin::Logging.silent = true
 
   @thread = Thread.new do
-    Rack::Handler::Thin.run(@app, :Host => @host='0.0.0.0', :Port => @port=9204) do |server|
+    Rack::Handler::Thin.run(@app, :Host => @host='127.0.0.1', :Port => @port=9204) do |server|
       @server = server
     end
   end
@@ -30,7 +30,7 @@ describe Rack::Handler::Thin do
     response["HTTP_VERSION"].should.equal "HTTP/1.1"
     response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
     response["SERVER_PORT"].should.equal "9204"
-    response["SERVER_NAME"].should.equal "0.0.0.0"
+    response["SERVER_NAME"].should.equal "127.0.0.1"
   end
 
   should "have rack headers" do
diff --git a/test/spec_utils.rb b/test/spec_utils.rb
index f4fe4e8..a787763 100644
--- a/test/spec_utils.rb
+++ b/test/spec_utils.rb
@@ -3,6 +3,24 @@ require 'rack/utils'
 require 'rack/mock'
 
 describe Rack::Utils do
+  def kcodeu
+    one8 = RUBY_VERSION.to_f < 1.9
+    default_kcode, $KCODE = $KCODE, 'U' if one8
+    yield
+  ensure
+    $KCODE = default_kcode if one8
+  end
+
+  should "round trip binary data" do
+    r = [218, 0].pack 'CC'
+    if defined?(::Encoding)
+      z = Rack::Utils.unescape(Rack::Utils.escape(r), Encoding::BINARY)
+    else
+      z = Rack::Utils.unescape(Rack::Utils.escape(r))
+    end
+    r.should.equal z
+  end
+
   should "escape correctly" do
     Rack::Utils.escape("fo<o>bar").should.equal "fo%3Co%3Ebar"
     Rack::Utils.escape("a space").should.equal "a+space"
@@ -21,23 +39,27 @@ describe Rack::Utils do
 
   if RUBY_VERSION[/^\d+\.\d+/] == '1.8'
     should "escape correctly for multibyte characters if $KCODE is set to 'U'" do
-      default_kcode, $KCODE = $KCODE, 'U'
-
-      matz_name = "\xE3\x81\xBE\xE3\x81\xA4\xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsumoto
-      matz_name.force_encoding("UTF-8") if matz_name.respond_to? :force_encoding
-      Rack::Utils.escape(matz_name).should.equal '%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8'
-      matz_name_sep = "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsu moto
-      matz_name_sep.force_encoding("UTF-8") if matz_name_sep.respond_to? :force_encoding
-      Rack::Utils.escape(matz_name_sep).should.equal '%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8'
-
-      $KCODE = default_kcode
+      kcodeu do
+        matz_name = "\xE3\x81\xBE\xE3\x81\xA4\xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsumoto
+        matz_name.force_encoding("UTF-8") if matz_name.respond_to? :force_encoding
+        Rack::Utils.escape(matz_name).should.equal '%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8'
+        matz_name_sep = "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsu moto
+        matz_name_sep.force_encoding("UTF-8") if matz_name_sep.respond_to? :force_encoding
+        Rack::Utils.escape(matz_name_sep).should.equal '%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8'
+      end
     end
 
     should "unescape multibyte characters correctly if $KCODE is set to 'U'" do
-      default_kcode, $KCODE = $KCODE, 'U'
-      Rack::Utils.unescape('%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8').should.equal(
-        "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0])
-      $KCODE = default_kcode
+      kcodeu do
+        Rack::Utils.unescape('%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8').should.equal(
+          "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0])
+      end
+    end
+  end
+
+  should "escape objects that responds to to_s" do
+    kcodeu do
+      Rack::Utils.escape(:id).should.equal "id"
     end
   end
 
@@ -46,6 +68,16 @@ describe Rack::Utils do
       Rack::Utils.escape("ø".encode("ISO-8859-1")).should.equal "%F8"
     end
   end
+  
+  should "not hang on escaping long strings that end in % (http://redmine.ruby-lang.org/issues/5149)" do
+    lambda {
+      timeout(1) do
+        lambda {
+          URI.decode_www_form_component "A string that causes catastrophic backtracking as it gets longer %"
+        }.should.raise(ArgumentError)
+      end
+    }.should.not.raise(Timeout::Error)
+  end
 
   should "escape path spaces with %20" do
     Rack::Utils.escape_path("foo bar").should.equal  "foo%20bar"
@@ -185,22 +217,22 @@ describe Rack::Utils do
     # unordered hash. Test that build_nested_query performs the inverse
     # function of parse_nested_query.
     [{"foo" => nil, "bar" => ""},
-     {"foo" => "bar", "baz" => ""},
-     {"foo" => ["1", "2"]},
-     {"foo" => "bar", "baz" => ["1", "2", "3"]},
-     {"foo" => ["bar"], "baz" => ["1", "2", "3"]},
-     {"foo" => ["1", "2"]},
-     {"foo" => "bar", "baz" => ["1", "2", "3"]},
-     {"x" => {"y" => {"z" => "1"}}},
-     {"x" => {"y" => {"z" => ["1"]}}},
-     {"x" => {"y" => {"z" => ["1", "2"]}}},
-     {"x" => {"y" => [{"z" => "1"}]}},
-     {"x" => {"y" => [{"z" => ["1"]}]}},
-     {"x" => {"y" => [{"z" => "1", "w" => "2"}]}},
-     {"x" => {"y" => [{"v" => {"w" => "1"}}]}},
-     {"x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}},
-     {"x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}},
-     {"x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}}
+      {"foo" => "bar", "baz" => ""},
+      {"foo" => ["1", "2"]},
+      {"foo" => "bar", "baz" => ["1", "2", "3"]},
+      {"foo" => ["bar"], "baz" => ["1", "2", "3"]},
+      {"foo" => ["1", "2"]},
+      {"foo" => "bar", "baz" => ["1", "2", "3"]},
+      {"x" => {"y" => {"z" => "1"}}},
+      {"x" => {"y" => {"z" => ["1"]}}},
+      {"x" => {"y" => {"z" => ["1", "2"]}}},
+      {"x" => {"y" => [{"z" => "1"}]}},
+      {"x" => {"y" => [{"z" => ["1"]}]}},
+      {"x" => {"y" => [{"z" => "1", "w" => "2"}]}},
+      {"x" => {"y" => [{"v" => {"w" => "1"}}]}},
+      {"x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}},
+      {"x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}},
+      {"x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}}
     ].each { |params|
       qs = Rack::Utils.build_nested_query(params)
       Rack::Utils.parse_nested_query(qs).should.equal params
@@ -211,7 +243,18 @@ describe Rack::Utils do
       message.should.equal "value must be a Hash"
   end
 
-  should "should escape html entities [&><'\"/]" do
+  should "parse query strings that have a non-existent value" do
+    key = "post/2011/08/27/Deux-%22rat%C3%A9s%22-de-l-Universit"
+    Rack::Utils.parse_query(key).should.equal Rack::Utils.unescape(key) => nil
+  end
+
+  should "build query strings without = with non-existent values" do
+    key = "post/2011/08/27/Deux-%22rat%C3%A9s%22-de-l-Universit"
+    key = Rack::Utils.unescape(key)
+    Rack::Utils.build_query(key => nil).should.equal Rack::Utils.escape(key)
+  end
+
+  should "escape html entities [&><'\"/]" do
     Rack::Utils.escape_html("foo").should.equal "foo"
     Rack::Utils.escape_html("f&o").should.equal "f&o"
     Rack::Utils.escape_html("f<o").should.equal "f<o"
@@ -222,6 +265,27 @@ describe Rack::Utils do
     Rack::Utils.escape_html("<foo></foo>").should.equal "<foo><&#x2F;foo>"
   end
 
+  should "escape html entities even on MRI when it's bugged" do
+    test_escape = lambda do
+      kcodeu do
+        Rack::Utils.escape_html("\300<").should.equal "\300<"
+      end
+    end
+
+    if RUBY_VERSION.to_f < 1.9
+      test_escape.call
+    else
+      test_escape.should.raise(ArgumentError)
+    end
+  end
+
+  if "".respond_to?(:encode)
+    should "escape html entities in unicode strings" do
+      # the following will cause warnings if the regex is poorly encoded:
+      Rack::Utils.escape_html("☃").should.equal "☃"
+    end
+  end
+
   should "figure out which encodings are acceptable" do
     helper = lambda do |a, b|
       Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => a))
diff --git a/test/spec_webrick.rb b/test/spec_webrick.rb
index e3af233..d121c69 100644
--- a/test/spec_webrick.rb
+++ b/test/spec_webrick.rb
@@ -6,7 +6,7 @@ Thread.abort_on_exception = true
 describe Rack::Handler::WEBrick do
   extend TestRequest::Helpers
 
-  @server = WEBrick::HTTPServer.new(:Host => @host='0.0.0.0',
+  @server = WEBrick::HTTPServer.new(:Host => @host='127.0.0.1',
                                     :Port => @port=9202,
                                     :Logger => WEBrick::Log.new(nil, WEBrick::BasicLog::WARN),
                                     :AccessLog => [])
@@ -28,7 +28,7 @@ describe Rack::Handler::WEBrick do
     response["HTTP_VERSION"].should.equal "HTTP/1.1"
     response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
     response["SERVER_PORT"].should.equal "9202"
-    response["SERVER_NAME"].should.equal "0.0.0.0"
+    response["SERVER_NAME"].should.equal "127.0.0.1"
   end
 
   should "have rack headers" do
@@ -106,7 +106,9 @@ describe Rack::Handler::WEBrick do
     block_ran = false
     catch(:done) {
       Rack::Handler::WEBrick.run(lambda {},
-                                 {:Port => 9210,
+                                 {
+                                   :Host => '127.0.0.1',
+                                   :Port => 9210,
                                    :Logger => WEBrick::Log.new(nil, WEBrick::BasicLog::WARN),
                                    :AccessLog => []}) { |server|
         block_ran = true
diff --git a/test/static/index.html b/test/static/index.html
new file mode 100644
index 0000000..fc2d9ad
--- /dev/null
+++ b/test/static/index.html
@@ -0,0 +1 @@
+index!

-- 
ruby-rack.git



More information about the Pkg-ruby-extras-commits mailing list