summary refs log tree commit
diff options
context:
space:
mode:
authorChristian Neukirchen <chneukirchen@gmail.com>2009-08-12 00:36:25 +0200
committerChristian Neukirchen <chneukirchen@gmail.com>2009-08-12 00:36:25 +0200
commit503130e322cb9390cba028a0ef58c1e24848ff21 (patch)
treea33662299014936ac107c656e0097b451bde1929
parent7b6046b764eafd332b3b2d9d93b3915c425fae54 (diff)
parentca6f9f9f9232d5f27b5161135b1bd668c1dc9790 (diff)
downloadrack-503130e322cb9390cba028a0ef58c1e24848ff21.tar.gz
Merge commit 'official/master'
-rw-r--r--lib/rack/auth/openid.rb239
-rw-r--r--lib/rack/deflater.rb2
-rw-r--r--lib/rack/handler/lsws.rb21
-rw-r--r--lib/rack/handler/mongrel.rb5
-rw-r--r--lib/rack/handler/scgi.rb7
-rw-r--r--lib/rack/handler/webrick.rb6
-rw-r--r--lib/rack/lint.rb11
-rw-r--r--lib/rack/mime.rb3
-rw-r--r--lib/rack/mock.rb11
-rw-r--r--lib/rack/reloader.rb9
-rw-r--r--lib/rack/request.rb2
-rw-r--r--lib/rack/response.rb38
-rw-r--r--lib/rack/rewindable_input.rb2
-rw-r--r--lib/rack/session/abstract/id.rb8
-rw-r--r--lib/rack/session/cookie.rb7
-rw-r--r--lib/rack/utils.rb80
-rw-r--r--test/spec_rack_commonlogger.rb12
-rw-r--r--test/spec_rack_lint.rb36
-rw-r--r--test/spec_rack_request.rb6
-rw-r--r--test/spec_rack_utils.rb40
20 files changed, 351 insertions, 194 deletions
diff --git a/lib/rack/auth/openid.rb b/lib/rack/auth/openid.rb
index c5f6a514..43cbe4f9 100644
--- a/lib/rack/auth/openid.rb
+++ b/lib/rack/auth/openid.rb
@@ -1,13 +1,14 @@
-# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
+# 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' #gem
-require 'openid/extension' #gem
-require 'openid/store/memory' #gem
+require 'openid'
+require 'openid/extension'
+require 'openid/store/memory'
 
 module Rack
   class Request
@@ -45,108 +46,111 @@ module Rack
     #
     # 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
-      # Required for ruby-openid
-      ValidStatus = [:success, :setup_needed, :cancel, :failure]
+      # 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
+      ]
 
-      # = Arguments
-      #
       # 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 optional second argument is a hash of options.
-      #
-      # == Options
+      # 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 <tt>:return_to</tt> is not
-      # provided, return_to will be the current url which allows flexibility
-      # with caveats.
+      # 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.
-      # It defaults to 'rack.session'.
+      # 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 will be used.
+      # 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 main module, the value
-      # should be an array of arguments for extension::Request.new.
+      # 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.
-      #
-      # == 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
-      #   ...
 
       def initialize(realm, options={})
         realm = URI(realm)
@@ -162,7 +166,7 @@ module Rack
           ruri = URI(ruri)
           raise ArgumentError, "Invalid return_to: #{ruri}" \
             unless ruri.absolute? \
-            and ruri.scheme  =~ /^https?$/ \
+            and ruri.scheme =~ /^https?$/ \
             and ruri.fragment.nil?
           raise ArgumentError, "return_to #{ruri} not within realm #{realm}" \
             unless self.within_realm?(ruri)
@@ -174,10 +178,10 @@ module Rack
         @store        = options[:store]         || ::OpenID::Store::Memory.new
         @immediate    = !!options[:immediate]
 
-        @extensions = {}
-        if extensions = options.delete(:extensions)
+        @extensions   = {}
+        if extensions = options[:extensions]
           extensions.each do |ext, args|
