Packages
bandit
1.4.2
1.12.0
1.11.1
1.11.0
1.10.4
1.10.3
1.10.2
1.10.1
1.10.0
retired
1.9.0
1.8.0
1.7.0
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.7
1.5.6
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.2
1.4.1
1.4.0
1.3.0
1.2.3
1.2.2
1.2.1
1.2.0
1.1.3
1.1.2
1.1.1
1.1.0
1.0.0
1.0.0-pre.18
1.0.0-pre.17
1.0.0-pre.16
1.0.0-pre.15
1.0.0-pre.14
1.0.0-pre.13
1.0.0-pre.12
1.0.0-pre.11
1.0.0-pre.10
1.0.0-pre.9
1.0.0-pre.8
1.0.0-pre.7
1.0.0-pre.6
1.0.0-pre.5
1.0.0-pre.4
1.0.0-pre.3
1.0.0-pre.2
1.0.0-pre.1
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.2.3
0.2.2
0.2.1
0.2.0
0.1.1
0.1.0
A pure-Elixir HTTP server built for Plug & WebSock apps
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/bandit/websocket/handshake.ex
defmodule Bandit.WebSocket.Handshake do
@moduledoc false
# Functions to support WebSocket handshaking as described in RFC6455§4.2 & RFC7692
import Plug.Conn
@type extensions :: [{String.t(), [{String.t(), String.t() | true}]}]
@spec handshake(Plug.Conn.t(), keyword(), keyword()) ::
{:ok, Plug.Conn.t(), Keyword.t()} | {:error, String.t()}
def handshake(%Plug.Conn{} = conn, opts, websocket_opts) do
with :ok <- Bandit.WebSocket.UpgradeValidation.validate_upgrade(conn) do
do_handshake(conn, opts, websocket_opts)
end
end
@spec do_handshake(Plug.Conn.t(), keyword(), keyword()) :: {:ok, Plug.Conn.t(), keyword()}
defp do_handshake(conn, opts, websocket_opts) do
requested_extensions = requested_extensions(conn)
{negotiated_params, returned_data} =
if Keyword.get(opts, :compress) && Keyword.get(websocket_opts, :compress, true) do
Bandit.WebSocket.PerMessageDeflate.negotiate(requested_extensions, websocket_opts)
else
{nil, []}
end
conn = send_handshake(conn, returned_data)
{:ok, conn, Keyword.put(opts, :compress, negotiated_params)}
end
@spec requested_extensions(Plug.Conn.t()) :: extensions()
defp requested_extensions(%Plug.Conn{} = conn) do
conn
|> get_req_header("sec-websocket-extensions")
|> Enum.flat_map(&Plug.Conn.Utils.list/1)
|> Enum.map(fn extension ->
[name | params] =
extension
|> String.split(";", trim: true)
|> Enum.map(&String.trim/1)
params = split_params(params)
{name, params}
end)
end
@spec split_params([String.t()]) :: [{String.t(), String.t() | true}]
defp split_params(params) do
params
|> Enum.map(fn param ->
param
|> String.split("=", trim: true)
|> Enum.map(&String.trim/1)
|> case do
[key, value] -> {key, value}
[key] -> {key, true}
end
end)
end
@spec send_handshake(Plug.Conn.t(), extensions()) :: Plug.Conn.t()
defp send_handshake(%Plug.Conn{} = conn, extensions) do
# Taken from RFC6455§4.2.2/5. Note that we can take for granted the existence of the
# sec-websocket-key header in the request, since we check for it in the handshake? call above
[client_key] = get_req_header(conn, "sec-websocket-key")
concatenated_key = client_key <> "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
hashed_key = :crypto.hash(:sha, concatenated_key)
server_key = Base.encode64(hashed_key)
conn
|> resp(101, "")
|> put_resp_header("upgrade", "websocket")
|> put_resp_header("connection", "Upgrade")
|> put_resp_header("sec-websocket-accept", server_key)
|> put_websocket_extension_header(extensions)
|> send_resp()
end
@spec put_websocket_extension_header(Plug.Conn.t(), extensions()) :: Plug.Conn.t()
defp put_websocket_extension_header(conn, []), do: conn
defp put_websocket_extension_header(conn, extensions) do
extensions =
extensions
|> Enum.map_join(",", fn {extension, params} ->
params =
params
|> Enum.flat_map(fn
{_param, false} -> []
{param, true} -> [to_string(param)]
{param, value} -> [to_string(param) <> "=" <> to_string(value)]
end)
[to_string(extension) | params]
|> Enum.join(";")
end)
put_resp_header(conn, "sec-websocket-extensions", extensions)
end
end