summary refs log tree commit
diff options
context:
space:
mode:
authorChristian Neukirchen <chneukirchen@gmail.com>2009-12-20 14:49:02 +0100
committerChristian Neukirchen <chneukirchen@gmail.com>2009-12-20 14:49:02 +0100
commit602a3d120d5f6dc4be5ef7b5ccd553f523ed55a3 (patch)
tree192f70db35a70a77ae0c6b4ea434980696086f91
parent202afb4bc865788eeffe33bc8dc9bce5a35f24ab (diff)
parent4cc6af9b4f0b633b076f27d3a76bf86ebf9fe64e (diff)
downloadrack-602a3d120d5f6dc4be5ef7b5ccd553f523ed55a3.tar.gz
Merge branch 'master' of github.com:rack/rack
-rw-r--r--README11
-rw-r--r--Rakefile3
-rwxr-xr-xbin/rackup178
-rw-r--r--lib/rack.rb8
-rw-r--r--lib/rack/auth/openid.rb487
-rw-r--r--lib/rack/builder.rb17
-rw-r--r--lib/rack/chunked.rb4
-rw-r--r--lib/rack/commonlogger.rb9
-rw-r--r--lib/rack/config.rb15
-rw-r--r--lib/rack/content_type.rb2
-rw-r--r--lib/rack/etag.rb23
-rw-r--r--lib/rack/handler.rb19
-rw-r--r--lib/rack/handler/fastcgi.rb3
-rw-r--r--lib/rack/handler/mongrel.rb3
-rw-r--r--lib/rack/handler/webrick.rb4
-rw-r--r--lib/rack/lint.rb39
-rw-r--r--lib/rack/logger.rb20
-rw-r--r--lib/rack/mime.rb1
-rw-r--r--lib/rack/nulllogger.rb18
-rw-r--r--lib/rack/request.rb7
-rw-r--r--lib/rack/response.rb6
-rw-r--r--lib/rack/runtime.rb27
-rw-r--r--lib/rack/sendfile.rb142
-rw-r--r--lib/rack/server.rb212
-rw-r--r--lib/rack/session/memcache.rb96
-rw-r--r--lib/rack/session/pool.rb2
-rw-r--r--lib/rack/utils.rb28
-rw-r--r--test/multipart/bad_robots259
-rw-r--r--test/multipart/fail_16384_nofile814
-rw-r--r--test/rackup/.gitignore1
-rw-r--r--test/rackup/config.ru31
-rw-r--r--test/spec_rack_auth_openid.rb84
-rw-r--r--test/spec_rack_cgi.rb4
-rw-r--r--test/spec_rack_config.rb24
-rw-r--r--test/spec_rack_etag.rb17
-rw-r--r--test/spec_rack_fastcgi.rb2
-rw-r--r--test/spec_rack_lint.rb31
-rw-r--r--test/spec_rack_logger.rb21
-rw-r--r--test/spec_rack_mongrel.rb6
-rw-r--r--test/spec_rack_nulllogger.rb13
-rw-r--r--test/spec_rack_response.rb3
-rw-r--r--test/spec_rack_runtime.rb35
-rw-r--r--test/spec_rack_sendfile.rb86
-rw-r--r--test/spec_rack_session_memcache.rb49
-rw-r--r--test/spec_rack_utils.rb54
-rw-r--r--test/spec_rack_webrick.rb6
-rw-r--r--test/spec_rackup.rb154
-rw-r--r--test/testrequest.rb17
48 files changed, 2228 insertions, 867 deletions
diff --git a/README b/README
index c58009ff..230d98a9 100644
--- a/README
+++ b/README
@@ -130,7 +130,7 @@ Either with the embedded WEBrick starter:
 
 Or with rackup:
 
-    bin/rackup -Ilib example/lobster.ru
+    bin/rackup -Ilib example/lobster.ru
 
 By default, the lobster is found at http://localhost:9292.
 
@@ -285,14 +285,12 @@ run on port 11211) and memcache-client installed.
 
 == Contact
 
-Please mail bugs, suggestions and patches to
-<mailto:rack-devel@googlegroups.com>.
+Please post bugs, suggestions and patches to
+the bug tracker at <http://rack.lighthouseapp.com/>.
 
 Mailing list archives are available at
 <http://groups.google.com/group/rack-devel>.
 
-There is a bug tracker at <http://rack.lighthouseapp.com/>.
-
 Git repository (send Git patches to the mailing list):
 * http://github.com/rack/rack
 * http://git.vuxu.org/cgi-bin/gitweb.cgi?p=rack.git
@@ -349,7 +347,7 @@ all copies or substantial portions of the Software.
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
@@ -358,6 +356,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 Rack:: <http://rack.rubyforge.org/>
 Rack's Rubyforge project:: <http://rubyforge.org/projects/rack>
 Official Rack repositories:: <http://github.com/rack>
+Rack Lighthouse Bug Tracking:: <http://rack.lighthouseapp.com/>
 rack-devel mailing list:: <http://groups.google.com/group/rack-devel>
 
 Christian Neukirchen:: <http://chneukirchen.org/>
diff --git a/Rakefile b/Rakefile
index c88f7aff..2da515ff 100644
--- a/Rakefile
+++ b/Rakefile
@@ -86,7 +86,7 @@ end
 
 desc "Run all the fast tests"
 task :test do
-  sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS'] || '-t "^(?!Rack::Handler|Rack::Adapter|Rack::Session::Memcache|Rack::Auth::OpenID)"'}"
+  sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS'] || '-t "^(?!Rack::Handler|Rack::Adapter|Rack::Session::Memcache)"'}"
 end
 
 desc "Run all the tests"
@@ -135,7 +135,6 @@ Also see http://rack.rubyforge.org.
       s.add_development_dependency 'fcgi'
       s.add_development_dependency 'memcache-client'
       s.add_development_dependency 'mongrel'
-      s.add_development_dependency 'ruby-openid', '~> 2.0.0'
       s.add_development_dependency 'thin'
     end
 