-            add_extension ext, *args
+            add_extension(ext, *args)
           end
         end
 
@@ -199,33 +203,29 @@ module Rack
       # 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, #unauthorized is called.
+      # 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'
+          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'
+          raise NoSession, 'Incompatible openid session.'
         end
 
         request = Rack::Request.new(env)
         consumer = ::OpenID::Consumer.new(session, @store)
 
         if mode = request.GET['openid.mode']
-          if session.key?(:openid_param)
-            finish(consumer, session, request)
-          else
-            bad_request
-          end
+          finish(consumer, session, request)
         elsif request.GET[@openid_param]
           check(consumer, session, request)
         else
-          unauthorized
+          bad_request
         end
       end
 
@@ -263,14 +263,13 @@ module Rack
         immediate = session.key?(:setup_needed) ? false : immediate
 
         if oid.send_redirect?(realm, return_to_uri, immediate)
-          uri = oid.redirect_url(realm, return_to_uri, immediate)
-          redirect(uri)
+          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")
+        req.env['rack.errors'].puts( [e.message, *e.backtrace]*"\n" )
         return foreign_server_failure
       end
 
@@ -290,21 +289,24 @@ module Rack
         req.env['rack.errors'].puts(oid.message)
         p oid if $DEBUG
 
-        raise unless ValidStatus.include?(oid.status)
-        __send__(oid.status, oid, req, session)
+        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
+      # * 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, #get_extension_args will be
-      # called on the result to attain the gathered data.
+      # 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.
@@ -344,28 +346,27 @@ module Rack
         return false unless uri.host.match(realm_match)
         return true
       end
+
       alias_method :include?, :within_realm?
 
       protected
 
-      ### These methods define some of the boilerplate responses.
-
       # 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)
-        Rack::Response.new.finish do |r|
-          r.write '<html><head><title>Confirm...</title></head><body>'
-          r.write oid.form_markup(realm, return_to, immediate)
-          r.write '</body></html>'
-        end
+        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-Length'=>'0', 'Content-Type'=>'text/plain',
+        [ 303, {'Content-Type'=>'text/plain', 'Content-Length'=>'0',
           'Location' => uri},
           [] ]
       end
@@ -401,10 +402,6 @@ module Rack
 
       private
 
-      ### These methods are called after a transaction is completed, depending
-      # on its outcome. These should all return a rack compatible response.
-      # You'd want to override these to provide additional functionality.
-
       # 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
@@ -430,7 +427,7 @@ module Rack
       def setup_needed(oid, request, session)
         identifier = session[:openid_param]
         session[:setup_needed] = true
-        redirect req.script_name + '?' + openid_param + '=' + identifier
+        redirect(req.script_name + '?' + openid_param + '=' + identifier)
       end
 
       # Called if the user indicates they wish to cancel identification.
@@ -448,6 +445,16 @@ module Rack
       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
@@ -472,8 +479,8 @@ module Rack
       end
 
       def call(env)
-        to = auth.call(env) ? @app : @oid
-        to.call env
+        to = @authenticator.call(env) ? @app : @oid
+        to.call(env)
       end
     end
   end
diff --git a/lib/rack/deflater.rb b/lib/rack/deflater.rb
index 14137a94..ad0f5316 100644
--- a/lib/rack/deflater.rb
+++ b/lib/rack/deflater.rb
@@ -60,7 +60,7 @@ module Rack
         @writer = block
         gzip  =::Zlib::GzipWriter.new(self)
         gzip.mtime = @mtime
-        @body.each { |part| gzip << part }
+        @body.each { |part| gzip.write(part) }
         @body.close if @body.respond_to?(:close)
         gzip.close
         @writer = nil
