Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions lib/protocol/http/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,17 @@ class RemoteError < Error
# @deprecated Use {RefusedError} instead.
RequestRefusedError = RefusedError

# Represents a bad request error (as opposed to a server error).
# This is used to indicate that the request was malformed or invalid.
# Marks errors which may indicate a malformed or invalid request when raised while processing an incoming request.
module BadRequest
end

# Raised when a singleton (e.g. `content-length`) header is duplicated in a request or response.
class DuplicateHeaderError < Error
# Raised when an HTTP header is malformed or invalid. When raised while processing an incoming request, it may be treated as a bad request.
class InvalidHeaderError < Error
include BadRequest

end

# Raised when a singleton (e.g. `content-length`) header is duplicated in a request or response.
class DuplicateHeaderError < InvalidHeaderError
# @parameter key [String] The header key that was duplicated.
def initialize(key, existing_value, new_value)
super("Duplicate singleton header key: #{key.inspect}")
Expand Down Expand Up @@ -62,9 +64,7 @@ def detailed_message(highlight: false)
end

# Raised when an invalid trailer header is encountered in headers.
class InvalidTrailerError < Error
include BadRequest

class InvalidTrailerError < InvalidHeaderError
# @parameter key [String] The trailer key that is invalid.
def initialize(key)
super("Invalid trailer key: #{key.inspect}")
Expand Down
2 changes: 1 addition & 1 deletion lib/protocol/http/header/accept.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class Accept < Split
(?=,|\z) # Match until a comma or end of string
/x

ParseError = Class.new(Error)
ParseError = Class.new(InvalidHeaderError)

