diff --git a/lib/mint/core/headers.ex b/lib/mint/core/headers.ex index d6c86b79..04870030 100644 --- a/lib/mint/core/headers.ex +++ b/lib/mint/core/headers.ex @@ -1,95 +1,97 @@ defmodule Mint.Core.Headers do - @type canonical_header() :: + @moduledoc false + + @type canonical() :: {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 + @type raw() :: {original_name :: String.t(), value :: String.t()} + + @unallowed_trailers 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([raw()]) :: [canonical()] + def from_raw(headers) do Enum.map(headers, fn {name, value} -> {name, String.downcase(name, :ascii), value} end) end - @spec to_raw_headers([canonical_header()], boolean()) :: [raw_header()] - def to_raw_headers(headers, _case_sensitive_headers = true) do + @spec to_raw([canonical()], boolean()) :: [raw()] + def to_raw(headers, _case_sensitive = true) do Enum.map(headers, fn {name, _canonical_name, value} -> {name, value} end) end - def to_raw_headers(headers, _case_sensitive_headers = false) do + def to_raw(headers, _case_sensitive = 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 + @spec find([canonical()], String.t()) :: {String.t(), String.t()} | nil + def find(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 + @spec replace([canonical()], String.t(), String.t(), String.t()) :: + [canonical()] + def replace(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 + @spec has?([canonical()], String.t()) :: boolean() + def has?(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 + @spec put_new([canonical()], String.t(), String.t(), String.t() | nil) :: + [canonical()] + def put_new(headers, _name, _canonical_name, nil) do headers end - def put_new_header(headers, name, canonical_name, value) do + def put_new(headers, name, canonical_name, value) do if List.keymember?(headers, canonical_name, 1) do headers else @@ -97,9 +99,9 @@ defmodule Mint.Core.Headers do 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 + @spec put_new([canonical()], String.t(), String.t(), (-> String.t())) :: + [canonical()] + def put_new_lazy(headers, name, canonical_name, fun) do if List.keymember?(headers, canonical_name, 1) do headers else @@ -107,19 +109,19 @@ defmodule Mint.Core.Headers do end end - @spec find_unallowed_trailer([canonical_header()]) :: String.t() | nil + @spec find_unallowed_trailer([canonical()]) :: 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 + if canonical_name in @unallowed_trailers do raw_name end end) end - @spec remove_unallowed_trailer([raw_header()]) :: [raw_header()] + @spec remove_unallowed_trailer([raw()]) :: [raw()] def remove_unallowed_trailer(headers) do - Enum.reject(headers, fn {name, _value} -> name in @unallowed_trailer_headers end) + Enum.reject(headers, fn {name, _value} -> name in @unallowed_trailers end) end @spec lower_raw(String.t()) :: String.t() @@ -127,7 +129,7 @@ defmodule Mint.Core.Headers do String.downcase(name, :ascii) end - @spec lower_raws([raw_header()]) :: [raw_header()] + @spec lower_raws([raw()]) :: [raw()] def lower_raws(headers) do Enum.map(headers, fn {name, value} -> {lower_raw(name), value} end) end diff --git a/lib/mint/http1.ex b/lib/mint/http1.ex index 5c70c97f..628b661b 100644 --- a/lib/mint/http1.ex +++ b/lib/mint/http1.ex @@ -120,9 +120,9 @@ defmodule Mint.HTTP1 do ## Additional Options - * `:case_sensitive_headers` - (boolean) if set to true the case of the supplied + * `:case_sensitive_headers` - (boolean) if set to `true` the case of the supplied headers in requests will be preserved. The default is to lowercase the headers - because http/1.1 header names are not case-insensitive. + because HTTP/1.1 header names are case-insensitive. """ @spec connect(Types.scheme(), Types.address(), :inet.port_number(), keyword()) :: @@ -270,7 +270,7 @@ defmodule Mint.HTTP1 do headers = headers - |> Headers.from_raw_headers() + |> Headers.from_raw() |> add_default_headers(conn) with {:ok, headers, encoding} <- add_content_length_or_transfer_encoding(headers, body), @@ -278,7 +278,7 @@ defmodule Mint.HTTP1 do Request.encode( method, path, - Headers.to_raw_headers(headers, conn.case_sensitive_headers), + Headers.to_raw(headers, conn.case_sensitive_headers), body ), :ok <- transport.send(socket, iodata) do @@ -405,13 +405,13 @@ defmodule Mint.HTTP1 do end end - defp validate_chunk(conn, {:eof, trailer_headers}) do - headers = Headers.from_raw_headers(trailer_headers) + defp validate_chunk(conn, {:eof, trailers}) do + trailers = Headers.from_raw(trailers) - if unallowed_header = Headers.find_unallowed_trailer(headers) do + if unallowed_header = Headers.find_unallowed_trailer(trailers) do {:error, wrap_error({:unallowed_trailing_header, unallowed_header})} else - {:ok, {:eof, Headers.to_raw_headers(headers, conn.case_sensitive_headers)}} + {:ok, {:eof, Headers.to_raw(trailers, conn.case_sensitive_headers)}} end end @@ -988,8 +988,8 @@ defmodule Mint.HTTP1 do defp add_default_headers(headers, conn) do headers - |> Headers.put_new_header("User-Agent", "user-agent", @user_agent) - |> Headers.put_new_header("Host", "host", default_host_header(conn)) + |> Headers.put_new("User-Agent", "user-agent", @user_agent) + |> Headers.put_new("Host", "host", default_host_header(conn)) end # If the port is the default for the scheme, don't add it to the host header @@ -1003,10 +1003,10 @@ defmodule Mint.HTTP1 do defp add_content_length_or_transfer_encoding(headers, :stream) do cond do - Headers.has_header?(headers, "content-length") -> + Headers.has?(headers, "content-length") -> {:ok, headers, :identity} - found = Headers.find_header(headers, "transfer-encoding") -> + found = Headers.find(headers, "transfer-encoding") -> {raw_name, value} = found with {:ok, tokens} <- Parse.transfer_encoding_header(value) do @@ -1014,7 +1014,7 @@ defmodule Mint.HTTP1 do {:ok, headers, :identity} else headers = - Headers.replace_header(headers, raw_name, "transfer-encoding", value <> ",chunked") + Headers.replace(headers, raw_name, "transfer-encoding", value <> ",chunked") {:ok, headers, :chunked} end @@ -1024,7 +1024,7 @@ defmodule Mint.HTTP1 do # chunked transfer-encoding and handle the encoding ourselves. true -> headers = - Headers.put_new_header(headers, "Transfer-Encoding", "transfer-encoding", "chunked") + Headers.put_new(headers, "Transfer-Encoding", "transfer-encoding", "chunked") {:ok, headers, :chunked} end @@ -1037,7 +1037,7 @@ defmodule Mint.HTTP1 do defp add_content_length_or_transfer_encoding(headers, body) do length_fun = fn -> body |> IO.iodata_length() |> Integer.to_string() end - {:ok, Headers.put_new_header_lazy(headers, "Content-Length", "content-length", length_fun), + {:ok, Headers.put_new_lazy(headers, "Content-Length", "content-length", length_fun), :identity} end diff --git a/lib/mint/http2.ex b/lib/mint/http2.ex index 935698db..9beaef0a 100644 --- a/lib/mint/http2.ex +++ b/lib/mint/http2.ex @@ -1106,15 +1106,15 @@ defmodule Mint.HTTP2 do encode_data(conn, stream_id, "", [:end_stream]) end - defp encode_stream_body_request_payload(conn, stream_id, {:eof, trailer_headers}) do - trailer_headers = Headers.from_raw_headers(trailer_headers) + defp encode_stream_body_request_payload(conn, stream_id, {:eof, trailers}) do + trailers = Headers.from_raw(trailers) - if unallowed_trailer_header = Headers.find_unallowed_trailer(trailer_headers) do + if unallowed_trailer_header = Headers.find_unallowed_trailer(trailers) do error = wrap_error({:unallowed_trailing_header, unallowed_trailer_header}) throw({:mint, conn, error}) end - trailer_headers = Headers.to_raw_headers(trailer_headers, _case_sensitive_headers = false) + trailer_headers = Headers.to_raw(trailers, _case_sensitive = false) encode_headers(conn, stream_id, trailer_headers, [:end_headers, :end_stream]) end