[DRE-commits] [SCM] ruby-rack.git branch, master, updated. debian/1.4.0-1-5-g065db2a

Youhei SASAKI uwabami at gfd-dennou.org
Tue Mar 6 16:10:32 UTC 2012


The following commit has been merged in the master branch:
commit c1dbf280518e82936f97674a5351a669d18240b2
Author: Youhei SASAKI <uwabami at gfd-dennou.org>
Date:   Wed Mar 7 00:43:46 2012 +0900

    Imported Upstream version 1.4.1

diff --git a/README.rdoc b/README.rdoc
index 664f021..f31506e 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -1,4 +1,4 @@
-= Rack, a modular Ruby webserver interface
+= Rack, a modular Ruby webserver interface {<img src="https://secure.travis-ci.org/rack/rack.png" alt="Build Status" />}[http://travis-ci.org/rack/rack] {<img src="https://gemnasium.com/rack/rack.png" alt="Dependency Status" />}[https://gemnasium.com/rack/rack]
 
 Rack provides a minimal, modular and adaptable interface for developing
 web applications in Ruby.  By wrapping HTTP requests and responses in
@@ -392,6 +392,22 @@ run on port 11211) and memcache-client installed.
   * Support added for HTTP_X_FORWARDED_SCHEME
   * Numerous bug fixes, including many fixes for new and alternate rubies
 
+* January 22nd, 2012: Twenty fifth public release 1.4.1
+  * Alter the keyspace limit calculations to reduce issues with nested params
+  * Add a workaround for multipart parsing where files contian unescaped "%"
+  * Added Rack::Response::Helpers#method_not_allowed? (code 405)
+  * Rack::File now returns 404's for illegal directory traversals
+  * Rack::File now returns 405's for illegal methods (non HEAD/GET)
+  * Rack::Cascade now catches 405 by default, as well as 404
+  * Cookies missing '--' no longer cause an exception to be raised
+  * Various style changes and documentation spelling errors
+  * Rack::BodyProxy always ensures to execute it's block
+  * Additional test coverage around cookies and secrets
+  * Rack::Session::Cookie can now be supplied either secret or old_secret
+  * Tests are no longer dependent on set order
+  * Rack::Static no longer defaults to serving index files
+  * Rack.release was fixed
+
 == Contact
 
 Please post bugs, suggestions and patches to
diff --git a/lib/rack.rb b/lib/rack.rb
index 4306d41..acfcb5a 100644
--- a/lib/rack.rb
+++ b/lib/rack.rb
@@ -20,7 +20,7 @@ module Rack
 
   # Return the Rack release as a dotted string.
   def self.release
-    "1.3"
+    "1.4"
   end
 
   autoload :Builder, "rack/builder"
diff --git a/lib/rack/body_proxy.rb b/lib/rack/body_proxy.rb
index 6362c5f..64984e6 100644
--- a/lib/rack/body_proxy.rb
+++ b/lib/rack/body_proxy.rb
@@ -11,8 +11,11 @@ module Rack
     def close
       return if @closed
       @closed = true
-      @body.close if @body.respond_to? :close
-      @block.call
+      begin
+        @body.close if @body.respond_to? :close
+      ensure
+        @block.call
+      end
     end
 
     def closed?
diff --git a/lib/rack/cascade.rb b/lib/rack/cascade.rb
index 4b9ba35..77c9724 100644
--- a/lib/rack/cascade.rb
+++ b/lib/rack/cascade.rb
@@ -8,7 +8,7 @@ module Rack
 
     attr_reader :apps
 
-    def initialize(apps, catch=404)
+    def initialize(apps, catch=[404, 405])
       @apps = []; @has_app = {}
       apps.each { |app| add app }
 
diff --git a/lib/rack/file.rb b/lib/rack/file.rb
index 8973195..2bb31b9 100644
--- a/lib/rack/file.rb
+++ b/lib/rack/file.rb
@@ -34,7 +34,7 @@ module Rack
 
     def _call(env)
       unless ALLOWED_VERBS.include? env["REQUEST_METHOD"]
-        return fail(403, "Forbidden")
+        return fail(405, "Method Not Allowed")
       end
 
       @path_info = Utils.unescape(env["PATH_INFO"])
@@ -45,7 +45,7 @@ module Rack
         when '', '.'
           depth
         when '..'
-          return fail(403, "Forbidden") if depth - 1 < 0
+          return fail(404, "Not Found") if depth - 1 < 0
           depth - 1
         else
           depth + 1