diff --git a/bin/rackup b/bin/rackup
index 91abe17b..a6f98913 100755
--- a/bin/rackup
+++ b/bin/rackup
@@ -1,176 +1,2 @@
-#!/usr/bin/env ruby
-# -*- ruby -*-
-
-require 'rack'
-
-require 'optparse'
-
-automatic = false
-server = nil
-env = "development"
-daemonize = false
-pid = nil
-options = {:Port => 9292, :Host => "0.0.0.0", :AccessLog => []}
-
-# Don't evaluate CGI ISINDEX parameters.
-# http://hoohoo.ncsa.uiuc.edu/cgi/cl.html
-ARGV.clear  if ENV.include?("REQUEST_METHOD")
-
-opts = OptionParser.new("", 24, '  ') { |opts|
-  opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]"
-
-  opts.separator ""
-  opts.separator "Ruby options:"
-
-  lineno = 1
-  opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
-    eval line, TOPLEVEL_BINDING, "-e", lineno
-    lineno += 1
-  }
-
-  opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
-    $DEBUG = true
-  }
-  opts.on("-w", "--warn", "turn warnings on for your script") {
-    $-w = true
-  }
-
-  opts.on("-I", "--include PATH",
-          "specify $LOAD_PATH (may be used more than once)") { |path|
-    $LOAD_PATH.unshift(*path.split(":"))
-  }
-
-  opts.on("-r", "--require LIBRARY",
-          "require the library, before executing your script") { |library|
-    require library
-  }
-
-  opts.separator ""
-  opts.separator "Rack options:"
-  opts.on("-s", "--server SERVER", "serve using SERVER (webrick/mongrel)") { |s|
-    server = s
-  }
-
-  opts.on("-o", "--host HOST", "listen on HOST (default: 0.0.0.0)") { |host|
-    options[:Host] = host
-  }
-
-  opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
-    options[:Port] = port
-  }
-
-  opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
-    env = e
-  }
-
-  opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
-    daemonize = d ? true : false
-  }
-
-  opts.on("-P", "--pid FILE", "file to store PID (default: rack.pid)") { |f|
-    pid = File.expand_path(f)
-  }
-
-  opts.separator ""
-  opts.separator "Common options:"
-
-  opts.on_tail("-h", "--help", "Show this message") do
-    puts opts
-    exit
-  end
-
-  opts.on_tail("--version", "Show version") do
-    puts "Rack #{Rack.version}"
-    exit
-  end
-
-  opts.parse! ARGV
-}
-
-require 'pp'  if $DEBUG
-
-config = ARGV[0] || "config.ru"
-if !File.exist? config
-  abort "configuration #{config} not found"
-end
-
-if config =~ /\.ru$/
-  cfgfile = File.read(config)
-  if cfgfile[/^#\\(.*)/]
-    opts.parse! $1.split(/\s+/)
-  end
-  cfgfile.sub!(/^__END__\n.*/, '')
-  inner_app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app",
-                   nil, config
-else
-  require config
-  inner_app = Object.const_get(File.basename(config, '.rb').capitalize)
-end
-
-unless server = Rack::Handler.get(server)
-  # Guess.
-  if ENV.include?("PHP_FCGI_CHILDREN")
-    server = Rack::Handler::FastCGI
-
-    # We already speak FastCGI
-    options.delete :File
-    options.delete :Port
-  elsif ENV.include?("REQUEST_METHOD")
-    server = Rack::Handler::CGI
-  else
-    begin
-      server = Rack::Handler::Mongrel
-    rescue LoadError => e
-      server = Rack::Handler::WEBrick
-    end
-  end
-end
-
-p server  if $DEBUG
-
-case env
-when "development"
-  app = Rack::Builder.new {
-    use Rack::CommonLogger, $stderr  unless server.name =~ /CGI/
-    use Rack::ShowExceptions
-    use Rack::Lint
-    run inner_app
-  }.to_app
-
-when "deployment"
-  app = Rack::Builder.new {
-    use Rack::CommonLogger, $stderr  unless server.name =~ /CGI/
-    run inner_app
-  }.to_app
-
-when "none"
-  app = inner_app
-
-end
-
-if $DEBUG
-  pp app
-  pp inner_app
-end
-
-if daemonize
-  if RUBY_VERSION < "1.9"
-    exit if fork
-    Process.setsid
-    exit if fork
-    Dir.chdir "/"
-    File.umask 0000
-    STDIN.reopen "/dev/null"
-    STDOUT.reopen "/dev/null", "a"
-    STDERR.reopen "/dev/null", "a"
-  else
-    Process.daemon
-  end
-
-  if pid
-    File.open(pid, 'w'){ |f| f.write("#{Process.pid}") }
-    at_exit { File.delete(pid) if File.exist?(pid) }
-  end
-end
-
-server.run app, options
+require "rack"
+Rack::Server.start
diff --git a/lib/rack.rb b/lib/rack.rb
index 8d0815b6..6bdb878f 100644
--- a/lib/rack.rb
+++ b/lib/rack.rb
@@ -28,8 +28,10 @@ module Rack
   autoload :Chunked, "rack/chunked"
   autoload :CommonLogger, "rack/commonlogger"
   autoload :ConditionalGet, "rack/conditionalget"
+  autoload :Config, "rack/config"
   autoload :ContentLength, "rack/content_length"
   autoload :ContentType, "rack/content_type"
+  autoload :ETag, "rack/etag"
   autoload :File, "rack/file"
   autoload :Deflater, "rack/deflater"
   autoload :Directory, "rack/directory"
@@ -38,10 +40,15 @@ module Rack
   autoload :Head, "rack/head"
   autoload :Lint, "rack/lint"
   autoload :Lock, "rack/lock"
+  autoload :Logger, "rack/logger"
   autoload :MethodOverride, "rack/methodoverride"
   autoload :Mime, "rack/mime"
+  autoload :NullLogger, "rack/nulllogger"
   autoload :Recursive, "rack/recursive"
   autoload :Reloader, "rack/reloader"
+  autoload :Runtime, "rack/runtime"
+  autoload :Sendfile, "rack/sendfile"
+  autoload :Server, "rack/server"
   autoload :ShowExceptions, "rack/showexceptions"
   autoload :ShowStatus, "rack/showstatus"
   autoload :Static, "rack/static"
@@ -58,7 +65,6 @@ module Rack
     autoload :Basic, "rack/auth/basic"
     autoload :AbstractRequest, "rack/auth/abstract/request"
     autoload :AbstractHandler, "rack/auth/abstract/handler"
-    autoload :OpenID, "rack/auth/openid"
     module Digest
       autoload :MD5, "rack/auth/digest/md5"
       autoload :Nonce, "rack/auth/digest/nonce"
diff --git a/lib/rack/auth/openid.rb b/lib/rack/auth/openid.rb
deleted file mode 100644
index 43cbe4f9..00000000
--- a/lib/rack/auth/openid.rb
+++ /dev/null
@@ -1,487 +0,0 @@
-# AUTHOR: Scytrin dai Kinthra <scytrin@gmail.com>; blink#ruby-lang@irc.freenode.net
-
-gem 'ruby-openid', '~> 2' if defined? Gem
-require 'rack/request'
-require 'rack/utils'
-require 'rack/auth/abstract/handler'
-
-require 'uri'
-require 'openid'
-require 'openid/extension'
-require 'openid/store/memory'
-
-module Rack
-  class Request
-    def openid_request
-      @env['rack.auth.openid.request']
-    end
-
-    def openid_response
-      @env['rack.auth.openid.response']
-    end
-  end
-
-  module Auth
-
-    # Rack::Auth::OpenID provides a simple method for setting up an OpenID
-    # Consumer. It requires the ruby-openid library from janrain to operate,
-    # as well as a rack method of session management.
-    #
-    # The ruby-openid home page is at http://openidenabled.com/ruby-openid/.
-    #
-    # The OpenID specifications can be found at
-    # http://openid.net/specs/openid-authentication-1_1.html
-    # and
-    # http://openid.net/specs/openid-authentication-2_0.html. Documentation
-    # for published OpenID extensions and related topics can be found at
-    # http://openid.net/developers/specs/.
-    #
-    # It is recommended to read through the OpenID spec, as well as
-    # ruby-openid's documentation, to understand what exactly goes on. However
-    # a setup as simple as the presented examples is enough to provide
-    # Consumer functionality.
-    #
-    # This library strongly intends to utilize the OpenID 2.0 features of the
-    # ruby-openid library, which provides OpenID 1.0 compatiblity.
-    #
-    # NOTE: Due to the amount of data that this library stores in the
-    # session, Rack::Session::Cookie may fault.
-    #
-    # == Examples
-    #
-    #   simple_oid = OpenID.new('http://mysite.com/')
-    #
-    #   return_oid = OpenID.new('http://mysite.com/', {
-    #     :return_to => 'http://mysite.com/openid'
-    #   })
-    #
-    #   complex_oid = OpenID.new('http://mysite.com/',
-    #     :immediate => true,
-    #     :extensions => {
-    #       ::OpenID::SReg => [['email'],['nickname']]
-    #     }
-    #   )
-    #
-    # = Advanced
-    #
-    # Most of the functionality of this library is encapsulated such that
-    # expansion and overriding functions isn't difficult nor tricky.
-    # Alternately, to avoid opening up singleton objects or subclassing, a
-    # wrapper rack middleware can be composed to act upon Auth::OpenID's
-    # responses. See #check and #finish for locations of pertinent data.
-    #
-    # == Responses
-    #
-    # To change the responses that Auth::OpenID returns, override the methods
-    # #redirect, #bad_request, #unauthorized, #access_denied, and
-    # #foreign_server_failure.
-    #
-    # Additionally #confirm_post_params is used when the URI would exceed
-    # length limits on a GET request when doing the initial verification
-    # request.
-    #
-    # == Processing
-    #
-    # To change methods of processing completed transactions, override the
-    # methods #success, #setup_needed, #cancel, and #failure. Please ensure
-    # the returned object is a rack compatible response.
-    #
-    # The first argument is an OpenID::Response, the second is a
-    # Rack::Request of the current request, the last is the hash used in
-    # ruby-openid handling, which can be found manually at
-    # env['rack.session'][:openid].
-    #
-    # This is useful if you wanted to expand the processing done, such as
-    # setting up user accounts.
-    #
-    #   oid_app = Rack::Auth::OpenID.new realm, :return_to => return_to
-    #   def oid_app.success oid, request, session
-    #     user = Models::User[oid.identity_url]
-    #     user ||= Models::User.create_from_openid oid
-    #     request['rack.session'][:user] = user.id
-    #     redirect MyApp.site_home
-    #   end
-    #
-    #   site_map['/openid'] = oid_app
-    #   map = Rack::URLMap.new site_map
-    #   ...
-
-    class OpenID
-      # Raised if an incompatible session is being used.
-      class NoSession < RuntimeError; end
-      # Raised if an extension not matching specifications is provided.
-      class BadExtension < RuntimeError; end
-      # Possible statuses returned from consumer responses. See definitions
-      # in the ruby-openid library.
-      ValidStatus = [
-        ::OpenID::Consumer::SUCCESS,
-        ::OpenID::Consumer::FAILURE,
-        ::OpenID::Consumer::CANCEL,
-        ::OpenID::Consumer::SETUP_NEEDED
-      ]
-
-      # The first argument is the realm, identifying the site they are trusting
-      # with their identity. This is required, also treated as the trust_root
-      # in OpenID 1.x exchanges.
-      #
-      # The lits of acceptable options include :return_to, :session_key,
-      # :openid_param, :store, :immediate, :extensions.
-      #
-      # <tt>:return_to</tt> defines the url to return to after the client
-      # authenticates with the openid service provider. This url should point
-      # to where Rack::Auth::OpenID is mounted. If unprovided, the url of
-      # the current request is used.
-      #
-      # <tt>:session_key</tt> defines the key to the session hash in the env.
-      # The default is 'rack.session'.
-      #
-      # <tt>:openid_param</tt> defines at what key in the request parameters to
-      # find the identifier to resolve. As per the 2.0 spec, the default is
-      # 'openid_identifier'.
-      #
-      # <tt>:store</tt> defined what OpenID Store to use for persistant
-      # information. By default a Store::Memory is used.
-      #
-      # <tt>:immediate</tt> as true will make initial requests to be of an
-      # immediate type. This is false by default. See OpenID specification
-      # documentation.
-      #
-      # <tt>:extensions</tt> should be a hash of openid extension
-      # implementations. The key should be the extension module, the value
-      # should be an array of arguments for extension::Request.new().
-      # The hash is iterated over and passed to #add_extension for processing.
-      # Please see #add_extension for further documentation.
-
-      def initialize(realm, options={})
-        realm = URI(realm)
-        raise ArgumentError, "Invalid realm: #{realm}" \
-          unless realm.absolute? \
-          and realm.fragment.nil? \
-          and realm.scheme =~ /^https?$/ \
-          and realm.host =~ /^(\*\.)?#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+/
-        realm.path = '/' if realm.path.empty?
-        @realm = realm.to_s
-
-        if ruri = options[:return_to]
-          ruri = URI(ruri)
-          raise ArgumentError, "Invalid return_to: #{ruri}" \
-            unless ruri.absolute? \
-            and ruri.scheme =~ /^https?$/ \
-            and ruri.fragment.nil?
-          raise ArgumentError, "return_to #{ruri} not within realm #{realm}" \
-            unless self.within_realm?(ruri)
-          @return_to = ruri.to_s
-        end
-
-        @session_key  = options[:session_key]   || 'rack.session'
-        @openid_param = options[:openid_param]  || 'openid_identifier'
-        @store        = options[:store]         || ::OpenID::Store::Memory.new
-        @immediate    = !!options[:immediate]
-
-        @extensions   = {}
-        if extensions = options[:extensions]
-          extensions.each do |ext, args|
-            add_extension(ext, *args)
-          end
-        end
-
-        # Undocumented, semi-experimental
-        @anonymous    = !!options[:anonymous]
-      end
-
-      attr_reader :realm, :return_to, :session_key, :openid_param, :store,
-        :immediate, :extensions
-
-      # Sets up and uses session data at <tt>:openid</tt> within the session.
-      # Errors in this setup will raise a NoSession exception.
-      #
-      # If the parameter 'openid.mode' is set, which implies a followup from
-      # the openid server, processing is passed to #finish and the result is
-      # returned. However, if there is no appropriate openid information in the
-      # session, a 400 error is returned.
-      #
-      # If the parameter specified by <tt>options[:openid_param]</tt> is
-      # present, processing is passed to #check and the result is returned.
-      #
-      # If neither of these conditions are met, #bad_request is called.
-
-      def call(env)
-        env['rack.auth.openid'] = self
-        env_session = env[@session_key]
-        unless env_session and env_session.is_a?(Hash)
-          raise NoSession, 'No compatible session.'
-        end
-        # let us work in our own namespace...
-        session = (env_session[:openid] ||= {})
-        unless session and session.is_a?(Hash)
-          raise NoSession, 'Incompatible openid session.'
-        end
-
-        request = Rack::Request.new(env)
-        consumer = ::OpenID::Consumer.new(session, @store)
-
-        if mode = request.GET['openid.mode']
-          finish(consumer, session, request)
-        elsif request.GET[@openid_param]
-          check(consumer, session, request)
-        else
-          bad_request
-        end
-      end
-
-      # As the first part of OpenID consumer action, #check retrieves the data
-      # required for completion.
-      #
-      # If all parameters fit within the max length of a URI, a 303 redirect
-      # will be returned. Otherwise #confirm_post_params will be called.
-      #
-      # Any messages from OpenID's request are logged to env['rack.errors']
-      #
-      # <tt>env['rack.auth.openid.request']</tt> is the openid checkid request
-      # instance.
-      #
-      # <tt>session[:openid_param]</tt> is set to the openid identifier
-      # provided by the user.
-      #
-      # <tt>session[:return_to]</tt> is set to the return_to uri given to the
-      # identity provider.
-
-      def check(consumer, session, req)
-        oid = consumer.begin(req.GET[@openid_param], @anonymous)
-        req.env['rack.auth.openid.request'] = oid
-        req.env['rack.errors'].puts(oid.message)
-        p oid if $DEBUG
-
-        ## Extension support
-        extensions.each do |ext,args|
-          oid.add_extension(ext::Request.new(*args))
-        end
-
-        session[:openid_param] = req.GET[openid_param]
-        return_to_uri = return_to ? return_to : req.url
-        session[:return_to] = return_to_uri
-        immediate = session.key?(:setup_needed) ? false : immediate
-
-        if oid.send_redirect?(realm, return_to_uri, immediate)
-          redirect(oid.redirect_url(realm, return_to_uri, immediate))
-        else
-          confirm_post_params(oid, realm, return_to_uri, immediate)
-        end
-      rescue ::OpenID::DiscoveryFailure => e
-        # thrown from inside OpenID::Consumer#begin by yadis stuff
-        req.env['rack.errors'].puts( [e.message, *e.backtrace]*"\n" )
-        return foreign_server_failure
-      end
-
-      # This is the final portion of authentication.
-      # If successful, a redirect to the realm is be returned.
-      # Data gathered from extensions are stored in session[:openid] with the
-      # extension's namespace uri as the key.
-      #
-      # Any messages from OpenID's response are logged to env['rack.errors']
-      #
-      # <tt>env['rack.auth.openid.response']</tt> will contain the openid
-      # response.
-
-      def finish(consumer, session, req)
-        oid = consumer.complete(req.GET, req.url)
-        req.env['rack.auth.openid.response'] = oid
-        req.env['rack.errors'].puts(oid.message)
-        p oid if $DEBUG
-
-        if ValidStatus.include?(oid.status)
-          __send__(oid.status, oid, req, session)
-        else
-          invalid_status(oid, req, session)
-        end
-      end
-
-      # The first argument should be the main extension module.
-      # The extension module should contain the constants:
-      # * class Request, should have OpenID::Extension as an ancestor
-      # * class Response, should have OpenID::Extension as an ancestor
-      # * string NS_URI, which defining the namespace of the extension
-      #
-      # All trailing arguments will be passed to extension::Request.new in
-      # #check.
-      # The openid response will be passed to
-      # extension::Response#from_success_response, oid#get_extension_args will
-      # be called on the result to attain the gathered data.
-      #
-      # This method returns the key at which the response data will be found in
-      # the session, which is the namespace uri by default.
-
-      def add_extension(ext, *args)
-        raise BadExtension unless valid_extension?(ext)
-        extensions[ext] = args
-        return ext::NS_URI
-      end
-
-      # Checks the validitity, in the context of usage, of a submitted
-      # extension.
-
-      def valid_extension?(ext)
-        if not %w[NS_URI Request Response].all?{|c| ext.const_defined?(c) }
-          raise ArgumentError, 'Extension is missing constants.'
-        elsif not ext::Response.respond_to?(:from_success_response)
-          raise ArgumentError, 'Response is missing required method.'
-        end
-        return true
-      rescue
-        return false
-      end
-
-      # Checks the provided uri to ensure it'd be considered within the realm.
-      # is currently not compatible with wildcard realms.
-
-      def within_realm? uri
-        uri = URI.parse(uri.to_s)
-        realm = URI.parse(self.realm)
-        return false unless uri.absolute?
-        return false unless uri.path[0, realm.path.size] == realm.path
-        return false unless uri.host == realm.host or realm.host[/^\*\./]
-        # for wildcard support, is awkward with URI limitations
-        realm_match = Regexp.escape(realm.host).
-          sub(/^\*\./,"^#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+.")+'$'
-        return false unless uri.host.match(realm_match)
-        return true
-      end
-
-      alias_method :include?, :within_realm?
-
-      protected
-
-      # Returns an html form page for posting to an Identity Provider if the
-      # GET request would exceed the upper URI length limit.
-
-      def confirm_post_params(oid, realm, return_to, immediate)
-        response = Rack::Response.new '<html>'+
-          '<head><title>Confirm...</title></head>'+
-          '<body>'+oid.form_markup(realm, return_to, immediate)+'</body>'+
-          '</html>'
-        response.finish
-      end
-
-      # Returns a 303 redirect with the destination of that provided by the
-      # argument.
-
-      def redirect(uri)
-        [ 303, {'Content-Type'=>'text/plain', 'Content-Length'=>'0',
-          'Location' => uri},
-          [] ]
-      end
-
-      # Returns an empty 400 response.
-
-      def bad_request
-        [ 400, {'Content-Type'=>'text/plain', 'Content-Length'=>'0'},
-          [''] ]
-      end
-
-      # Returns a basic unauthorized 401 response.
-
-      def unauthorized
-        [ 401, {'Content-Type' => 'text/plain', 'Content-Length' => '13'},
-          ['Unauthorized.'] ]
-      end
-
-      # Returns a basic access denied 403 response.
-
-      def access_denied
-        [ 403, {'Content-Type' => 'text/plain', 'Content-Length' => '14'},
-          ['Access denied.'] ]
-      end
-
-      # Returns a 503 response to be used if communication with the remote
-      # OpenID server fails.
-
-      def foreign_server_failure
-        [ 503, {'Content-Type'=>'text/plain', 'Content-Length' => '23'},
-          ['Foreign server failure.'] ]
-      end
-
-      private
-
-      # Called to complete processing on a successful transaction.
-      # Within the openid session, :openid_identity and :openid_identifier are
-      # set to the user friendly and the standard representation of the
-      # validated identity. All other data in the openid session is cleared.
-
-      def success(oid, request, session)
-        session.clear
-        session[:openid_identity]   = oid.display_identifier
-        session[:openid_identifier] = oid.identity_url
-        extensions.keys.each do |ext|
-          label     = ext.name[/[^:]+$/].downcase
-          response  = ext::Response.from_success_response(oid)
-          session[label] = response.data
-        end
-        redirect(realm)
-      end
-
-      # Called if the Identity Provider indicates further setup by the user is
-      # required.
-      # The identifier is retrived from the openid session at :openid_param.
-      # And :setup_needed is set to true to prevent looping.
-
-      def setup_needed(oid, request, session)
-        identifier = session[:openid_param]
-        session[:setup_needed] = true
-        redirect(req.script_name + '?' + openid_param + '=' + identifier)
-      end
-
-      # Called if the user indicates they wish to cancel identification.
-      # Data within openid session is cleared.
-
-      def cancel(oid, request, session)
-        session.clear
-        access_denied
-      end
-
-      # Called if the Identity Provider indicates the user is unable to confirm
-      # their identity. Data within the openid session is left alone, in case
-      # of swarm auth attacks.
-
-      def failure(oid, request, session)
-        unauthorized
-      end
-
-      # To be called if there is no method for handling the OpenID response
-      # status.
-
-      def invalid_status(oid, request, session)
-        msg = 'Invalid status returned by the OpenID authorization reponse.'
-        [ 500,
-          {'Content-Type'=>'text/plain','Content-Length'=>msg.length.to_s},
-          [msg] ]
-      end
-    end
-
-    # A class developed out of the request to use OpenID as an authentication
-    # middleware. The request will be sent to the OpenID instance unless the
-    # block evaluates to true. For example in rackup, you can use it as such:
-    #
-    #   use Rack::Session::Pool
-    #   use Rack::Auth::OpenIDAuth, realm, openid_options do |env|
-    #     env['rack.session'][:authkey] == a_string
-    #   end
-    #   run RackApp
-    #
-    # Or simply:
-    #
-    #   app = Rack::Auth::OpenIDAuth.new app, realm, openid_options, &auth
-
-    class OpenIDAuth < Rack::Auth::AbstractHandler
-      attr_reader :oid
-      def initialize(app, realm, options={}, &auth)
-        @oid = OpenID.new(realm, options)
-        super(app, &auth)
-      end
-
-      def call(env)
-        to = @authenticator.call(env) ? @app : @oid
-        to.call(env)
-      end
-    end
-  end
-end
diff --git a/lib/rack/builder.rb b/lib/rack/builder.rb
index 295235e5..530f0aaf 100644
--- a/lib/rack/builder.rb
+++ b/lib/rack/builder.rb
@@ -24,6 +24,23 @@ module Rack
   # You can use +map+ to construct a Rack::URLMap in a convenient way.
 
   class Builder
+    def self.parse_file(config, opts = Server::Options.new)
+      options = {}
+      if config =~ /\.ru$/
+        cfgfile = ::File.read(config)
+        if cfgfile[/^#\\(.*)/] && opts
+          options = opts.parse! $1.split(/\s+/)
+        end
+        cfgfile.sub!(/^__END__\n.*/, '')
+        app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app",
+          TOPLEVEL_BINDING, config
+      else
+        require config
+        app = Object.const_get(::File.basename(config, '.rb').capitalize)
+      end
+      return app, options
+    end
+
     def initialize(&block)
       @ins = []
       instance_eval(&block) if block_given?
diff --git a/lib/rack/chunked.rb b/lib/rack/chunked.rb
index 280d89dd..dddf9694 100644
--- a/lib/rack/chunked.rb
+++ b/lib/rack/chunked.rb
@@ -19,7 +19,7 @@ module Rack
          STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
          headers['Content-Length'] ||
          headers['Transfer-Encoding']
-        [status, headers.to_hash, body]
+        [status, headers, body]
       else
         dup.chunk(status, headers, body)
       end
@@ -29,7 +29,7 @@ module Rack
       @body = body
       headers.delete('Content-Length')
       headers['Transfer-Encoding'] = 'chunked'
-      [status, headers.to_hash, self]
+      [status, headers, self]
     end
 
     def each
diff --git a/lib/rack/commonlogger.rb b/lib/rack/commonlogger.rb
index 880f0fbf..1edc9b83 100644
--- a/lib/rack/commonlogger.rb
+++ b/lib/rack/commonlogger.rb
@@ -16,6 +16,7 @@ module Rack
     def call(env)
       began_at = Time.now
       status, header, body = @app.call(env)
+      header = Utils::HeaderHash.new(header)
       log(env, status, header, began_at)
       [status, header, body]
     end
@@ -41,12 +42,8 @@ module Rack
     end
 
     def extract_content_length(headers)
-      headers.each do |key, value|
-        if key.downcase == 'content-length'
-          return value.to_s == '0' ? '-' : value
-        end
-      end
-      '-'
+      value = headers['Content-Length'] or return '-'
+      value.to_s == '0' ? '-' : value
     end
   end
 end
diff --git a/lib/rack/config.rb b/lib/rack/config.rb
new file mode 100644
index 00000000..c6d446c0
--- /dev/null
+++ b/lib/rack/config.rb
@@ -0,0 +1,15 @@
+module Rack
+  # Rack::Config modifies the environment using the block given during
+  # initialization.
+  class Config
+    def initialize(app, &block)
+      @app = app
+      @block = block
+    end
+
+    def call(env)
+      @block.call(env)
+      @app.call(env)
+    end
+  end
+end
diff --git a/lib/rack/content_type.rb b/lib/rack/content_type.rb
index 0c1e1ca3..874c28cd 100644
--- a/lib/rack/content_type.rb
+++ b/lib/rack/content_type.rb
@@ -17,7 +17,7 @@ module Rack
       status, headers, body = @app.call(env)
       headers = Utils::HeaderHash.new(headers)
       headers['Content-Type'] ||= @content_type
-      [status, headers.to_hash, body]
+      [status, headers, body]
     end
   end
 end
diff --git a/lib/rack/etag.rb b/lib/rack/etag.rb
new file mode 100644
index 00000000..06dbc6aa
--- /dev/null
+++ b/lib/rack/etag.rb
@@ -0,0 +1,23 @@
+require 'digest/md5'
+
+module Rack
+  # Automatically sets the ETag header on all String bodies
+  class ETag
+    def initialize(app)
+      @app = app
+    end
+
+    def call(env)
+      status, headers, body = @app.call(env)
+
+      if !headers.has_key?('ETag')
+        parts = []
+        body.each { |part| parts << part.to_s }
+        headers['ETag'] = %("#{Digest::MD5.hexdigest(parts.join(""))}")
+        [status, headers, parts]
+      else
+        [status, headers, body]
+      end
+    end
+  end
+end
diff --git a/lib/rack/handler.rb b/lib/rack/handler.rb
index 5624a1e7..3c09883e 100644
--- a/lib/rack/handler.rb
+++ b/lib/rack/handler.rb
@@ -22,6 +22,25 @@ module Rack
       end
     end
 
+    def self.default(options = {})
+      # Guess.
+      if ENV.include?("PHP_FCGI_CHILDREN")
+        # We already speak FastCGI
+        options.delete :File
+        options.delete :Port
+
+        Rack::Handler::FastCGI
+      elsif ENV.include?("REQUEST_METHOD")
+        Rack::Handler::CGI
+      else
+        begin
+          Rack::Handler::Mongrel
+        rescue LoadError => e
+          Rack::Handler::WEBrick
+        end
+      end
+    end
+
     # Transforms server-name constants to their canonical form as filenames,
     # then tries to require them but silences the LoadError if not found
     #
diff --git a/lib/rack/handler/fastcgi.rb b/lib/rack/handler/fastcgi.rb
index 1739d659..d8301474 100644
--- a/lib/rack/handler/fastcgi.rb
+++ b/lib/rack/handler/fastcgi.rb
@@ -33,7 +33,7 @@ module Rack
         env.delete "HTTP_CONTENT_LENGTH"
 
         env["SCRIPT_NAME"] = ""  if env["SCRIPT_NAME"] == "/"
-        
+
         rack_input = RewindableInput.new(request.in)
 
         env.update({"rack.version" => [1,0],
@@ -50,7 +50,6 @@ module Rack
         env["QUERY_STRING"] ||= ""
         env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
         env["REQUEST_PATH"] ||= "/"
-        env.delete "PATH_INFO"  if env["PATH_INFO"] == ""
         env.delete "CONTENT_TYPE"  if env["CONTENT_TYPE"] == ""
         env.delete "CONTENT_LENGTH"  if env["CONTENT_LENGTH"] == ""
 
diff --git a/lib/rack/handler/mongrel.rb b/lib/rack/handler/mongrel.rb
index cd1af2de..21f9f134 100644
--- a/lib/rack/handler/mongrel.rb
+++ b/lib/rack/handler/mongrel.rb
@@ -14,7 +14,7 @@ module Rack
           options[:throttle]       || 0,
           options[:timeout]        || 60)
         # Acts like Rack::URLMap, utilizing Mongrel's own path finding methods.
-        # Use is similar to #run, replacing the app argument with a hash of
+        # Use is similar to #run, replacing the app argument with a hash of
         # { path=>app, ... } or an instance of Rack::URLMap.
         if options[:map]
           if app.is_a? Hash
@@ -63,7 +63,6 @@ module Rack
                      "rack.url_scheme" => "http",
                    })
         env["QUERY_STRING"] ||= ""
-        env.delete "PATH_INFO"  if env["PATH_INFO"] == ""
 
         status, headers, body = @app.call(env)
 
diff --git a/lib/rack/handler/webrick.rb b/lib/rack/handler/webrick.rb
index 5b9ae740..43286113 100644
--- a/lib/rack/handler/webrick.rb
+++ b/lib/rack/handler/webrick.rb
@@ -40,9 +40,7 @@ module Rack
         env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
         env["QUERY_STRING"] ||= ""
         env["REQUEST_PATH"] ||= "/"
-        if env["PATH_INFO"] == ""
-          env.delete "PATH_INFO"
-        else
+        unless env["PATH_INFO"] == ""
           path, n = req.request_uri.path, env["SCRIPT_NAME"].length
           env["PATH_INFO"] = path[n, path.length-n]
         end
diff --git a/lib/rack/lint.rb b/lib/rack/lint.rb
index a1fcc3c6..493982ac 100644
--- a/lib/rack/lint.rb
+++ b/lib/rack/lint.rb
@@ -148,6 +148,35 @@ module Rack
         }
       end
 
