Packages
bandit
1.10.3
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/http2/frame/settings.ex
defmodule Bandit.HTTP2.Frame.Settings do
@moduledoc false
import Bandit.HTTP2.Frame.Flags
import Bitwise
@max_window_size (1 <<< 31) - 1
@min_frame_size 1 <<< 14
@max_frame_size (1 <<< 24) - 1
defstruct ack: false, settings: nil
@typedoc "An HTTP/2 SETTINGS frame"
@type t :: %__MODULE__{ack: true, settings: nil} | %__MODULE__{ack: false, settings: map()}
@ack_bit 0
@spec deserialize(Bandit.HTTP2.Frame.flags(), Bandit.HTTP2.Stream.stream_id(), iodata()) ::
{:ok, t()} | {:error, Bandit.HTTP2.Errors.error_code(), binary()}
def deserialize(flags, 0, payload) when clear?(flags, @ack_bit) do
payload
|> Stream.unfold(fn
<<>> -> nil
<<setting::16, value::32, rest::binary>> -> {{:ok, {setting, value}}, rest}
<<rest::binary>> -> {{:error, rest}, <<>>}
end)
|> Enum.reduce_while({:ok, %{}}, fn
{:ok, {0x01, value}}, {:ok, acc} ->
{:cont, {:ok, Map.put(acc, :header_table_size, value)}}
{:ok, {0x02, val}}, {:ok, acc} when val in [0x00, 0x01] ->
{:cont, {:ok, acc}}
{:ok, {0x02, _value}}, {:ok, _acc} ->
{:halt,
{:error, Bandit.HTTP2.Errors.protocol_error(), "Invalid enable_push value (RFC9113§6.5)"}}
{:ok, {0x03, value}}, {:ok, acc} ->
{:cont, {:ok, Map.put(acc, :max_concurrent_streams, value)}}
{:ok, {0x04, value}}, {:ok, _acc} when value > @max_window_size ->
{:halt,
{:error, Bandit.HTTP2.Errors.flow_control_error(), "Invalid window_size (RFC9113§6.5)"}}
{:ok, {0x04, value}}, {:ok, acc} ->
{:cont, {:ok, Map.put(acc, :initial_window_size, value)}}
{:ok, {0x05, value}}, {:ok, _acc} when value < @min_frame_size ->
{:halt,
{:error, Bandit.HTTP2.Errors.frame_size_error(), "Invalid max_frame_size (RFC9113§6.5)"}}
{:ok, {0x05, value}}, {:ok, _acc} when value > @max_frame_size ->
{:halt,
{:error, Bandit.HTTP2.Errors.frame_size_error(), "Invalid max_frame_size (RFC9113§6.5)"}}
{:ok, {0x05, value}}, {:ok, acc} ->
{:cont, {:ok, Map.put(acc, :max_frame_size, value)}}
{:ok, {0x06, value}}, {:ok, acc} ->
{:cont, {:ok, Map.put(acc, :max_header_list_size, value)}}
{:ok, {_setting, _value}}, {:ok, acc} ->
{:cont, {:ok, acc}}
{:error, _rest}, _acc ->
{:halt,
{:error, Bandit.HTTP2.Errors.frame_size_error(), "Invalid SETTINGS size (RFC9113§6.5)"}}
end)
|> case do
{:ok, settings} -> {:ok, %__MODULE__{ack: false, settings: settings}}
{:error, error_code, reason} -> {:error, error_code, reason}
end
end
def deserialize(flags, 0, <<>>) when set?(flags, @ack_bit) do
{:ok, %__MODULE__{ack: true}}
end
def deserialize(flags, 0, _payload) when set?(flags, @ack_bit) do
{:error, Bandit.HTTP2.Errors.frame_size_error(),
"SETTINGS ack frame with non-empty payload (RFC9113§6.5)"}
end
def deserialize(_flags, _stream_id, _payload) do
{:error, Bandit.HTTP2.Errors.protocol_error(), "Invalid SETTINGS frame (RFC9113§6.5)"}
end
defimpl Bandit.HTTP2.Frame.Serializable do
@ack_bit 0
def serialize(%Bandit.HTTP2.Frame.Settings{ack: true}, _max_frame_size),
do: [{0x4, set([@ack_bit]), 0, <<>>}]
def serialize(%Bandit.HTTP2.Frame.Settings{ack: false} = frame, _max_frame_size) do
# Encode default settings values as empty binaries so that we do not send
# them. This means we can't restore settings back to default values if we
# change them, but since we don't ever change our settings this is fine
payload =
frame.settings
|> Enum.uniq_by(fn {setting, _} -> setting end)
|> Enum.map(fn
{:header_table_size, 4_096} -> <<>>
{:header_table_size, value} -> <<0x01::16, value::32>>
{:max_concurrent_streams, :infinity} -> <<>>
{:max_concurrent_streams, value} -> <<0x03::16, value::32>>
{:initial_window_size, 65_535} -> <<>>
{:initial_window_size, value} -> <<0x04::16, value::32>>
{:max_frame_size, 16_384} -> <<>>
{:max_frame_size, value} -> <<0x05::16, value::32>>
{:max_header_list_size, :infinity} -> <<>>
{:max_header_list_size, value} -> <<0x06::16, value::32>>
end)
[{0x4, 0x0, 0, payload}]
end
end
end