Packages
plug
1.15.6
1.20.3
1.20.2
1.20.1
retired
1.20.0
retired
1.19.5
1.19.4
1.19.3
1.19.2
1.19.1
1.19.0
1.18.5
1.18.4
1.18.3
1.18.2
1.18.1
1.18.0
1.17.4
1.17.3
1.17.2
1.17.1
1.17.0
1.16.6
1.16.5
1.16.4
1.16.3
1.16.2
1.16.1
1.16.0
1.15.6
1.15.5
1.15.4
1.15.3
1.15.2
1.15.1
1.15.0
1.14.2
1.14.1
1.14.0
1.13.6
1.13.5
1.13.4
1.13.3
1.13.2
1.13.1
retired
1.13.0
retired
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
1.9.0
1.8.3
1.8.2
1.8.1
1.8.0
1.7.2
1.7.1
1.7.0
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.5.0-rc.2
1.5.0-rc.1
1.5.0-rc.0
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.4.0-rc.0
1.3.6
1.3.5
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.2.0-rc.0
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.14.0
0.13.1
0.13.0
0.12.2
0.12.1
0.12.0
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.0
0.5.3
0.5.2
0.5.1
0.5.0
0.4.4
0.4.3
0.4.2
0.4.1
Compose web applications with functions
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/plug/router/utils.ex
defmodule Plug.Router.InvalidSpecError do
defexception message: "invalid route specification"
end
defmodule Plug.Router.MalformedURIError do
defexception message: "malformed URI", plug_status: 400
end
defmodule Plug.Router.Utils do
@moduledoc false
@doc """
Decodes path information for dispatching.
"""
def decode_path_info!(conn) do
# TODO: Remove rescue as this can't fail from Elixir v1.13
try do
Enum.map(conn.path_info, &URI.decode/1)
rescue
e in ArgumentError ->
reason = %Plug.Router.MalformedURIError{message: e.message}
Plug.Conn.WrapperError.reraise(conn, :error, reason, __STACKTRACE__)
end
end
@doc """
Converts a given method to its connection representation.
The request method is stored in the `Plug.Conn` struct as an uppercase string
(like `"GET"` or `"POST"`). This function converts `method` to that
representation.
## Examples
iex> Plug.Router.Utils.normalize_method(:get)
"GET"
"""
def normalize_method(method) do
method |> to_string |> String.upcase()
end
@doc ~S"""
Builds the pattern that will be used to match against the request's host
(provided via the `:host`) option.
If `host` is `nil`, a wildcard match (`_`) will be returned. If `host` ends
with a dot, a match like `"host." <> _` will be returned.
## Examples
iex> Plug.Router.Utils.build_host_match(nil)
{:_, [], Plug.Router.Utils}
iex> Plug.Router.Utils.build_host_match("foo.com")
"foo.com"
iex> "api." |> Plug.Router.Utils.build_host_match() |> Macro.to_string()
"\"api.\" <> _"
"""
def build_host_match(host) do
cond do
is_nil(host) -> quote do: _
String.last(host) == "." -> quote do: unquote(host) <> _
is_binary(host) -> host
end
end
@doc """
Generates a representation that will only match routes
according to the given `spec`.
If a non-binary spec is given, it is assumed to be
custom match arguments and they are simply returned.
## Examples
iex> Plug.Router.Utils.build_path_match("/foo/:id")
{[:id], ["foo", {:id, [], nil}]}
"""
def build_path_match(path, context \\ nil) when is_binary(path) do
case build_path_clause(path, true, context) do
{params, match, true, _post_match} ->
{Enum.map(params, &String.to_atom(&1)), match}
{_, _, _, _} ->
raise Plug.Router.InvalidSpecError,
"invalid dynamic path. Only letters, numbers, and underscore are allowed after : in " <>
inspect(path)
end
end
@doc """
Builds a list of path param names and var match pairs.
This is used to build parameter maps from existing variables.
Excludes variables with underscore.
## Examples
iex> Plug.Router.Utils.build_path_params_match(["id"])
[{"id", {:id, [], nil}}]
iex> Plug.Router.Utils.build_path_params_match(["_id"])
[]
iex> Plug.Router.Utils.build_path_params_match([:id])
[{"id", {:id, [], nil}}]
iex> Plug.Router.Utils.build_path_params_match([:_id])
[]
"""
def build_path_params_match(params, context \\ nil)
def build_path_params_match([param | _] = params, context) when is_binary(param) do
params
|> Enum.reject(&match?("_" <> _, &1))
|> Enum.map(&{&1, Macro.var(String.to_atom(&1), context)})
end
def build_path_params_match([param | _] = params, context) when is_atom(param) do
params
|> Enum.map(&{Atom.to_string(&1), Macro.var(&1, context)})
|> Enum.reject(&match?({"_" <> _var, _macro}, &1))
end
def build_path_params_match([], _context) do
[]
end
@doc """
Builds a clause with match, guards, and post matches,
including the known parameters.
"""
def build_path_clause(path, guard, context \\ nil) when is_binary(path) do
compiled = :binary.compile_pattern([":", "*"])
{params, match, guards, post_match} =
path
|> split()
|> build_path_clause([], [], [], [], context, compiled)
if guard != true and guards != [] do
raise ArgumentError, "cannot use \"when\" guards in route when using suffix matches"
end
params = params |> Enum.uniq() |> Enum.reverse()
guards = Enum.reduce(guards, guard, "e(do: unquote(&1) and unquote(&2)))
{params, match, guards, post_match}
end
defp build_path_clause([segment | rest], params, match, guards, post_match, context, compiled) do
case :binary.matches(segment, compiled) do
[] ->
build_path_clause(rest, params, [segment | match], guards, post_match, context, compiled)
[{prefix_size, _}] ->
suffix_size = byte_size(segment) - prefix_size - 1
<<prefix::binary-size(prefix_size), char, suffix::binary-size(suffix_size)>> = segment
{param, suffix} = parse_suffix(suffix)
params = [param | params]
var = Macro.var(String.to_atom(param), context)
case char do
?* when suffix != "" ->
raise Plug.Router.InvalidSpecError,
"globs (*var) cannot be followed by suffixes, got: #{inspect(segment)}"
?* when rest != [] ->
raise Plug.Router.InvalidSpecError,
"globs (*var) must always be in the last path, got glob in: #{inspect(segment)}"
?* ->
submatch =
if prefix != "" do
IO.warn("""
doing a prefix match with globs is deprecated, invalid segment #{inspect(segment)}.
You can either replace by a single segment match:
/foo/bar-:var
Or by mixing single segment match with globs:
/foo/bar-:var/*rest
""")
quote do: [unquote(prefix) <> _ | _] = unquote(var)
else
var
end
match =
case match do
[] ->
submatch
[last | match] ->
Enum.reverse([quote(do: unquote(last) | unquote(submatch)) | match])
end
{params, match, guards, post_match}
?: ->
match =
if prefix == "",
do: [var | match],
else: [quote(do: unquote(prefix) <> unquote(var)) | match]
{post_match, guards} =
if suffix == "" do
{post_match, guards}
else
guard =
quote do
binary_part(
unquote(var),
byte_size(unquote(var)) - unquote(byte_size(suffix)),
unquote(byte_size(suffix))
) == unquote(suffix)
end
trim =
quote do
unquote(var) = String.trim_trailing(unquote(var), unquote(suffix))
end
{[trim | post_match], [guard | guards]}
end
build_path_clause(rest, params, match, guards, post_match, context, compiled)
end
[_ | _] ->
raise Plug.Router.InvalidSpecError,
"only one dynamic entry (:var or *glob) per path segment is allowed, got: " <>
inspect(segment)
end
end
defp build_path_clause([], params, match, guards, post_match, _context, _compiled) do
{params, Enum.reverse(match), guards, post_match}
end
defp parse_suffix(<<h, t::binary>>) when h in ?a..?z or h == ?_,
do: parse_suffix(t, <<h>>)
defp parse_suffix(suffix) do
raise Plug.Router.InvalidSpecError,
"invalid dynamic path. The characters : and * must be immediately followed by " <>
"lowercase letters or underscore, got: :#{suffix}"
end
defp parse_suffix(<<h, t::binary>>, acc)
when h in ?a..?z or h in ?A..?Z or h in ?0..?9 or h == ?_,
do: parse_suffix(t, <<acc::binary, h>>)
defp parse_suffix(rest, acc),
do: {acc, rest}
@doc """
Splits the given path into several segments.
It ignores both leading and trailing slashes in the path.
## Examples
iex> Plug.Router.Utils.split("/foo/bar")
["foo", "bar"]
iex> Plug.Router.Utils.split("/:id/*")
[":id", "*"]
iex> Plug.Router.Utils.split("/foo//*_bar")
["foo", "*_bar"]
"""
def split(bin) do
for segment <- String.split(bin, "/"), segment != "", do: segment
end
@deprecated "Use Plug.forward/4 instead"
defdelegate forward(conn, new_path, target, opts), to: Plug
end