Packages
bandit
1.1.3
1.12.4
1.12.3
1.12.2
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/pipeline.ex
defmodule Bandit.Pipeline do
@moduledoc false
# Provides a common pipeline for HTTP/1.1 and h2 adapters, factoring together shared
# functionality relating to `Plug.Conn` management
@type plug_def :: {function() | module(), Plug.opts()}
@type request_target ::
{scheme(), nil | Plug.Conn.host(), nil | Plug.Conn.port_number(), path()}
@type scheme :: String.t() | nil
@type path :: String.t() | :*
@spec run(
Plug.Conn.adapter(),
Bandit.TransportInfo.t(),
Plug.Conn.method(),
request_target(),
Plug.Conn.headers(),
plug_def()
) :: {:ok, Plug.Conn.t()} | {:ok, :websocket, Plug.Conn.t(), tuple()} | {:error, term()}
def run(req, transport_info, method, request_target, headers, plug) do
with {:ok, conn} <- build_conn(req, transport_info, method, request_target, headers),
conn <- call_plug(conn, plug),
{:ok, :no_upgrade} <- maybe_upgrade(conn) do
{:ok, commit_response(conn)}
end
end
@spec build_conn(
Plug.Conn.adapter(),
Bandit.TransportInfo.t(),
Plug.Conn.method(),
request_target(),
Plug.Conn.headers()
) :: {:ok, Plug.Conn.t()} | {:error, String.t()}
defp build_conn({mod, req}, transport_info, method, request_target, headers) do
with {:ok, scheme} <- determine_scheme(transport_info, request_target),
version <- mod.get_http_protocol(req),
{:ok, host, port} <- determine_host_and_port(scheme, version, request_target, headers),
{path, query} <- determine_path_and_query(request_target) do
uri = %URI{scheme: scheme, host: host, port: port, path: path, query: query}
%Bandit.TransportInfo{peername: {remote_ip, _port}} = transport_info
{:ok, Plug.Conn.Adapter.conn({mod, req}, method, uri, remote_ip, headers)}
end
end
@spec determine_scheme(Bandit.TransportInfo.t(), request_target()) ::
{:ok, String.t()} | {:error, String.t()}
defp determine_scheme(%Bandit.TransportInfo{secure?: secure?}, {scheme, _, _, _}) do
case {scheme, secure?} do
{nil, true} -> {:ok, "https"}
{nil, false} -> {:ok, "http"}
{scheme, _} -> {:ok, scheme}
end
end
@spec determine_host_and_port(
scheme :: binary(),
version :: atom(),
request_target(),
Plug.Conn.headers()
) ::
{:ok, Plug.Conn.host(), Plug.Conn.port_number()} | {:error, String.t()}
defp determine_host_and_port(scheme, version, {_, nil, nil, _}, headers) do
with host_header when is_binary(host_header) <- Bandit.Headers.get_header(headers, "host"),
{:ok, host, port} <- Bandit.Headers.parse_hostlike_header(host_header) do
{:ok, host, port || URI.default_port(scheme)}
else
nil ->
case version do
:"HTTP/1.0" -> {:ok, "", URI.default_port(scheme)}
_ -> {:error, "No host header"}
end
error ->
error
end
end
defp determine_host_and_port(scheme, _version, {_, host, port, _}, _headers),
do: {:ok, to_string(host), port || URI.default_port(scheme)}
@spec determine_path_and_query(request_target()) :: {String.t(), nil | String.t()}
defp determine_path_and_query({_, _, _, :*}), do: {"*", nil}
defp determine_path_and_query({_, _, _, path}), do: split_path(path)
@spec split_path(String.t()) :: {String.t(), nil | String.t()}
defp split_path(path) do
path
|> to_string()
|> :binary.split("#")
|> hd()
|> :binary.split("?")
|> case do
[path, query] -> {path, query}
[path] -> {path, nil}
end
end
@spec call_plug(Plug.Conn.t(), plug_def()) :: Plug.Conn.t() | no_return()
defp call_plug(%Plug.Conn{} = conn, {plug, plug_opts}) when is_atom(plug) do
case plug.call(conn, plug_opts) do
%Plug.Conn{} = conn -> conn
other -> raise("Expected #{plug}.call/2 to return %Plug.Conn{} but got: #{inspect(other)}")
end
end
defp call_plug(%Plug.Conn{} = conn, {plug_fn, plug_opts}) when is_function(plug_fn) do
case plug_fn.(conn, plug_opts) do
%Plug.Conn{} = conn -> conn
other -> raise("Expected Plug function to return %Plug.Conn{} but got: #{inspect(other)}")
end
end
@spec maybe_upgrade(Plug.Conn.t()) ::
{:ok, :no_upgrade} | {:ok, :websocket, Plug.Conn.t(), tuple()} | {:error, any()}
defp maybe_upgrade(
%Plug.Conn{
state: :upgraded,
adapter:
{_,
%{upgrade: {:websocket, {websock, websock_opts, connection_opts}, websocket_opts}}}
} = conn
) do
# We can safely unset the state, since we match on :upgraded above
case Bandit.WebSocket.Handshake.handshake(
%{conn | state: :unset},
connection_opts,
websocket_opts
) do
{:ok, conn, connection_opts} ->
{:ok, :websocket, conn, {websock, websock_opts, connection_opts}}
{:error, reason} ->
_ = %{conn | state: :unset} |> Plug.Conn.send_resp(400, reason)
{:error, reason}
end
end
defp maybe_upgrade(_conn), do: {:ok, :no_upgrade}
@spec commit_response(Plug.Conn.t()) :: Plug.Conn.t() | no_return()
defp commit_response(conn) do
case conn do
%Plug.Conn{state: :unset} ->
raise(Plug.Conn.NotSentError)
%Plug.Conn{state: :set} ->
Plug.Conn.send_resp(conn)
%Plug.Conn{state: :chunked, adapter: {mod, req}} ->
mod.chunk(req, "")
conn
%Plug.Conn{} ->
conn
end
end
end