From b9bbf845fcb02ed10217ce29055b32d21a3b6552 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 6 Jan 2026 11:34:44 +1300 Subject: [PATCH 1/4] Add release notes. --- releases.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/releases.md b/releases.md index 78672d8..2ee02a3 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,9 @@ # Releases +## Unreleased + + - Always use `#parse` when parsing header values from strings to ensure proper normalization and validation. + ## v0.56.0 - Introduce `Header::*.parse(value)` which parses a raw header value string into a header instance. From e8b9d53a7fdd4ae6dd5ac473b604ee218078b398 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 3 Jan 2026 11:43:57 +1300 Subject: [PATCH 2/4] Introduce `InvalidTrailerError`. --- lib/protocol/http/error.rb | 13 +++++++++++++ lib/protocol/http/headers.rb | 6 ++++-- test/protocol/http/headers.rb | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/protocol/http/error.rb b/lib/protocol/http/error.rb index 0e9e05e..8db8c6f 100644 --- a/lib/protocol/http/error.rb +++ b/lib/protocol/http/error.rb @@ -26,5 +26,18 @@ def initialize(key) # @attribute [String] key The header key that was duplicated. attr :key end + + # Raised when an invalid trailer header is encountered in headers. + class InvalidTrailerError < Error + include BadRequest + + # @parameter key [String] The trailer key that is invalid. + def initialize(key) + super("Invalid trailer key: #{key.inspect}") + end + + # @attribute [String] key The trailer key that is invalid. + attr :key + end end end diff --git a/lib/protocol/http/headers.rb b/lib/protocol/http/headers.rb index ef1b0ac..9ee359f 100644 --- a/lib/protocol/http/headers.rb +++ b/lib/protocol/http/headers.rb @@ -407,7 +407,7 @@ def delete(key) if policy = @policy[key] # Check if we're adding to trailers and this header is allowed: if trailer && !policy.trailer? - return false + raise InvalidTrailerError, key end if current_value = hash[key] @@ -418,7 +418,7 @@ def delete(key) else # By default, headers are not allowed in trailers: if trailer - return false + raise InvalidTrailerError, key end if hash.key?(key) @@ -431,6 +431,8 @@ def delete(key) # Compute a hash table of headers, where the keys are normalized to lower case and the values are normalized according to the policy for that header. # + # This will enforce policy rules, such as merging multiple headers into arrays, or raising errors for duplicate headers. + # # @returns [Hash] A hash table of `{key, value}` pairs. def to_h unless @indexed diff --git a/test/protocol/http/headers.rb b/test/protocol/http/headers.rb index 8b73d10..12b98db 100644 --- a/test/protocol/http/headers.rb +++ b/test/protocol/http/headers.rb @@ -343,7 +343,7 @@ it "can't add a #{key.inspect} header in the trailer", unique: key do trailer = headers.trailer! headers.add(key, "example") - expect(headers).not.to be(:include?, key) + expect{headers.to_h}.to raise_exception(Protocol::HTTP::InvalidTrailerError) end end end From 96ae11e3d4e3cb46076d1c1a9687ed6d1f28c84d Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 6 Jan 2026 10:32:28 +1300 Subject: [PATCH 3/4] Prefer `self.to_h` in `Headers#each`. --- lib/protocol/http/headers.rb | 7 ++++--- test/protocol/http/headers.rb | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/protocol/http/headers.rb b/lib/protocol/http/headers.rb index 9ee359f..21f930e 100644 --- a/lib/protocol/http/headers.rb +++ b/lib/protocol/http/headers.rb @@ -191,7 +191,7 @@ def empty? # @parameter key [String] The header key. # @parameter value [String] The raw header value. def each(&block) - @fields.each(&block) + self.to_h.each(&block) end # @returns [Boolean] Whether the headers include the specified key. @@ -279,7 +279,7 @@ def []=(key, value) # @parameter key [String] The header key. # @returns [String | Array | Object] The header value. def [] key - to_h[key] + self.to_h[key] end # Merge the headers into this instance. @@ -403,6 +403,7 @@ def delete(key) # @parameter hash [Hash] The hash to merge into. # @parameter key [String] The header key. # @parameter value [String] The raw header value. + # @parameter trailer [Boolean] Whether this header is in the trailer section. protected def merge_into(hash, key, value, trailer = @tail) if policy = @policy[key] # Check if we're adding to trailers and this header is allowed: @@ -463,7 +464,7 @@ def inspect def == other case other when Hash - to_h == other + self.to_h == other when Headers @fields == other.fields else diff --git a/test/protocol/http/headers.rb b/test/protocol/http/headers.rb index 12b98db..f880dee 100644 --- a/test/protocol/http/headers.rb +++ b/test/protocol/http/headers.rb @@ -153,7 +153,7 @@ end it "can enumerate fields" do - headers.each.with_index do |field, index| + headers.fields.each_with_index do |field, index| expect(field).to be == fields[index] end end From d17a505be6566ff48e713c524fe62cb32b3763b2 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 6 Jan 2026 11:35:57 +1300 Subject: [PATCH 4/4] Add release notes. --- releases.md | 1 + 1 file changed, 1 insertion(+) diff --git a/releases.md b/releases.md index 2ee02a3..2c4dbd4 100644 --- a/releases.md +++ b/releases.md @@ -3,6 +3,7 @@ ## Unreleased - Always use `#parse` when parsing header values from strings to ensure proper normalization and validation. + - Introduce `Protocol::HTTP::InvalidTrailerError` which is raised when a trailer header is not allowed by the current policy. ## v0.56.0