+      ## <tt>rack.logger</tt>:: A common object interface for logging messages.
+      ##                        The object must implement:
+      if logger = env['rack.logger']
+        ##                         info(message, &block)
+        assert("logger #{logger.inspect} must respond to info") {
+          logger.respond_to?(:info)
+        }
+
+        ##                         debug(message, &block)
+        assert("logger #{logger.inspect} must respond to debug") {
+          logger.respond_to?(:debug)
+        }
+
+        ##                         warn(message, &block)
+        assert("logger #{logger.inspect} must respond to warn") {
+          logger.respond_to?(:warn)
+        }
+
+        ##                         error(message, &block)
+        assert("logger #{logger.inspect} must respond to error") {
+          logger.respond_to?(:error)
+        }
+
+        ##                         fatal(message, &block)
+        assert("logger #{logger.inspect} must respond to fatal") {
+          logger.respond_to?(:fatal)
+        }
+      end
+
       ## The server or the application can store their own data in the
       ## environment, too.  The keys must contain at least one dot,
       ## and should be prefixed uniquely.  The prefix <tt>rack.</tt>
@@ -243,7 +272,7 @@ module Rack
       assert("rack.input #{input} is not opened in binary mode") {
         input.binmode?
       } if input.respond_to?(:binmode?)
-      
+
       ## The input stream must respond to +gets+, +each+, +read+ and +rewind+.
       [:gets, :each, :read, :rewind].each { |method|
         assert("rack.input #{input} does not respond to ##{method}") {
@@ -300,9 +329,9 @@ module Rack
             args[1].kind_of?(String)
           }
         end
-        
+
         v = @input.read(*args)
-        
+
         assert("rack.input#read didn't return nil or a String") {
           v.nil? or v.kind_of? String
         }
@@ -311,7 +340,7 @@ module Rack
             !v.nil?
           }
         end
