Skip to content

Relax header parsing to allow high bytes and most control characters. #45

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 20, 2025
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
7 changes: 3 additions & 4 deletions lib/protocol/http1/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@ module HTTP1

# HTTP/1.x header parser:
FIELD_NAME = TOKEN
WS = /[ \t]/ # Whitespace.
OWS = /#{WS}*/ # Optional whitespace.
VCHAR = /[!-~]/ # Match visible characters from ASCII 33 to 126.
FIELD_VALUE = /#{VCHAR}+(?:#{WS}+#{VCHAR}+)*/.freeze
OWS = /[ \t]*/
# A field value is any string of characters that does not contain a null character, CR, or LF. After reflecting on the RFCs and surveying real implementations, I came to the conclusion that the RFCs are too restrictive. Most servers only check for the presence of null bytes, and obviously CR/LF characters have semantic meaning in the parser. So, I decided to follow this defacto standard, even if I'm not entirely happy with it.
FIELD_VALUE = /[^\0\r\n]+/.freeze
HEADER = /\A(#{FIELD_NAME}):#{OWS}(?:(#{FIELD_VALUE})#{OWS})?\z/.freeze

VALID_FIELD_NAME = /\A#{FIELD_NAME}\z/.freeze
Expand Down
14 changes: 14 additions & 0 deletions test/protocol/http1/connection/headers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@
"user-agent: Mozilla\x7FHacker Browser"
]}

it "allows the request" do
authority, method, target, version, headers, body = server.read_request

expect(headers).to have_keys(
"user-agent" => be == "Mozilla\x7FHacker Browser"
)
end
end

with "header that contains null character" do
let(:headers) {[
"user-agent: Mozilla\x00Hacker Browser"
]}

it "rejects the request" do
expect do
server.read_request
Expand Down
Loading