Packages
bandit
1.0.0-pre.4
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
def valid_upgrade?(%Plug.Conn{} = conn) do
validate_upgrade(conn) == :ok
end
defp validate_upgrade(conn) do
# Cases from RFC6455§4.2.1
with {:http_version, :"HTTP/1.1"} <- {:http_version, get_http_protocol(conn)},
{:method, "GET"} <- {:method, conn.method},
{:host_header, header} when header != [] <- {:host_header, get_req_header(conn, "host")},
{:upgrade_header, true} <-
{:upgrade_header, header_contains?(conn, "upgrade", "websocket")},
{:connection_header, true} <-
{:connection_header, header_contains?(conn, "connection", "upgrade")},
{:sec_websocket_key_header, true} <-
{:sec_websocket_key_header,
match?([<<_::binary>>], get_req_header(conn, "sec-websocket-key"))},
{:sec_websocket_version_header, ["13"]} <-
{:sec_websocket_version_header, get_req_header(conn, "sec-websocket-version")} do
:ok
else
{step, _detail} -> {:error, "WebSocket upgrade failed: error in #{step} check"}
end
end
def handshake(%Plug.Conn{} = conn, opts, websocket_opts) do
with :ok <- validate_upgrade(conn) do
do_handshake(conn, opts, websocket_opts)
end
end
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
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
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
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
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
defp header_contains?(conn, field, value) do
value = String.downcase(value, :ascii)
conn
|> get_req_header(field)
|> Enum.flat_map(&Plug.Conn.Utils.list/1)
|> Enum.any?(&(String.downcase(&1, :ascii) == value))
end
end