-        
+
         v
       end
 
@@ -325,7 +354,7 @@ module Rack
           yield line
         }
       end
-      
+
       ## * +rewind+ must be called without arguments. It rewinds the input
       ##   stream back to the beginning. It must not raise Errno::ESPIPE:
       ##   that is, it may not be a pipe or a socket. Therefore, handler
diff --git a/lib/rack/logger.rb b/lib/rack/logger.rb
new file mode 100644
index 00000000..d67d8ce2
--- /dev/null
+++ b/lib/rack/logger.rb
@@ -0,0 +1,20 @@
+require 'logger'
+
+module Rack
+  # Sets up rack.logger to write to rack.errors stream
+  class Logger
+    def initialize(app, level = ::Logger::INFO)
+      @app, @level = app, level
+    end
+
+    def call(env)
+      logger = ::Logger.new(env['rack.errors'])
+      logger.level = @level
+
+      env['rack.logger'] = logger
+      @app.call(env)
+    ensure
+      logger.close
+    end
+  end
+end
diff --git a/lib/rack/mime.rb b/lib/rack/mime.rb
index 853f16bd..1414d19a 100644
--- a/lib/rack/mime.rb
+++ b/lib/rack/mime.rb
@@ -105,6 +105,7 @@ module Rack
       ".m3u"     => "audio/x-mpegurl",
       ".m4v"     => "video/mp4",
       ".man"     => "text/troff",
+      ".manifest"=> "text/cache-manifest",
       ".mathml"  => "application/mathml+xml",
       ".mbox"    => "application/mbox",
       ".mdoc"    => "text/troff",
diff --git a/lib/rack/nulllogger.rb b/lib/rack/nulllogger.rb
new file mode 100644
index 00000000..77fb637d
--- /dev/null
+++ b/lib/rack/nulllogger.rb
@@ -0,0 +1,18 @@
+module Rack
+  class NullLogger
+    def initialize(app)
+      @app = app
+    end
+
+    def call(env)
+      env['rack.logger'] = self
+      @app.call(env)
+    end
+
+    def info(progname = nil, &block);  end
+    def debug(progname = nil, &block); end
+    def warn(progname = nil, &block);  end
+    def error(progname = nil, &block); end
+    def fatal(progname = nil, &block); end
+  end
+end
diff --git a/lib/rack/request.rb b/lib/rack/request.rb
index 248ce18d..d5070257 100644
--- a/lib/rack/request.rb
+++ b/lib/rack/request.rb
@@ -32,6 +32,7 @@ module Rack
     def content_type;    @env['CONTENT_TYPE']                     end
     def session;         @env['rack.session'] ||= {}              end
     def session_options; @env['rack.session.options'] ||= {}      end
+    def logger;          @env['rack.logger']                      end
 
     # The media type (type/subtype) portion of the CONTENT_TYPE header
     # without any media type parameters. e.g., when CONTENT_TYPE is
@@ -92,7 +93,7 @@ module Rack
     PARSEABLE_DATA_MEDIA_TYPES = [
       'multipart/related',
       'multipart/mixed'
-    ]  
+    ]
 
     # Determine whether the request body contains form-data by checking
     # the request media_type against registered form-data media-types:
@@ -216,11 +217,11 @@ module Rack
 
       url
     end
-    
+
     def path
       script_name + path_info
     end
-    
+
     def fullpath
       query_string.empty? ? path : "#{path}?#{query_string}"
     end
diff --git a/lib/rack/response.rb b/lib/rack/response.rb
index d1f6a123..a7f9bf2b 100644
--- a/lib/rack/response.rb
+++ b/lib/rack/response.rb
@@ -19,7 +19,7 @@ module Rack
     attr_accessor :length
 
     def initialize(body=[], status=200, header={}, &block)
-      @status = status
+      @status = status.to_i
       @header = Utils::HeaderHash.new({"Content-Type" => "text/html"}.
                                       merge(header))
 
@@ -71,9 +71,9 @@ module Rack
 
       if [204, 304].include?(status.to_i)
         header.delete "Content-Type"
-        [status.to_i, header.to_hash, []]
+        [status.to_i, header, []]
       else
-        [status.to_i, header.to_hash, self]
+        [status.to_i, header, self]
       end
     end
     alias to_a finish           # For *response
diff --git a/lib/rack/runtime.rb b/lib/rack/runtime.rb
new file mode 100644
index 00000000..1bd411fd
--- /dev/null
+++ b/lib/rack/runtime.rb
@@ -0,0 +1,27 @@
+module Rack
+  # Sets an "X-Runtime" response header, indicating the response
+  # time of the request, in seconds
+  #
+  # You can put it right before the application to see the processing
+  # time, or before all the other middlewares to include time for them,
+  # too.
+  class Runtime
+    def initialize(app, name = nil)
+      @app = app
+      @header_name = "X-Runtime"
+      @header_name << "-#{name}" if name
+    end
+
+    def call(env)
+      start_time = Time.now
+      status, headers, body = @app.call(env)
+      request_time = Time.now - start_time
+
+      if !headers.has_key?(@header_name)
+        headers[@header_name] = "%0.6f" % request_time
+      end
+
+      [status, headers, body]
+    end
+  end
+end
diff --git a/lib/rack/sendfile.rb b/lib/rack/sendfile.rb
new file mode 100644
index 00000000..4fa82946
--- /dev/null
+++ b/lib/rack/sendfile.rb
@@ -0,0 +1,142 @@
+require 'rack/file'
+
+module Rack
+  class File #:nodoc:
+    alias :to_path :path
+  end
+
+  # = Sendfile
+  #
+  # The Sendfile middleware intercepts responses whose body is being
+  # served from a file and replaces it with a server specific X-Sendfile
+  # header. The web server is then responsible for writing the file contents
+  # to the client. This can dramatically reduce the amount of work required
+  # by the Ruby backend and takes advantage of the web servers optimized file
+  # delivery code.
+  #
+  # In order to take advantage of this middleware, the response body must
+  # respond to +to_path+ and the request must include an X-Sendfile-Type
+  # header. Rack::File and other components implement +to_path+ so there's
+  # rarely anything you need to do in your application. The X-Sendfile-Type
+  # header is typically set in your web servers configuration. The following
+  # sections attempt to document
+  #
+  # === Nginx
+  #
+  # Nginx supports the X-Accel-Redirect header. This is similar to X-Sendfile
+  # but requires parts of the filesystem to be mapped into a private URL
+  # hierarachy.
+  #
+  # The following example shows the Nginx configuration required to create
+  # a private "/files/" area, enable X-Accel-Redirect, and pass the special
+  # X-Sendfile-Type and X-Accel-Mapping headers to the backend:
+  #
+  #   location /files/ {
+  #     internal;
+  #     alias /var/www/;
+  #   }
+  #
+  #   location / {
+  #     proxy_redirect     false;
+  #
+  #     proxy_set_header   Host                $host;
+  #     proxy_set_header   X-Real-IP           $remote_addr;
+  #     proxy_set_header   X-Forwarded-For     $proxy_add_x_forwarded_for;
+  #
+  #     proxy_set_header   X-Sendfile-Type     X-Accel-Redirect
+  #     proxy_set_header   X-Accel-Mapping     /files/=/var/www/;
+  #
+  #     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 name of the private URL pattern,
+  # followed by an equals sign (=), followed by the location on the file system
+  # that it maps to. The middleware performs a simple substitution on the
+  # resulting path.
+  #
+  # See Also: http://wiki.codemongers.com/NginxXSendfile
+  #
+  # === lighttpd
+  #
+  # Lighttpd has supported some variation of the X-Sendfile header for some
+  # time, although only recent version support X-Sendfile in a reverse proxy
+  # configuration.
+  #
+  #   $HTTP["host"] == "example.com" {
+  #      proxy-core.protocol = "http"
+  #      proxy-core.balancer = "round-robin"
+  #      proxy-core.backends = (
+  #        "127.0.0.1:8000",
+  #        "127.0.0.1:8001",
+  #        ...
+  #      )
+  #
+  #      proxy-core.allow-x-sendfile = "enable"
+  #      proxy-core.rewrite-request = (
+  #        "X-Sendfile-Type" => (".*" => "X-Sendfile")
+  #      )
+  #    }
+  #
+  # See Also: http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModProxyCore
+  #
+  # === Apache
+  #
+  # X-Sendfile is supported under Apache 2.x using a separate module:
+  #
+  # http://tn123.ath.cx/mod_xsendfile/
+  #
+  # Once the module is compiled and installed, you can enable it using
+  # XSendFile config directive:
+  #
+  #   RequestHeader Set X-Sendfile-Type X-Sendfile
+  #   ProxyPassReverse / http://localhost:8001/
+  #   XSendFile on
+
+  class Sendfile
+    F = ::File
+
+    def initialize(app, variation=nil)
+      @app = app
+      @variation = variation
+    end
+
+    def call(env)
+      status, headers, body = @app.call(env)
+      if body.respond_to?(:to_path)
+        case type = variation(env)
+        when 'X-Accel-Redirect'
+          path = F.expand_path(body.to_path)
+          if url = map_accel_path(env, path)
+            headers[type] = url
+            body = []
+          else
+            env['rack.errors'] << "X-Accel-Mapping header missing"
+          end
+        when 'X-Sendfile', 'X-Lighttpd-Send-File'
+          path = F.expand_path(body.to_path)
+          headers[type] = path
+          body = []
+        when '', nil
+        else
+          env['rack.errors'] << "Unknown x-sendfile variation: '#{variation}'.\n"
+        end
+      end
+      [status, headers, body]
+    end
+
+    private
+      def variation(env)
+        @variation ||
+          env['sendfile.type'] ||
+          env['HTTP_X_SENDFILE_TYPE']
+      end
+
+      def map_accel_path(env, file)
+        if mapping = env['HTTP_X_ACCEL_MAPPING']
+          internal, external = mapping.split('=', 2).map{ |p| p.strip }
+          file.sub(/^#{internal}/i, external)
+        end
+      end
+  end
+end
diff --git a/lib/rack/server.rb b/lib/rack/server.rb
new file mode 100644
index 00000000..7d0705ad
--- /dev/null
+++ b/lib/rack/server.rb
@@ -0,0 +1,212 @@
+require 'optparse'
+
+module Rack
+  class Server
+    class Options
+      def parse!(args)
+        options = {}
+        opt_parser = OptionParser.new("", 24, '  ') do |opts|
+          opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]"
+
+          opts.separator ""
+          opts.separator "Ruby options:"
+
+          lineno = 1
+          opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
+            eval line, TOPLEVEL_BINDING, "-e", lineno
+            lineno += 1
+          }
+
+          opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
+            options[:debug] = true
+          }
+          opts.on("-w", "--warn", "turn warnings on for your script") {
+            options[:warn] = true
+          }
+
+          opts.on("-I", "--include PATH",
+                  "specify $LOAD_PATH (may be used more than once)") { |path|
+            options[:include] = path.split(":")
+          }
+
+          opts.on("-r", "--require LIBRARY",
+                  "require the library, before executing your script") { |library|
+            options[:require] = library
+          }
+
+          opts.separator ""
+          opts.separator "Rack options:"
+          opts.on("-s", "--server SERVER", "serve using SERVER (webrick/mongrel)") { |s|
+            options[:server] = s
+          }
+
+          opts.on("-o", "--host HOST", "listen on HOST (default: 0.0.0.0)") { |host|
+            options[:Host] = host
+          }
+
+          opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
+            options[:Port] = port
+          }
+
+          opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
+            options[:environment] = e
+          }
+
+          opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
+            options[:daemonize] = d ? true : false
+          }
+
+          opts.on("-P", "--pid FILE", "file to store PID (default: rack.pid)") { |f|
+            options[:pid] = ::File.expand_path(f)
+          }
+
+          opts.separator ""
+          opts.separator "Common options:"
+
+          opts.on_tail("-h", "--help", "Show this message") do
+            puts opts
+            exit
+          end
+
+          opts.on_tail("--version", "Show version") do
+            puts "Rack #{Rack.version}"
+            exit
+          end
+        end
+        opt_parser.parse! args
+        options[:rack_file] = args.last if args.last
+        options
+      end
+    end
+
+    def self.start
+      new.start
+    end
+
+    attr_accessor :options
+
+    def initialize(options = nil)
+      @options = options
+    end
+
+    def options
+      @options ||= parse_options(ARGV)
+    end
+
+    def default_options
+      {
+        :environment => "development",
+        :pid         => nil,
+        :Port        => 9292,
+        :Host        => "0.0.0.0",
+        :AccessLog   => [],
+        :rack_file   => ::File.expand_path("config.ru")
+      }
+    end
+
+    def app
+      @app ||= begin
+        if !::File.exist? options[:rack_file]
+          abort "configuration #{options[:rack_file]} not found"
+        end
+
+        app, options = Rack::Builder.parse_file(self.options[:rack_file], opt_parser)
+        self.options.merge! options
+        app
+      end
+    end
+
+    def self.middleware
+      @middleware ||= begin
+        m = Hash.new {|h,k| h[k] = []}
+        m["deployment"].concat  [lambda {|server| server.server =~ /CGI/ ? nil : [Rack::CommonLogger, $stderr] }]
+        m["development"].concat m["deployment"] + [[Rack::ShowExceptions], [Rack::Lint]]
+        m
+      end
+    end
+
+    def middleware
+      self.class.middleware
+    end
+
+    def start
+      if options[:debug]
+        $DEBUG = true
+        require 'pp'
+        p options[:server]
+        pp wrapped_app
+        pp app
+      end
+
+      if options[:warn]
+        $-w = true
+      end
+
+      if includes = options[:include]
+        $LOAD_PATH.unshift *includes
+      end
+
+      if library = options[:require]
+        require library
+      end
+
+      daemonize_app if options[:daemonize]
+      write_pid if options[:pid]
+      server.run wrapped_app, options
+    end
+
+    def server
+      @_server ||= Rack::Handler.get(options[:server]) || Rack::Handler.default
+    end
+
+    private
+      def parse_options(args)
+        options = default_options
+
+        # Don't evaluate CGI ISINDEX parameters.
+        # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html
+        args.clear if ENV.include?("REQUEST_METHOD")
+
+        options.merge! opt_parser.parse! args
+        options
+      end
+
+      def opt_parser
+        Options.new
+      end
+
+      def build_app(app)
+        middleware[options[:environment]].reverse_each do |middleware|
+          middleware = middleware.call(self) if middleware.respond_to?(:call)
+          next unless middleware
+          klass = middleware.shift
+          app = klass.new(app, *middleware)
+        end
+        app
+      end
+
+      def wrapped_app
+        @wrapped_app ||= build_app app
+      end
+
+      def daemonize_app
+        if RUBY_VERSION < "1.9"
+          exit if fork
+          Process.setsid
+          exit if fork
+          Dir.chdir "/"
+          ::File.umask 0000
+          STDIN.reopen "/dev/null"
+          STDOUT.reopen "/dev/null", "a"
+          STDERR.reopen "/dev/null", "a"
+        else
+          Process.daemon
+        end
+      end
+
+      def write_pid
+        ::File.open(options[:pid], 'w'){ |f| f.write("#{Process.pid}") }
+        at_exit { ::File.delete(options[:pid]) if ::File.exist?(options[:pid]) }
+      end
+  end
+end
diff --git a/lib/rack/session/memcache.rb b/lib/rack/session/memcache.rb
index 4a65cbf3..44629da3 100644
--- a/lib/rack/session/memcache.rb
+++ b/lib/rack/session/memcache.rb
@@ -29,9 +29,13 @@ module Rack
         super
 
         @mutex = Mutex.new