diff --git a/lib/rack/handler/lsws.rb b/lib/rack/handler/lsws.rb
index 7231336d..b4ddf4bb 100644
--- a/lib/rack/handler/lsws.rb
+++ b/lib/rack/handler/lsws.rb
@@ -15,14 +15,19 @@ module Rack
         env = ENV.to_hash
         env.delete "HTTP_CONTENT_LENGTH"
         env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
-        env.update({"rack.version" => [1,0],
-                     "rack.input" => StringIO.new($stdin.read.to_s),
-                     "rack.errors" => $stderr,
-                     "rack.multithread" => false,
-                     "rack.multiprocess" => true,
-                     "rack.run_once" => false,
-                     "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
-                   })
+
+        rack_input = RewindableInput.new($stdin.read.to_s)
+
+        env.update(
+          "rack.version" => [1,0],
+          "rack.input" => rack_input,
+          "rack.errors" => $stderr,
+          "rack.multithread" => false,
+          "rack.multiprocess" => true,
+          "rack.run_once" => false,
+          "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
+        )
+
         env["QUERY_STRING"] ||= ""
         env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
         env["REQUEST_PATH"] ||= "/"
diff --git a/lib/rack/handler/mongrel.rb b/lib/rack/handler/mongrel.rb
index 3a5ef32d..7b448261 100644
--- a/lib/rack/handler/mongrel.rb
+++ b/lib/rack/handler/mongrel.rb
@@ -45,8 +45,11 @@ module Rack
 
         env["SCRIPT_NAME"] = ""  if env["SCRIPT_NAME"] == "/"
 