diff --git a/lib/rack/multipart/parser.rb b/lib/rack/multipart/parser.rb
index 2b55cf9..98eceaa 100644
--- a/lib/rack/multipart/parser.rb
+++ b/lib/rack/multipart/parser.rb
@@ -14,9 +14,6 @@ module Rack
 
         fast_forward_to_first_boundary
 
-        max_key_space = Utils.key_space_limit
-        bytes = 0
-
         loop do
           head, filename, content_type, name, body =
             get_current_head_and_filename_and_content_type_and_name_and_body
@@ -31,13 +28,6 @@ module Rack
 
           filename, data = get_data(filename, body, content_type, name, head)
 
-          if name
-            bytes += name.size
-            if bytes > max_key_space
-              raise RangeError, "exceeded available parameter key space"
-            end
-          end
-
           Utils.normalize_params(@params, name, data) unless data.nil?
 
           # break if we're at the end of a buffer, but not if it is the end of a field
@@ -46,7 +36,7 @@ module Rack
 
         @io.rewind
 
-        @params
+        @params.to_params_hash
       end
 
       private
@@ -56,7 +46,7 @@ module Rack
         @boundary = "--#{$1}"
 
         @buf = ""
-        @params = {}
+        @params = Utils::KeySpaceConstrainedParams.new
 
         @content_length = @env['CONTENT_LENGTH'].to_i
         @io = @env['rack.input']
@@ -135,8 +125,11 @@ module Rack
           filename = $1
         end
 
+        if filename && filename.scan(/%.?.?/).all? { |s| s =~ /%[0-9a-fA-F]{2}/ }
+          filename = Utils.unescape(filename)
+        end
         if filename && filename !~ /\\[^\\"]/
-          filename = Utils.unescape(filename).gsub(/\\(.)/, '\1')
+          filename = filename.gsub(/\\(.)/, '\1')
         end
         filename
       end
diff --git a/lib/rack/request.rb b/lib/rack/request.rb
index fbbf00b..6daedca 100644
--- a/lib/rack/request.rb
+++ b/lib/rack/request.rb
@@ -177,7 +177,7 @@ module Rack
       PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
     end
 
-    # Returns the data recieved in the query string.
+    # Returns the data received in the query string.
     def GET
       if @env["rack.request.query_string"] == query_string
         @env["rack.request.query_hash"]
@@ -187,7 +187,7 @@ module Rack
       end
     end
 
-    # Returns the data recieved in the request body.
+    # Returns the data received in the request body.
     #
     # This method support both application/x-www-form-urlencoded and
     # multipart/form-data.
diff --git a/lib/rack/response.rb b/lib/rack/response.rb
index 12180d8..a9973f4 100644
--- a/lib/rack/response.rb
+++ b/lib/rack/response.rb
@@ -12,7 +12,7 @@ module Rack
   # You can use Response#write to iteratively generate your response,
   # but note that this is buffered by Rack::Response until you call
   # +finish+.  +finish+ however can take a block inside which calls to
-  # +write+ are syncronous with the Rack response.
+  # +write+ are synchronous with the Rack response.
   #
   # Your application's +call+ should end returning Response#finish.
 
@@ -112,21 +112,22 @@ module Rack
     alias headers header
 
     module Helpers
-      def invalid?;       status < 100 || status >= 600;        end
-
-      def informational?; status >= 100 && status < 200;        end
-      def successful?;    status >= 200 && status < 300;        end
-      def redirection?;   status >= 300 && status < 400;        end
-      def client_error?;  status >= 400 && status < 500;        end
-      def server_error?;  status >= 500 && status < 600;        end
-
-      def ok?;            status == 200;                        end
-      def bad_request?;   status == 400;                        end
-      def forbidden?;     status == 403;                        end
-      def not_found?;     status == 404;                        end
-      def unprocessable?; status == 422;                        end
-
-      def redirect?;      [301, 302, 303, 307].include? status; end
+      def invalid?;            status < 100 || status >= 600;        end
+
+      def informational?;      status >= 100 && status < 200;        end
+      def successful?;         status >= 200 && status < 300;        end
+      def redirection?;        status >= 300 && status < 400;        end
+      def client_error?;       status >= 400 && status < 500;        end
+      def server_error?;       status >= 500 && status < 600;        end
+
+      def ok?;                 status == 200;                        end
+      def bad_request?;        status == 400;                        end
+      def forbidden?;          status == 403;                        end
+      def not_found?;          status == 404;                        end
+      def method_not_allowed?; status == 405;                        end
+      def unprocessable?;      status == 422;                        end
+
+      def redirect?;           [301, 302, 303, 307].include? status; end
 
       # Headers
       attr_reader :headers, :original_headers
diff --git a/lib/rack/session/abstract/id.rb b/lib/rack/session/abstract/id.rb
index 641e3c0..9e19d1d 100644
--- a/lib/rack/session/abstract/id.rb
+++ b/lib/rack/session/abstract/id.rb
@@ -36,7 +36,7 @@ module Rack
       private
 
         def session_id_not_loaded?
-          !key?(:id) && !@session_id_loaded
+          !(@session_id_loaded || key?(:id))
         end
 
         def load_session_id!
@@ -183,7 +183,7 @@ module Rack
           :renew =>         false,
           :sidbits =>       128,
           :cookie_only =>   true,
-          :secure_random => begin ::SecureRandom rescue false end
+          :secure_random => (::SecureRandom rescue false)
         }
 
         attr_reader :key, :default_options
