-
Notifications
You must be signed in to change notification settings - Fork 115
Add :downcase_request_headers option to HTTP1.connect #399
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
ericmj
merged 12 commits into
elixir-mint:main
from
DunyaKokoschka:preserve_header_case
Feb 12, 2024
Merged
Changes from 11 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
299a52b
Add :downcase_request_headers option to HTTP1.connect to not downcase…
DunyaKokoschka c4037c5
Rename contains_header -> has_header?
DunyaKokoschka c05d572
Reorder parameters in replace_header to match put_new_header
DunyaKokoschka 82f5120
Add test to preserve Transfer-Encoding case
DunyaKokoschka a39ba5f
Test that we preserve the users header name for Transfer-Encoding
DunyaKokoschka 35b491d
Use typespecs for headers
DunyaKokoschka e116eb6
Use Enum.map over for comprehensions for http1 headers
DunyaKokoschka 0e58494
Rename downcase_request_headers to case_sensitive_headers
DunyaKokoschka 8fd0806
Various clean up
ericmj 1c624db
Fix type
ericmj 6f57d4b
Format
ericmj 17d2697
Feedback
ericmj File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
defmodule Mint.Core.Headers do | ||
@type canonical_header() :: | ||
{original_name :: String.t(), canonical_name :: String.t(), value :: String.t()} | ||
@type raw_header() :: {original_name :: String.t(), value :: String.t()} | ||
|
||
@unallowed_trailer_headers MapSet.new([ | ||
"content-encoding", | ||
"content-length", | ||
"content-range", | ||
"content-type", | ||
"trailer", | ||
"transfer-encoding", | ||
|
||
# Control headers (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.5.1) | ||
"cache-control", | ||
"expect", | ||
"host", | ||
"max-forwards", | ||
"pragma", | ||
"range", | ||
"te", | ||
|
||
# Conditionals (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.5.2) | ||
"if-match", | ||
"if-none-match", | ||
"if-modified-since", | ||
"if-unmodified-since", | ||
"if-range", | ||
|
||
# Authentication/authorization (https://tools.ietf.org/html/rfc7235#section-5.3) | ||
"authorization", | ||
"proxy-authenticate", | ||
"proxy-authorization", | ||
"www-authenticate", | ||
|
||
# Cookie management (https://tools.ietf.org/html/rfc6265) | ||
"cookie", | ||
"set-cookie", | ||
|
||
# Control data (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.7.1) | ||
"age", | ||
"cache-control", | ||
"expires", | ||
"date", | ||
"location", | ||
"retry-after", | ||
"vary", | ||
"warning" | ||
]) | ||
|
||
@spec from_raw_headers([raw_header()]) :: [canonical_header()] | ||
def from_raw_headers(headers) do | ||
Enum.map(headers, fn {name, value} -> {name, String.downcase(name, :ascii), value} end) | ||
ericmj marked this conversation as resolved.
Show resolved
Hide resolved
ericmj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
end | ||
|
||
@spec to_raw_headers([canonical_header()], boolean()) :: [raw_header()] | ||
def to_raw_headers(headers, _case_sensitive_headers = true) do | ||
Enum.map(headers, fn {name, _canonical_name, value} -> {name, value} end) | ||
end | ||
|
||
def to_raw_headers(headers, _case_sensitive_headers = false) do | ||
Enum.map(headers, fn {_name, canonical_name, value} -> | ||
{canonical_name, value} | ||
end) | ||
end | ||
|
||
@spec find_header([canonical_header()], String.t()) :: {String.t(), String.t()} | nil | ||
def find_header(headers, name) do | ||
case List.keyfind(headers, name, 1) do | ||
nil -> nil | ||
{name, _canonical_name, value} -> {name, value} | ||
end | ||
end | ||
|
||
@spec replace_header([canonical_header()], String.t(), String.t(), String.t()) :: | ||
[canonical_header()] | ||
def replace_header(headers, new_name, canonical_name, value) do | ||
List.keyreplace(headers, canonical_name, 1, {new_name, canonical_name, value}) | ||
end | ||
|
||
@spec has_header?([canonical_header()], String.t()) :: boolean() | ||
def has_header?(headers, name) do | ||
List.keymember?(headers, name, 1) | ||
end | ||
|
||
@spec put_new_header([canonical_header()], String.t(), String.t(), String.t() | nil) :: | ||
[canonical_header()] | ||
def put_new_header(headers, _name, _canonical_name, nil) do | ||
ericmj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
headers | ||
end | ||
|
||
def put_new_header(headers, name, canonical_name, value) do | ||
if List.keymember?(headers, canonical_name, 1) do | ||
headers | ||
else | ||
[{name, canonical_name, value} | headers] | ||
end | ||
end | ||
|
||
@spec put_new_header([canonical_header()], String.t(), String.t(), (-> String.t())) :: | ||
[canonical_header()] | ||
def put_new_header_lazy(headers, name, canonical_name, fun) do | ||
if List.keymember?(headers, canonical_name, 1) do | ||
headers | ||
else | ||
[{name, canonical_name, fun.()} | headers] | ||
end | ||
end | ||
|
||
@spec find_unallowed_trailer([canonical_header()]) :: String.t() | nil | ||
def find_unallowed_trailer(headers) do | ||
Enum.find_value(headers, fn | ||
{raw_name, canonical_name, _value} -> | ||
if canonical_name in @unallowed_trailer_headers do | ||
raw_name | ||
end | ||
end) | ||
end | ||
|
||
@spec remove_unallowed_trailer([raw_header()]) :: [raw_header()] | ||
def remove_unallowed_trailer(headers) do | ||
Enum.reject(headers, fn {name, _value} -> name in @unallowed_trailer_headers end) | ||
end | ||
|
||
@spec lower_raw(String.t()) :: String.t() | ||
def lower_raw(name) do | ||
String.downcase(name, :ascii) | ||
end | ||
|
||
@spec lower_raws([raw_header()]) :: [raw_header()] | ||
def lower_raws(headers) do | ||
Enum.map(headers, fn {name, value} -> {lower_raw(name), value} end) | ||
end | ||
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe I mentioned it on some other occasion: "trailer headers" feels like a misnomer. Quickly looking at RFC 7231 the naming didn't seem to be super explicit, but in the newer one that surpasses it, RFC 9110, it is: http has fields: header fields (colloquially "headers") and trailer fields (colloquially "trailers")
I only mention this as an offer to send a patch to use "trailers" or "trailer fields" naming internally. It's probably not worth it to go through deprecations if the trailer_headers name was used in public APIs so I wouldn't touch these.
Feel free to ignore, it's not a big deal in the grand scheme of things. :)