Packages
bandit
0.6.0
1.12.1
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
# credo:disable-for-this-file Credo.Check.Design.AliasUsage
def valid_upgrade?(%Plug.Conn{} = conn) do
case get_http_protocol(conn) do
:"HTTP/1.1" ->
# Cases from RFC6455§4.2.1
conn.method == "GET" and
get_req_header(conn, "host") != [] and
header_contains?(conn, "upgrade", "websocket") and
header_contains?(conn, "connection", "upgrade") and
match?([<<_::binary>>], get_req_header(conn, "sec-websocket-key")) and
get_req_header(conn, "sec-websocket-version") == ["13"]
_ ->
false
end
end
def handshake(%Plug.Conn{} = conn, opts) do
if valid_upgrade?(conn) do
do_handshake(conn, opts)
else
{:error, "Not a valid WebSocket upgrade request"}
end
end
defp do_handshake(conn, opts) do
requested_extensions = requested_extensions(conn)
{negotiated_params, returned_data} =
if Keyword.get(opts, :compress) do
Bandit.WebSocket.PerMessageDeflate.negotiate(requested_extensions)
else
{nil, []}
end
send_handshake(conn, returned_data)
{:ok, 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