From 9f11e14809360f4c2fa02fe08e76e624efc1c97e Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 2 Aug 2026 23:06:13 +1200 Subject: [PATCH] Classify header parse errors as bad requests. --- lib/protocol/http/header/accept.rb | 4 +++- lib/protocol/http/header/accept_charset.rb | 4 +++- lib/protocol/http/header/accept_encoding.rb | 4 +++- lib/protocol/http/header/accept_language.rb | 4 +++- lib/protocol/http/header/digest.rb | 4 +++- lib/protocol/http/header/range.rb | 4 +++- lib/protocol/http/header/server_timing.rb | 4 +++- lib/protocol/http/header/te.rb | 4 +++- readme.md | 1 + releases.md | 1 + test/protocol/http/header/accept.rb | 4 ++++ test/protocol/http/header/accept_charset.rb | 4 ++++ test/protocol/http/header/accept_encoding.rb | 4 ++++ test/protocol/http/header/accept_language.rb | 4 ++++ test/protocol/http/header/digest.rb | 4 ++++ test/protocol/http/header/range.rb | 4 ++++ test/protocol/http/header/server_timing.rb | 4 ++++ test/protocol/http/header/te.rb | 4 ++++ 18 files changed, 58 insertions(+), 8 deletions(-) diff --git a/lib/protocol/http/header/accept.rb b/lib/protocol/http/header/accept.rb index bbad9ff..a0b0373 100644 --- a/lib/protocol/http/header/accept.rb +++ b/lib/protocol/http/header/accept.rb @@ -23,7 +23,9 @@ class Accept < Split (?=,|\z) # Match until a comma or end of string /x - ParseError = Class.new(Error) + ParseError = Class.new(Error) do + include BadRequest + end MEDIA_RANGE = /\A(?#{TOKEN})\/(?#{TOKEN})(?.*)\z/ diff --git a/lib/protocol/http/header/accept_charset.rb b/lib/protocol/http/header/accept_charset.rb index 73b8db6..689b882 100644 --- a/lib/protocol/http/header/accept_charset.rb +++ b/lib/protocol/http/header/accept_charset.rb @@ -12,7 +12,9 @@ module HTTP module Header # The `accept-charset` header represents a list of character sets that the client can accept. class AcceptCharset < Split - ParseError = Class.new(Error) + ParseError = Class.new(Error) do + include BadRequest + end # https://tools.ietf.org/html/rfc7231#section-5.3.3 CHARSET = /\A(?#{TOKEN})(;q=(?#{QVALUE}))?\z/ diff --git a/lib/protocol/http/header/accept_encoding.rb b/lib/protocol/http/header/accept_encoding.rb index 4f492ac..6cfd4a1 100644 --- a/lib/protocol/http/header/accept_encoding.rb +++ b/lib/protocol/http/header/accept_encoding.rb @@ -12,7 +12,9 @@ module HTTP module Header # The `accept-encoding` header represents a list of encodings that the client can accept. class AcceptEncoding < Split - ParseError = Class.new(Error) + ParseError = Class.new(Error) do + include BadRequest + end # https://tools.ietf.org/html/rfc7231#section-5.3.1 QVALUE = /0(\.[0-9]{0,3})?|1(\.[0]{0,3})?/ diff --git a/lib/protocol/http/header/accept_language.rb b/lib/protocol/http/header/accept_language.rb index 45e2ce9..123e967 100644 --- a/lib/protocol/http/header/accept_language.rb +++ b/lib/protocol/http/header/accept_language.rb @@ -12,7 +12,9 @@ module HTTP module Header # The `accept-language` header represents a list of languages that the client can accept. class AcceptLanguage < Split - ParseError = Class.new(Error) + ParseError = Class.new(Error) do + include BadRequest + end # https://tools.ietf.org/html/rfc3066#section-2.1 NAME = /\*|[A-Z]{1,8}(-[A-Z0-9]{1,8})*/i diff --git a/lib/protocol/http/header/digest.rb b/lib/protocol/http/header/digest.rb index 7a6b63d..47f9cb6 100644 --- a/lib/protocol/http/header/digest.rb +++ b/lib/protocol/http/header/digest.rb @@ -23,7 +23,9 @@ module Header # # => "sha-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=, md5=9bb58f26192e4ba00f01e2e7b136bbd8" # ``` class Digest < Split - ParseError = Class.new(Error) + ParseError = Class.new(Error) do + include BadRequest + end # https://tools.ietf.org/html/rfc3230#section-4.3.2 ENTRY = /\A(?[a-zA-Z0-9][a-zA-Z0-9\-]*)\s*=\s*(?.*)\z/ diff --git a/lib/protocol/http/header/range.rb b/lib/protocol/http/header/range.rb index c017668..a88e2bb 100644 --- a/lib/protocol/http/header/range.rb +++ b/lib/protocol/http/header/range.rb @@ -10,7 +10,9 @@ module HTTP module Header # Represents a `range` request header. class Range - ParseError = Class.new(Error) + ParseError = Class.new(Error) do + include BadRequest + end TOKEN = /[!#$%&'*+\-.0-9A-Z^_`a-z|~]+/ HEADER = /\A(?#{TOKEN})=(?.*)\z/ diff --git a/lib/protocol/http/header/server_timing.rb b/lib/protocol/http/header/server_timing.rb index 74444aa..39b15e0 100644 --- a/lib/protocol/http/header/server_timing.rb +++ b/lib/protocol/http/header/server_timing.rb @@ -23,7 +23,9 @@ module Header # # => "db;dur=53.2, cache;dur=12.1;desc=\"Redis lookup\"" # ``` class ServerTiming < Split - ParseError = Class.new(Error) + ParseError = Class.new(Error) do + include BadRequest + end # https://www.w3.org/TR/server-timing/ METRIC = /\A(?[a-zA-Z0-9][a-zA-Z0-9_\-]*)(;(?.*))?\z/ diff --git a/lib/protocol/http/header/te.rb b/lib/protocol/http/header/te.rb index 8bd01bd..e7025e8 100644 --- a/lib/protocol/http/header/te.rb +++ b/lib/protocol/http/header/te.rb @@ -14,7 +14,9 @@ module Header # # The `te` header allows a client to indicate which transfer encodings it can handle, and in what order of preference using quality factors. class TE < Split - ParseError = Class.new(Error) + ParseError = Class.new(Error) do + include BadRequest + end # Transfer encoding token pattern TOKEN = /[!#$%&'*+\-.0-9A-Z^_`a-z|~]+/ diff --git a/readme.md b/readme.md index 8fbeed2..38f1ed9 100644 --- a/readme.md +++ b/readme.md @@ -33,6 +33,7 @@ Please see the [project releases](https://socketry.github.io/protocol-http/relea ### v0.67.0 - Parse and resolve HTTP `Range` header values according to the default headers policy. + - Classify malformed header values as bad requests. ### v0.66.0 diff --git a/releases.md b/releases.md index 3e517a5..5eec9cf 100644 --- a/releases.md +++ b/releases.md @@ -3,6 +3,7 @@ ## v0.67.0 - Parse and resolve HTTP `Range` header values according to the default headers policy. + - Classify malformed header values as bad requests. ## v0.66.0 diff --git a/test/protocol/http/header/accept.rb b/test/protocol/http/header/accept.rb index 2c09b0a..8f1b509 100644 --- a/test/protocol/http/header/accept.rb +++ b/test/protocol/http/header/accept.rb @@ -20,6 +20,10 @@ end describe Protocol::HTTP::Header::Accept do + it "classifies parse errors as bad requests" do + expect(subject::ParseError.new).to be_a(Protocol::HTTP::BadRequest) + end + let(:header) {subject.parse(description)} let(:media_ranges) {header.media_ranges.sort} diff --git a/test/protocol/http/header/accept_charset.rb b/test/protocol/http/header/accept_charset.rb index 5d4ef95..ffe876d 100644 --- a/test/protocol/http/header/accept_charset.rb +++ b/test/protocol/http/header/accept_charset.rb @@ -13,6 +13,10 @@ end describe Protocol::HTTP::Header::AcceptCharset do + it "classifies parse errors as bad requests" do + expect(subject::ParseError.new).to be_a(Protocol::HTTP::BadRequest) + end + let(:header) {subject.parse(description)} let(:charsets) {header.charsets.sort} diff --git a/test/protocol/http/header/accept_encoding.rb b/test/protocol/http/header/accept_encoding.rb index 5c666dc..1426f39 100644 --- a/test/protocol/http/header/accept_encoding.rb +++ b/test/protocol/http/header/accept_encoding.rb @@ -13,6 +13,10 @@ end describe Protocol::HTTP::Header::AcceptEncoding do + it "classifies parse errors as bad requests" do + expect(subject::ParseError.new).to be_a(Protocol::HTTP::BadRequest) + end + let(:header) {subject.parse(description)} let(:encodings) {header.encodings.sort} diff --git a/test/protocol/http/header/accept_language.rb b/test/protocol/http/header/accept_language.rb index 43bf7c5..213d91a 100644 --- a/test/protocol/http/header/accept_language.rb +++ b/test/protocol/http/header/accept_language.rb @@ -13,6 +13,10 @@ end describe Protocol::HTTP::Header::AcceptLanguage do + it "classifies parse errors as bad requests" do + expect(subject::ParseError.new).to be_a(Protocol::HTTP::BadRequest) + end + let(:header) {subject.parse(description)} let(:languages) {header.languages.sort} diff --git a/test/protocol/http/header/digest.rb b/test/protocol/http/header/digest.rb index 1ebef76..07c804f 100644 --- a/test/protocol/http/header/digest.rb +++ b/test/protocol/http/header/digest.rb @@ -7,6 +7,10 @@ require "sus" describe Protocol::HTTP::Header::Digest do + it "classifies parse errors as bad requests" do + expect(subject::ParseError.new).to be_a(Protocol::HTTP::BadRequest) + end + let(:header) {subject.parse(description)} with "empty header" do diff --git a/test/protocol/http/header/range.rb b/test/protocol/http/header/range.rb index 4ec8220..3dd0669 100644 --- a/test/protocol/http/header/range.rb +++ b/test/protocol/http/header/range.rb @@ -6,6 +6,10 @@ require "protocol/http/header/range" describe Protocol::HTTP::Header::Range do + it "classifies parse errors as bad requests" do + expect(subject::ParseError.new).to be_a(Protocol::HTTP::BadRequest) + end + with ".parse" do it "parses byte ranges" do header = subject.parse("bytes=0-4, 10-, -5") diff --git a/test/protocol/http/header/server_timing.rb b/test/protocol/http/header/server_timing.rb index 6aa336b..1452827 100644 --- a/test/protocol/http/header/server_timing.rb +++ b/test/protocol/http/header/server_timing.rb @@ -7,6 +7,10 @@ require "sus" describe Protocol::HTTP::Header::ServerTiming do + it "classifies parse errors as bad requests" do + expect(subject::ParseError.new).to be_a(Protocol::HTTP::BadRequest) + end + let(:header) {subject.parse(description)} with "empty header" do diff --git a/test/protocol/http/header/te.rb b/test/protocol/http/header/te.rb index 7ba434b..ef96985 100644 --- a/test/protocol/http/header/te.rb +++ b/test/protocol/http/header/te.rb @@ -6,6 +6,10 @@ require "protocol/http/header/te" describe Protocol::HTTP::Header::TE do + it "classifies parse errors as bad requests" do + expect(subject::ParseError.new).to be_a(Protocol::HTTP::BadRequest) + end + let(:header) {subject.parse(description)} with "chunked" do