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..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,11 +403,12 @@ 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: if trailer && !policy.trailer? - return false + raise InvalidTrailerError, key end if current_value = hash[key] @@ -418,7 +419,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 +432,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 @@ -461,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/releases.md b/releases.md index 78672d8..2c4dbd4 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,10 @@ # Releases +## 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 - Introduce `Header::*.parse(value)` which parses a raw header value string into a header instance. diff --git a/test/protocol/http/headers.rb b/test/protocol/http/headers.rb index 8b73d10..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 @@ -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