@@ -191,7 +191,7 @@ module Rack
         def initialize(app, options={})
           @app = app
           @default_options = self.class::DEFAULT_OPTIONS.merge(options)
-          @key = options[:key] || "rack.session"
+          @key = @default_options.delete(:key)
           @cookie_only = @default_options.delete(:cookie_only)
           initialize_sid
         end
diff --git a/lib/rack/session/cookie.rb b/lib/rack/session/cookie.rb
index 1d87384..ed9ee58 100644
--- a/lib/rack/session/cookie.rb
+++ b/lib/rack/session/cookie.rb
@@ -81,8 +81,7 @@ module Rack
       attr_reader :coder
 
       def initialize(app, options={})
-        @secret = options[:secret]
-        @old_secret = options[:old_secret]
+        @secrets = options.values_at(:secret, :old_secret).compact
         @coder  = options[:coder] ||= Base64::Marshal.new
         super(app, options.merge!(:cookie_only => true))
       end
@@ -104,11 +103,16 @@ module Rack
           request = Rack::Request.new(env)
           session_data = request.cookies[@key]
 
-          if (@secret || @old_secret) && session_data
+          if @secrets.size > 0 && session_data
             session_data, digest = session_data.split("--")
-            if (digest != generate_hmac(session_data, @secret)) && (digest != generate_hmac(session_data, @old_secret))
-              session_data = nil
+
+            if session_data && digest
+              ok = @secrets.any? do |secret|
+                secret && digest == generate_hmac(session_data, secret)
+              end
             end
+
+            session_data = nil unless ok
           end
 
           coder.decode(session_data) || {}
@@ -131,8 +135,8 @@ module Rack
         session = session.merge("session_id" => session_id)
         session_data = coder.encode(session)
 
-        if @secret
-          session_data = "#{session_data}--#{generate_hmac(session_data, @secret)}"
+        if @secrets.first
+          session_data = "#{session_data}--#{generate_hmac(session_data, @secrets.first)}"
         end
 
         if session_data.size > (4096 - @key.size)
diff --git a/lib/rack/showstatus.rb b/lib/rack/showstatus.rb
index e1fff99..5a9506f 100644
--- a/lib/rack/showstatus.rb
+++ b/lib/rack/showstatus.rb
@@ -3,8 +3,8 @@ require 'rack/request'
 require 'rack/utils'
 
 module Rack
-  # Rack::ShowStatus catches all empty responses the app it wraps and
-  # replaces them with a site explaining the error.
+  # Rack::ShowStatus catches all empty responses and replaces them 
+  # with a site explaining the error.
   #
   # Additional details can be put into <tt>rack.showstatus.detail</tt>
   # and will be shown as HTML.  If such details exist, the error page
diff --git a/lib/rack/static.rb b/lib/rack/static.rb
index 1b843a0..727ecbb 100644
--- a/lib/rack/static.rb
+++ b/lib/rack/static.rb
@@ -38,7 +38,7 @@ module Rack
     def initialize(app, options={})
       @app = app
       @urls = options[:urls] || ["/favicon.ico"]
-      @index = options[:index] || "index.html"
+      @index = options[:index]
       root = options[:root] || Dir.pwd
       cache_control = options[:cache_control]
       @file_server = Rack::File.new(root, cache_control)
diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb
index 7bceb45..b706279 100644
--- a/lib/rack/utils.rb
+++ b/lib/rack/utils.rb
@@ -61,21 +61,11 @@ module Rack
     # cookies by changing the characters used in the second
     # parameter (which defaults to '&;').
     def parse_query(qs, d = nil)
