Packages
plug
1.6.2
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.Utils do
@moduledoc false
@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(spec, context \\ nil) when is_binary(spec) do
build_path_match(split(spec), context, [], [])
end
@doc """
Builds a list of path param names and var match pairs that can bind
to dynamic path segment values. Excludes params with underscores;
otherwise, the compiler will warn about used underscored variables
when they are unquoted in the macro.
## Examples
iex> Plug.Router.Utils.build_path_params_match([:id])
[{"id", {:id, [], nil}}]
"""
def build_path_params_match(vars) do
vars
|> Enum.map(&{Atom.to_string(&1), Macro.var(&1, nil)})
|> Enum.reject(&match?({"_" <> _var, _macro}, &1))
end
@doc """
Forwards requests to another Plug at a new path.
"""
def forward(%Plug.Conn{path_info: path, script_name: script} = conn, new_path, target, opts) do
{base, split_path} = Enum.split(path, length(path) - length(new_path))
conn = do_forward(target, %{conn | path_info: split_path, script_name: script ++ base}, opts)
%{conn | path_info: path, script_name: script}
end
defp do_forward({mod, fun}, conn, opts), do: apply(mod, fun, [conn, opts])
defp do_forward(mod, conn, opts), do: mod.call(conn, opts)
@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
## Helpers
# Loops each segment checking for matches.
defp build_path_match([h | t], context, vars, acc) do
handle_segment_match(segment_match(h, "", context), t, context, vars, acc)
end
defp build_path_match([], _context, vars, acc) do
{vars |> Enum.uniq() |> Enum.reverse(), Enum.reverse(acc)}
end
# Handle each segment match. They can either be a
# :literal ("foo"), an :identifier (":bar") or a :glob ("*path")
defp handle_segment_match({:literal, literal}, t, context, vars, acc) do
build_path_match(t, context, vars, [literal | acc])
end
defp handle_segment_match({:identifier, identifier, expr}, t, context, vars, acc) do
build_path_match(t, context, [identifier | vars], [expr | acc])
end
defp handle_segment_match({:glob, _identifier, _expr}, t, _context, _vars, _acc) when t != [] do
raise Plug.Router.InvalidSpecError, message: "cannot have a *glob followed by other segments"
end
defp handle_segment_match({:glob, identifier, expr}, _t, context, vars, [hs | ts]) do
acc = [{:|, [], [hs, expr]} | ts]
build_path_match([], context, [identifier | vars], acc)
end
defp handle_segment_match({:glob, identifier, expr}, _t, context, vars, _) do
{vars, expr} = build_path_match([], context, [identifier | vars], [expr])
{vars, hd(expr)}
end
# In a given segment, checks if there is a match.
defp segment_match(":" <> argument, buffer, context) do
identifier = binary_to_identifier(":", argument)
expr =
quote_if_buffer(identifier, buffer, context, fn var ->
quote do: unquote(buffer) <> unquote(var)
end)
{:identifier, identifier, expr}
end
defp segment_match("*" <> argument, buffer, context) do
underscore = {:_, [], context}
identifier = binary_to_identifier("*", argument)
expr =
quote_if_buffer(identifier, buffer, context, fn var ->
quote do: [unquote(buffer) <> unquote(underscore) | unquote(underscore)] = unquote(var)
end)
{:glob, identifier, expr}
end
defp segment_match(<<h, t::binary>>, buffer, context) do
segment_match(t, buffer <> <<h>>, context)
end
defp segment_match(<<>>, buffer, _context) do
{:literal, buffer}
end
defp quote_if_buffer(identifier, "", context, _fun) do
{identifier, [], context}
end
defp quote_if_buffer(identifier, _buffer, context, fun) do
fun.({identifier, [], context})
end
defp binary_to_identifier(prefix, <<letter, _::binary>> = binary)
when letter in ?a..?z or letter == ?_ do
if binary =~ ~r/^\w+$/ do
String.to_atom(binary)
else
raise Plug.Router.InvalidSpecError,
message: "#{prefix}identifier in routes must be made of letters, numbers and underscores"
end
end
defp binary_to_identifier(prefix, _) do
raise Plug.Router.InvalidSpecError,
message: "#{prefix} in routes must be followed by lowercase letters or underscore"
end
end