diff --git a/lib/protocol/http1/connection.rb b/lib/protocol/http1/connection.rb index 845c2b4..4304756 100644 --- a/lib/protocol/http1/connection.rb +++ b/lib/protocol/http1/connection.rb @@ -500,7 +500,14 @@ def read_headers break if line.empty? if match = line.match(HEADER) - fields << [match[1], match[2] || ""] + # The RFCs require stripping of optional whitespace, but only at the end of the field value: + if value = match[2] + value.rstrip! + else + value = "" + end + + fields << [match[1], value] else raise BadHeader, "Could not parse header: #{line.inspect}" end diff --git a/releases.md b/releases.md index 7755873..87cafb0 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,9 @@ # Releases +## Unreleased + + - Strip optional whitespace (OWS) from the end of header field values when parsing headers. + ## v0.37.0 - `Protocol::HTTP1::BadRequest` now includes `Protocol::HTTP::BadRequest` for better interoperability and handling of bad request errors across different HTTP protocol implementations. diff --git a/test/protocol/http1/connection/headers.rb b/test/protocol/http1/connection/headers.rb index c43dbaa..41b872a 100644 --- a/test/protocol/http1/connection/headers.rb +++ b/test/protocol/http1/connection/headers.rb @@ -207,5 +207,103 @@ def validate_headers!(expected_headers = self.headers) end.to raise_exception(Protocol::HTTP1::BadHeader) end end + + with "a header with leading whitespace (spaces)" do + let(:headers) {[ + "x-test: here it is" + ]} + + it "strips leading spaces" do + authority, method, target, version, headers, body = server.read_request + + expect(headers).to have_keys( + "x-test" => be == ["here it is"] + ) + end + end + + with "a header with leading whitespace (tabs)" do + let(:headers) {[ + "x-test:\t\there it is" + ]} + + it "strips leading tabs" do + authority, method, target, version, headers, body = server.read_request + + expect(headers).to have_keys( + "x-test" => be == ["here it is"] + ) + end + end + + with "a header with leading whitespace (mixed)" do + let(:headers) {[ + "x-test: \t \there it is" + ]} + + it "strips leading spaces and tabs" do + authority, method, target, version, headers, body = server.read_request + + expect(headers).to have_keys( + "x-test" => be == ["here it is"] + ) + end + end + + with "a header with trailing whitespace (spaces)" do + let(:headers) {[ + "x-test: here it is " + ]} + + it "strips trailing spaces" do + authority, method, target, version, headers, body = server.read_request + + expect(headers).to have_keys( + "x-test" => be == ["here it is"] + ) + end + end + + with "a header with trailing whitespace (tabs)" do + let(:headers) {[ + "x-test: here it is\t\t" + ]} + + it "strips trailing tabs" do + authority, method, target, version, headers, body = server.read_request + + expect(headers).to have_keys( + "x-test" => be == ["here it is"] + ) + end + end + + with "a header with trailing whitespace (mixed)" do + let(:headers) {[ + "x-test: here it is \t \t" + ]} + + it "strips trailing spaces and tabs" do + authority, method, target, version, headers, body = server.read_request + + expect(headers).to have_keys( + "x-test" => be == ["here it is"] + ) + end + end + + with "a header with both leading and trailing whitespace" do + let(:headers) {[ + "x-test: \t here it is \t " + ]} + + it "strips both leading and trailing whitespace" do + authority, method, target, version, headers, body = server.read_request + + expect(headers).to have_keys( + "x-test" => be == ["here it is"] + ) + end + end end end