diff options
-rw-r--r-- | lib/rack/deflater.rb | 43 | ||||
-rw-r--r-- | lib/rack/etag.rb | 2 | ||||
-rw-r--r-- | lib/rack/multipart/parser.rb | 4 | ||||
-rw-r--r-- | lib/rack/nulllogger.rb | 23 | ||||
-rw-r--r-- | lib/rack/request.rb | 10 | ||||
-rw-r--r-- | lib/rack/session/abstract/id.rb | 3 | ||||
-rw-r--r-- | lib/rack/utils.rb | 14 | ||||
-rw-r--r-- | test/multipart/ie11 | 6 | ||||
-rw-r--r-- | test/spec_deflater.rb | 431 | ||||
-rw-r--r-- | test/spec_etag.rb | 4 | ||||
-rw-r--r-- | test/spec_multipart.rb | 22 | ||||
-rw-r--r-- | test/spec_request.rb | 10 | ||||
-rw-r--r-- | test/spec_utils.rb | 10 |
13 files changed, 382 insertions, 200 deletions
diff --git a/lib/rack/deflater.rb b/lib/rack/deflater.rb index 2e55f97e..9df510bd 100644 --- a/lib/rack/deflater.rb +++ b/lib/rack/deflater.rb @@ -17,19 +17,26 @@ module Rack # directive of 'no-transform' is present, or when the response status # code is one that doesn't allow an entity body. class Deflater - def initialize(app) + ## + # Creates Rack::Deflater middleware. + # + # [app] rack app instance + # [options] hash of deflater options, i.e. + # 'if' - a lambda enabling / disabling deflation based on returned boolean value + # e.g use Rack::Deflater, :if => lambda { |env, status, headers, body| body.length > 512 } + # 'include' - a list of content types that should be compressed + def initialize(app, options = {}) @app = app + + @condition = options[:if] + @compressible_types = options[:include] end def call(env) status, headers, body = @app.call(env) headers = Utils::HeaderHash.new(headers) - # Skip compressing empty entity body responses and responses with - # no-transform set. - if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) || - headers['Cache-Control'].to_s =~ /\bno-transform\b/ || - (headers['Content-Encoding'] && headers['Content-Encoding'] !~ /\bidentity\b/) + unless should_deflate?(env, status, headers, body) return [status, headers, body] end @@ -58,9 +65,9 @@ module Rack when "identity" [status, headers, body] when nil - body.close if body.respond_to?(:close) message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found." - [406, {"Content-Type" => "text/plain", "Content-Length" => message.length.to_s}, [message]] + bp = Rack::BodyProxy.new([message]) { body.close if body.respond_to?(:close) } + [406, {"Content-Type" => "text/plain", "Content-Length" => message.length.to_s}, bp] end end @@ -124,5 +131,25 @@ module Rack @body.close if @body.respond_to?(:close) end end + + private + + def should_deflate?(env, status, headers, body) + # Skip compressing empty entity body responses and responses with + # no-transform set. + if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) || + headers['Cache-Control'].to_s =~ /\bno-transform\b/ || + (headers['Content-Encoding'] && headers['Content-Encoding'] !~ /\bidentity\b/) + return false + end + + # Skip if @compressible_types are given and does not include request's content type + return false if @compressible_types && !(headers.has_key?('Content-Type') && @compressible_types.include?(headers['Content-Type'][/[^;]*/])) + + # Skip if @condition lambda is given and evaluates to false + return false if @condition && !@condition.call(env, status, headers, body) + + true + end end end diff --git a/lib/rack/etag.rb b/lib/rack/etag.rb index 99a1a4c0..fefe671f 100644 --- a/lib/rack/etag.rb +++ b/lib/rack/etag.rb @@ -28,7 +28,7 @@ module Rack body = Rack::BodyProxy.new(new_body) do original_body.close if original_body.respond_to?(:close) end - headers['ETag'] = %("#{digest}") if digest + headers['ETag'] = %(W/"#{digest}") if digest end unless headers['Cache-Control'] diff --git a/lib/rack/multipart/parser.rb b/lib/rack/multipart/parser.rb index 324c7d8c..22f9734b 100644 --- a/lib/rack/multipart/parser.rb +++ b/lib/rack/multipart/parser.rb @@ -116,10 +116,6 @@ module Rack name = filename end - if filename && filename.empty? - filename = name - end - if filename (@env['rack.tempfiles'] ||= []) << body = Tempfile.new("RackMultipart") body.binmode if body.respond_to?(:binmode) diff --git a/lib/rack/nulllogger.rb b/lib/rack/nulllogger.rb index 77fb637d..2d5a2c97 100644 --- a/lib/rack/nulllogger.rb +++ b/lib/rack/nulllogger.rb @@ -9,10 +9,29 @@ module Rack @app.call(env) end - def info(progname = nil, &block); end + def info(progname = nil, &block); end def debug(progname = nil, &block); end - def warn(progname = nil, &block); end + def warn(progname = nil, &block); end def error(progname = nil, &block); end def fatal(progname = nil, &block); end + def unknown(progname = nil, &block); end + def info? ; end + def debug? ; end + def warn? ; end + def error? ; end + def fatal? ; end + def level ; end + def progname ; end + def datetime_format ; end + def formatter ; end + def sev_threshold ; end + def level=(level); end + def progname=(progname); end + def datetime_format=(datetime_format); end + def formatter=(formatter); end + def sev_threshold=(sev_threshold); end + def close ; end + def add(severity, message = nil, progname = nil, &block); end + def <<(msg); end end end diff --git a/lib/rack/request.rb b/lib/rack/request.rb index 52ea652c..4f038384 100644 --- a/lib/rack/request.rb +++ b/lib/rack/request.rb @@ -148,7 +148,8 @@ module Rack # for form-data / param parsing. FORM_DATA_MEDIA_TYPES = [ 'application/x-www-form-urlencoded', - 'multipart/form-data' + 'multipart/form-data', + 'application/json' ] # The set of media-types. Requests that do not indicate @@ -366,7 +367,12 @@ module Rack end def parse_query(qs) - Utils.parse_nested_query(qs, '&') + case media_type + when 'application/json' + (qs && qs != '') ? ::Rack::Utils::OkJson.decode(qs) : {} + else + Utils.parse_nested_query(qs, '&') + end end def parse_multipart(env) diff --git a/lib/rack/session/abstract/id.rb b/lib/rack/session/abstract/id.rb index e9edeb7f..62bdb04b 100644 --- a/lib/rack/session/abstract/id.rb +++ b/lib/rack/session/abstract/id.rb @@ -310,7 +310,7 @@ module Rack end def force_options?(options) - options.values_at(:renew, :drop, :defer, :expire_after).any? + options.values_at(:max_age, :renew, :drop, :defer, :expire_after).any? end def security_matches?(env, options) @@ -347,6 +347,7 @@ module Rack cookie = Hash.new cookie[:value] = data cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after] + cookie[:expires] = Time.now + options[:max_age] if options[:max_age] set_cookie(env, headers, cookie.merge!(options)) end diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb index 53303995..42b2f5a0 100644 --- a/lib/rack/utils.rb +++ b/lib/rack/utils.rb @@ -178,12 +178,12 @@ module Rack when Hash value.map { |k, v| build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k)) - }.join("&") - when String + }.reject(&:empty?).join('&') + when nil + prefix + else raise ArgumentError, "value must be a Hash" if prefix.nil? "#{prefix}=#{escape(value)}" - else - prefix end end module_function :build_nested_query @@ -267,9 +267,9 @@ module Rack 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] - max_age = "; max-age=" + value[:max_age] if value[:max_age] + domain = "; domain=" + value[:domain] if value[:domain] + path = "; path=" + value[:path] if value[:path] + max_age = "; max-age=" + value[:max_age].to_s if value[:max_age] # There is an RFC mess in the area of date formatting for Cookies. Not # only are there contradicting RFCs and examples within RFC text, but # there are also numerous conflicting names of fields and partially diff --git a/test/multipart/ie11 b/test/multipart/ie11 deleted file mode 100644 index 6c3da47f..00000000 --- a/test/multipart/ie11 +++ /dev/null @@ -1,6 +0,0 @@ ---AaB03x
-Content-Disposition: form-data; name="files"; filename=""
-Content-Type: text/plain
-
-contents
---AaB03x--
diff --git a/test/spec_deflater.rb b/test/spec_deflater.rb index 6f5137ca..1e921eff 100644 --- a/test/spec_deflater.rb +++ b/test/spec_deflater.rb @@ -6,199 +6,334 @@ require 'rack/mock' require 'zlib' describe Rack::Deflater do - def deflater(app) - Rack::Lint.new Rack::Deflater.new(app) - end - def build_response(status, body, accept_encoding, headers = {}) - body = [body] if body.respond_to? :to_str + def build_response(status, body, accept_encoding, options = {}) + body = [body] if body.respond_to? :to_str app = lambda do |env| - res = [status, {}, body] - res[1]["Content-Type"] = "text/plain" unless res[0] == 304 + res = [status, options['response_headers'] || {}, body] + res[1]['Content-Type'] = 'text/plain' unless res[0] == 304 res end - request = Rack::MockRequest.env_for("", headers.merge("HTTP_ACCEPT_ENCODING" => accept_encoding)) - response = deflater(app).call(request) - return response - end + request = Rack::MockRequest.env_for('', (options['request_headers'] || {}).merge('HTTP_ACCEPT_ENCODING' => accept_encoding)) + deflater = Rack::Lint.new Rack::Deflater.new(app, options['deflater_options'] || {}) - def inflate(buf) - inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS) - inflater.inflate(buf) << inflater.finish + deflater.call(request) end - should "be able to deflate bodies that respond to each" do - body = Object.new - class << body; def each; yield("foo"); yield("bar"); end; end - - response = build_response(200, body, "deflate") - - response[0].should.equal(200) - response[1].should.equal({ - "Content-Encoding" => "deflate", - "Vary" => "Accept-Encoding", - "Content-Type" => "text/plain" - }) - buf = '' - response[2].each { |part| buf << part } - inflate(buf).should.equal("foobar") - end - - should "flush deflated chunks to the client as they become ready" do - body = Object.new - class << body; def each; yield("foo"); yield("bar"); end; end + ## + # Constructs response object and verifies if it yields right results + # + # [expected_status] expected response status, e.g. 200, 304 + # [expected_body] expected response body + # [accept_encoing] what Accept-Encoding header to send and expect, e.g. + # 'deflate' - accepts and expects deflate encoding in response + # { 'gzip' => nil } - accepts gzip but expects no encoding in response + # [options] hash of request options, i.e. + # 'app_status' - what status dummy app should return (may be changed by deflater at some point) + # 'app_body' - what body dummy app should return (may be changed by deflater at some point) + # 'request_headers' - extra reqest headers to be sent + # 'response_headers' - extra response headers to be returned + # 'deflater_options' - options passed to deflater middleware + # [block] useful for doing some extra verification + def verify(expected_status, expected_body, accept_encoding, options = {}, &block) + accept_encoding, expected_encoding = if accept_encoding.kind_of?(Hash) + [accept_encoding.keys.first, accept_encoding.values.first] + else + [accept_encoding, accept_encoding.dup] + end - response = build_response(200, body, "deflate") + # build response + status, headers, body = build_response( + options['app_status'] || expected_status, + options['app_body'] || expected_body, + accept_encoding, + options + ) + + # verify status + status.should.equal(expected_status) + + # verify body + unless options['skip_body_verify'] + body_text = '' + body.each { |part| body_text << part } + + deflated_body = case expected_encoding + when 'deflate' + inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS) + inflater.inflate(body_text) << inflater.finish + when 'gzip' + io = StringIO.new(body_text) + gz = Zlib::GzipReader.new(io) + tmp = gz.read + gz.close + tmp + else + body_text + end + + deflated_body.should.equal(expected_body) + end - response[0].should.equal(200) - response[1].should.equal({ - "Content-Encoding" => "deflate", - "Vary" => "Accept-Encoding", - "Content-Type" => "text/plain" - }) - buf = [] - inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS) - response[2].each { |part| buf << inflater.inflate(part) } - buf << inflater.finish - buf.delete_if { |part| part.empty? } - buf.join.should.equal("foobar") + # yield full response verification + yield(status, headers, body) if block_given? end - # TODO: This is really just a special case of the above... - should "be able to deflate String bodies" do - response = build_response(200, "Hello world!", "deflate") + should 'be able to deflate bodies that respond to each' do + app_body = Object.new + class << app_body; def each; yield('foo'); yield('bar'); end; end - response[0].should.equal(200) - response[1].should.equal({ - "Content-Encoding" => "deflate", - "Vary" => "Accept-Encoding", - "Content-Type" => "text/plain" - }) - buf = '' - response[2].each { |part| buf << part } - inflate(buf).should.equal("Hello world!") + verify(200, 'foobar', 'deflate', { 'app_body' => app_body }) do |status, headers, body| + headers.should.equal({ + 'Content-Encoding' => 'deflate', + 'Vary' => 'Accept-Encoding', + 'Content-Type' => 'text/plain' + }) + end end - should "be able to gzip bodies that respond to each" do - body = Object.new - class << body; def each; yield("foo"); yield("bar"); end; end + should 'flush deflated chunks to the client as they become ready' do + app_body = Object.new + class << app_body; def each; yield('foo'); yield('bar'); end; end - response = build_response(200, body, "gzip") + verify(200, app_body, 'deflate', { 'skip_body_verify' => true }) do |status, headers, body| + headers.should.equal({ + 'Content-Encoding' => 'deflate', + 'Vary' => 'Accept-Encoding', + 'Content-Type' => 'text/plain' + }) - response[0].should.equal(200) - response[1].should.equal({ - "Content-Encoding" => "gzip", - "Vary" => "Accept-Encoding", - "Content-Type" => "text/plain" - }) + buf = [] + inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS) + body.each { |part| buf << inflater.inflate(part) } + buf << inflater.finish - buf = '' - response[2].each { |part| buf << part } - io = StringIO.new(buf) - gz = Zlib::GzipReader.new(io) - gz.read.should.equal("foobar") - gz.close + buf.delete_if { |part| part.empty? }.join.should.equal('foobar') + end end - should "flush gzipped chunks to the client as they become ready" do - body = Object.new - class << body; def each; yield("foo"); yield("bar"); end; end + # TODO: This is really just a special case of the above... + should 'be able to deflate String bodies' do + verify(200, 'Hello world!', 'deflate') do |status, headers, body| + headers.should.equal({ + 'Content-Encoding' => 'deflate', + 'Vary' => 'Accept-Encoding', + 'Content-Type' => 'text/plain' + }) + end + end - response = build_response(200, body, "gzip") + should 'be able to gzip bodies that respond to each' do + app_body = Object.new + class << app_body; def each; yield('foo'); yield('bar'); end; end - response[0].should.equal(200) - response[1].should.equal({ - "Content-Encoding" => "gzip", - "Vary" => "Accept-Encoding", - "Content-Type" => "text/plain" - }) - buf = [] - inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 32) - response[2].each { |part| buf << inflater.inflate(part) } - buf << inflater.finish - buf.delete_if { |part| part.empty? } - buf.join.should.equal("foobar") + verify(200, 'foobar', 'gzip', { 'app_body' => app_body }) do |status, headers, body| + headers.should.equal({ + 'Content-Encoding' => 'gzip', + 'Vary' => 'Accept-Encoding', + 'Content-Type' => 'text/plain' + }) + end end - should "be able to fallback to no deflation" do - response = build_response(200, "Hello world!", "superzip") + should 'flush gzipped chunks to the client as they become ready' do + app_body = Object.new + class << app_body; def each; yield('foo'); yield('bar'); end; end + + verify(200, app_body, 'gzip', { 'skip_body_verify' => true }) do |status, headers, body| + headers.should.equal({ + 'Content-Encoding' => 'gzip', + 'Vary' => 'Accept-Encoding', + 'Content-Type' => 'text/plain' + }) + + buf = [] + inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 32) + body.each { |part| buf << inflater.inflate(part) } + buf << inflater.finish - response[0].should.equal(200) - response[1].should.equal({ "Vary" => "Accept-Encoding", "Content-Type" => "text/plain" }) - response[2].to_enum.to_a.should.equal(["Hello world!"]) + buf.delete_if { |part| part.empty? }.join.should.equal('foobar') + end end - should "be able to skip when there is no response entity body" do - response = build_response(304, [], "gzip") + should 'be able to fallback to no deflation' do + verify(200, 'Hello world!', 'superzip') do |status, headers, body| + headers.should.equal({ + 'Vary' => 'Accept-Encoding', + 'Content-Type' => 'text/plain' + }) + end + end - response[0].should.equal(304) - response[1].should.equal({}) - response[2].to_enum.to_a.should.equal([]) + should 'be able to skip when there is no response entity body' do + verify(304, '', { 'gzip' => nil }, { 'app_body' => [] }) do |status, headers, body| + headers.should.equal({}) + end end - should "handle the lack of an acceptable encoding" do - response1 = build_response(200, "Hello world!", "identity;q=0", "PATH_INFO" => "/") - response1[0].should.equal(406) - response1[1].should.equal({"Content-Type" => "text/plain", "Content-Length" => "71"}) - response1[2].to_enum.to_a.should.equal(["An acceptable encoding for the requested resource / could not be found."]) + should 'handle the lack of an acceptable encoding' do + app_body = 'Hello world!' + not_found_body1 = 'An acceptable encoding for the requested resource / could not be found.' + not_found_body2 = 'An acceptable encoding for the requested resource /foo/bar could not be found.' + options1 = { + 'app_status' => 200, + 'app_body' => app_body, + 'request_headers' => { + 'PATH_INFO' => '/' + } + } + options2 = { + 'app_status' => 200, + 'app_body' => app_body, + 'request_headers' => { + 'PATH_INFO' => '/foo/bar' + } + } + + verify(406, not_found_body1, 'identity;q=0', options1) do |status, headers, body| + headers.should.equal({ + 'Content-Type' => 'text/plain', + 'Content-Length' => not_found_body1.length.to_s + }) + end - response2 = build_response(200, "Hello world!", "identity;q=0", "SCRIPT_NAME" => "/foo", "PATH_INFO" => "/bar") - response2[0].should.equal(406) - response2[1].should.equal({"Content-Type" => "text/plain", "Content-Length" => "78"}) - response2[2].to_enum.to_a.should.equal(["An acceptable encoding for the requested resource /foo/bar could not be found."]) + verify(406, not_found_body2, 'identity;q=0', options2) do |status, headers, body| + headers.should.equal({ + 'Content-Type' => 'text/plain', + 'Content-Length' => not_found_body2.length.to_s + }) + end end - should "handle gzip response with Last-Modified header" do + should 'handle gzip response with Last-Modified header' do last_modified = Time.now.httpdate + options = { + 'response_headers' => { + 'Content-Type' => 'text/plain', + 'Last-Modified' => last_modified + } + } + + verify(200, 'Hello World!', 'gzip', options) do |status, headers, body| + headers.should.equal({ + 'Content-Encoding' => 'gzip', + 'Vary' => 'Accept-Encoding', + 'Last-Modified' => last_modified, + 'Content-Type' => 'text/plain' + }) + end + end - app = lambda { |env| [200, { "Content-Type" => "text/plain", "Last-Modified" => last_modified }, ["Hello World!"]] } - request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip") - response = deflater(app).call(request) + should 'do nothing when no-transform Cache-Control directive present' do + options = { + 'response_headers' => { + 'Content-Type' => 'text/plain', + 'Cache-Control' => 'no-transform' + } + } + verify(200, 'Hello World!', { 'gzip' => nil }, options) do |status, headers, body| + headers.should.not.include 'Content-Encoding' + end + end + + should 'do nothing when Content-Encoding already present' do + options = { + 'response_headers' => { + 'Content-Type' => 'text/plain', + 'Content-Encoding' => 'gzip' + } + } + verify(200, 'Hello World!', { 'gzip' => nil }, options) + end - response[0].should.equal(200) - response[1].should.equal({ - "Content-Encoding" => "gzip", - "Vary" => "Accept-Encoding", - "Last-Modified" => last_modified, - "Content-Type" => "text/plain" - }) + should 'deflate when Content-Encoding is identity' do + options = { + 'response_headers' => { + 'Content-Type' => 'text/plain', + 'Content-Encoding' => 'identity' + } + } + verify(200, 'Hello World!', 'deflate', options) + end - buf = '' - response[2].each { |part| buf << part } - io = StringIO.new(buf) - gz = Zlib::GzipReader.new(io) - gz.read.should.equal("Hello World!") - gz.close + should "deflate if content-type matches :include" do + options = { + 'response_headers' => { + 'Content-Type' => 'text/plain' + }, + 'deflater_options' => { + :include => %w(text/plain) + } + } + verify(200, 'Hello World!', 'gzip', options) end - should "do nothing when no-transform Cache-Control directive present" do - app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Cache-Control' => 'no-transform'}, ['Hello World!']] } - request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip") - response = deflater(app).call(request) + should "deflate if content-type is included it :include" do + options = { + 'response_headers' => { + 'Content-Type' => 'text/plain; charset=us-ascii' + }, + 'deflater_options' => { + :include => %w(text/plain) + } + } + verify(200, 'Hello World!', 'gzip', options) + end - response[0].should.equal(200) - response[1].should.not.include "Content-Encoding" - response[2].to_enum.to_a.join.should.equal("Hello World!") + should "not deflate if content-type is not set but given in :include" do + options = { + 'deflater_options' => { + :include => %w(text/plain) + } + } + verify(304, 'Hello World!', { 'gzip' => nil }, options) end - should "do nothing when Content-Encoding already present" do - app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Content-Encoding' => 'gzip'}, ['Hello World!']] } - request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip") - response = deflater(app).call(request) + should "not deflate if content-type do not match :include" do + options = { + 'response_headers' => { + 'Content-Type' => 'text/plain' + }, + 'deflater_options' => { + :include => %w(text/json) + } + } + verify(200, 'Hello World!', { 'gzip' => nil }, options) + end - response[0].should.equal(200) - response[2].to_enum.to_a.join.should.equal("Hello World!") + should "deflate response if :if lambda evaluates to true" do + options = { + 'deflater_options' => { + :if => lambda { |env, status, headers, body| true } + } + } + verify(200, 'Hello World!', 'deflate', options) end - should "deflate when Content-Encoding is identity" do - app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Content-Encoding' => 'identity'}, ['Hello World!']] } - request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "deflate") - response = deflater(app).call(request) + should "not deflate if :if lambda evaluates to false" do + options = { + 'deflater_options' => { + :if => lambda { |env, status, headers, body| false } + } + } + verify(200, 'Hello World!', { 'gzip' => nil }, options) + end - response[0].should.equal(200) - buf = '' - response[2].each { |part| buf << part } - inflate(buf).should.equal("Hello World!") + should "check for Content-Length via :if" do + body = 'Hello World!' + body_len = body.length + options = { + 'response_headers' => { + 'Content-Length' => body_len.to_s + }, + 'deflater_options' => { + :if => lambda { |env, status, headers, body| + headers['Content-Length'].to_i >= body_len + } + } + } + + verify(200, body, 'gzip', options) end end diff --git a/test/spec_etag.rb b/test/spec_etag.rb index b8b8b637..c075d9d0 100644 --- a/test/spec_etag.rb +++ b/test/spec_etag.rb @@ -21,13 +21,13 @@ describe Rack::ETag do should "set ETag if none is set if status is 200" do app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] } response = etag(app).call(request) - response[1]['ETag'].should.equal "\"65a8e27d8879283831b664bd8b7f0ad4\"" + response[1]['ETag'].should.equal "W/\"65a8e27d8879283831b664bd8b7f0ad4\"" end should "set ETag if none is set if status is 201" do app = lambda { |env| [201, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] } response = etag(app).call(request) - response[1]['ETag'].should.equal "\"65a8e27d8879283831b664bd8b7f0ad4\"" + response[1]['ETag'].should.equal "W/\"65a8e27d8879283831b664bd8b7f0ad4\"" end should "set Cache-Control to 'max-age=0, private, must-revalidate' (default) if none is set" do diff --git a/test/spec_multipart.rb b/test/spec_multipart.rb index 959a5cff..2acb6e0d 100644 --- a/test/spec_multipart.rb +++ b/test/spec_multipart.rb @@ -230,15 +230,12 @@ describe Rack::Multipart do params["files"][:tempfile].read.should.equal "contents" end - # n.b. this case used to be "do not include", but because ie11 now does this - # for selected files, that can no longer be done. It was decided that not - # losing data is better, and no browser is documented with this behavior. - should "include file params if no file was selected" do + should "not include file params if no file was selected" do env = Rack::MockRequest.env_for("/", multipart_fixture(:none)) params = Rack::Multipart.parse_multipart(env) params["submit-name"].should.equal "Larry" - params["files"].should.not.equal nil - params.keys.should.include "files" + params["files"].should.equal nil + params.keys.should.not.include "files" end should "parse multipart/mixed" do @@ -270,19 +267,6 @@ describe Rack::Multipart do params["files"][:tempfile].read.should.equal "contents" end - should "parse IE11 multipart upload" do - env = Rack::MockRequest.env_for("/", multipart_fixture(:ie11)) - params = Rack::Multipart.parse_multipart(env) - params["files"][:type].should.equal "text/plain" - params["files"][:filename].should.equal "files" - params["files"][:head].should.equal "Content-Disposition: form-data; " + - "name=\"files\"; " + - 'filename=""' + - "\r\nContent-Type: text/plain\r\n" - params["files"][:name].should.equal "files" - params["files"][:tempfile].read.should.equal "contents" - end - should "parse filename and modification param" do env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_and_modification_param)) params = Rack::Multipart.parse_multipart(env) diff --git a/test/spec_request.rb b/test/spec_request.rb index 8a2b4760..c4a7400a 100644 --- a/test/spec_request.rb +++ b/test/spec_request.rb @@ -267,6 +267,16 @@ describe Rack::Request do input.read.should.equal "foo=bar&quux=bla" end + should "accept application/json content type" do + input = StringIO.new('{"foo":"bar","quxx":"bla"}') + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", + "CONTENT_TYPE" => 'application/json', + :input => input) + req.params.should.equal "foo" => "bar", "quxx" => "bla" + input.read.should.equal '{"foo":"bar","quxx":"bla"}' + end + should "clean up Safari's ajax POST body" do req = Rack::Request.new \ Rack::MockRequest.env_for("/", diff --git a/test/spec_utils.rb b/test/spec_utils.rb index 4b989db7..06ed5636 100644 --- a/test/spec_utils.rb +++ b/test/spec_utils.rb @@ -248,6 +248,8 @@ describe Rack::Utils do Rack::Utils.build_nested_query("foo" => "1", "bar" => "2"). should.be equal_query_to("foo=1&bar=2") + Rack::Utils.build_nested_query("foo" => 1, "bar" => 2). + should.be equal_query_to("foo=1&bar=2") Rack::Utils.build_nested_query("my weird field" => "q1!2\"'w$5&7/z8)?"). should.be equal_query_to("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F") @@ -257,6 +259,14 @@ describe Rack::Utils do should.equal "foo[]=" Rack::Utils.build_nested_query("foo" => ["bar"]). should.equal "foo[]=bar" + Rack::Utils.build_nested_query('foo' => []). + should.equal '' + Rack::Utils.build_nested_query('foo' => {}). + should.equal '' + Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => []). + should.equal 'foo=bar' + Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => {}). + should.equal 'foo=bar' # The ordering of the output query string is unpredictable with 1.8's # unordered hash. Test that build_nested_query performs the inverse |