-        @pool = MemCache.
-          new @default_options[:memcache_server], @default_options
-        raise 'No memcache servers' unless @pool.servers.any?{|s|s.alive?}
+        mserv = @default_options[:memcache_server]
+        mopts = @default_options.
+          reject{|k,v| MemCache::DEFAULT_OPTIONS.include? k }
+        @pool = MemCache.new mserv, mopts
+        unless @pool.active? and @pool.servers.any?{|c| c.alive? }
+          raise 'No memcache servers'
+        end
       end
 
       def generate_sid
@@ -41,24 +45,23 @@ module Rack
         end
       end
 
-      def get_session(env, sid)
-        session = @pool.get(sid) if sid
+      def get_session(env, session_id)
         @mutex.lock if env['rack.multithread']
-        unless sid and session
-          env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil?
-          session = {}
-          sid = generate_sid
-          ret = @pool.add sid, session
-          raise "Session collision on '#{sid.inspect}'" unless /^STORED/ =~ ret
+        unless session_id and session = @pool.get(session_id)
+          session_id, session = generate_sid, {}
+          unless /^STORED/ =~ @pool.add(session_id, session)
+            raise "Session collision on '#{session_id.inspect}'"
+          end
         end
-        session.instance_variable_set('@old', {}.merge(session))
-        return [sid, session]
-      rescue MemCache::MemCacheError, Errno::ECONNREFUSED # MemCache server cannot be contacted
-        warn "#{self} is unable to find server."
+        session.instance_variable_set '@old', @pool.get(session_id, true)
+        return [session_id, session]
+      rescue MemCache::MemCacheError, Errno::ECONNREFUSED
+        # MemCache server cannot be contacted
+        warn "#{self} is unable to find memcached server."
         warn $!.inspect
         return [ nil, {} ]
       ensure
-        @mutex.unlock if env['rack.multithread']
+        @mutex.unlock if @mutex.locked?
       end
 
       def set_session(env, session_id, new_session, options)
@@ -66,43 +69,50 @@ module Rack
         expiry = expiry.nil? ? 0 : expiry + 1
 
         @mutex.lock if env['rack.multithread']
-        session = @pool.get(session_id) || {}
         if options[:renew] or options[:drop]
           @pool.delete session_id
           return false if options[:drop]
           session_id = generate_sid
-          @pool.add session_id, 0 # so we don't worry about cache miss on #set
+          @pool.add session_id, {} # so we don't worry about cache miss on #set
         end
-        old_session = new_session.instance_variable_get('@old') || {}
-        session = merge_sessions session_id, old_session, new_session, session
-        @pool.set session_id, session, expiry
-        return session_id
-      rescue MemCache::MemCacheError, Errno::ECONNREFUSED # MemCache server cannot be contacted
-        warn "#{self} is unable to find server."
-        warn $!.inspect
-        return false
-      ensure
-        @mutex.unlock if env['rack.multithread']
-      end
 
-      private
+        session = @pool.get(session_id) || {}
+        old_session = new_session.instance_variable_get '@old'
+        old_session = old_session ? Marshal.load(old_session) : {}
 
-      def merge_sessions sid, old, new, cur=nil
-        cur ||= {}
-        unless Hash === old and Hash === new
-          warn 'Bad old or new sessions provided.'
-          return cur
-        end
+        unless Hash === old_session and Hash === new_session
+          env['rack.errors'].
+            puts 'Bad old_session or new_session sessions provided.'
+        else # merge sessions
+          # alterations are either update or delete, making as few changes as
+          # possible to prevent possible issues.
 
-        delete = old.keys - new.keys
-        warn "//@#{sid}: delete #{delete*','}" if $VERBOSE and not delete.empty?
-        delete.each{|k| cur.delete k }
+          # removed keys
+          delete = old_session.keys - new_session.keys
+          if $VERBOSE and not delete.empty?
+            env['rack.errors'].
+              puts "//@#{session_id}: delete #{delete*','}"
+          end
+          delete.each{|k| session.delete k }
 
-        update = new.keys.select{|k| new[k] != old[k] }
-        warn "//@#{sid}: update #{update*','}" if $VERBOSE and not update.empty?
-        update.each{|k| cur[k] = new[k] }
+          # added or altered keys
+          update = new_session.keys.
+            select{|k| new_session[k] != old_session[k] }
+          if $VERBOSE and not update.empty?
+            env['rack.errors'].puts "//@#{session_id}: update #{update*','}"
+          end
+          update.each{|k| session[k] = new_session[k] }
+        end
 
-        cur
+        @pool.set session_id, session, expiry
+        return session_id
+      rescue MemCache::MemCacheError, Errno::ECONNREFUSED
+        # MemCache server cannot be contacted
+        warn "#{self} is unable to find memcached server."
+        warn $!.inspect
+        return false
+      ensure
+        @mutex.unlock if @mutex.locked?
       end
     end
   end
diff --git a/lib/rack/session/pool.rb b/lib/rack/session/pool.rb
index f6f87408..b3f8bd72 100644
--- a/lib/rack/session/pool.rb
+++ b/lib/rack/session/pool.rb
@@ -13,7 +13,7 @@ module Rack
     # In the context of a multithreaded environment, sessions being
     # committed to the pool is done in a merging manner.
     #
-    # The :drop option is available in rack.session.options if you with to
+    # The :drop option is available in rack.session.options if you wish to
     # explicitly remove the session from the session cache.
     #
     # Example:
diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb
index 884e6045..333d8bf5 100644
--- a/lib/rack/utils.rb
+++ b/lib/rack/utils.rb
@@ -27,7 +27,7 @@ module Rack
     module_function :unescape
 
     DEFAULT_SEP = /[&;] */n
-    
+
     # 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