-      params = {}
-
-      max_key_space = Utils.key_space_limit
-      bytes = 0
+      params = KeySpaceConstrainedParams.new
 
       (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
         k, v = p.split('=', 2).map { |x| unescape(x) }
 
-        if k
-          bytes += k.size
-          if bytes > max_key_space
-            raise RangeError, "exceeded available parameter key space"
-          end
-        end
-
         if cur = params[k]
           if cur.class == Array
             params[k] << v
@@ -87,30 +77,20 @@ module Rack
         end
       end
 
-      return params
+      return params.to_params_hash
     end
     module_function :parse_query
 
     def parse_nested_query(qs, d = nil)
-      params = {}
-
-      max_key_space = Utils.key_space_limit
-      bytes = 0
+      params = KeySpaceConstrainedParams.new
 
       (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
         k, v = p.split('=', 2).map { |s| unescape(s) }
 
-        if k
-          bytes += k.size
-          if bytes > max_key_space
-            raise RangeError, "exceeded available parameter key space"
-          end
-        end
-
         normalize_params(params, k, v)
       end
 
-      return params
+      return params.to_params_hash
     end
     module_function :parse_nested_query
 
@@ -131,14 +111,14 @@ module Rack
         child_key = $1
         params[k] ||= []
         raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
-        if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
+        if params_hash_type?(params[k].last) && !params[k].last.key?(child_key)
           normalize_params(params[k].last, child_key, v)
         else
-          params[k] << normalize_params({}, child_key, v)
+          params[k] << normalize_params(params.class.new, child_key, v)
         end
       else
-        params[k] ||= {}
-        raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
+        params[k] ||= params.class.new
+        raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
         params[k] = normalize_params(params[k], after, v)
       end
 
@@ -146,6 +126,11 @@ module Rack
     end
     module_function :normalize_params
 
+    def params_hash_type?(obj)
+      obj.kind_of?(KeySpaceConstrainedParams) || obj.kind_of?(Hash)
+    end
+    module_function :params_hash_type?
+
     def build_query(params)
       params.map { |k, v|
         if v.class == Array
@@ -445,6 +430,41 @@ module Rack
       end
     end
 
+    class KeySpaceConstrainedParams
+      def initialize(limit = Utils.key_space_limit)
+        @limit  = limit
+        @size   = 0
+        @params = {}
+      end
+
+      def [](key)
+        @params[key]
+      end
+
+      def []=(key, value)
+        @size += key.size unless @params.key?(key)
+        raise RangeError, 'exceeded available parameter key space' if @size > @limit
+        @params[key] = value
+      end
+
+      def key?(key)
+        @params.key?(key)
+      end
+
+      def to_params_hash
+        hash = @params
+        hash.keys.each do |key|
+          value = hash[key]
+          if value.kind_of?(self.class)
+            hash[key] = value.to_params_hash
+          elsif value.kind_of?(Array)
+            value.map! {|x| x.kind_of?(self.class) ? x.to_params_hash : x}
+          end
+        end
+        hash
+      end
+    end
+
     # Every standard HTTP code mapped to the appropriate message.
     # Generated with:
     #   curl -s http://www.iana.org/assignments/http-status-codes | \
diff --git a/metadata.yml b/metadata.yml
index 3234b9b..15a43bd 100644
--- a/metadata.yml
+++ b/metadata.yml
@@ -1,13 +1,13 @@
 --- !ruby/object:Gem::Specification 
 name: rack
 version: !ruby/object:Gem::Version 
-  hash: 7
+  hash: 5
   prerelease: 
   segments: 
   - 1
   - 4
-  - 0
-  version: 1.4.0
+  - 1
+  version: 1.4.1
 platform: ruby
 authors: 
 - Christian Neukirchen
@@ -15,7 +15,7 @@ autorequire:
 bindir: bin
 cert_chain: []
 
-date: 2011-12-28 00:00:00 Z
+date: 2012-01-23 00:00:00 Z
 dependencies: 
 - !ruby/object:Gem::Dependency 
   name: bacon
@@ -213,6 +213,9 @@ files:
 - test/multipart/filename_with_escaped_quotes
 - test/multipart/filename_with_escaped_quotes_and_modification_param
 - test/multipart/filename_with_percent_escaped_quotes
+- test/multipart/filename_with_unescaped_percentages
+- test/multipart/filename_with_unescaped_percentages2
+- test/multipart/filename_with_unescaped_percentages3
 - test/multipart/filename_with_unescaped_quotes
 - test/multipart/ie
 - test/multipart/mixed_files
diff --git a/rack.gemspec b/rack.gemspec
index 0d6fa07..988edee 100644
--- a/rack.gemspec
+++ b/rack.gemspec
@@ -1,6 +1,6 @@
 Gem::Specification.new do |s|
   s.name            = "rack"
-  s.version         = "1.4.0"
+  s.version         = "1.4.1"
   s.platform        = Gem::Platform::RUBY
   s.summary         = "a modular Ruby webserver interface"
 
diff --git a/test/multipart/filename_with_unescaped_percentages b/test/multipart/filename_with_unescaped_percentages
new file mode 100644
index 0000000..f63dd22
--- /dev/null
+++ b/test/multipart/filename_with_unescaped_percentages
@@ -0,0 +1,6 @@
+------WebKitFormBoundary2NHc7OhsgU68l3Al
+Content-Disposition: form-data; name="document[attachment]"; filename="100% of a photo.jpeg"
+Content-Type: image/jpeg
+
+contents
+------WebKitFormBoundary2NHc7OhsgU68l3Al--
diff --git a/test/multipart/filename_with_unescaped_percentages2 b/test/multipart/filename_with_unescaped_percentages2
new file mode 100644
index 0000000..83eac36
--- /dev/null
+++ b/test/multipart/filename_with_unescaped_percentages2
@@ -0,0 +1,6 @@
+------WebKitFormBoundary2NHc7OhsgU68l3Al
+Content-Disposition: form-data; name="document[attachment]"; filename="100%a"
+Content-Type: image/jpeg
+
+contents
+------WebKitFormBoundary2NHc7OhsgU68l3Al--
diff --git a/test/multipart/filename_with_unescaped_percentages3 b/test/multipart/filename_with_unescaped_percentages3
new file mode 100644
index 0000000..4dba3c8
--- /dev/null
+++ b/test/multipart/filename_with_unescaped_percentages3
@@ -0,0 +1,6 @@
+------WebKitFormBoundary2NHc7OhsgU68l3Al
+Content-Disposition: form-data; name="document[attachment]"; filename="100%"
+Content-Type: image/jpeg
+
+contents
+------WebKitFormBoundary2NHc7OhsgU68l3Al--
diff --git a/test/spec_body_proxy.rb b/test/spec_body_proxy.rb
index 64bd65f..2f9f040 100644
--- a/test/spec_body_proxy.rb
+++ b/test/spec_body_proxy.rb
@@ -1,4 +1,5 @@
 require 'rack/body_proxy'
+require 'stringio'
 
 describe Rack::BodyProxy do
   should 'call each on the wrapped body' do
@@ -32,6 +33,22 @@ describe Rack::BodyProxy do
     called.should.equal true
   end
 
+  should 'call the passed block on close even if there is an exception' do
+    object = Object.new
+    def object.close() raise "No!" end
+    called = false
+
+    begin
+      proxy  = Rack::BodyProxy.new(object) { called = true }
+      called.should.equal false
+      proxy.close
+    rescue RuntimeError => e
+    end
+
+    raise "Expected exception to have been raised" unless e
+    called.should.equal true
+  end
+
   should 'not close more than one time' do
     count = 0
     proxy = Rack::BodyProxy.new([]) { count += 1; raise "Block invoked more than 1 time!" if count > 1 }
diff --git a/test/spec_cascade.rb b/test/spec_cascade.rb
index cfd1164..8db570d 100644
--- a/test/spec_cascade.rb
+++ b/test/spec_cascade.rb
@@ -17,12 +17,15 @@ describe Rack::Cascade do
   app3 = Rack::URLMap.new("/foo" => lambda { |env|
                             [200, { "Content-Type" => "text/plain"}, [""]]})
 
-  should "dispatch onward on 404 by default" do
+  should "dispatch onward on 404 and 405 by default" do
     cascade = cascade([app1, app2, app3])
     Rack::MockRequest.new(cascade).get("/cgi/test").should.be.ok
     Rack::MockRequest.new(cascade).get("/foo").should.be.ok
     Rack::MockRequest.new(cascade).get("/toobad").should.be.not_found
-    Rack::MockRequest.new(cascade).get("/cgi/../..").should.be.forbidden
+    Rack::MockRequest.new(cascade).get("/cgi/../..").should.be.client_error
+
+    # Put is not allowed by Rack::File so it'll 405.
+    Rack::MockRequest.new(cascade).put("/foo").should.be.ok
   end
 
   should "dispatch onward on whatever is passed" do
@@ -42,7 +45,7 @@ describe Rack::Cascade do
     Rack::MockRequest.new(cascade).get('/cgi/../bla').should.be.not_found
     cascade << app1
     Rack::MockRequest.new(cascade).get('/cgi/test').should.be.ok
-    Rack::MockRequest.new(cascade).get('/cgi/../..').should.be.forbidden
+    Rack::MockRequest.new(cascade).get('/cgi/../..').should.be.client_error
     Rack::MockRequest.new(cascade).get('/foo').should.be.not_found
     cascade << app3
     Rack::MockRequest.new(cascade).get('/foo').should.be.ok
diff --git a/test/spec_file.rb b/test/spec_file.rb
index 2a4d214..2c90732 100644
--- a/test/spec_file.rb
+++ b/test/spec_file.rb
@@ -64,13 +64,15 @@ describe Rack::File do
     req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
 
     res = req.get("/../README")
-    res.should.be.forbidden
+    res.should.be.client_error
 
     res = req.get("../test")
-    res.should.be.forbidden
+    res.should.be.client_error
 
     res = req.get("..")
-    res.should.be.forbidden
+    res.should.be.client_error
+
+    res.should.be.not_found
   end
 
   should "allow files with .. in their name" do
@@ -89,7 +91,8 @@ describe Rack::File do
     res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
       get("/%2E%2E/README")
 
-    res.should.be.forbidden
+    res.should.be.client_error?
+    res.should.be.not_found
   end
 
   should "allow safe directory traversal with encoded periods" do
@@ -159,7 +162,8 @@ describe Rack::File do
     forbidden.each do |method|
 
       res = req.send(method, "/cgi/test")
-      res.should.be.forbidden
+      res.should.be.client_error
+      res.should.be.method_not_allowed
     end
 
     allowed = %w[get head]
@@ -169,4 +173,11 @@ describe Rack::File do
     end
   end
 
+  should "set Content-Length correctly for HEAD requests" do
+    req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
+    res = req.head "/cgi/test"
+    res.should.be.successful
+    res['Content-Length'].should.equal "193"
+  end
+
 end
diff --git a/test/spec_multipart.rb b/test/spec_multipart.rb
index 1dc2f4d..b0bf57c 100644
--- a/test/spec_multipart.rb
+++ b/test/spec_multipart.rb
@@ -2,11 +2,11 @@ require 'rack/utils'
 require 'rack/mock'
 
 describe Rack::Multipart do
-  def multipart_fixture(name)
+  def multipart_fixture(name, boundary = "AaB03x")
     file = multipart_file(name)
     data = File.open(file, 'rb') { |io| io.read }
 
-    type = "multipart/form-data; boundary=AaB03x"
+    type = "multipart/form-data; boundary=#{boundary}"
     length = data.respond_to?(:bytesize) ? data.bytesize : data.size
 
     { "CONTENT_TYPE" => type,
@@ -211,6 +211,51 @@ describe Rack::Multipart do
     params["files"][:tempfile].read.should.equal "contents"
   end
 
+  should "parse filename with unescaped percentage characters" do
+    env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_percentages, "----WebKitFormBoundary2NHc7OhsgU68l3Al"))
+    params = Rack::Multipart.parse_multipart(env)
+    files = params["document"]["attachment"]
+    files[:type].should.equal "image/jpeg"
+    files[:filename].should.equal "100% of a photo.jpeg"
+    files[:head].should.equal <<-MULTIPART
+Content-Disposition: form-data; name="document[attachment]"; filename="100% of a photo.jpeg"\r
+Content-Type: image/jpeg\r
+    MULTIPART
+
+    files[:name].should.equal "document[attachment]"
+    files[:tempfile].read.should.equal "contents"
+  end
+
+  should "parse filename with unescaped percentage characters that look like partial hex escapes" do
+    env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_percentages2, "----WebKitFormBoundary2NHc7OhsgU68l3Al"))
+    params = Rack::Multipart.parse_multipart(env)
+    files = params["document"]["attachment"]
+    files[:type].should.equal "image/jpeg"
+    files[:filename].should.equal "100%a"
+    files[:head].should.equal <<-MULTIPART
+Content-Disposition: form-data; name="document[attachment]"; filename="100%a"\r
+Content-Type: image/jpeg\r
+    MULTIPART
+
+    files[:name].should.equal "document[attachment]"
+    files[:tempfile].read.should.equal "contents"
+  end
+
+  should "parse filename with unescaped percentage characters that look like partial hex escapes" do
+    env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_percentages3, "----WebKitFormBoundary2NHc7OhsgU68l3Al"))
+    params = Rack::Multipart.parse_multipart(env)
+    files = params["document"]["attachment"]
+    files[:type].should.equal "image/jpeg"
+    files[:filename].should.equal "100%"
+    files[:head].should.equal <<-MULTIPART
+Content-Disposition: form-data; name="document[attachment]"; filename="100%"\r
+Content-Type: image/jpeg\r
+    MULTIPART
+
+    files[:name].should.equal "document[attachment]"
+    files[:tempfile].read.should.equal "contents"
+  end
+
   it "rewinds input after parsing upload" do
     options = multipart_fixture(:text)
     input = options[:input]
