util.ex (6131B)
1 defmodule Mint.Core.Util do 2 @moduledoc false 3 4 @unallowed_trailing_headers MapSet.new([ 5 "content-encoding", 6 "content-length", 7 "content-range", 8 "content-type", 9 "trailer", 10 "transfer-encoding", 11 12 # Control headers (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.5.1) 13 "cache-control", 14 "expect", 15 "host", 16 "max-forwards", 17 "pragma", 18 "range", 19 "te", 20 21 # Conditionals (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.5.2) 22 "if-match", 23 "if-none-match", 24 "if-modified-since", 25 "if-unmodified-since", 26 "if-range", 27 28 # Authentication/authorization (https://tools.ietf.org/html/rfc7235#section-5.3) 29 "authorization", 30 "proxy-authenticate", 31 "proxy-authorization", 32 "www-authenticate", 33 34 # Cookie management (https://tools.ietf.org/html/rfc6265) 35 "cookie", 36 "set-cookie", 37 38 # Control data (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.7.1) 39 "age", 40 "cache-control", 41 "expires", 42 "date", 43 "location", 44 "retry-after", 45 "vary", 46 "warning" 47 ]) 48 49 # We have to do this if/else dance inside the macro because defguard 50 # is not available in Elixir 1.5, and macro expansion would raise 51 # when expanding the if even if we were on Elixir 1.5. This way, we 52 # only expand to the defguard code if we are on Elixir 1.10 and on 53 # (which is where this macro is supported). 54 defmacro define_is_connection_message_guard do 55 # TODO: Remove the conditional definition when we depend on Elixir 1.10+ 56 # TODO: Use is_struct/2 and map.field access when we depend on Elixir 1.11+ 57 if Version.match?(System.version(), ">= 1.10.0") do 58 quote do 59 @doc since: "1.1.0" 60 defguard is_connection_message(conn, message) 61 when is_map(conn) and 62 is_tuple(message) and 63 is_map_key(conn, :__struct__) and 64 is_map_key(conn, :socket) and 65 is_atom(:erlang.map_get(:__struct__, conn)) and 66 elem(message, 1) == :erlang.map_get(:socket, conn) and 67 ((elem(message, 0) in [:ssl, :tcp] and tuple_size(message) == 3) or 68 (elem(message, 0) in [:ssl_closed, :tcp_closed] and 69 tuple_size(message) == 2) or 70 (elem(message, 0) in [:ssl_error, :tcp_error] and 71 tuple_size(message) == 3)) 72 end 73 else 74 quote do 75 defmacro is_connection_message(_conn, _message) do 76 raise ArgumentError, 77 "the is_connection_message/2 macro is only available with Elixir 1.10+" 78 end 79 end 80 end 81 end 82 83 def hostname(opts, address) do 84 case Keyword.fetch(opts, :hostname) do 85 {:ok, hostname} -> 86 hostname 87 88 :error when is_binary(address) -> 89 address 90 91 :error -> 92 raise ArgumentError, "the :hostname option is required when address is not a binary" 93 end 94 end 95 96 def inet_opts(transport, socket) do 97 with {:ok, opts} <- transport.getopts(socket, [:sndbuf, :recbuf, :buffer]), 98 buffer = calculate_buffer(opts), 99 :ok <- transport.setopts(socket, buffer: buffer) do 100 :ok 101 end 102 end 103 104 def scheme_to_transport(:http), do: Mint.Core.Transport.TCP 105 def scheme_to_transport(:https), do: Mint.Core.Transport.SSL 106 def scheme_to_transport(module) when is_atom(module), do: module 107 108 defp calculate_buffer(opts) do 109 Keyword.fetch!(opts, :buffer) 110 |> max(Keyword.fetch!(opts, :sndbuf)) 111 |> max(Keyword.fetch!(opts, :recbuf)) 112 end 113 114 # Adds a header to the list of headers unless it's nil or it's already there. 115 def put_new_header(headers, name, value) 116 117 def put_new_header(headers, _name, nil) do 118 headers 119 end 120 121 def put_new_header(headers, name, value) do 122 if List.keymember?(headers, name, 0) do 123 headers 124 else 125 [{name, value} | headers] 126 end 127 end 128 129 def put_new_header_lazy(headers, name, fun) do 130 if List.keymember?(headers, name, 0) do 131 headers 132 else 133 [{name, fun.()} | headers] 134 end 135 end 136 137 # Lowercases an ASCII string more efficiently than 138 # String.downcase/1. 139 def downcase_ascii(string), 140 do: for(<<char <- string>>, do: <<downcase_ascii_char(char)>>, into: "") 141 142 def downcase_ascii_char(char) when char in ?A..?Z, do: char + 32 143 def downcase_ascii_char(char) when char in 0..127, do: char 144 145 # If the buffer is empty, reusing the incoming data saves 146 # a potentially large allocation of memory. 147 # This should be fixed in a subsequent OTP release. 148 def maybe_concat(<<>>, data), do: data 149 def maybe_concat(buffer, data) when is_binary(buffer), do: buffer <> data 150 151 def find_unallowed_trailing_header(headers) do 152 Enum.find(headers, fn {name, _value} -> name in @unallowed_trailing_headers end) 153 end 154 155 def remove_unallowed_trailing_headers(headers) do 156 Enum.reject(headers, fn {name, _value} -> name in @unallowed_trailing_headers end) 157 end 158 end