@@ -38,7 +38,9 @@ module Rack
 
       (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
         k, v = p.split('=', 2).map { |x| unescape(x) }
-
+        if v =~ /^("|')(.*)\1$/
+          v = $2.gsub('\\'+$1, $1)
+        end
         if cur = params[k]
           if cur.class == Array
             params[k] << v
@@ -67,6 +69,9 @@ module Rack
     module_function :parse_nested_query
 
     def normalize_params(params, name, v = nil)
+      if v and v =~ /^("|')(.*)\1$/
+        v = $2.gsub('\\'+$1, $1)
+      end
       name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
       k = $1 || ''
       after = $' || ''
@@ -258,12 +263,22 @@ module Rack
     # A case-insensitive Hash that preserves the original case of a
     # header when set.
     class HeaderHash < Hash
+      def self.new(hash={})
+        HeaderHash === hash ? hash : super(hash)
+      end
+
       def initialize(hash={})
         super()
         @names = {}
         hash.each { |k, v| self[k] = v }
       end
 
+      def each
+        super do |k, v|
+          yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
+        end
+      end
+
       def to_hash
         inject({}) do |hash, (k,v)|
           if v.respond_to? :to_ary
@@ -379,6 +394,12 @@ 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)
 
+    SYMBOL_TO_STATUS_CODE = HTTP_STATUS_CODES.inject({}) { |hash, (code, message)|
+      hash[message.downcase.gsub(/\s|-/, '_').to_sym] = code
+      hash
+    }
+
+
     # A multipart form data parser, adapted from IOWA.
     #
     # Usually, Rack::Request#POST takes care of calling this.
@@ -507,7 +528,8 @@ module Rack
 
             Utils.normalize_params(params, name, data) unless data.nil?
 
-            break  if buf.empty? || content_length == -1
+            # break if we're at the end of a buffer, but not if it is the end of a field
+            break if (buf.empty? && $1 != EOL) || content_length == -1
           }
 
           input.rewind
diff --git a/test/multipart/bad_robots b/test/multipart/bad_robots
new file mode 100644
index 00000000..7e5bd418
--- /dev/null
+++ b/test/multipart/bad_robots
@@ -0,0 +1,259 @@
+--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
+Content-Disposition: form-data; name="bbbbbbbbbbbbbbb"
+
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaa
+
+--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
+Content-Disposition: form-data; name="ccccccc"
+
+ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
+--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
+Content-Disposition: form-data; name="file.name"
+
+INPUTMSG.gz
+--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
+Content-Disposition: form-data; name="file.content_type"
+
+application/octet-stream
+--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
+Content-Disposition: form-data; name="file.path"
+
+/var/tmp/uploads/4/0001728414
+--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
+Content-Disposition: form-data; name="file.md5"
+
+aa73198feb4b4c1c3186f5e7466cbbcc
+--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
+Content-Disposition: form-data; name="file.size"
+
+13212
+--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
+Content-Disposition: form-data; name="size"
+
+80892
+--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
+Content-Disposition: form-data; name="mail_server_id"
+
+<1111111111.22222222.3333333333333.JavaMail.app@ffff-aaaa.dddd>
+
+--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
+Content-Disposition: form-data; name="addresses"
+
+{"campsy_programmer@pinkedum.com":{"domain":"pinkedum.com","name":"Campsy Programmer","type":["env_sender"],"mailbox":"campsy_programmer"},"tex@rapidcity.com":{"domain":"rapidcity.com","name":"Big Tex","type":["env_recipients","to"],"mailbox":"tex"},"group-digests@linkedin.com":{"domain":"linkedin.com","name":"Group Members","type":["from"],"mailbox":"group-digests"}}
+--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
+Content-Disposition: form-data; name="received_on"
+
+2009-11-15T14:21:11Z
+--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
+Content-Disposition: form-data; name="id"
+
+dbfd9804d26d11deab24e3037639bf77
+--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
+Content-Disposition: form-data; name="ip_address"
+
+127.0.0.1
+--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon--
diff --git a/test/multipart/fail_16384_nofile b/test/multipart/fail_16384_nofile
new file mode 100644
index 00000000..bdcd3320
--- /dev/null
+++ b/test/multipart/fail_16384_nofile
@@ -0,0 +1,814 @@
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="_method"
+
+put
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="authenticity_token"
+
+XCUgSyYsZ+iHQunq/yCSKFzjeVmsXV/WcphHQ0J+05I=
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[SESE]"
+
+BooBar
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[BBBBBBBBB]"
+
+18
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[CCCCCCCCCCCCCCCCCCC]"
+
+0
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[STARTFOO]"
+
+2009-11-04
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[ENDFOO]"
+
+2009-12-01
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[DDDDDDDD]"
+
+0
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[DDDDDDDD]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[EEEEEEEEEE]"
+
+10000
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[FFFFFFFFF]"
+
+boskoizcool
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[GGGGGGGGGGG]"
+
+0
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[GGGGGGGGGGG]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[YYYYYYYYYYYYYYY]"
+
+5.00
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[ZZZZZZZZZZZZZ]"
+
+mille
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[XXXXXXXXXXXXXXXXXXXXX]"
+
+0
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][9]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][10]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][11]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][12]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][13]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][14]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][15]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][16]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][17]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][18]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][19]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][20]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][21]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][22]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][23]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][0]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][1]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][2]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][3]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][4]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][5]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][6]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][7]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][8]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][9]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][10]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][11]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][12]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][13]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][14]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][15]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][16]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][17]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][18]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][19]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][20]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][21]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][22]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][23]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][0]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][1]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][2]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][3]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][4]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][5]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][6]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][7]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][8]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][9]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][10]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][11]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][12]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][13]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][14]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][15]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][16]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][17]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][18]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][19]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][20]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][21]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][22]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][23]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][0]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][1]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][2]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][3]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][4]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][5]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][6]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][7]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][8]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][9]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][10]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][11]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][12]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][13]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][14]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][15]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][16]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][17]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][18]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][19]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][20]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][21]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][22]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][23]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][0]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][1]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][2]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][3]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][4]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][5]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][6]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][7]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][8]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][9]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][10]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][11]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][12]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][13]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][14]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][15]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][16]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][17]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][18]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][19]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][20]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][21]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][22]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][23]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][0]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][1]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][2]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][3]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][4]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][5]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][6]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][7]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][8]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][9]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][10]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][11]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][12]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][13]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][14]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][15]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][16]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][17]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][18]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][19]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][20]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][21]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][22]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][23]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][0]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][1]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][2]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][3]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][4]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][5]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][6]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][7]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][8]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][9]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][10]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][11]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][12]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][13]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][14]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][15]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][16]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][17]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][18]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][19]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][20]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][21]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][22]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][23]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][0]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][1]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][2]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][3]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][4]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][5]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][6]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][7]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][8]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[WWWWWWWWWWWWWWWWWWWWWWWWW][678][ZEZE]"
+
+PLAPLAPLAINCINCINC
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[WWWWWWWWWWWWWWWWWWWWWWWWW][678][123412341234e]"
+
+SITE
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[WWWWWWWWWWWWWWWWWWWWWWWWW][678][12345678901]"
+
+56
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_type]"
+
+none
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][has_hashashas_has]"
+
+0
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][frefrefre_fre_freee]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][frefrefre_fre_frefre]"
+
+forever
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][self_block]"
+
+0
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][GGG_RULES][][COUCOUN]"
+
+
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][GGG_RULES][][REGREG]"
+
+
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][GGG_RULES][][c1c1]"
+
+
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA_TARTARTAR_wizard_rule"
+
+
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_rule]"
+
+
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[selection_selection]"
+
+R
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[PLAPLAPLA_MEMMEMMEMM_ATTRATTRER][new][-1][selection_selection]"
+
+1
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[PLAPLAPLA_MEMMEMMEMM_ATTRATTRER][new][-1][ba_unit_id]"
+
+1015
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[PLAPLAPLA_MEMMEMMEMM_ATTRATTRER][new][-2][selection_selection]"
+
+2
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[PLAPLAPLA_MEMMEMMEMM_ATTRATTRER][new][-2][ba_unit_id]"
+
+1017
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo
+Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[tile_name]"
+
+
+------WebKitFormBoundaryWsY0GnpbI5U7ztzo--
+
diff --git a/test/rackup/.gitignore b/test/rackup/.gitignore
new file mode 100644
index 00000000..2f9d279a
--- /dev/null
+++ b/test/rackup/.gitignore
@@ -0,0 +1 @@
+log_output
diff --git a/test/rackup/config.ru b/test/rackup/config.ru
new file mode 100644
index 00000000..f1e2e1f3
--- /dev/null
+++ b/test/rackup/config.ru
@@ -0,0 +1,31 @@
+require "#{File.dirname(__FILE__)}/../testrequest"
+
+$stderr = File.open("#{File.dirname(__FILE__)}/log_output", "w")
+
+class EnvMiddleware
+  def initialize(app)
+    @app = app
+  end
+
+  def call(env)
+    # provides a way to test that lint is present
+    if env["PATH_INFO"] == "/broken_lint"
+      return [200, {}, ["Broken Lint"]]
+    # provides a way to kill the process without knowing the pid
+    elsif env["PATH_INFO"] == "/die"
+      exit!
+    end
+
+    env["test.$DEBUG"]      = $DEBUG
+    env["test.$EVAL"]       = BUKKIT if defined?(BUKKIT)
+    env["test.$VERBOSE"]    = $VERBOSE
+    env["test.$LOAD_PATH"]  = $LOAD_PATH
+    env["test.stderr"]      = File.expand_path($stderr.path)
+    env["test.Ping"]        = defined?(Ping)
+    env["test.pid"]         = Process.pid
+    @app.call(env)
+  end
+end
+
+use EnvMiddleware
+run TestRequest.new
diff --git a/test/spec_rack_auth_openid.rb b/test/spec_rack_auth_openid.rb
deleted file mode 100644
index ba248445..00000000
--- a/test/spec_rack_auth_openid.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-require 'test/spec'
-
-begin
-# requires the ruby-openid gem
-require 'rack/auth/openid'
-
-context "Rack::Auth::OpenID" do
-  OID = Rack::Auth::OpenID
-  host = 'host'
-  subd = 'sub.host'
-  wild = '*.host'
-  path = 'path'
-  long = 'path/long'
-  scheme = 'http://'
-  realm = scheme+host+'/'+path
-
-  specify 'realm uri should be valid' do
-    lambda{OID.new('/'+path)}.should.raise ArgumentError
-    lambda{OID.new('/'+long)}.should.raise ArgumentError
-    lambda{OID.new(scheme+host)}.should.not.raise
-    lambda{OID.new(scheme+host+'/')}.should.not.raise
-    lambda{OID.new(scheme+host+'/'+path)}.should.not.raise
-    lambda{OID.new(scheme+subd)}.should.not.raise
-    lambda{OID.new(scheme+subd+'/')}.should.not.raise
-    lambda{OID.new(scheme+subd+'/'+path)}.should.not.raise
-  end
-
-  specify 'should be able to check if a uri is within the realm' do
-  end
-
-  specify 'return_to should be valid' do
-    uri = '/'+path
-    lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError
-    uri = '/'+long
-    lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError
-    uri = scheme+host
-    lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError
-    uri = scheme+host+'/'+path
-    lambda{OID.new(realm, :return_to=>uri)}.should.not.raise
-    uri = scheme+subd+'/'+path
-    lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError
-    uri = scheme+host+'/'+long
-    lambda{OID.new(realm, :return_to=>uri)}.should.not.raise
-    uri = scheme+subd+'/'+long
-    lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError
-  end
-
-  specify 'extensions should have required constants defined' do
-    badext = Rack::Auth::OpenID::BadExtension
-    ext = Object.new
-    lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
-    ext = Module.new
-    lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
-    ext::Request = nil
-    lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
-    ext::Response = nil
-    lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
-    ext::NS_URI = nil
-    lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
-  end
-
-  specify 'extensions should have Request and Response defined and inherit from OpenID::Extension' do
-    $-w, w = nil, $-w               # yuck
-    badext = Rack::Auth::OpenID::BadExtension
-    ext = Module.new
-    ext::Request = nil
-    ext::Response = nil
-    ext::NS_URI = nil
-    lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
-    ext::Request = Class.new()
-    lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
-    ext::Response = Class.new()
-    lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
-    ext::Request = Class.new(::OpenID::Extension)
-    lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
-    ext::Response = Class.new(::OpenID::Extension)
-    lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
-    $-w = w
-  end
-end
-
-rescue LoadError
-  $stderr.puts "Skipping Rack::Auth::OpenID tests (ruby-openid 2 is required). `gem install ruby-openid` and try again."
-end
diff --git a/test/spec_rack_cgi.rb b/test/spec_rack_cgi.rb
index 818fabdf..97456320 100644
--- a/test/spec_rack_cgi.rb
+++ b/test/spec_rack_cgi.rb
@@ -3,7 +3,7 @@ require 'testrequest'
 
 context "Rack::Handler::CGI" do
   include TestRequest::Helpers
-  
+
   setup do
     @host = '0.0.0.0'
     @port = 9203
@@ -47,7 +47,7 @@ context "Rack::Handler::CGI" do
     response["REQUEST_METHOD"].should.equal "GET"
     response["SCRIPT_NAME"].should.equal "/test"
     response["REQUEST_PATH"].should.equal "/"
-    response["PATH_INFO"].should.be.nil
+    response["PATH_INFO"].should.equal ""
     response["QUERY_STRING"].should.equal ""
     response["test.postdata"].should.equal ""
 
diff --git a/test/spec_rack_config.rb b/test/spec_rack_config.rb
new file mode 100644
index 00000000..a508ea4b
--- /dev/null
+++ b/test/spec_rack_config.rb
@@ -0,0 +1,24 @@
+require 'test/spec'
+require 'rack/mock'
+require 'rack/builder'
+require 'rack/content_length'
+require 'rack/config'
+
+context "Rack::Config" do
+
+  specify "should accept a block that modifies the environment" do
+    app = Rack::Builder.new do
+      use Rack::Lint
+      use Rack::ContentLength
+      use Rack::Config do |env|
+        env['greeting'] = 'hello'
+      end
+      run lambda { |env|
+        [200, {'Content-Type' => 'text/plain'}, [env['greeting'] || '']]
+      }
+    end
+    response = Rack::MockRequest.new(app).get('/')
+    response.body.should.equal('hello')
+  end
+
+end
diff --git a/test/spec_rack_etag.rb b/test/spec_rack_etag.rb
new file mode 100644
index 00000000..73cd31ac
--- /dev/null
+++ b/test/spec_rack_etag.rb
@@ -0,0 +1,17 @@
+require 'test/spec'
+require 'rack/mock'
+require 'rack/etag'
+
+context "Rack::ETag" do
+  specify "sets ETag if none is set" do
+    app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
+    response = Rack::ETag.new(app).call({})
+    response[1]['ETag'].should.equal "\"65a8e27d8879283831b664bd8b7f0ad4\""
+  end
+
+  specify "does not change ETag if it is already set" do
+    app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'ETag' => '"abc"'}, ["Hello, World!"]] }
+    response = Rack::ETag.new(app).call({})
+    response[1]['ETag'].should.equal "\"abc\""
+  end
+end
diff --git a/test/spec_rack_fastcgi.rb b/test/spec_rack_fastcgi.rb
index 69478de5..98963c0e 100644
--- a/test/spec_rack_fastcgi.rb
+++ b/test/spec_rack_fastcgi.rb
@@ -47,7 +47,7 @@ context "Rack::Handler::FastCGI" do
     response["REQUEST_METHOD"].should.equal "GET"
     response["SCRIPT_NAME"].should.equal "/test.fcgi"
     response["REQUEST_PATH"].should.equal "/"