diff --git a/test/spec_request.rb b/test/spec_request.rb
index d20585c..57d34ab 100644
--- a/test/spec_request.rb
+++ b/test/spec_request.rb
@@ -137,6 +137,19 @@ describe Rack::Request do
     end
   end
 
+  should "limit the key size per nested params hash" do
+    nested_query = Rack::MockRequest.env_for("/?foo[bar][baz][qux]=1")
+    plain_query  = Rack::MockRequest.env_for("/?foo_bar__baz__qux_=1")
+
+    old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 3
+    begin
+      lambda { Rack::Request.new(nested_query).GET }.should.not.raise(RangeError)
+      lambda { Rack::Request.new(plain_query).GET  }.should.raise(RangeError)
+    ensure
+      Rack::Utils.key_space_limit = old
+    end
+  end
+
   should "not unify GET and POST when calling params" do
     mr = Rack::MockRequest.env_for("/?foo=quux",
       "REQUEST_METHOD" => 'POST',
diff --git a/test/spec_response.rb b/test/spec_response.rb
index 07dd012..395bf91 100644
--- a/test/spec_response.rb
+++ b/test/spec_response.rb
@@ -1,4 +1,3 @@
-require 'set'
 require 'rack/response'
 require 'stringio'
 
@@ -125,7 +124,6 @@ describe Rack::Response do
     response = Rack::Response.new
     response.redirect "/foo"
     status, header, body = response.finish
-
     status.should.equal 302
     header["Location"].should.equal "/foo"
 
@@ -147,7 +145,12 @@ describe Rack::Response do
     str = ""; body.each { |part| str << part }
     str.should.equal "foobar"
 
-    r = Rack::Response.new(["foo", "bar"].to_set)
+    object_with_each = Object.new
+    def object_with_each.each
+      yield "foo"
+      yield "bar"
+    end
+    r = Rack::Response.new(object_with_each)
     r.write "foo"
     status, header, body = r.finish
     str = ""; body.each { |part| str << part }
@@ -218,6 +221,11 @@ describe Rack::Response do
     res.should.be.client_error
     res.should.be.not_found
 
+    res.status = 405
+    res.should.not.be.successful
+    res.should.be.client_error
+    res.should.be.method_not_allowed
+
     res.status = 422
     res.should.not.be.successful
     res.should.be.client_error
diff --git a/test/spec_session_cookie.rb b/test/spec_session_cookie.rb
index 514bb3b..ab7d518 100644
--- a/test/spec_session_cookie.rb
+++ b/test/spec_session_cookie.rb
@@ -123,6 +123,10 @@ describe Rack::Session::Cookie do
     res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
       get("/", "HTTP_COOKIE" => "rack.session=blarghfasel")
     res.body.should.equal '{"counter"=>1}'
+
+    app = Rack::Session::Cookie.new(incrementor, :secret => 'test')
+    res = Rack::MockRequest.new(app).get("/", "HTTP_COOKIE" => "rack.session=")
+    res.body.should.equal '{"counter"=>1}'
   end
 
   bigcookie = lambda do |env|
@@ -137,7 +141,7 @@ describe Rack::Session::Cookie do
     }.should.raise(Rack::MockRequest::FatalWarning)
   end
 
