diff --git a/lib/thin/env.rb b/lib/thin/env.rb new file mode 100644 index 00000000..add0700a --- /dev/null +++ b/lib/thin/env.rb @@ -0,0 +1,33 @@ + +module Thin + module Env + def self.with_defaults(env) + if ::Rack.release >= "3" + rack_env_class = Rack3 + else + rack_env_class = Rack2 + end + + rack_env_class.env.merge(env) + end + end + + private + + class Rack2 + def self.env + { + ::Thin::Request::RACK_VERSION => ::Thin::VERSION::RACK, + ::Thin::Request::RACK_MULTITHREAD => false, + ::Thin::Request::RACK_MULTIPROCESS => false, + ::Thin::Request::RACK_RUN_ONCE => false + } + end + end + + class Rack3 + def self.env + {} + end + end +end diff --git a/lib/thin/request.rb b/lib/thin/request.rb index 4b3fe4df..4e570762 100644 --- a/lib/thin/request.rb +++ b/lib/thin/request.rb @@ -1,4 +1,5 @@ require 'tempfile' +require_relative './env' module Thin # Raised when an incoming request is not valid @@ -55,20 +56,14 @@ def initialize @data = String.new @nparsed = 0 @body = StringIO.new(INITIAL_BODY.dup) - @env = { + @env = Env.with_defaults({ SERVER_SOFTWARE => SERVER, SERVER_NAME => LOCALHOST, # Rack stuff RACK_INPUT => @body, - - RACK_VERSION => VERSION::RACK, RACK_ERRORS => STDERR, - - RACK_MULTITHREAD => false, - RACK_MULTIPROCESS => false, - RACK_RUN_ONCE => false - } + }) end # Parse a chunk of data into the request environment diff --git a/spec/request/parser_spec.rb b/spec/request/parser_spec.rb index 7be5cac6..cb79c77a 100644 --- a/spec/request/parser_spec.rb +++ b/spec/request/parser_spec.rb @@ -19,28 +19,28 @@ expect(request.env["rack.url_scheme"]).to eq('http') expect(request.env['FRAGMENT'].to_s).to be_empty expect(request.env['QUERY_STRING'].to_s).to be_empty - + expect(request).to validate_with_lint end - + it 'should upcase headers' do request = R("GET / HTTP/1.1\r\nX-Invisible: yo\r\n\r\n") expect(request.env['HTTP_X_INVISIBLE']).to eq('yo') end - + it 'should not prepend HTTP_ to Content-Type and Content-Length' do request = R("POST / HTTP/1.1\r\nHost: localhost\r\nContent-Type: text/html\r\nContent-Length: 2\r\n\r\naa") expect(request.env.keys).not_to include('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH') expect(request.env.keys).to include('CONTENT_TYPE', 'CONTENT_LENGTH') - + expect(request).to validate_with_lint end - + it 'should raise error on invalid request line' do expect { R("GET / SsUTF/1.1") }.to raise_error(InvalidRequest) expect { R("GET / HTTP/1.1yousmelllikecheeze") }.to raise_error(InvalidRequest) end - + it 'should support fragment in uri' do request = R("GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\nHost: localhost\r\n\r\n") @@ -48,19 +48,19 @@ expect(request.env['PATH_INFO']).to eq('/forums/1/topics/2375') expect(request.env['QUERY_STRING']).to eq('page=1') expect(request.env['FRAGMENT']).to eq('posts-17408') - + expect(request).to validate_with_lint end - + it 'should parse path with query string' do request = R("GET /index.html?234235 HTTP/1.1\r\nHost: localhost\r\n\r\n") expect(request.env['REQUEST_PATH']).to eq('/index.html') expect(request.env['QUERY_STRING']).to eq('234235') expect(request.env['FRAGMENT']).to be_nil - + expect(request).to validate_with_lint end - + it 'should parse headers from GET request' do request = R(<<-EOS, true) GET / HTTP/1.1 @@ -151,7 +151,7 @@ expect(request).to validate_with_lint end - + it 'should parse even with stupid Content-Length' do body = <<-EOS.chomp POST / HTTP/1.1 @@ -165,7 +165,7 @@ request.body.rewind expect(request.body.read).to eq('aye') end - + it "should parse ie6 urls" do %w(/some/random/path" /some/random/path> @@ -184,14 +184,14 @@ expect(parser).not_to be_error end end - + it "should parse absolute request URI" do request = R(<<-EOS, true) GET http://localhost:3000/hi?qs#f HTTP/1.1 Host: localhost:3000 EOS - + expect(request.env['PATH_INFO']).to eq('/hi') expect(request.env['REQUEST_PATH']).to eq('/hi') expect(request.env['REQUEST_URI']).to eq('/hi?qs') @@ -236,7 +236,7 @@ expect(req).to have_key('HTTP_HOS_T') } end - + it "should parse PATH_INFO with semicolon" do qs = "QUERY_STRING" pi = "PATH_INFO" @@ -257,14 +257,14 @@ expect(env["REQUEST_URI"]).to eq(uri) next if uri == "*" - + # Validate w/ Ruby's URI.parse uri = URI.parse("http://example.com#{uri}") expect(env[qs]).to eq(uri.query.to_s) expect(env[pi]).to eq(uri.path) end end - + it "should parse IE7 badly encoded URL" do body = <<-EOS.chomp GET /H%uFFFDhnchenbrustfilet HTTP/1.1 @@ -275,4 +275,18 @@ expect(request.env['REQUEST_URI']).to eq("/H%uFFFDhnchenbrustfilet") end + + describe "with Rack < 3", unless: ::Rack.release >= "3" do + it "should add required env" do + request = R("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n") + expect(request.env).to include("rack.version", "rack.multithread", "rack.multiprocess", "rack.run_once") + end + end + + describe "with Rack >= 3", if: ::Rack.release >= "3" do + it "should not add not required env" do + request = R("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n") + expect(request.env).not_to include("rack.version", "rack.multithread", "rack.multiprocess", "rack.run_once") + end + end end