+        rack_input = request.body || StringIO.new('')
+        rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
+
         env.update({"rack.version" => [1,0],
-                     "rack.input" => request.body || StringIO.new(""),
+                     "rack.input" => rack_input,
                      "rack.errors" => $stderr,
 
                      "rack.multithread" => true,
diff --git a/lib/rack/handler/scgi.rb b/lib/rack/handler/scgi.rb
index 6c4932df..bd860a5d 100644
--- a/lib/rack/handler/scgi.rb
+++ b/lib/rack/handler/scgi.rb
@@ -32,10 +32,13 @@ module Rack
         env["PATH_INFO"] = env["REQUEST_PATH"]
         env["QUERY_STRING"] ||= ""
         env["SCRIPT_NAME"] = ""
+
+        rack_input = StringIO.new(input_body)
+        rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
+
         env.update({"rack.version" => [1,0],
-                     "rack.input" => StringIO.new(input_body),
+                     "rack.input" => rack_input,
                      "rack.errors" => $stderr,
-
                      "rack.multithread" => true,
                      "rack.multiprocess" => true,
                      "rack.run_once" => false,
diff --git a/lib/rack/handler/webrick.rb b/lib/rack/handler/webrick.rb
index 2bdc83a9..5b9ae740 100644
--- a/lib/rack/handler/webrick.rb
+++ b/lib/rack/handler/webrick.rb
@@ -6,6 +6,7 @@ module Rack
   module Handler
     class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
       def self.run(app, options={})
+        options[:BindAddress] = options.delete(:Host) if options[:Host]
         server = ::WEBrick::HTTPServer.new(options)
         server.mount "/", Rack::Handler::WEBrick, app
         trap(:INT) { server.shutdown }
@@ -22,8 +23,11 @@ module Rack
         env = req.meta_vars
         env.delete_if { |k, v| v.nil? }
 
+        rack_input = StringIO.new(req.body.to_s)
+        rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
+
         env.update({"rack.version" => [1,0],
-                     "rack.input" => StringIO.new(req.body.to_s),
+                     "rack.input" => rack_input,
                      "rack.errors" => $stderr,
 
                      "rack.multithread" => true,
diff --git a/lib/rack/lint.rb b/lib/rack/lint.rb
index bf2e9787..796807a0 100644
--- a/lib/rack/lint.rb
+++ b/lib/rack/lint.rb
@@ -233,8 +233,17 @@ module Rack
     ## === The Input Stream
     ##
     ## The input stream is an IO-like object which contains the raw HTTP
-    ## POST data. If it is a file then it must be opened in binary mode.
+    ## POST data.
     def check_input(input)
+      ## When applicable, its external encoding must be "ASCII-8BIT" and it
+      ## must be opened in binary mode, for Ruby 1.9 compatibility.
+      assert("rack.input #{input} does not have ASCII-8BIT as its external encoding") {
+        input.external_encoding.name == "ASCII-8BIT"
+      } if input.respond_to?(:external_encoding)
+      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}") {
diff --git a/lib/rack/mime.rb b/lib/rack/mime.rb
index 5a6a73a9..853f16bd 100644
--- a/lib/rack/mime.rb
+++ b/lib/rack/mime.rb
@@ -14,7 +14,7 @@ module Rack
     #     Rack::Mime::MIME_TYPES.fetch('.foo', 'application/octet-stream')
 
     def mime_type(ext, fallback='application/octet-stream')
-      MIME_TYPES.fetch(ext, fallback)
+      MIME_TYPES.fetch(ext.to_s.downcase, fallback)
     end
     module_function :mime_type
 
@@ -126,6 +126,7 @@ module Rack
       ".ods"     => "application/vnd.oasis.opendocument.spreadsheet",
       ".odt"     => "application/vnd.oasis.opendocument.text",
       ".ogg"     => "application/ogg",
+      ".ogv"     => "video/ogg",
       ".p"       => "text/x-pascal",
       ".pas"     => "text/x-pascal",
       ".pbm"     => "image/x-portable-bitmap",
diff --git a/lib/rack/mock.rb b/lib/rack/mock.rb
index fdefb034..7964c447 100644
--- a/lib/rack/mock.rb
+++ b/lib/rack/mock.rb
@@ -114,13 +114,18 @@ module Rack
         end
       end
 
-      opts[:input] ||= ""
+      empty_str = ""
+      empty_str.force_encoding("ASCII-8BIT") if empty_str.respond_to? :force_encoding
+      opts[:input] ||= empty_str
       if String === opts[:input]
-        env["rack.input"] = StringIO.new(opts[:input])
+        rack_input = StringIO.new(opts[:input])
       else
-        env["rack.input"] = opts[:input]
+        rack_input = opts[:input]
       end
 
+      rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
+      env['rack.input'] = rack_input
+
       env["CONTENT_LENGTH"] ||= env["rack.input"].length.to_s
 
       opts.each { |field, value|
diff --git a/lib/rack/reloader.rb b/lib/rack/reloader.rb
index aa2f060b..a06de23a 100644
--- a/lib/rack/reloader.rb
+++ b/lib/rack/reloader.rb
@@ -1,5 +1,6 @@
 #          Copyright (c) 2009 Michael Fellinger m.fellinger@gmail.com
-# All files in this distribution are subject to the terms of the Ruby license.
+#       Rack::Reloader is subject to the terms of an MIT-style license.
+#      See COPYING or http://www.opensource.org/licenses/mit-license.php.
 
 require 'pathname'
 
@@ -70,7 +71,7 @@ module Rack
           next if file =~ /\.(so|bundle)$/ # cannot reload compiled files
 
           found, stat = figure_path(file, paths)
-          next unless found and stat and mtime = stat.mtime
+          next unless found && stat && mtime = stat.mtime
 
           @cache[file] = found
 
@@ -87,11 +88,13 @@ module Rack
         found, stat = safe_stat(found)
         return found, stat if found
 
-        paths.each do |possible_path|
+        paths.find do |possible_path|
           path = ::File.join(possible_path, file)
           found, stat = safe_stat(path)
           return ::File.expand_path(found), stat if found
         end
+
+        return false, false
       end
 
       def safe_stat(file)
diff --git a/lib/rack/request.rb b/lib/rack/request.rb
index 4c4cf61a..248ce18d 100644
--- a/lib/rack/request.rb
+++ b/lib/rack/request.rb
@@ -65,7 +65,7 @@ module Rack
 
     def host
       # Remove port number.
-      (@env["HTTP_HOST"] || @env["SERVER_NAME"]).gsub(/:\d+\z/, '')
+      (@env["HTTP_HOST"] || @env["SERVER_NAME"]).to_s.gsub(/:\d+\z/, '')
     end
 
     def script_name=(s); @env["SCRIPT_NAME"] = s.to_s             end
diff --git a/lib/rack/response.rb b/lib/rack/response.rb
index 28b4d830..d1f6a123 100644
--- a/lib/rack/response.rb
+++ b/lib/rack/response.rb
@@ -54,45 +54,11 @@ module Rack
     end
 
     def set_cookie(key, value)
-      case value
-      when Hash
-        domain  = "; domain="  + value[:domain]    if value[:domain]
-        path    = "; path="    + value[:path]      if value[:path]
-        # According to RFC 2109, we need dashes here.
-        # N.B.: cgi.rb uses spaces...
-        expires = "; expires=" + value[:expires].clone.gmtime.
-          strftime("%a, %d-%b-%Y %H:%M:%S GMT")    if value[:expires]
-        secure = "; secure"  if value[:secure]
-        httponly = "; HttpOnly" if value[:httponly]
-        value = value[:value]
-      end
-      value = [value]  unless Array === value
-      cookie = Utils.escape(key) + "=" +
-        value.map { |v| Utils.escape v }.join("&") +
-        "#{domain}#{path}#{expires}#{secure}#{httponly}"
-
-      case self["Set-Cookie"]
-      when Array
-        self["Set-Cookie"] << cookie
-      when String
-        self["Set-Cookie"] = [self["Set-Cookie"], cookie]
-      when nil
-        self["Set-Cookie"] = cookie
-      end
+      Utils.set_cookie_header!(header, key, value)
     end
 
     def delete_cookie(key, value={})
-      unless Array === self["Set-Cookie"]
-        self["Set-Cookie"] = [self["Set-Cookie"]].compact
-      end
-
-      self["Set-Cookie"].reject! { |cookie|
-        cookie =~ /\A#{Utils.escape(key)}=/
-      }
-
-      set_cookie(key,
-                 {:value => '', :path => nil, :domain => nil,
-                   :expires => Time.at(0) }.merge(value))
+      Utils.delete_cookie_header!(header, key, value)
     end
 
     def redirect(target, status=302)
diff --git a/lib/rack/rewindable_input.rb b/lib/rack/rewindable_input.rb
index 9e9b21ff..accd96be 100644
--- a/lib/rack/rewindable_input.rb
+++ b/lib/rack/rewindable_input.rb
@@ -72,6 +72,8 @@ module Rack
       # access it because we have the file handle open.
       @rewindable_io = Tempfile.new('RackRewindableInput')
       @rewindable_io.chmod(0000)
+      @rewindable_io.set_encoding(Encoding::BINARY) if @rewindable_io.respond_to?(:set_encoding)
+      @rewindable_io.binmode
       if filesystem_has_posix_semantics?
         @rewindable_io.unlink
         @unlinked = true
diff --git a/lib/rack/session/abstract/id.rb b/lib/rack/session/abstract/id.rb
index 218144c1..98746705 100644
--- a/lib/rack/session/abstract/id.rb
+++ b/lib/rack/session/abstract/id.rb
@@ -107,18 +107,16 @@ module Rack
 
           if not session_id = set_session(env, session_id, session, options)
             env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
-            [status, headers, body]
           elsif options[:defer] and not options[:renew]
             env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE
-            [status, headers, body]
           else
             cookie = Hash.new
             cookie[:value] = session_id
             cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
-            response = Rack::Response.new(body, status, headers)
-            response.set_cookie(@key, cookie.merge(options))
-            response.to_a
+            Utils.set_cookie_header!(headers, @key, cookie.merge(options))
           end
+
+          [status, headers, body]
         end
 
         # All thread safety and session retrival proceedures should occur here.
diff --git a/lib/rack/session/cookie.rb b/lib/rack/session/cookie.rb
index eace9bd0..240e6c8d 100644
--- a/lib/rack/session/cookie.rb
+++ b/lib/rack/session/cookie.rb
@@ -70,16 +70,15 @@ module Rack
 
         if session_data.size > (4096 - @key.size)
           env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K. Content dropped.")
-          [status, headers, body]
         else
           options = env["rack.session.options"]
           cookie = Hash.new
           cookie[:value] = session_data
           cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
-          response = Rack::Response.new(body, status, headers)
-          response.set_cookie(@key, cookie.merge(options))
-          response.to_a
+          Utils.set_cookie_header!(headers, @key, cookie.merge(options))
         end
+
+        [status, headers, body]
       end
 
       def generate_hmac(data)
diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb
index 228488c1..74303eff 100644
--- a/lib/rack/utils.rb
+++ b/lib/rack/utils.rb
@@ -13,7 +13,7 @@ module Rack
     # version since it's faster.  (Stolen from Camping).
     def escape(s)
       s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
-        '%'+$1.unpack('H2'*$1.size).join('%').upcase
+        '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
       }.tr(' ', '+')
     end
     module_function :escape
@@ -168,6 +168,54 @@ module Rack
     end
     module_function :select_best_encoding
 
+    def set_cookie_header!(header, key, value)
+      case value
+      when Hash
+        domain  = "; domain="  + value[:domain] if value[:domain]
+        path    = "; path="    + value[:path]   if value[:path]
+        # According to RFC 2109, we need dashes here.
+        # N.B.: cgi.rb uses spaces...
+        expires = "; expires=" + value[:expires].clone.gmtime.
+          strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
+        secure = "; secure"  if value[:secure]
+        httponly = "; HttpOnly" if value[:httponly]
+        value = value[:value]
+      end
+      value = [value] unless Array === value
+      cookie = escape(key) + "=" +
+        value.map { |v| escape v }.join("&") +
+        "#{domain}#{path}#{expires}#{secure}#{httponly}"
+
+      case header["Set-Cookie"]
+      when Array
+        header["Set-Cookie"] << cookie
+      when String
+        header["Set-Cookie"] = [header["Set-Cookie"], cookie]
+      when nil
+        header["Set-Cookie"] = cookie
+      end
+
+      nil
+    end
+    module_function :set_cookie_header!
+
+    def delete_cookie_header!(header, key, value = {})
+      unless Array === header["Set-Cookie"]
+        header["Set-Cookie"] = [header["Set-Cookie"]].compact
+      end
+
+      header["Set-Cookie"].reject! { |cookie|
+        cookie =~ /\A#{escape(key)}=/
+      }
+
+      set_cookie_header!(header, key,
+                 {:value => '', :path => nil, :domain => nil,
+                   :expires => Time.at(0) }.merge(value))
+
+      nil
+    end
+    module_function :delete_cookie_header!
+
     # Return the bytesize of String; uses String#length under Ruby 1.8 and
     # String#bytesize under 1.9.
     if ''.respond_to?(:bytesize)
@@ -211,6 +259,7 @@ module Rack
     # header when set.
     class HeaderHash < Hash
       def initialize(hash={})
+        super()
         @names = {}
         hash.each { |k, v| self[k] = v }
       end
@@ -238,8 +287,9 @@ module Rack
 
       def delete(k)
         canonical = k.downcase
-        super @names.delete(canonical)
+        result = super @names.delete(canonical)
         @names.delete_if { |name,| name.downcase == canonical }
+        result
       end
 
       def include?(k)
@@ -259,13 +309,23 @@ module Rack
         hash = dup
         hash.merge! other
       end
+
+      def replace(other)
+        clear
+        other.each { |k, v| self[k] = v }
+        self
+      end
     end
 
     # Every standard HTTP code mapped to the appropriate message.
-    # Stolen from Mongrel.
+    # Generated with:
+    #   curl -s http://www.iana.org/assignments/http-status-codes | \
+    #     ruby -ane 'm = /^(\d{3}) +(\S[^\[(]+)/.match($_) and
+    #                puts "      #{m[1]}  => \x27#{m[2].strip}x27,"'
     HTTP_STATUS_CODES = {
       100  => 'Continue',
       101  => 'Switching Protocols',
+      102  => 'Processing',
       200  => 'OK',
       201  => 'Created',
       202  => 'Accepted',
@@ -273,12 +333,15 @@ module Rack
       204  => 'No Content',
       205  => 'Reset Content',
       206  => 'Partial Content',
+      207  => 'Multi-Status',
+      226  => 'IM Used',
       300  => 'Multiple Choices',
       301  => 'Moved Permanently',
       302  => 'Found',
       303  => 'See Other',
       304  => 'Not Modified',
       305  => 'Use Proxy',
+      306  => 'Reserved',
       307  => 'Temporary Redirect',
       400  => 'Bad Request',
       401  => 'Unauthorized',
@@ -294,16 +357,23 @@ module Rack
       411  => 'Length Required',
       412  => 'Precondition Failed',
       413  => 'Request Entity Too Large',
-      414  => 'Request-URI Too Large',
+      414  => 'Request-URI Too Long',
       415  => 'Unsupported Media Type',
       416  => 'Requested Range Not Satisfiable',
       417  => 'Expectation Failed',
+      422  => 'Unprocessable Entity',
+      423  => 'Locked',
+      424  => 'Failed Dependency',
+      426  => 'Upgrade Required',
       500  => 'Internal Server Error',
       501  => 'Not Implemented',
       502  => 'Bad Gateway',
       503  => 'Service Unavailable',
       504  => 'Gateway Timeout',
-      505  => 'HTTP Version Not Supported'
+      505  => 'HTTP Version Not Supported',
+      506  => 'Variant Also Negotiates',
+      507  => 'Insufficient Storage',
+      510  => 'Not Extended',
     }
 
     # Responses with HTTP status codes that should not have an entity body
diff --git a/test/spec_rack_commonlogger.rb b/test/spec_rack_commonlogger.rb
index 9d212d5e..46a72e86 100644
--- a/test/spec_rack_commonlogger.rb
+++ b/test/spec_rack_commonlogger.rb
@@ -46,4 +46,16 @@ context "Rack::CommonLogger" do
     res.errors.should.not.be.empty
     res.errors.should =~ /"GET \/ " 200 - /
   end
+  
+  def length
+    self.class.length
+  end
+  
+  def self.length
+    123
+  end
+  
+  def self.obj
+    "hello world"
+  end
 end
diff --git a/test/spec_rack_lint.rb b/test/spec_rack_lint.rb
index 8c6419dd..9c5d5031 100644
--- a/test/spec_rack_lint.rb
+++ b/test/spec_rack_lint.rb
@@ -110,6 +110,28 @@ 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?
+        false
+      end
+      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
+        result = Object.new
+        def result.name
+          "US-ASCII"
+        end
+        result
+      end
+      Rack::Lint.new(nil).call(env("rack.input" => input))
+    }.should.raise(Rack::Lint::LintError).
+      message.should.match(/does not have ASCII-8BIT as its external encoding/)
   end
 
   specify "notices error errors" do
@@ -432,46 +454,48 @@ context "Rack::Lint" do
   end
   
   specify "passes valid read calls" do
+    hello_str = "hello world"
+    hello_str.force_encoding("ASCII-8BIT") if hello_str.respond_to? :force_encoding
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.input"].read
                        [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
-                     }).call(env({"rack.input" => StringIO.new("hello world")}))
+                     }).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 world")}))
+                     }).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 world")}))
+                     }).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 world")}))
+                     }).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 world")}))
+                     }).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 world")}))
+                     }).call(env({"rack.input" => StringIO.new(hello_str)}))
     }.should.not.raise(Rack::Lint::LintError)
   end
 end
