Packages
bandit
0.5.6
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/connection.ex
defmodule Bandit.WebSocket.Connection do
@moduledoc false
# Implementation of a WebSocket lifecycle, implemented using a Socket protocol for communication
alias Bandit.WebSocket.{Frame, Handshake}
defstruct sock: nil, sock_state: nil, state: :open, fragment_frame: nil
@typedoc "Conection state"
@type state :: :open | :closing
@typedoc "Encapsulates the state of a WebSocket connection"
@type t :: %__MODULE__{
sock: module(),
sock_state: Sock.state(),
state: state(),
fragment_frame: Frame.Text.t() | Frame.Binary.t() | nil
}
def init({sock, sock_state}) do
%__MODULE__{sock: sock, sock_state: sock_state}
end
def handle_connection(conn, socket, connection) do
case connection.sock.negotiate(conn, connection.sock_state) do
{:accept, conn, sock_state, opts} ->
Handshake.send_handshake(conn)
connection.sock.handle_connection(socket, sock_state)
|> handle_continutation(socket, connection)
|> case do
{:continue, connection} -> process_options(connection, opts)
other -> other
end
{:refuse, conn, _sock_state} ->
if conn.state != :sent, do: Plug.Conn.send_resp(conn)
{:close, connection}
end
end
defp process_options(connection, opts) do
case Keyword.get(opts, :timeout) do
nil -> {:continue, connection}
timeout -> {:continue, connection, {:persistent, timeout}}
end
end
def handle_frame(frame, socket, %{fragment_frame: nil} = connection) do
case frame do
%Frame.Continuation{} ->
do_error(1002, "Received unexpected continuation frame (RFC6455§5.4)", socket, connection)
%Frame.Text{fin: true} = frame ->
if String.valid?(frame.data) do
connection.sock.handle_text_frame(frame.data, socket, connection.sock_state)
|> handle_continutation(socket, connection)
else
do_error(1007, "Received non UTF-8 text frame (RFC6455§8.1)", socket, connection)
end
%Frame.Text{fin: false} = frame ->
{:continue, %{connection | fragment_frame: frame}}
%Frame.Binary{fin: true} = frame ->
connection.sock.handle_binary_frame(frame.data, socket, connection.sock_state)
|> handle_continutation(socket, connection)
%Frame.Binary{fin: false} = frame ->
{:continue, %{connection | fragment_frame: frame}}
frame ->
handle_control_frame(frame, socket, connection)
end
end
def handle_frame(frame, socket, %{fragment_frame: fragment_frame} = connection)
when not is_nil(fragment_frame) do
case frame do
%Frame.Continuation{fin: true} = frame ->
data = connection.fragment_frame.data <> frame.data
frame = %{connection.fragment_frame | fin: true, data: data}
handle_frame(frame, socket, %{connection | fragment_frame: nil})
%Frame.Continuation{fin: false} = frame ->
data = connection.fragment_frame.data <> frame.data
frame = %{connection.fragment_frame | fin: true, data: data}
{:continue, %{connection | fragment_frame: frame}}
%Frame.Text{} ->
do_error(1002, "Received unexpected text frame (RFC6455§5.4)", socket, connection)
%Frame.Binary{} ->
do_error(1002, "Received unexpected binary frame (RFC6455§5.4)", socket, connection)
frame ->
handle_control_frame(frame, socket, connection)
end
end
defp handle_control_frame(frame, socket, connection) do
case frame do
%Frame.ConnectionClose{} = frame ->
do_connection_close_remote(frame.code, socket, connection)
{:close, %{connection | state: :closing}}
%Frame.Ping{} = frame ->
Sock.Socket.send_pong_frame(socket, frame.data)
connection.sock.handle_ping_frame(frame.data, socket, connection.sock_state)
|> handle_continutation(socket, connection)
%Frame.Pong{} = frame ->
connection.sock.handle_pong_frame(frame.data, socket, connection.sock_state)
|> handle_continutation(socket, connection)
end
end
def handle_close(socket, connection), do: do_error(1006, :closed, socket, connection)
def handle_shutdown(socket, connection), do: do_connection_close_local(1001, socket, connection)
def handle_error(reason, socket, connection), do: do_error(1011, reason, socket, connection)
def handle_timeout(socket, connection) do
if connection.state == :open do
connection.sock.handle_timeout(socket, connection.sock_state)
Bandit.WebSocket.Socket.close(socket, 1002)
end
end
def handle_info(msg, socket, connection) do
connection.sock.handle_info(msg, socket, connection.sock_state)
|> handle_continutation(socket, connection)
end
defp handle_continutation(continutation, socket, connection) do
case continutation do
{:continue, sock_state} ->
{:continue, %{connection | sock_state: sock_state}}
{:close, sock_state} ->
do_connection_close_local(1000, socket, %{connection | sock_state: sock_state})
{:continue, %{connection | sock_state: sock_state, state: :closing}}
{:error, reason, sock_state} ->
do_error(1011, reason, socket, %{connection | sock_state: sock_state})
end
end
defp do_connection_close_local(code, socket, connection) do
if connection.state == :open do
connection.sock.handle_close({:local, code}, socket, connection.sock_state)
Bandit.WebSocket.Socket.close(socket, code)
end
end
defp do_connection_close_remote(code, socket, connection) do
if connection.state == :open do
connection.sock.handle_close({:remote, code || 1005}, socket, connection.sock_state)
# This is a bit of a subtle case, see RFC6455§7.4.1-2
to_send =
case code do
code when code in 0..999 or code in 1004..1006 or code in 1012..2999 -> 1002
_code -> 1000
end
Bandit.WebSocket.Socket.close(socket, to_send)
end
end
defp do_error(code, reason, socket, connection) do
if connection.state == :open do
connection.sock.handle_error(reason, socket, connection.sock_state)
Bandit.WebSocket.Socket.close(socket, code)
end
{:error, reason, %{connection | state: :closing}}
end
end