require 'stringio' require 'cgi' require 'rack/request' require 'rack/mock' require 'securerandom' describe Rack::Request do should "wrap the rack variables" do req = Rack::Request.new(Rack::MockRequest.env_for("http://example.com:8080/")) req.body.should.respond_to? :gets req.scheme.should.equal "http" req.request_method.should.equal "GET" req.should.be.get req.should.not.be.post req.should.not.be.put req.should.not.be.delete req.should.not.be.head req.should.not.be.patch req.script_name.should.equal "" req.path_info.should.equal "/" req.query_string.should.equal "" req.host.should.equal "example.com" req.port.should.equal 8080 req.content_length.should.equal "0" req.content_type.should.be.nil end should "figure out the correct host" do req = Rack::Request.new \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org") req.host.should.equal "www2.example.org" req = Rack::Request.new \ Rack::MockRequest.env_for("/", "SERVER_NAME" => "example.org", "SERVER_PORT" => "9292") req.host.should.equal "example.org" req = Rack::Request.new \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org:9292") req.host.should.equal "example.org" env = Rack::MockRequest.env_for("/", "SERVER_ADDR" => "192.168.1.1", "SERVER_PORT" => "9292") env.delete("SERVER_NAME") req = Rack::Request.new(env) req.host.should.equal "192.168.1.1" env = Rack::MockRequest.env_for("/") env.delete("SERVER_NAME") req = Rack::Request.new(env) req.host.should.equal "" end should "figure out the correct port" do req = Rack::Request.new \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org") req.port.should.equal 80 req = Rack::Request.new \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org:81") req.port.should.equal 81 req = Rack::Request.new \ Rack::MockRequest.env_for("/", "SERVER_NAME" => "example.org", "SERVER_PORT" => "9292") req.port.should.equal 9292 req = Rack::Request.new \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org:9292") req.port.should.equal 9292 req = Rack::Request.new \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org") req.port.should.equal 80 req = Rack::Request.new \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "HTTP_X_FORWARDED_SSL" => "on") req.port.should.equal 443 req = Rack::Request.new \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "HTTP_X_FORWARDED_PROTO" => "https") req.port.should.equal 443 req = Rack::Request.new \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "HTTP_X_FORWARDED_PORT" => "9393") req.port.should.equal 9393 req = Rack::Request.new \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org:9393", "SERVER_PORT" => "80") req.port.should.equal 9393 req = Rack::Request.new \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "SERVER_PORT" => "9393") req.port.should.equal 80 req = Rack::Request.new \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost", "HTTP_X_FORWARDED_PROTO" => "https", "SERVER_PORT" => "80") req.port.should.equal 443 req = Rack::Request.new \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost", "HTTP_X_FORWARDED_PROTO" => "https,https", "SERVER_PORT" => "80") req.port.should.equal 443 end should "figure out the correct host with port" do req = Rack::Request.new \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org") req.host_with_port.should.equal "www2.example.org" req = Rack::Request.new \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81") req.host_with_port.should.equal "localhost:81" req = Rack::Request.new \ Rack::MockRequest.env_for("/", "SERVER_NAME" => "example.org", "SERVER_PORT" => "9292") req.host_with_port.should.equal "example.org:9292" req = Rack::Request.new \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org:9292") req.host_with_port.should.equal "example.org:9292" req = Rack::Request.new \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "SERVER_PORT" => "9393") req.host_with_port.should.equal "example.org" end should "parse the query string" do req = Rack::Request.new(Rack::MockRequest.env_for("/?foo=bar&quux=bla")) req.query_string.should.equal "foo=bar&quux=bla" req.GET.should.equal "foo" => "bar", "quux" => "bla" req.POST.should.be.empty req.params.should.equal "foo" => "bar", "quux" => "bla" end should "not truncate query strings containing semi-colons #543" do req = Rack::Request.new(Rack::MockRequest.env_for("/?foo=bar&quux=b;la")) req.query_string.should.equal "foo=bar&quux=b;la" req.GET.should.equal "foo" => "bar", "quux" => "b;la" req.POST.should.be.empty req.params.should.equal "foo" => "bar", "quux" => "b;la" end should "limit the keys from the GET query string" do env = Rack::MockRequest.env_for("/?foo=bar") old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 1 begin req = Rack::Request.new(env) lambda { req.GET }.should.raise(RangeError) ensure Rack::Utils.key_space_limit = old end end should "limit the key size per nested params hash" do nested_query = Rack::MockRequest.env_for("/?foo%5Bbar%5D%5Bbaz%5D%5Bqux%5D=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', :input => "foo=bar&quux=bla" ) req = Rack::Request.new mr req.params req.GET.should.equal "foo" => "quux" req.POST.should.equal "foo" => "bar", "quux" => "bla" req.params.should.equal req.GET.merge(req.POST) end should "raise if input params has invalid %-encoding" do mr = Rack::MockRequest.env_for("/?foo=quux", "REQUEST_METHOD" => 'POST', :input => "a%=1" ) req = Rack::Request.new mr lambda { req.POST }. should.raise(Rack::Utils::InvalidParameterError). message.should.equal "invalid %-encoding (a%)" end should "raise if rack.input is missing" do req = Rack::Request.new({}) lambda { req.POST }.should.raise(RuntimeError) end should "parse POST data when method is POST and no Content-Type given" do req = Rack::Request.new \ Rack::MockRequest.env_for("/?foo=quux", "REQUEST_METHOD" => 'POST', :input => "foo=bar&quux=bla") req.content_type.should.be.nil req.media_type.should.be.nil req.query_string.should.equal "foo=quux" req.GET.should.equal "foo" => "quux" req.POST.should.equal "foo" => "bar", "quux" => "bla" req.params.should.equal "foo" => "bar", "quux" => "bla" end should "limit the keys from the POST form data" do env = Rack::MockRequest.env_for("", "REQUEST_METHOD" => 'POST', :input => "foo=bar&quux=bla") old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 1 begin req = Rack::Request.new(env) lambda { req.POST }.should.raise(RangeError) ensure Rack::Utils.key_space_limit = old end end should "parse POST data with explicit content type regardless of method" do req = Rack::Request.new \ Rack::MockRequest.env_for("/", "CONTENT_TYPE" => 'application/x-www-form-urlencoded;foo=bar', :input => "foo=bar&quux=bla") req.content_type.should.equal 'application/x-www-form-urlencoded;foo=bar' req.media_type.should.equal 'application/x-www-form-urlencoded' req.media_type_params['foo'].should.equal 'bar' req.POST.should.equal "foo" => "bar", "quux" => "bla" req.params.should.equal "foo" => "bar", "quux" => "bla" end should "not parse POST data when media type is not form-data" do req = Rack::Request.new \ Rack::MockRequest.env_for("/?foo=quux", "REQUEST_METHOD" => 'POST', "CONTENT_TYPE" => 'text/plain;charset=utf-8', :input => "foo=bar&quux=bla") req.content_type.should.equal 'text/plain;charset=utf-8' req.media_type.should.equal 'text/plain' req.media_type_params['charset'].should.equal 'utf-8' req.POST.should.be.empty req.params.should.equal "foo" => "quux" req.body.read.should.equal "foo=bar&quux=bla" end should "parse POST data on PUT when media type is form-data" do req = Rack::Request.new \ Rack::MockRequest.env_for("/?foo=quux", "REQUEST_METHOD" => 'PUT', "CONTENT_TYPE" => 'application/x-www-form-urlencoded', :input => "foo=bar&quux=bla") req.POST.should.equal "foo" => "bar", "quux" => "bla" req.body.read.should.equal "foo=bar&quux=bla" end should "rewind input after parsing POST data" do input = StringIO.new("foo=bar&quux=bla") req = Rack::Request.new \ Rack::MockRequest.env_for("/", "CONTENT_TYPE" => 'application/x-www-form-urlencoded;foo=bar', :input => input) req.params.should.equal "foo" => "bar", "quux" => "bla" input.read.should.equal "foo=bar&quux=bla" end should "clean up Safari's ajax POST body" do req = Rack::Request.new \ Rack::MockRequest.env_for("/", 'REQUEST_METHOD' => 'POST', :input => "foo=bar&quux=bla\0") req.POST.should.equal "foo" => "bar", "quux" => "bla" end should "get value by key from params with #[]" do req = Rack::Request.new \ Rack::MockRequest.env_for("?foo=quux") req['foo'].should.equal 'quux' req[:foo].should.equal 'quux' end should "set value to key on params with #[]=" do req = Rack::Request.new \ Rack::MockRequest.env_for("?foo=duh") req['foo'].should.equal 'duh' req[:foo].should.equal 'duh' req.params.should.equal 'foo' => 'duh' req['foo'] = 'bar' req.params.should.equal 'foo' => 'bar' req['foo'].should.equal 'bar' req[:foo].should.equal 'bar' req[:foo] = 'jaz' req.params.should.equal 'foo' => 'jaz' req['foo'].should.equal 'jaz' req[:foo].should.equal 'jaz' end should "return values for the keys in the order given from values_at" do req = Rack::Request.new \ Rack::MockRequest.env_for("?foo=baz&wun=der&bar=ful") req.values_at('foo').should.equal ['baz'] req.values_at('foo', 'wun').should.equal ['baz', 'der'] req.values_at('bar', 'foo', 'wun').should.equal ['ful', 'baz', 'der'] end should "extract referrer correctly" do req = Rack::Request.new \ Rack::MockRequest.env_for("/", "HTTP_REFERER" => "/some/path") req.referer.should.equal "/some/path" req = Rack::Request.new \ Rack::MockRequest.env_for("/") req.referer.should.equal nil end should "extract user agent correctly" do req = Rack::Request.new \ Rack::MockRequest.env_for("/", "HTTP_USER_AGENT" => "Mozilla/4.0 (compatible)") req.user_agent.should.equal "Mozilla/4.0 (compatible)" req = Rack::Request.new \ Rack::MockRequest.env_for("/") req.user_agent.should.equal nil end should "treat missing content type as nil" do req = Rack::Request.new \ Rack::MockRequest.env_for("/") req.content_type.should.equal nil end should "treat empty content type as nil" do req = Rack::Request.new \ Rack::MockRequest.env_for("/", "CONTENT_TYPE" => "") req.content_type.should.equal nil end should "return nil media type for empty content type" do req = Rack::Request.new \ Rack::MockRequest.env_for("/", "CONTENT_TYPE" => "") req.media_type.should.equal nil end should "cache, but invalidates the cache" do req = Rack::Request.new \ Rack::MockRequest.env_for("/?foo=quux", "CONTENT_TYPE" => "application/x-www-form-urlencoded", :input => "foo=bar&quux=bla") req.GET.should.equal "foo" => "quux" req.GET.should.equal "foo" => "quux" req.env["QUERY_STRING"] = "bla=foo" req.GET.should.equal "bla" => "foo" req.GET.should.equal "bla" => "foo" req.POST.should.equal "foo" => "bar", "quux" => "bla" req.POST.should.equal "foo" => "bar", "quux" => "bla" req.env["rack.input"] = StringIO.new("foo=bla&quux=bar") req.POST.should.equal "foo" => "bla", "quux" => "bar" req.POST.should.equal "foo" => "bla", "quux" => "bar" end should "figure out if called via XHR" do req = Rack::Request.new(Rack::MockRequest.env_for("")) req.should.not.be.xhr req = Rack::Request.new \ Rack::MockRequest.env_for("", "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest") req.should.be.xhr end should "ssl detection" do request = Rack::Request.new(Rack::MockRequest.env_for("/")) request.scheme.should.equal "http" request.should.not.be.ssl? request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTPS' => 'on')) request.scheme.should.equal "https" request.should.be.ssl? request = Rack::Request.new(Rack::MockRequest.env_for("/", 'rack.url_scheme' => 'https')) request.scheme.should.equal "https" request.should.be.ssl? request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_HOST' => 'www.example.org:8080')) request.scheme.should.equal "http" request.should.not.be.ssl? request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_HOST' => 'www.example.org:8443', 'HTTPS' => 'on')) request.scheme.should.equal "https" request.should.be.ssl? request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_HOST' => 'www.example.org:8443', 'HTTP_X_FORWARDED_SSL' => 'on')) request.scheme.should.equal "https" request.should.be.ssl? request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_SCHEME' => 'https')) request.scheme.should.equal "https" request.should.be.ssl? request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_PROTO' => 'https')) request.scheme.should.equal "https" request.should.be.ssl? request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_PROTO' => 'https, http, http')) request.scheme.should.equal "https" request.should.be.ssl? end should "parse cookies" do req = Rack::Request.new \ Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;quux=h&m") req.cookies.should.equal "foo" => "bar", "quux" => "h&m" req.cookies.should.equal "foo" => "bar", "quux" => "h&m" req.env.delete("HTTP_COOKIE") req.cookies.should.equal({}) end should "always return the same hash object" do req = Rack::Request.new \ Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;quux=h&m") hash = req.cookies req.env.delete("HTTP_COOKIE") req.cookies.should.equal(hash) req.env["HTTP_COOKIE"] = "zoo=m" req.cookies.should.equal(hash) end should "modify the cookies hash in place" do req = Rack::Request.new(Rack::MockRequest.env_for("")) req.cookies.should.equal({}) req.cookies['foo'] = 'bar' req.cookies.should.equal 'foo' => 'bar' end should "not modify the params hash in place" do e = Rack::MockRequest.env_for("") req1 = Rack::Request.new(e) req1.params.should.equal({}) req1.params['foo'] = 'bar' req1.params.should.equal 'foo' => 'bar' req2 = Rack::Request.new(e) req2.params.should.equal({}) end should "modify params hash if param is in GET" do e = Rack::MockRequest.env_for("?foo=duh") req1 = Rack::Request.new(e) req1.params.should.equal 'foo' => 'duh' req1.update_param 'foo', 'bar' req1.params.should.equal 'foo' => 'bar' req2 = Rack::Request.new(e) req2.params.should.equal 'foo' => 'bar' end should "modify params hash if param is in POST" do e = Rack::MockRequest.env_for("", "REQUEST_METHOD" => 'POST', :input => 'foo=duh') req1 = Rack::Request.new(e) req1.params.should.equal 'foo' => 'duh' req1.update_param 'foo', 'bar' req1.params.should.equal 'foo' => 'bar' req2 = Rack::Request.new(e) req2.params.should.equal 'foo' => 'bar' end should "modify params hash, even if param didn't exist before" do e = Rack::MockRequest.env_for("") req1 = Rack::Request.new(e) req1.params.should.equal({}) req1.update_param 'foo', 'bar' req1.params.should.equal 'foo' => 'bar' req2 = Rack::Request.new(e) req2.params.should.equal 'foo' => 'bar' end should "modify params hash by changing only GET" do e = Rack::MockRequest.env_for("?foo=duhget") req = Rack::Request.new(e) req.GET.should.equal 'foo' => 'duhget' req.POST.should.equal({}) req.update_param 'foo', 'bar' req.GET.should.equal 'foo' => 'bar' req.POST.should.equal({}) end should "modify params hash by changing only POST" do e = Rack::MockRequest.env_for("", "REQUEST_METHOD" => 'POST', :input => "foo=duhpost") req = Rack::Request.new(e) req.GET.should.equal({}) req.POST.should.equal 'foo' => 'duhpost' req.update_param 'foo', 'bar' req.GET.should.equal({}) req.POST.should.equal 'foo' => 'bar' end should "modify params hash, even if param is defined in both POST and GET" do e = Rack::MockRequest.env_for("?foo=duhget", "REQUEST_METHOD" => 'POST', :input => "foo=duhpost") req1 = Rack::Request.new(e) req1.GET.should.equal 'foo' => 'duhget' req1.POST.should.equal 'foo' => 'duhpost' req1.params.should.equal 'foo' => 'duhpost' req1.update_param 'foo', 'bar' req1.GET.should.equal 'foo' => 'bar' req1.POST.should.equal 'foo' => 'bar' req1.params.should.equal 'foo' => 'bar' req2 = Rack::Request.new(e) req2.GET.should.equal 'foo' => 'bar' req2.POST.should.equal 'foo' => 'bar' req2.params.should.equal 'foo' => 'bar' req2.params.should.equal 'foo' => 'bar' end should "allow deleting from params hash if param is in GET" do e = Rack::MockRequest.env_for("?foo=bar") req1 = Rack::Request.new(e) req1.params.should.equal 'foo' => 'bar' req1.delete_param('foo').should.equal 'bar' req1.params.should.equal({}) req2 = Rack::Request.new(e) req2.params.should.equal({}) end should "allow deleting from params hash if param is in POST" do e = Rack::MockRequest.env_for("", "REQUEST_METHOD" => 'POST', :input => 'foo=bar') req1 = Rack::Request.new(e) req1.params.should.equal 'foo' => 'bar' req1.delete_param('foo').should.equal 'bar' req1.params.should.equal({}) req2 = Rack::Request.new(e) req2.params.should.equal({}) end should "pass through non-uri escaped cookies as-is" do req = Rack::Request.new Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=%") req.cookies["foo"].should == "%" end should "parse cookies according to RFC 2109" do req = Rack::Request.new \ Rack::MockRequest.env_for('', 'HTTP_COOKIE' => 'foo=bar;foo=car') req.cookies.should.equal 'foo' => 'bar' end should 'parse cookies with quotes' do req = Rack::Request.new Rack::MockRequest.env_for('', { 'HTTP_COOKIE' => '$Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"; Part_Number="Rocket_Launcher_0001"; $Path="/acme"' }) req.cookies.should.equal({ '$Version' => '"1"', 'Customer' => '"WILE_E_COYOTE"', '$Path' => '"/acme"', 'Part_Number' => '"Rocket_Launcher_0001"', }) end should "provide setters" do req = Rack::Request.new(e=Rack::MockRequest.env_for("")) req.script_name.should.equal "" req.script_name = "/foo" req.script_name.should.equal "/foo" e["SCRIPT_NAME"].should.equal "/foo" req.path_info.should.equal "/" req.path_info = "/foo" req.path_info.should.equal "/foo" e["PATH_INFO"].should.equal "/foo" end should "provide the original env" do req = Rack::Request.new(e = Rack::MockRequest.env_for("")) req.env.should == e end should "restore the base URL" do Rack::Request.new(Rack::MockRequest.env_for("")).base_url. should.equal "http://example.org" Rack::Request.new(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).base_url. should.equal "http://example.org" end should "restore the URL" do Rack::Request.new(Rack::MockRequest.env_for("")).url. should.equal "http://example.org/" Rack::Request.new(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).url. should.equal "http://example.org/foo/" Rack::Request.new(Rack::MockRequest.env_for("/foo")).url. should.equal "http://example.org/foo" Rack::Request.new(Rack::MockRequest.env_for("?foo")).url. should.equal "http://example.org/?foo" Rack::Request.new(Rack::MockRequest.env_for("http://example.org:8080/")).url. should.equal "http://example.org:8080/" Rack::Request.new(Rack::MockRequest.env_for("https://example.org/")).url. should.equal "https://example.org/" Rack::Request.new(Rack::MockRequest.env_for("coffee://example.org/")).url. should.equal "coffee://example.org/" Rack::Request.new(Rack::MockRequest.env_for("coffee://example.org:443/")).url. should.equal "coffee://example.org:443/" Rack::Request.new(Rack::MockRequest.env_for("https://example.com:8080/foo?foo")).url. should.equal "https://example.com:8080/foo?foo" end should "restore the full path" do Rack::Request.new(Rack::MockRequest.env_for("")).fullpath. should.equal "/" Rack::Request.new(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).fullpath. should.equal "/foo/" Rack::Request.new(Rack::MockRequest.env_for("/foo")).fullpath. should.equal "/foo" Rack::Request.new(Rack::MockRequest.env_for("?foo")).fullpath. should.equal "/?foo" Rack::Request.new(Rack::MockRequest.env_for("http://example.org:8080/")).fullpath. should.equal "/" Rack::Request.new(Rack::MockRequest.env_for("https://example.org/")).fullpath. should.equal "/" Rack::Request.new(Rack::MockRequest.env_for("https://example.com:8080/foo?foo")).fullpath. should.equal "/foo?foo" end should "handle multiple media type parameters" do req = Rack::Request.new \ Rack::MockRequest.env_for("/", "CONTENT_TYPE" => 'text/plain; foo=BAR,baz=bizzle dizzle;BLING=bam;blong="boo";zump="zoo\"o";weird=lol"') req.should.not.be.form_data req.media_type_params.should.include 'foo' req.media_type_params['foo'].should.equal 'BAR' req.media_type_params.should.include 'baz' req.media_type_params['baz'].should.equal 'bizzle dizzle' req.media_type_params.should.not.include 'BLING' req.media_type_params.should.include 'bling' req.media_type_params['bling'].should.equal 'bam' req.media_type_params['blong'].should.equal 'boo' req.media_type_params['zump'].should.equal 'zoo\"o' req.media_type_params['weird'].should.equal 'lol"' end should "parse with junk before boundry" do # Adapted from RFC 1867. input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) req.POST.should.include "fileupload" req.POST.should.include "reply" req.should.be.form_data req.content_length.should.equal input.size req.media_type.should.equal 'multipart/form-data' req.media_type_params.should.include 'boundary' req.media_type_params['boundary'].should.equal 'AaB03x' req.POST["reply"].should.equal "yes" f = req.POST["fileupload"] f.should.be.kind_of Hash f[:type].should.equal "image/jpeg" f[:filename].should.equal "dj.jpg" f.should.include :tempfile f[:tempfile].size.should.equal 76 end should "not infinite loop with a malformed HTTP request" do # Adapted from RFC 1867. input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) lambda{req.POST}.should.raise(EOFError) end should "parse multipart form data" do # Adapted from RFC 1867. input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) req.POST.should.include "fileupload" req.POST.should.include "reply" req.should.be.form_data req.content_length.should.equal input.size req.media_type.should.equal 'multipart/form-data' req.media_type_params.should.include 'boundary' req.media_type_params['boundary'].should.equal 'AaB03x' req.POST["reply"].should.equal "yes" f = req.POST["fileupload"] f.should.be.kind_of Hash f[:type].should.equal "image/jpeg" f[:filename].should.equal "dj.jpg" f.should.include :tempfile f[:tempfile].size.should.equal 76 end should "MultipartPartLimitError when request has too many multipart parts if limit set" do begin data = 10000.times.map { "--AaB03x\r\nContent-Type: text/plain\r\nContent-Disposition: attachment; name=#{SecureRandom.hex(10)}; filename=#{SecureRandom.hex(10)}\r\n\r\ncontents\r\n" }.join("\r\n") data += "--AaB03x--\r" options = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", "CONTENT_LENGTH" => data.length.to_s, :input => StringIO.new(data) } request = Rack::Request.new Rack::MockRequest.env_for("/", options) lambda { request.POST }.should.raise(Rack::Multipart::MultipartPartLimitError) end end should "parse big multipart form data" do input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) req.POST["huge"][:tempfile].size.should.equal 32768 req.POST["mean"][:tempfile].size.should.equal 10 req.POST["mean"][:tempfile].read.should.equal "--AaB03xha" end should "record tempfiles from multipart form data in env[rack.tempfiles]" do input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) req = Rack::Request.new(env) req.params env['rack.tempfiles'].size.should.equal(2) end should "detect invalid multipart form data" do input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) lambda { req.POST }.should.raise(EOFError) input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) lambda { req.POST }.should.raise(EOFError) input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) lambda { req.POST }.should.raise(EOFError) end should "consistently raise EOFError on bad multipart form data" do input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) lambda { req.POST }.should.raise(EOFError) lambda { req.POST }.should.raise(EOFError) end should "correctly parse the part name from Content-Id header" do input = <\r Content-Transfer-Encoding: 7bit\r \r foo\r --AaB03x--\r EOF req = Rack::Request.new Rack::MockRequest.env_for("/", "CONTENT_TYPE" => "multipart/related, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) req.params.keys.should.equal [""] end should "not try to interpret binary as utf8" do if /regexp/.respond_to?(:kcode) # < 1.9 begin original_kcode = $KCODE $KCODE='UTF8' input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) lambda{req.POST}.should.not.raise(EOFError) req.POST["fileupload"][:tempfile].size.should.equal 4 ensure $KCODE = original_kcode end else # >= 1.9 input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) lambda{req.POST}.should.not.raise(EOFError) req.POST["fileupload"][:tempfile].size.should.equal 4 end end should "work around buggy 1.8.* Tempfile equality" do input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => rack_input) lambda{ req.POST }.should.not.raise lambda{ req.POST }.should.not.raise("input re-processed!") end should "use form_hash when form_input is a Tempfile" do input = "{foo: 'bar'}" rack_input = Tempfile.new("rackspec") rack_input.write(input) rack_input.rewind req = Rack::Request.new Rack::MockRequest.env_for("/", "rack.request.form_hash" => {'foo' => 'bar'}, "rack.request.form_input" => rack_input, :input => rack_input) req.POST.should.equal(req.env['rack.request.form_hash']) end should "conform to the Rack spec" do app = lambda { |env| content = Rack::Request.new(env).POST["file"].inspect size = content.respond_to?(:bytesize) ? content.bytesize : content.size [200, {"Content-Type" => "text/html", "Content-Length" => size.to_s}, [content]] } input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size.to_s, "rack.input" => StringIO.new(input) res.should.be.ok end should "parse Accept-Encoding correctly" do parser = lambda do |x| Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => x)).accept_encoding end parser.call(nil).should.equal([]) parser.call("compress, gzip").should.equal([["compress", 1.0], ["gzip", 1.0]]) parser.call("").should.equal([]) parser.call("*").should.equal([["*", 1.0]]) parser.call("compress;q=0.5, gzip;q=1.0").should.equal([["compress", 0.5], ["gzip", 1.0]]) parser.call("gzip;q=1.0, identity; q=0.5, *;q=0").should.equal([["gzip", 1.0], ["identity", 0.5], ["*", 0] ]) parser.call("gzip ; q=0.9").should.equal([["gzip", 0.9]]) parser.call("gzip ; deflate").should.equal([["gzip", 1.0]]) end should "parse Accept-Language correctly" do parser = lambda do |x| Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_LANGUAGE" => x)).accept_language end parser.call(nil).should.equal([]) parser.call("fr, en").should.equal([["fr", 1.0], ["en", 1.0]]) parser.call("").should.equal([]) parser.call("*").should.equal([["*", 1.0]]) parser.call("fr;q=0.5, en;q=1.0").should.equal([["fr", 0.5], ["en", 1.0]]) parser.call("fr;q=1.0, en; q=0.5, *;q=0").should.equal([["fr", 1.0], ["en", 0.5], ["*", 0] ]) parser.call("fr ; q=0.9").should.equal([["fr", 0.9]]) parser.call("fr").should.equal([["fr", 1.0]]) end ip_app = lambda { |env| request = Rack::Request.new(env) response = Rack::Response.new response.write request.ip response.finish } should 'provide ip information' do mock = Rack::MockRequest.new(Rack::Lint.new(ip_app)) res = mock.get '/', 'REMOTE_ADDR' => '1.2.3.4' res.body.should.equal '1.2.3.4' res = mock.get '/', 'REMOTE_ADDR' => 'fe80::202:b3ff:fe1e:8329' res.body.should.equal 'fe80::202:b3ff:fe1e:8329' res = mock.get '/', 'REMOTE_ADDR' => '1.2.3.4,3.4.5.6' res.body.should.equal '1.2.3.4' end should 'deals with proxies' do mock = Rack::MockRequest.new(Rack::Lint.new(ip_app)) res = mock.get '/', 'REMOTE_ADDR' => '1.2.3.4', 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' res.body.should.equal '1.2.3.4' res = mock.get '/', 'REMOTE_ADDR' => '1.2.3.4', 'HTTP_X_FORWARDED_FOR' => 'unknown' res.body.should.equal '1.2.3.4' res = mock.get '/', 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' res.body.should.equal '3.4.5.6' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,3.4.5.6' res.body.should.equal '3.4.5.6' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '192.168.0.1,3.4.5.6' res.body.should.equal '3.4.5.6' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '10.0.0.1,3.4.5.6' res.body.should.equal '3.4.5.6' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 10.0.0.1, 3.4.5.6' res.body.should.equal '3.4.5.6' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '127.0.0.1, 3.4.5.6' res.body.should.equal '3.4.5.6' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1' res.body.should.equal 'unknown' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'other,unknown,192.168.0.1' res.body.should.equal 'unknown' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,localhost,192.168.0.1' res.body.should.equal 'unknown' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4' res.body.should.equal '3.4.5.6' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '::1,2620:0:1c00:0:812c:9583:754b:ca11' res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '2620:0:1c00:0:812c:9583:754b:ca11,::1' res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'fd5b:982e:9130:247f:0000:0000:0000:0000,2620:0:1c00:0:812c:9583:754b:ca11' res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '2620:0:1c00:0:812c:9583:754b:ca11,fd5b:982e:9130:247f:0000:0000:0000:0000' res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '1.1.1.1, 127.0.0.1', 'HTTP_CLIENT_IP' => '1.1.1.1' res.body.should.equal '1.1.1.1' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, 9.9.9.9' res.body.should.equal '9.9.9.9' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, fe80::202:b3ff:fe1e:8329' res.body.should.equal 'fe80::202:b3ff:fe1e:8329' # Unix Sockets res = mock.get '/', 'REMOTE_ADDR' => 'unix', 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' res.body.should.equal '3.4.5.6' res = mock.get '/', 'REMOTE_ADDR' => 'unix:/tmp/foo', 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' res.body.should.equal '3.4.5.6' end should "not allow IP spoofing via Client-IP and X-Forwarded-For headers" do mock = Rack::MockRequest.new(Rack::Lint.new(ip_app)) # IP Spoofing attempt: # Client sends X-Forwarded-For: 6.6.6.6 # Client-IP: 6.6.6.6 # Load balancer adds X-Forwarded-For: 2.2.2.3, 192.168.0.7 # App receives: X-Forwarded-For: 6.6.6.6 # X-Forwarded-For: 2.2.2.3, 192.168.0.7 # Client-IP: 6.6.6.6 # Rack env: HTTP_X_FORWARDED_FOR: '6.6.6.6, 2.2.2.3, 192.168.0.7' # HTTP_CLIENT_IP: '6.6.6.6' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '6.6.6.6, 2.2.2.3, 192.168.0.7', 'HTTP_CLIENT_IP' => '6.6.6.6' res.body.should.equal '2.2.2.3' end should "regard local addresses as proxies" do req = Rack::Request.new(Rack::MockRequest.env_for("/")) req.trusted_proxy?('127.0.0.1').should.equal 0 req.trusted_proxy?('10.0.0.1').should.equal 0 req.trusted_proxy?('172.16.0.1').should.equal 0 req.trusted_proxy?('172.20.0.1').should.equal 0 req.trusted_proxy?('172.30.0.1').should.equal 0 req.trusted_proxy?('172.31.0.1').should.equal 0 req.trusted_proxy?('192.168.0.1').should.equal 0 req.trusted_proxy?('::1').should.equal 0 req.trusted_proxy?('fd00::').should.equal 0 req.trusted_proxy?('localhost').should.equal 0 req.trusted_proxy?('unix').should.equal 0 req.trusted_proxy?('unix:/tmp/sock').should.equal 0 req.trusted_proxy?("unix.example.org").should.equal nil req.trusted_proxy?("example.org\n127.0.0.1").should.equal nil req.trusted_proxy?("127.0.0.1\nexample.org").should.equal nil req.trusted_proxy?("11.0.0.1").should.equal nil req.trusted_proxy?("172.15.0.1").should.equal nil req.trusted_proxy?("172.32.0.1").should.equal nil req.trusted_proxy?("2001:470:1f0b:18f8::1").should.equal nil end class MyRequest < Rack::Request def params {:foo => "bar"} end end should "allow subclass request to be instantiated after parent request" do env = Rack::MockRequest.env_for("/?foo=bar") req1 = Rack::Request.new(env) req1.GET.should.equal "foo" => "bar" req1.params.should.equal "foo" => "bar" req2 = MyRequest.new(env) req2.GET.should.equal "foo" => "bar" req2.params.should.equal :foo => "bar" end should "allow parent request to be instantiated after subclass request" do env = Rack::MockRequest.env_for("/?foo=bar") req1 = MyRequest.new(env) req1.GET.should.equal "foo" => "bar" req1.params.should.equal :foo => "bar" req2 = Rack::Request.new(env) req2.GET.should.equal "foo" => "bar" req2.params.should.equal "foo" => "bar" end should "raise TypeError every time if request parameters are broken" do broken_query = Rack::MockRequest.env_for("/?foo%5B%5D=0&foo%5Bbar%5D=1") req = Rack::Request.new(broken_query) lambda{req.GET}.should.raise(TypeError) lambda{req.params}.should.raise(TypeError) end (0x20...0x7E).collect { |a| b = a.chr c = CGI.escape(b) should "not strip '#{a}' => '#{c}' => '#{b}' escaped character from parameters when accessed as string" do url = "/?foo=#{c}bar#{c}" env = Rack::MockRequest.env_for(url) req2 = Rack::Request.new(env) req2.GET.should.equal "foo" => "#{b}bar#{b}" req2.params.should.equal "foo" => "#{b}bar#{b}" end } end