diff --git a/test/spec_rack_request.rb b/test/spec_rack_request.rb
index 81f05eba..fe000c24 100644
--- a/test/spec_rack_request.rb
+++ b/test/spec_rack_request.rb
@@ -37,6 +37,11 @@ context "Rack::Request" do
     req = Rack::Request.new \
       Rack::MockRequest.env_for("/", "SERVER_NAME" => "example.org:9292")
     req.host.should.equal "example.org"
+
+    env = Rack::MockRequest.env_for("/")
+    env.delete("SERVER_NAME")
+    req = Rack::Request.new(env)
+    req.host.should.equal ""
   end
 
   specify "can parse the query string" do
@@ -424,6 +429,7 @@ Content-Transfer-Encoding: base64\r
 /9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg\r
 --AaB03x--\r
 EOF
+    input.force_encoding("ASCII-8BIT") if input.respond_to? :force_encoding
     res = Rack::MockRequest.new(Rack::Lint.new(app)).get "/",
       "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
       "CONTENT_LENGTH" => input.size.to_s, "rack.input" => StringIO.new(input)
diff --git a/test/spec_rack_utils.rb b/test/spec_rack_utils.rb
index 077ccc50..c3974299 100644
--- a/test/spec_rack_utils.rb
+++ b/test/spec_rack_utils.rb
@@ -12,6 +12,15 @@ context "Rack::Utils" do
       should.equal "q1%212%22%27w%245%267%2Fz8%29%3F%5C"
   end
 