-    response["PATH_INFO"].should.be.nil
+    response["PATH_INFO"].should.equal ""
     response["QUERY_STRING"].should.equal ""
     response["test.postdata"].should.equal ""
 
diff --git a/test/spec_rack_lint.rb b/test/spec_rack_lint.rb
index 9c5d5031..bbf75c17 100644
--- a/test/spec_rack_lint.rb
+++ b/test/spec_rack_lint.rb
@@ -72,6 +72,11 @@ context "Rack::Lint" do
       message.should.equal("session [] must respond to store and []=")
 
     lambda {
+      Rack::Lint.new(nil).call(env("rack.logger" => []))
+    }.should.raise(Rack::Lint::LintError).
+      message.should.equal("logger [] must respond to info")
+
+    lambda {
       Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "FUCKUP?"))
     }.should.raise(Rack::Lint::LintError).
       message.should.match(/REQUEST_METHOD/)
@@ -110,7 +115,7 @@ context "Rack::Lint" do
       Rack::Lint.new(nil).call(env("rack.input" => ""))
     }.should.raise(Rack::Lint::LintError).
       message.should.match(/does not respond to #gets/)
-    
+
     lambda {
       input = Object.new
       def input.binmode?
@@ -119,7 +124,7 @@ context "Rack::Lint" do
       Rack::Lint.new(nil).call(env("rack.input" => input))
     }.should.raise(Rack::Lint::LintError).
       message.should.match(/is not opened in binary mode/)
-    
+
     lambda {
       input = Object.new
       def input.external_encoding
@@ -347,25 +352,25 @@ context "Rack::Lint" do
         yield 23
         yield 42
       end
-      
+
       def rewind
         raise Errno::ESPIPE, "Errno::ESPIPE"
       end
     end
-    
+
     eof_weirdio = Object.new
     class << eof_weirdio
       def gets
         nil
       end
-      
+
       def read(*args)
         nil
       end
-      
+
       def each
       end
-      
+
       def rewind
       end
     end
@@ -452,7 +457,7 @@ context "Rack::Lint" do
     }.should.raise(Rack::Lint::LintError).
       message.should.match(/body was given for HEAD/)
   end
-  
+
   specify "passes valid read calls" do
     hello_str = "hello world"
     hello_str.force_encoding("ASCII-8BIT") if hello_str.respond_to? :force_encoding
@@ -462,35 +467,35 @@ context "Rack::Lint" do
                        [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
                      }).call(env({"rack.input" => StringIO.new(hello_str)}))
     }.should.not.raise(Rack::Lint::LintError)
-    
+
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.input"].read(0)
                        [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
                      }).call(env({"rack.input" => StringIO.new(hello_str)}))
     }.should.not.raise(Rack::Lint::LintError)
-    
+
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.input"].read(1)
                        [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
                      }).call(env({"rack.input" => StringIO.new(hello_str)}))
     }.should.not.raise(Rack::Lint::LintError)
-    
+
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.input"].read(nil)
                        [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
                      }).call(env({"rack.input" => StringIO.new(hello_str)}))
     }.should.not.raise(Rack::Lint::LintError)
-    
+
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.input"].read(nil, '')
                        [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
                      }).call(env({"rack.input" => StringIO.new(hello_str)}))
     }.should.not.raise(Rack::Lint::LintError)
-    
+
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.input"].read(1, '')
diff --git a/test/spec_rack_logger.rb b/test/spec_rack_logger.rb
new file mode 100644
index 00000000..d55b9c77
--- /dev/null
+++ b/test/spec_rack_logger.rb
@@ -0,0 +1,21 @@
+require 'rack/logger'
+require 'rack/lint'
+require 'stringio'
+
+context "Rack::Logger" do
+  specify "logs to rack.errors" do
+    app = lambda { |env|
+      log = env['rack.logger']
+      log.debug("Created logger")
+      log.info("Program started")
+      log.warn("Nothing to do!")
+
+      [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]]
+    }
+
+    errors = StringIO.new
+    Rack::Logger.new(app).call({'rack.errors' => errors})
+    errors.string.should.match "INFO -- : Program started"
+    errors.string.should.match "WARN -- : Nothing to do"
+  end
+end
diff --git a/test/spec_rack_mongrel.rb b/test/spec_rack_mongrel.rb
index d73e884c..6a69af75 100644
--- a/test/spec_rack_mongrel.rb
+++ b/test/spec_rack_mongrel.rb
@@ -6,14 +6,14 @@ require 'rack/urlmap'
 require 'rack/lint'
 require 'testrequest'
 require 'timeout'
-  
+
 Thread.abort_on_exception = true
 $tcp_defer_accept_opts = nil
 $tcp_cork_opts = nil
 
 context "Rack::Handler::Mongrel" do
   include TestRequest::Helpers
-  
+
   setup do
     server = Mongrel::HttpServer.new(@host='0.0.0.0', @port=9201)
     server.register('/test',
@@ -52,7 +52,7 @@ context "Rack::Handler::Mongrel" do
     response["REQUEST_METHOD"].should.equal "GET"
     response["SCRIPT_NAME"].should.equal "/test"
     response["REQUEST_PATH"].should.equal "/test"
-    response["PATH_INFO"].should.be.nil
+    response["PATH_INFO"].should.be.equal ""
     response["QUERY_STRING"].should.equal ""
     response["test.postdata"].should.equal ""
 
diff --git a/test/spec_rack_nulllogger.rb b/test/spec_rack_nulllogger.rb
new file mode 100644
index 00000000..b3c2bc9c
--- /dev/null
+++ b/test/spec_rack_nulllogger.rb
@@ -0,0 +1,13 @@
+require 'rack/nulllogger'
+require 'rack/lint'
+require 'rack/mock'
+
+context "Rack::NullLogger" do
+  specify "acks as a nop logger" do
+    app = lambda { |env|
+      env['rack.logger'].warn "b00m"
+      [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]]
+    }
+    Rack::NullLogger.new(app).call({})
+  end
+end
diff --git a/test/spec_rack_response.rb b/test/spec_rack_response.rb
index eb59b5c2..7989013d 100644
--- a/test/spec_rack_response.rb
+++ b/test/spec_rack_response.rb
@@ -118,6 +118,9 @@ context "Rack::Response" do
 
     r = Rack::Response.new([], 500)
     r.status.should.equal 500
+
+    r = Rack::Response.new([], "200 OK")
+    r.status.should.equal 200
   end
 
   specify "has a constructor that can take a block" do
diff --git a/test/spec_rack_runtime.rb b/test/spec_rack_runtime.rb
new file mode 100644
index 00000000..62d80956
--- /dev/null
+++ b/test/spec_rack_runtime.rb
@@ -0,0 +1,35 @@
+require 'test/spec'
+require 'rack/mock'
+require 'rack/runtime'
+
+context "Rack::Runtime" do
+  specify "sets X-Runtime is none is set" do
+    app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
+    response = Rack::Runtime.new(app).call({})
+    response[1]['X-Runtime'].should =~ /[\d\.]+/
+  end
+
+  specify "does not set the X-Runtime if it is already set" do
+    app = lambda { |env| [200, {'Content-Type' => 'text/plain', "X-Runtime" => "foobar"}, "Hello, World!"] }
+    response = Rack::Runtime.new(app).call({})
+    response[1]['X-Runtime'].should == "foobar"
+  end
+
+  specify "should allow a suffix to be set" do
+    app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
+    response = Rack::Runtime.new(app, "Test").call({})
+    response[1]['X-Runtime-Test'].should =~ /[\d\.]+/
+  end
+
+  specify "should allow multiple timers to be set" do
+    app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
+    runtime1 = Rack::Runtime.new(app, "App")
+    runtime2 = Rack::Runtime.new(runtime1, "All")
+    response = runtime2.call({})
+
+    response[1]['X-Runtime-App'].should =~ /[\d\.]+/
+    response[1]['X-Runtime-All'].should =~ /[\d\.]+/
+
+    Float(response[1]['X-Runtime-All']).should > Float(response[1]['X-Runtime-App'])
+  end
+end
diff --git a/test/spec_rack_sendfile.rb b/test/spec_rack_sendfile.rb
new file mode 100644
index 00000000..8cfe2017
--- /dev/null
+++ b/test/spec_rack_sendfile.rb
@@ -0,0 +1,86 @@
+require 'test/spec'
+require 'rack/mock'
+require 'rack/sendfile'
+
+context "Rack::File" do
+  specify "should respond to #to_path" do
+    Rack::File.new(Dir.pwd).should.respond_to :to_path
+  end
+end
+
+context "Rack::Sendfile" do
+  def sendfile_body
+    res = ['Hello World']
+    def res.to_path ; "/tmp/hello.txt" ; end
+    res
+  end
+
+  def simple_app(body=sendfile_body)
+    lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] }
+  end
+
+  def sendfile_app(body=sendfile_body)
+    Rack::Sendfile.new(simple_app(body))
+  end
+
+  setup do
+    @request = Rack::MockRequest.new(sendfile_app)
+  end
+
+  def request(headers={})
+    yield @request.get('/', headers)
+  end
+
+  specify "does nothing when no X-Sendfile-Type header present" do
+    request do |response|
+      response.should.be.ok
+      response.body.should.equal 'Hello World'
+      response.headers.should.not.include 'X-Sendfile'
+    end
+  end
+
+  specify "sets X-Sendfile response header and discards body" do
+    request 'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile' do |response|
+      response.should.be.ok
+      response.body.should.be.empty
+      response.headers['X-Sendfile'].should.equal '/tmp/hello.txt'
+    end
+  end
+
+  specify "sets X-Lighttpd-Send-File response header and discards body" do
+    request 'HTTP_X_SENDFILE_TYPE' => 'X-Lighttpd-Send-File' do |response|
+      response.should.be.ok
+      response.body.should.be.empty
+      response.headers['X-Lighttpd-Send-File'].should.equal '/tmp/hello.txt'
+    end
+  end
+
+  specify "sets X-Accel-Redirect response header and discards body" do
+    headers = {
+      'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect',
+      'HTTP_X_ACCEL_MAPPING' => '/tmp/=/foo/bar/'
+    }
+    request headers do |response|
+      response.should.be.ok
+      response.body.should.be.empty
+      response.headers['X-Accel-Redirect'].should.equal '/foo/bar/hello.txt'
+    end
+  end
+
+  specify 'writes to rack.error when no X-Accel-Mapping is specified' do
+    request 'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect' do |response|
+      response.should.be.ok
+      response.body.should.equal 'Hello World'
+      response.headers.should.not.include 'X-Accel-Redirect'
+      response.errors.should.include 'X-Accel-Mapping'
+    end
+  end
+
+  specify 'does nothing when body does not respond to #to_path' do
+    @request = Rack::MockRequest.new(sendfile_app(['Not a file...']))
+    request 'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile' do |response|
+      response.body.should.equal 'Not a file...'
+      response.headers.should.not.include 'X-Sendfile'
+    end
+  end
+end
diff --git a/test/spec_rack_session_memcache.rb b/test/spec_rack_session_memcache.rb
index f21ac3de..faac796e 100644
--- a/test/spec_rack_session_memcache.rb
+++ b/test/spec_rack_session_memcache.rb
@@ -6,11 +6,9 @@ begin
   require 'rack/response'
   require 'thread'
 
-  pool = Rack::Session::Memcache.new(lambda {})
-
   context "Rack::Session::Memcache" do
     session_key = Rack::Session::Memcache::DEFAULT_OPTIONS[:key]
-    session_match = /#{session_key}=[0-9a-fA-F]+;/
+    session_match = /#{session_key}=([0-9a-fA-F]+);/
     incrementor = lambda do |env|
       env["rack.session"]["counter"] ||= 0
       env["rack.session"]["counter"] += 1
@@ -29,22 +27,22 @@ begin
       incrementor.call(env)
     end
 
-    specify "MemCache can connect to existing server" do
-      test_pool = MemCache.new :namespace => 'test:rack:session'
-    end
-
     specify "faults on no connection" do
       if RUBY_VERSION < "1.9"
         lambda do
-          Rack::Session::Memcache.new(incrementor, :memcache_server => '')
+          Rack::Session::Memcache.new incrementor, :memcache_server => 'nosuchserver'
         end.should.raise
       else
         lambda do
-          Rack::Session::Memcache.new(incrementor, :memcache_server => '')
+          Rack::Session::Memcache.new incrementor, :memcache_server => 'nosuchserver'
         end.should.raise ArgumentError
       end
     end
 
+    specify "connect to existing server" do
+      test_pool = MemCache.new incrementor, :namespace => 'test:rack:session'
+    end
+
     specify "creates a new cookie" do
       pool = Rack::Session::Memcache.new(incrementor)
       res = Rack::MockRequest.new(pool).get("/")
@@ -159,6 +157,31 @@ begin
       res3.body.should.equal '{"counter"=>4}'
     end
 