-  it "loads from a cookie wih integrity hash" do
+  it "loads from a cookie with integrity hash" do
     res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).get("/")
     cookie = res["Set-Cookie"]
     res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).
@@ -147,6 +151,9 @@ describe Rack::Session::Cookie do
     res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).
       get("/", "HTTP_COOKIE" => cookie)
     res.body.should.equal '{"counter"=>3}'
+    res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'other')).
+      get("/", "HTTP_COOKIE" => cookie)
+    res.body.should.equal '{"counter"=>1}'
   end
 
   it "loads from a cookie wih accept-only integrity hash for graceful key rotation" do
@@ -165,16 +172,31 @@ describe Rack::Session::Cookie do
     app = Rack::Session::Cookie.new(incrementor, :secret => 'test')
     response1 = Rack::MockRequest.new(app).get("/")
     response1.body.should.equal '{"counter"=>1}'
+    response1 = Rack::MockRequest.new(app).get("/", "HTTP_COOKIE" => response1["Set-Cookie"])
+    response1.body.should.equal '{"counter"=>2}'
 
     _, digest = response1["Set-Cookie"].split("--")
     tampered_with_cookie = "hackerman-was-here" + "--" + digest
     response2 = Rack::MockRequest.new(app).get("/", "HTTP_COOKIE" =>
                                                tampered_with_cookie)
 