MEDIA_RANGE = /\A(?<type>#{TOKEN})\/(?<subtype>#{TOKEN})(?<parameters>.*)\z/

Expand Down
2 changes: 1 addition & 1 deletion lib/protocol/http/header/accept_charset.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ 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(InvalidHeaderError)

# https://tools.ietf.org/html/rfc7231#section-5.3.3
CHARSET = /\A(?<name>#{TOKEN})(;q=(?<q>#{QVALUE}))?\z/
Expand Down
2 changes: 1 addition & 1 deletion lib/protocol/http/header/accept_encoding.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ 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(InvalidHeaderError)

# https://tools.ietf.org/html/rfc7231#section-5.3.1
QVALUE = /0(\.[0-9]{0,3})?|1(\.[0]{0,3})?/
Expand Down
2 changes: 1 addition & 1 deletion lib/protocol/http/header/accept_language.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ 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(InvalidHeaderError)

# https://tools.ietf.org/html/rfc3066#section-2.1
NAME = /\*|[A-Z]{1,8}(-[A-Z0-9]{1,8})*/i
Expand Down
2 changes: 1 addition & 1 deletion lib/protocol/http/header/digest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ module Header
# # => "sha-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=, md5=9bb58f26192e4ba00f01e2e7b136bbd8"
# ```
class Digest < Split
ParseError = Class.new(Error)
ParseError = Class.new(InvalidHeaderError)

# https://tools.ietf.org/html/rfc3230#section-4.3.2
ENTRY = /\A(?<algorithm>[a-zA-Z0-9][a-zA-Z0-9\-]*)\s*=\s*(?<value>.*)\z/
Expand Down
2 changes: 1 addition & 1 deletion lib/protocol/http/header/range.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module HTTP
module Header
# Represents a `range` request header.
class Range
ParseError = Class.new(Error)
ParseError = Class.new(InvalidHeaderError)

TOKEN = /[!#$%&'*+\-.0-9A-Z^_`a-z|~]+/
HEADER = /\A(?<unit>#{TOKEN})=(?<ranges>.*)\z/
Expand Down
2 changes: 1 addition & 1 deletion lib/protocol/http/header/server_timing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ module Header
# # => "db;dur=53.2, cache;dur=12.1;desc=\"Redis lookup\""
# ```
class ServerTiming < Split
ParseError = Class.new(Error)
ParseError = Class.new(InvalidHeaderError)

# https://www.w3.org/TR/server-timing/
METRIC = /\A(?<name>[a-zA-Z0-9][a-zA-Z0-9_\-]*)(;(?<parameters>.*))?\z/
Expand Down
2 changes: 1 addition & 1 deletion lib/protocol/http/header/te.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ 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(InvalidHeaderError)

# Transfer encoding token pattern
TOKEN = /[!#$%&'*+\-.0-9A-Z^_`a-z|~]+/
Expand Down
4 changes: 4 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ Please see the [project documentation](https://socketry.github.io/protocol-http/

Please see the [project releases](https://socketry.github.io/protocol-http/releases/index) for all releases.

### Unreleased

- Introduce `Protocol::HTTP::InvalidHeaderError` for malformed or invalid headers, which can be treated as bad requests.

### v0.71.0

- Parse all cookie pairs from `Cookie` header fields, including multiple semicolon-separated pairs within each field.
Expand Down
1 change: 1 addition & 0 deletions releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

- Clarified body stream lifecycle and ownership, including the directional semantics of `Protocol::HTTP::Body::Stream#close_read`, `#close_write`, and `#close`, how premature input closure affects the associated HTTP exchange, and ownership of streams passed to `Streamable#call`.
- Introduce `Protocol::HTTP::InvalidHeaderError` for malformed or invalid headers, which can be treated as bad requests.

## v0.71.0

Expand Down
24 changes: 24 additions & 0 deletions test/protocol/http/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,29 @@
end
end

describe Protocol::HTTP::InvalidHeaderError do
let(:error) {subject.new("Invalid header.")}

it "is an HTTP error" do
expect(error).to be_a(Protocol::HTTP::Error)
end

it "can be treated as a bad request" do
expect(error).to be_a(Protocol::HTTP::BadRequest)
end
end

describe Protocol::HTTP::DuplicateHeaderError do
let(:key) {"content-length"}
let(:existing_value) {"100"}
let(:new_value) {"200"}
let(:error) {subject.new(key, existing_value, new_value)}

with "#initialize" do
it "is an invalid header error" do
expect(error).to be_a(Protocol::HTTP::InvalidHeaderError)
end

it "should set the key and values" do
expect(error.key).to be == key
expect(error.existing_value).to be == existing_value
Expand Down Expand Up @@ -73,3 +89,11 @@
end
end
end

describe Protocol::HTTP::InvalidTrailerError do
let(:error) {subject.new("content-length")}

it "is an invalid header error" do
expect(error).to be_a(Protocol::HTTP::InvalidHeaderError)
end
end
4 changes: 4 additions & 0 deletions test/protocol/http/header/accept.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
end

describe Protocol::HTTP::Header::Accept do
it "classifies parse errors as invalid headers" do
expect(subject::ParseError.new).to be_a(Protocol::HTTP::InvalidHeaderError)
end

let(:header) {subject.parse(description)}
let(:media_ranges) {header.preferred_media_ranges}

Expand Down
4 changes: 4 additions & 0 deletions test/protocol/http/header/accept_charset.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
end

describe Protocol::HTTP::Header::AcceptCharset do
it "classifies parse errors as invalid headers" do
expect(subject::ParseError.new).to be_a(Protocol::HTTP::InvalidHeaderError)
end

let(:header) {subject.parse(description)}
let(:charsets) {header.preferred_charsets}

Expand Down
4 changes: 4 additions & 0 deletions test/protocol/http/header/accept_encoding.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
end

describe Protocol::HTTP::Header::AcceptEncoding do
it "classifies parse errors as invalid headers" do
expect(subject::ParseError.new).to be_a(Protocol::HTTP::InvalidHeaderError)
end

let(:header) {subject.parse(description)}
let(:encodings) {header.preferred_encodings}

Expand Down
4 changes: 4 additions & 0 deletions test/protocol/http/header/accept_language.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
end

describe Protocol::HTTP::Header::AcceptLanguage do
it "classifies parse errors as invalid headers" do
expect(subject::ParseError.new).to be_a(Protocol::HTTP::InvalidHeaderError)
end

let(:header) {subject.parse(description)}
let(:languages) {header.preferred_languages}

Expand Down
4 changes: 4 additions & 0 deletions test/protocol/http/header/digest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
require "sus"

describe Protocol::HTTP::Header::Digest do
it "classifies parse errors as invalid headers" do
expect(subject::ParseError.new).to be_a(Protocol::HTTP::InvalidHeaderError)
end

let(:header) {subject.parse(description)}

with "empty header" do
Expand Down
4 changes: 4 additions & 0 deletions test/protocol/http/header/range.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
require "protocol/http/header/range"

describe Protocol::HTTP::Header::Range do
it "classifies parse errors as invalid headers" do
expect(subject::ParseError.new).to be_a(Protocol::HTTP::InvalidHeaderError)
end

with ".parse" do
it "parses byte ranges" do
header = subject.parse("bytes=0-4, 10-, -5")
Expand Down
4 changes: 4 additions & 0 deletions test/protocol/http/header/server_timing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
require "sus"

describe Protocol::HTTP::Header::ServerTiming do
it "classifies parse errors as invalid headers" do
expect(subject::ParseError.new).to be_a(Protocol::HTTP::InvalidHeaderError)
end

let(:header) {subject.parse(description)}

with "empty header" do
Expand Down
4 changes: 4 additions & 0 deletions test/protocol/http/header/te.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
require "protocol/http/header/te"

describe Protocol::HTTP::Header::TE do
it "classifies parse errors as invalid headers" do
expect(subject::ParseError.new).to be_a(Protocol::HTTP::InvalidHeaderError)
end

let(:header) {subject.parse(description)}

with "chunked" do
Expand Down
Loading