+    specify "deep hashes are correctly updated" do
+      store = nil
+      hash_check = proc do |env|
+        session = env['rack.session']
+        unless session.include? 'test'
+          session.update :a => :b, :c => { :d => :e },
+            :f => { :g => { :h => :i} }, 'test' => true
+        else
+          session[:f][:g][:h] = :j
+        end
+        [200, {}, session.inspect]
+      end
+      pool = Rack::Session::Memcache.new(hash_check)
+      req = Rack::MockRequest.new(pool)
+
+      res0 = req.get("/")
+      session_id = (cookie = res0["Set-Cookie"])[session_match, 1]
+      ses0 = pool.pool.get(session_id, true)
+
+      res1 = req.get("/", "HTTP_COOKIE" => cookie)
+      ses1 = pool.pool.get(session_id, true)
+
+      ses1.should.not.equal ses0
+    end
+
     # anyone know how to do this better?
     specify "multithread: should cleanly merge sessions" do
       next unless $DEBUG
@@ -169,7 +192,7 @@ begin
       res = req.get('/')
       res.body.should.equal '{"counter"=>1}'
       cookie = res["Set-Cookie"]
-      sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
+      session_id = cookie[session_match, 1]
 
       delta_incrementor = lambda do |env|
         # emulate disconjoinment of threading
@@ -191,7 +214,7 @@ begin
         request.body.should.include '"counter"=>2'
       end
 
-      session = pool.pool.get(sess_id)
+      session = pool.pool.get(session_id)
       session.size.should.be tnum+1 # counter
       session['counter'].should.be 2 # meeeh
 
@@ -215,7 +238,7 @@ begin
         request.body.should.include '"counter"=>3'
       end
 
-      session = pool.pool.get(sess_id)
+      session = pool.pool.get(session_id)
       session.size.should.be tnum+1
       session['counter'].should.be 3
 
@@ -237,7 +260,7 @@ begin
         request.body.should.include '"foo"=>"bar"'
       end
 
-      session = pool.pool.get(sess_id)
+      session = pool.pool.get(session_id)
       session.size.should.be r.size+1
       session['counter'].should.be.nil?
       session['foo'].should.equal 'bar'
diff --git a/test/spec_rack_utils.rb b/test/spec_rack_utils.rb
index 52333773..c61ab363 100644
--- a/test/spec_rack_utils.rb
+++ b/test/spec_rack_utils.rb
@@ -30,7 +30,10 @@ context "Rack::Utils" do
   end
 
   specify "should parse query strings correctly" do
-    Rack::Utils.parse_query("foo=bar").should.equal "foo" => "bar"
+    Rack::Utils.parse_query("foo=bar").
+      should.equal "foo" => "bar"
+    Rack::Utils.parse_query("foo=\"bar\"").
+      should.equal "foo" => "bar"
     Rack::Utils.parse_query("foo=bar&foo=quux").
       should.equal "foo" => ["bar", "quux"]
     Rack::Utils.parse_query("foo=1&bar=2").
@@ -47,6 +50,8 @@ context "Rack::Utils" do
       should.equal "foo" => ""
     Rack::Utils.parse_nested_query("foo=bar").
       should.equal "foo" => "bar"
+    Rack::Utils.parse_nested_query("foo=\"bar\"").
+      should.equal "foo" => "bar"
 
     Rack::Utils.parse_nested_query("foo=bar&foo=quux").
       should.equal "foo" => "quux"
@@ -268,6 +273,22 @@ context "Rack::Utils::HeaderHash" do
     h = Rack::Utils::HeaderHash.new("foo" => "bar")
     h.delete("Hello").should.be.nil
   end
+
+  specify "should avoid unnecessary object creation if possible" do
+    a = Rack::Utils::HeaderHash.new("foo" => "bar")
+    b = Rack::Utils::HeaderHash.new(a)
+    b.object_id.should.equal(a.object_id)
+    b.should.equal(a)
+  end
+
+  specify "should convert Array values to Strings when responding to #each" do
+    h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"])
+    h.each do |k,v|
+      k.should.equal("foo")
+      v.should.equal("bar\nbaz")
+    end
+  end
+
 end
 
 context "Rack::Utils::Context" do
@@ -457,6 +478,37 @@ context "Rack::Utils::Multipart" do
     params["people"][0]["files"][:filename].should.equal "file1.txt"
     params["people"][0]["files"][:tempfile].read.should.equal "contents"
   end
+  
+  specify "can parse fields that end at the end of the buffer" do
+    input = File.read(multipart_file("bad_robots"))
+    
+    req = Rack::Request.new Rack::MockRequest.env_for("/",
+                      "CONTENT_TYPE" => "multipart/form-data, boundary=1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon",
+                      "CONTENT_LENGTH" => input.size,
+                      :input => input)
+
+    req.POST['file.path'].should.equal "/var/tmp/uploads/4/0001728414"
+    req.POST['addresses'].should.not.equal nil
+  end
+
+  specify "builds complete params with the chunk size of 16384 slicing exactly on boundary" do
+    data = File.open(multipart_file("fail_16384_nofile")) { |f| f.read }.gsub(/\n/, "\r\n")
+    options = {
+      "CONTENT_TYPE" => "multipart/form-data; boundary=----WebKitFormBoundaryWsY0GnpbI5U7ztzo",
+      "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.not.equal nil
+    params.keys.should.include "AAAAAAAAAAAAAAAAAAA"
+    params["AAAAAAAAAAAAAAAAAAA"].keys.should.include "PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"
+    params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"].keys.should.include "new"
+    params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"].keys.should.include "-2"
+    params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"].keys.should.include "ba_unit_id"
+    params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"]["ba_unit_id"].should.equal "1017"
+  end
 
   specify "should return nil if no UploadedFiles were used" do
     data = Rack::Utils::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => "contents"}])
diff --git a/test/spec_rack_webrick.rb b/test/spec_rack_webrick.rb
index 3e63ea63..8951687a 100644
--- a/test/spec_rack_webrick.rb
+++ b/test/spec_rack_webrick.rb
@@ -9,7 +9,7 @@ Thread.abort_on_exception = true
 
 context "Rack::Handler::WEBrick" do
   include TestRequest::Helpers
-  
+
   setup do
     @server = WEBrick::HTTPServer.new(:Host => @host='0.0.0.0',
                                       :Port => @port=9202,
@@ -50,7 +50,7 @@ context "Rack::Handler::WEBrick" do
     response["REQUEST_METHOD"].should.equal "GET"
     response["SCRIPT_NAME"].should.equal "/test"
     response["REQUEST_PATH"].should.equal "/"
-    response["PATH_INFO"].should.be.nil
+    response["PATH_INFO"].should.be.equal ""
     response["QUERY_STRING"].should.equal ""
     response["test.postdata"].should.equal ""
 
@@ -60,7 +60,7 @@ context "Rack::Handler::WEBrick" do
     response["REQUEST_PATH"].should.equal "/"
     response["PATH_INFO"].should.equal "/foo"
     response["QUERY_STRING"].should.equal "quux=1"
-    
+
     GET("/test/foo%25encoding?quux=1")
     response["REQUEST_METHOD"].should.equal "GET"
     response["SCRIPT_NAME"].should.equal "/test"
diff --git a/test/spec_rackup.rb b/test/spec_rackup.rb
new file mode 100644
index 00000000..d9926fda
--- /dev/null
+++ b/test/spec_rackup.rb
@@ -0,0 +1,154 @@
+require 'test/spec'
+require 'testrequest'
+require 'rack/server'
+require 'open3'
+
+begin
+require "mongrel"
+
+context "rackup" do
+  include TestRequest::Helpers
+
+  def run_rackup(*args)
+    options = args.last.is_a?(Hash) ? args.pop : {}
+    flags   = args.first
+    @host = options[:host] || "0.0.0.0"
+    @port = options[:port] || 9292
+
+    Dir.chdir("#{root}/test/rackup") do
+      @in, @rackup, @err = Open3.popen3("#{Gem.ruby} -S #{rackup} #{flags}")
+    end
+
+    return if options[:port] == false
+
+    # Wait until the server is available
+    begin
+      GET("/")
+    rescue
+      sleep 0.05
+      retry
+    end
+  end
+
+  def output
+    @rackup.read
+  end
+
+  after do
+    # This doesn't actually return a response, so we rescue
+    GET "/die" rescue nil
+
+    Dir["#{root}/**/*.pid"].each do |file|
+      File.delete(file)
+    end
+
+    File.delete("#{root}/log_output") if File.exist?("#{root}/log_output")
+  end
+
+  specify "rackup" do
+    run_rackup
+    response["PATH_INFO"].should.equal '/'
+    response["test.$DEBUG"].should.be false
+    response["test.$EVAL"].should.be nil
+    response["test.$VERBOSE"].should.be false
+    response["test.Ping"].should.be nil
+    response["SERVER_SOFTWARE"].should.not =~ /webrick/
+  end
+
+  specify "rackup --help" do
+    run_rackup "--help", :port => false
+    output.should.match /--port/
+  end
+
+  specify "rackup --port" do
+    run_rackup "--port 9000", :port => 9000
+    response["SERVER_PORT"].should.equal "9000"
+  end
+
+  specify "rackup --debug" do
+    run_rackup "--debug"
+    response["test.$DEBUG"].should.be true
+  end
+
+  specify "rackup --eval" do
+    run_rackup %{--eval "BUKKIT = 'BUKKIT'"}
+    response["test.$EVAL"].should.equal "BUKKIT"
+  end
+
+  specify "rackup --warn" do
+    run_rackup %{--warn}
+    response["test.$VERBOSE"].should.be true
+  end
+
+  specify "rackup --include" do
+    run_rackup %{--include /foo/bar}
+    response["test.$LOAD_PATH"].should.include "/foo/bar"
+  end
+
+  specify "rackup --require" do
+    run_rackup %{--require ping}
+    response["test.Ping"].should.equal "constant"
+  end
+
+  specify "rackup --server" do
+    run_rackup %{--server webrick}
+    response["SERVER_SOFTWARE"].should =~ /webrick/i
+  end
+
+  specify "rackup --host" do
+    run_rackup %{--host 127.0.0.1}, :host => "127.0.0.1"
+    response["REMOTE_ADDR"].should.equal "127.0.0.1"
+  end
+
+  specify "rackup --daemonize --pid" do
+    run_rackup %{--daemonize --pid testing.pid}
+    status.should.be 200
+    @rackup.should.be.eof?
+    Dir["#{root}/**/testing.pid"].should.not.be.empty?
+  end
+
+  specify "rackup --pid" do
+    run_rackup %{--pid testing.pid}
+    status.should.be 200
+    Dir["#{root}/**/testing.pid"].should.not.be.empty?
+  end
+
+  specify "rackup --version" do
+    run_rackup %{--version}, :port => false
+    output.should =~ /1.0/
+  end
+
+  specify "rackup --env development includes lint" do
+    run_rackup
+    GET("/broken_lint")
+    status.should.be 500
+  end
+
+  specify "rackup --env deployment does not include lint" do
+    run_rackup %{--env deployment}
+    GET("/broken_lint")
+    status.should.be 200
+  end
+
+  specify "rackup --env none does not include lint" do
+    run_rackup %{--env none}
+    GET("/broken_lint")
+    status.should.be 200
+  end
+
+  specify "rackup --env deployment does log" do
+    run_rackup %{--env deployment}
+    log = File.read(response["test.stderr"])
+    log.should.be.empty?
+  end
+
+  specify "rackup --env none does not log" do
+    run_rackup %{--env none}
+    GET("/")
+    log = File.read(response["test.stderr"])
+    log.should.be.empty?
+  end
+end
+rescue LoadError
+  $stderr.puts "Skipping rackup --server tests (mongrel is required). `gem install thin` and try again."
+end \ No newline at end of file
diff --git a/test/testrequest.rb b/test/testrequest.rb
index 7b7190cb..0da2b126 100644
--- a/test/testrequest.rb
+++ b/test/testrequest.rb
@@ -13,6 +13,17 @@ class TestRequest
   module Helpers
     attr_reader :status, :response
 
+    ROOT = File.expand_path(File.dirname(__FILE__) + "/..")
+    ENV["RUBYOPT"] = "-I#{ROOT}/lib -rubygems"
+
+    def root
+      ROOT
+    end
+
+    def rackup
+      "#{ROOT}/bin/rackup"
+    end
+
     def GET(path, header={})
       Net::HTTP.start(@host, @port) { |http|
         user = header.delete(:user)
@@ -22,7 +33,11 @@ class TestRequest
         get.basic_auth user, passwd  if user && passwd
         http.request(get) { |response|
           @status = response.code.to_i
-          @response = YAML.load(response.body)
+          begin
+            @response = YAML.load(response.body)
+          rescue ArgumentError
+            @response = nil
+          end
         }
       }
     end