-    # Tampared cookie was ignored. Counter is back to 1.
+    # Tampered cookie was ignored. Counter is back to 1.
     response2.body.should.equal '{"counter"=>1}'
   end
 
+  it "supports either of secret or old_secret" do
+    app = Rack::Session::Cookie.new(incrementor, :secret => 'test')
+    res = Rack::MockRequest.new(app).get("/")
+    res.body.should.equal '{"counter"=>1}'
+    res = Rack::MockRequest.new(app).get("/", "HTTP_COOKIE" => res["Set-Cookie"])
+    res.body.should.equal '{"counter"=>2}'
+    app = Rack::Session::Cookie.new(incrementor, :old_secret => 'test')
+    res = Rack::MockRequest.new(app).get("/")
+    res.body.should.equal '{"counter"=>1}'
+    res = Rack::MockRequest.new(app).get("/", "HTTP_COOKIE" => res["Set-Cookie"])
+    res.body.should.equal '{"counter"=>2}'
+  end
+
   describe "1.9 bugs relating to inspecting yet-to-be-loaded from cookie data: Rack::Session::Abstract::SessionHash" do
 
     it "can handle Rack::Lint middleware" do
@@ -225,6 +247,7 @@ describe Rack::Session::Cookie do
 
     res = Rack::MockRequest.new(app).get("/", "HTTPS" => "on")
     res["Set-Cookie"].should.not.be.nil