+  specify "should escape correctly for multibyte characters" do
+    matz_name = "\xE3\x81\xBE\xE3\x81\xA4\xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsumoto
+    matz_name.force_encoding("UTF-8") if matz_name.respond_to? :force_encoding
+    Rack::Utils.escape(matz_name).should.equal '%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8'
+    matz_name_sep = "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsu moto
+    matz_name_sep.force_encoding("UTF-8") if matz_name_sep.respond_to? :force_encoding
+    Rack::Utils.escape(matz_name_sep).should.equal '%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8'
+  end
+
   specify "should unescape correctly" do
     Rack::Utils.unescape("fo%3Co%3Ebar").should.equal "fo<o>bar"
     Rack::Utils.unescape("a+space").should.equal "a space"
@@ -228,6 +237,37 @@ context "Rack::Utils::HeaderHash" do
     h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"])
     h.to_hash.should.equal({ "foo" => "bar\nbaz" })
   end
+
+  specify "should replace hashes correctly" do
+    h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz")
+    j = {"foo" => "bar"}
+    h.replace(j)
+    h["foo"].should.equal "bar"
+  end
+  
+  specify "should be able to delete the given key case-sensitively" do
+    h = Rack::Utils::HeaderHash.new("foo" => "bar")
+    h.delete("foo")
+    h["foo"].should.be.nil
+    h["FOO"].should.be.nil
+  end
+  
+  specify "should be able to delete the given key case-insensitively" do
+    h = Rack::Utils::HeaderHash.new("foo" => "bar")
+    h.delete("FOO")
+    h["foo"].should.be.nil
+    h["FOO"].should.be.nil
+  end
+  
+  specify "should return the deleted value when #delete is called on an existing key" do
+    h = Rack::Utils::HeaderHash.new("foo" => "bar")
+    h.delete("Foo").should.equal("bar")
+  end
+  
+  specify "should return nil when #delete is called on a non-existant key" do
+    h = Rack::Utils::HeaderHash.new("foo" => "bar")
+    h.delete("Hello").should.be.nil
+  end
 end
 
 context "Rack::Utils::Context" do