Current section

Files

Jump to
mint_web_socket lib mint web_socket utils.ex
Raw

lib/mint/web_socket/utils.ex

defmodule Mint.WebSocket.Utils do
@moduledoc false
alias Mint.WebSocket.Extension
@websocket_guid "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
def random_nonce do
:crypto.strong_rand_bytes(16) |> Base.encode64()
end
def headers({:http1, nonce}, extensions) when is_binary(nonce) do
[
{"upgrade", "websocket"},
{"connection", "upgrade"},
{"sec-websocket-version", "13"},
{"sec-websocket-key", nonce},
{"sec-websocket-extensions", extension_string(extensions)}
]
|> Enum.reject(fn {_k, v} -> v == "" end)
end
def headers(:http2, extensions) do
[
{"sec-websocket-version", "13"},
{"sec-websocket-extensions", extension_string(extensions)}
]
|> Enum.reject(fn {_k, v} -> v == "" end)
end
@spec check_accept_nonce(binary() | nil, Mint.Types.headers()) ::
:ok | {:error, :invalid_nonce}
def check_accept_nonce(nil, _response_headers) do
{:error, :invalid_nonce}
end
def check_accept_nonce(request_nonce, response_headers) do
with {:ok, response_nonce} <- fetch_header(response_headers, "sec-websocket-accept"),
true <- valid_accept_nonce?(request_nonce, response_nonce) do
:ok
else
_header_not_found_or_not_valid_nonce ->
{:error, :invalid_nonce}
end
end
def valid_accept_nonce?(request_nonce, response_nonce) do
expected_nonce = :crypto.hash(:sha, request_nonce <> @websocket_guid) |> Base.encode64()
# note that this is not a security measure so we do not need to make this
# a constant-time equality check
response_nonce == expected_nonce
end
defp fetch_header(headers, key) do
Enum.find_value(headers, :error, fn
{^key, value} -> {:ok, value}
_ -> false
end)
end
def maybe_concat(<<>>, data), do: data
def maybe_concat(a, b), do: a <> b
defp extension_string(extensions) when is_list(extensions) do
extensions
|> Enum.map(&extension_string/1)
|> Enum.join(", ")
end
defp extension_string(%Extension{name: name, params: []}), do: name
defp extension_string(%Extension{name: name, params: params}) do
params =
params
|> Enum.map(fn {key, value} ->
if value == "true", do: key, else: "#{key}=#{value}"
end)
Enum.join([name | params], "; ")
end
end