From e0fda0f8c492126b30558bbe33e5ddb5e2e6d975 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 10 Mar 2026 20:24:32 +1300 Subject: [PATCH 1/2] Strip trailing OWS from header values. Fixes #47. --- lib/protocol/http1/connection.rb | 9 ++- releases.md | 4 + test/protocol/http1/connection/headers.rb | 98 +++++++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) diff --git a/lib/protocol/http1/connection.rb b/lib/protocol/http1/connection.rb index 845c2b4..843a7bb 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!(" \t") + 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 From d6009f4522744778dfe48993a7bc7beb9339f14f Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 10 Mar 2026 20:39:25 +1300 Subject: [PATCH 2/2] `rstrip` on Ruby 3 does not accept an argument. --- lib/protocol/http1/connection.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/protocol/http1/connection.rb b/lib/protocol/http1/connection.rb index 843a7bb..4304756 100644 --- a/lib/protocol/http1/connection.rb +++ b/lib/protocol/http1/connection.rb @@ -502,7 +502,7 @@ def read_headers if match = line.match(HEADER) # The RFCs require stripping of optional whitespace, but only at the end of the field value: if value = match[2] - value.rstrip!(" \t") + value.rstrip! else value = "" end