Packages
bandit
1.6.0
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/extractor.ex
defmodule Bandit.Extractor do
@moduledoc false
# A state machine for efficiently extracting full frames from received packets
@type deserialize_result :: any()
@callback header_and_payload_length(binary(), max_frame_size :: integer()) ::
{:ok, {header_length :: integer(), payload_length :: integer()}}
| {:error, term()}
| :more
@callback deserialize(binary(), primitive_ops_module :: module()) :: deserialize_result()
@type t :: %__MODULE__{
header: binary(),
payload: iodata(),
payload_length: non_neg_integer(),
required_length: non_neg_integer(),
mode: :header_parsing | :payload_parsing,
max_frame_size: non_neg_integer(),
frame_parser: atom(),
primitive_ops_module: module()
}
defstruct header: <<>>,
payload: [],
payload_length: 0,
required_length: 0,
mode: :header_parsing,
max_frame_size: 0,
frame_parser: nil,
primitive_ops_module: nil
@spec new(module(), module(), Keyword.t()) :: t()
def new(frame_parser, primitive_ops_module, opts) do
max_frame_size = Keyword.get(opts, :max_frame_size, 0)
%__MODULE__{
max_frame_size: max_frame_size,
frame_parser: frame_parser,
primitive_ops_module: primitive_ops_module
}
end
@spec push_data(t(), binary()) :: t()
def push_data(%__MODULE__{} = state, data) do
case state do
%{mode: :header_parsing} ->
%{state | header: state.header <> data}
%{mode: :payload_parsing, payload: payload, payload_length: length} ->
%{state | payload: [payload, data], payload_length: length + byte_size(data)}
end
end
@spec pop_frame(t()) :: {t(), :more | deserialize_result()}
def pop_frame(state)
def pop_frame(%__MODULE__{mode: :header_parsing} = state) do
case state.frame_parser.header_and_payload_length(state.header, state.max_frame_size) do
{:ok, {header_length, required_length}} ->
state
|> transition_to_payload_parsing(header_length, required_length)
|> pop_frame()
{:error, message} ->
{state, {:error, message}}
:more ->
{state, :more}
end
end
def pop_frame(
%__MODULE__{
mode: :payload_parsing,
payload_length: payload_length,
required_length: required_length
} = state
) do
if payload_length >= required_length do
<<payload::binary-size(required_length), rest::binary>> =
IO.iodata_to_binary(state.payload)
frame = state.frame_parser.deserialize(state.header <> payload, state.primitive_ops_module)
state = transition_to_header_parsing(state, rest)
{state, frame}
else
{state, :more}
end
end
defp transition_to_payload_parsing(state, header_length, required_length) do
payload_length = byte_size(state.header) - header_length
state
|> Map.put(:header, binary_part(state.header, 0, header_length))
|> Map.put(:payload, binary_part(state.header, header_length, payload_length))
|> Map.put(:payload_length, payload_length)
|> Map.put(:required_length, required_length)
|> Map.put(:mode, :payload_parsing)
end
defp transition_to_header_parsing(state, rest) do
state
|> Map.put(:header, rest)
|> Map.put(:payload, [])
|> Map.put(:payload_length, 0)
|> Map.put(:required_length, 0)
|> Map.put(:mode, :header_parsing)
end
end