diff --git a/lib/protocol/http1/connection.rb b/lib/protocol/http1/connection.rb index 18eae64..4adecd9 100644 --- a/lib/protocol/http1/connection.rb +++ b/lib/protocol/http1/connection.rb @@ -43,7 +43,7 @@ module HTTP1 HEADER = /\A(#{FIELD_NAME}):#{OWS}(?:(#{FIELD_VALUE})#{OWS})?\z/.freeze VALID_FIELD_NAME = /\A#{FIELD_NAME}\z/.freeze - VALID_FIELD_VALUE = /\A#{FIELD_VALUE}\z/.freeze + VALID_FIELD_VALUE = /\A#{FIELD_VALUE}?\z/.freeze DEFAULT_MAXIMUM_LINE_LENGTH = 8192 diff --git a/test/protocol/http1/connection/headers.rb b/test/protocol/http1/connection/headers.rb index 5b1e8cf..2c7be24 100644 --- a/test/protocol/http1/connection/headers.rb +++ b/test/protocol/http1/connection/headers.rb @@ -9,112 +9,203 @@ describe Protocol::HTTP1::Connection do include_context ConnectionContext - let(:headers) {Array.new} + with "#write_response" do + before do + server.open! + end - before do - client.stream.write "GET / HTTP/1.1\r\nHost: localhost\r\n#{headers.join("\r\n")}\r\n\r\n" - client.stream.close - end + def validate_headers!(expected_headers = self.headers) + server.write_empty_body + client.open! + + version, status, reason, headers, body = client.read_response("GET") + + expect(headers).to be == headers + end - with "header that contains tab characters" do - let(:headers) {[ - "user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) \t\t\tChrome/55.0.2883.95 Safari/537.36" - ]} + with "a content-type header" do + let(:headers) {{"content-type" => "text/plain"}} - it "can parse the header" do - authority, method, target, version, headers, body = server.read_request + it "can parse the header" do + server.write_response("HTTP/1.1", 200, headers) - expect(headers).to have_keys( - "user-agent" => be == "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) \t\t\tChrome/55.0.2883.95 Safari/537.36" - ) + validate_headers! + end end - end - with "header that contains obsolete folding whitespace" do - let(:headers) {[ - "user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko)\n\tChrome/55.0.2883.95 Safari/537.36" - ]} + with "an empty header" do + let(:headers) {{"nothing" => ""}} - it "rejects the request" do - expect do - server.read_request - end.to raise_exception(Protocol::HTTP1::BadHeader) + it "can parse the header" do + server.write_response("HTTP/1.1", 200, headers) + + validate_headers! + end end - end - with "header that contains invalid characters" do - let(:headers) {[ - "user-agent: Mozilla\x00Hacker Browser" - ]} + with "a header that contains tab characters" do + let(:headers) {{"user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) \t\t\tChrome/55.0.2883.95 Safari/537.36"}} + + it "can parse the header" do + server.write_response("HTTP/1.1", 200, headers) - it "rejects the request" do - expect do - server.read_request - end.to raise_exception(Protocol::HTTP1::BadHeader) + validate_headers! + end end - end - with "header that contains invalid high characters" do - let(:headers) {[ - "user-agent: Mozilla\x7FHacker Browser" - ]} + with "a header that contains obsolete folding whitespace" do + let(:headers) {{"user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko)\n\tChrome/55.0.2883.95 Safari/537.36"}} + + it "rejects the response" do + expect do + server.write_response("HTTP/1.1", 200, headers) + end.to raise_exception(Protocol::HTTP1::BadHeader) + end + end - it "allows the request" do - authority, method, target, version, headers, body = server.read_request + with "a header that contains invalid characters" do + let(:headers) {{"user-agent" => "Mozilla\x00Hacker Browser"}} - expect(headers).to have_keys( - "user-agent" => be == "Mozilla\x7FHacker Browser" - ) + it "rejects the response" do + expect do + server.write_response("HTTP/1.1", 200, headers) + end.to raise_exception(Protocol::HTTP1::BadHeader) + end end - end - with "header that contains null character" do - let(:headers) {[ - "user-agent: Mozilla\x00Hacker Browser" - ]} + with "a header that contains invalid high characters" do + let(:headers) {{"user-agent" => "Mozilla\x7FHacker Browser"}} + + it "allows the response" do + server.write_response("HTTP/1.1", 200, headers) - it "rejects the request" do - expect do - server.read_request - end.to raise_exception(Protocol::HTTP1::BadHeader) + validate_headers! + end end end - with "header that has empty value" do - let(:headers) {[ - "user-agent: " - ]} + with "#read_request" do + let(:headers) {Array.new} + + before do + client.stream.write "GET / HTTP/1.1\r\nHost: localhost\r\n#{headers.join("\r\n")}\r\n\r\n" + client.stream.close + end + + with "a header that contains tab characters" do + let(:headers) {[ + "user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) \t\t\tChrome/55.0.2883.95 Safari/537.36" + ]} - it "can parse the header" do - authority, method, target, version, headers, body = server.read_request + it "can parse the header" do + authority, method, target, version, headers, body = server.read_request - expect(headers).to have_keys( - "user-agent" => be == "" - ) + expect(headers).to have_keys( + "user-agent" => be == "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) \t\t\tChrome/55.0.2883.95 Safari/537.36" + ) + end end - end - with "header that has invalid name" do - let(:headers) {[ - "invalid name: value" - ]} + with "a header that contains obsolete folding whitespace" do + let(:headers) {[ + "user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko)\n\tChrome/55.0.2883.95 Safari/537.36" + ]} - it "rejects the request" do - expect do - server.read_request - end.to raise_exception(Protocol::HTTP1::BadHeader) + it "rejects the request" do + expect do + server.read_request + end.to raise_exception(Protocol::HTTP1::BadHeader) + end + end + + with "a header that contains invalid characters" do + let(:headers) {[ + "user-agent: Mozilla\x00Hacker Browser" + ]} + + it "rejects the request" do + expect do + server.read_request + end.to raise_exception(Protocol::HTTP1::BadHeader) + end + end + + with "a header that contains invalid high characters" do + let(:headers) {[ + "user-agent: Mozilla\x7FHacker Browser" + ]} + + it "allows the request" do + authority, method, target, version, headers, body = server.read_request + + expect(headers).to have_keys( + "user-agent" => be == "Mozilla\x7FHacker Browser" + ) + end + end + + with "a header that contains null character" do + let(:headers) {[ + "user-agent: Mozilla\x00Hacker Browser" + ]} + + it "rejects the request" do + expect do + server.read_request + end.to raise_exception(Protocol::HTTP1::BadHeader) + end + end + + with "a header that has empty value but includes optional whitespace" do + let(:headers) {[ + "user-agent: " + ]} + + it "can parse the header" do + authority, method, target, version, headers, body = server.read_request + + expect(headers).to have_keys( + "user-agent" => be == "" + ) + end + end + + with "a header that has empty value" do + let(:headers) {[ + "user-agent:" + ]} + + it "can parse the header" do + authority, method, target, version, headers, body = server.read_request + + expect(headers).to have_keys( + "user-agent" => be == "" + ) + end + end + + with "a header that has invalid name" do + let(:headers) {[ + "invalid name: value" + ]} + + it "rejects the request" do + expect do + server.read_request + end.to raise_exception(Protocol::HTTP1::BadHeader) + end end - end - with "header that has empty name" do - let(:headers) {[ - ": value" - ]} + with "a header that has empty name" do + let(:headers) {[ + ": value" + ]} - it "rejects the request" do - expect do - server.read_request - end.to raise_exception(Protocol::HTTP1::BadHeader) + it "rejects the request" do + expect do + server.read_request + end.to raise_exception(Protocol::HTTP1::BadHeader) + end end end end