+    res["Set-Cookie"].should.match(/secure/)
   end
 
   it "does not return a cookie if cookie was not read/written" do
diff --git a/test/spec_static.rb b/test/spec_static.rb
index 21b2b6a..1860d13 100644
--- a/test/spec_static.rb
+++ b/test/spec_static.rb
@@ -40,6 +40,11 @@ describe Rack::Static do
     res.should.be.ok
     res.body.should =~ /index!/
   end
+  
+  it "doesn't call index file if :index option was omitted" do
+    res = @request.get("/")
+    res.body.should == "Hello World"
+  end
 
   it "serves hidden files" do
     res = @hash_request.get("/cgi/sekret")
diff --git a/test/spec_utils.rb b/test/spec_utils.rb
index a787763..32d0a6f 100644
--- a/test/spec_utils.rb
+++ b/test/spec_utils.rb
@@ -3,6 +3,15 @@ require 'rack/utils'
 require 'rack/mock'
 
 describe Rack::Utils do
+
+  # A helper method which checks
+  # if certain query parameters 
+  # are equal.
+  def equal_query_to(query)
+    parts = query.split('&')
+    lambda{|other| (parts & other.split('&')) == parts }
+  end
+
   def kcodeu
     one8 = RUBY_VERSION.to_f < 1.9
     default_kcode, $KCODE = $KCODE, 'U' if one8
@@ -179,7 +188,7 @@ describe Rack::Utils do
 
     lambda { Rack::Utils.parse_nested_query("x[y]=1&x[]=1") }.
       should.raise(TypeError).
-      message.should.equal "expected Array (got Hash) for param `x'"
+      message.should.match /expected Array \(got [^)]*\) for param `x'/
 
     lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y][][w]=2") }.
       should.raise(TypeError).
@@ -187,13 +196,13 @@ describe Rack::Utils do
   end
 
   should "build query strings correctly" do
-    Rack::Utils.build_query("foo" => "bar").should.equal "foo=bar"
+    Rack::Utils.build_query("foo" => "bar").should.be equal_query_to("foo=bar")
     Rack::Utils.build_query("foo" => ["bar", "quux"]).
-      should.equal "foo=bar&foo=quux"
+      should.be equal_query_to("foo=bar&foo=quux")
     Rack::Utils.build_query("foo" => "1", "bar" => "2").
-      should.equal "foo=1&bar=2"
+      should.be equal_query_to("foo=1&bar=2")
     Rack::Utils.build_query("my weird field" => "q1!2\"'w$5&7/z8)?").
-      should.equal "my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F"
+      should.be equal_query_to("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F")
   end
 
   should "build nested query strings correctly" do
@@ -202,9 +211,9 @@ describe Rack::Utils do
     Rack::Utils.build_nested_query("foo" => "bar").should.equal "foo=bar"
 
     Rack::Utils.build_nested_query("foo" => "1", "bar" => "2").
-      should.equal "foo=1&bar=2"
+      should.be equal_query_to("foo=1&bar=2")
     Rack::Utils.build_nested_query("my weird field" => "q1!2\"'w$5&7/z8)?").
-      should.equal "my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F"
+      should.be equal_query_to("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F")
 
     Rack::Utils.build_nested_query("foo" => [nil]).
       should.equal "foo[]"

-- 
ruby-rack.git



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