Page MenuHomePhorge

No OneTemporary

Size
44 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/remote_ip.ex b/lib/remote_ip.ex
index 08e1853..5021627 100644
--- a/lib/remote_ip.ex
+++ b/lib/remote_ip.ex
@@ -1,63 +1,105 @@
defmodule RemoteIp do
+ @moduledoc """
+ A plug to overwrite the `Plug.Conn`'s `remote_ip` based on headers such as
+ `X-Forwarded-For`.
+
+ To use, add the `RemoteIp` plug to your app's plug pipeline:
+
+ ```elixir
+ defmodule MyApp do
+ use Plug.Builder
+
+ plug RemoteIp
+ end
+ ```
+
+ There are 2 options that can be passed in:
+
+ * `:headers` - A list of strings naming the `req_headers` to use when
+ deriving the `remote_ip`. Order does not matter. Defaults to `~w[forwarded
+ x-forwarded-for x-client-ip x-real-ip]`.
+
+ * `:proxies` - A list of strings in
+ [CIDR](https://en.wikipedia.org/wiki/CIDR) notation specifying the IPs of
+ known proxies. Defaults to `[]`.
+
+ For example, if you know you are behind proxies in the IP block 1.2.x.x that
+ use the `X-Foo`, `X-Bar`, and `X-Baz` headers, you could say
+
+ ```elixir
+ defmodule MyApp do
+ use Plug.Builder
+
+ plug RemoteIp, headers: ~w[x-foo x-bar x-baz], proxies: ~w[1.2.0.0/16]
+ end
+ ```
+
+ Note that, due to limitations in the
+ [inet_cidr](https://github.com/Cobenian/inet_cidr) library used to parse
+ them, `:proxies` **must** be written in full CIDR notation, even if
+ specifying just a single IP. So instead of `"127.0.0.1"` and `"a:b::c:d"`,
+ you would use `"127.0.0.1/32"` and `"a:b::c:d/128"`.
+ """
+
@behaviour Plug
@headers ~w[
forwarded
x-forwarded-for
x-client-ip
x-real-ip
]
@proxies []
# https://en.wikipedia.org/wiki/Loopback
# https://en.wikipedia.org/wiki/Private_network
@reserved ~w[
127.0.0.0/8
::1/128
fc00::/7
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
]
def init(opts \\ []) do
headers = Keyword.get(opts, :headers, @headers)
headers = MapSet.new(headers)
proxies = Keyword.get(opts, :proxies, @proxies) ++ @reserved
proxies = proxies |> Enum.map(&InetCidr.parse/1)
{headers, proxies}
end
def call(conn, {headers, proxies}) do
case last_forwarded_ip(conn, headers, proxies) do
nil -> conn
ip -> %{conn | remote_ip: ip}
end
end
defp last_forwarded_ip(conn, headers, proxies) do
conn
|> ips_from(headers)
|> last_ip_forwarded_through(proxies)
end
defp ips_from(%Plug.Conn{req_headers: headers}, allowed) do
RemoteIp.Headers.parse(headers, allowed)
end
defp last_ip_forwarded_through(ips, proxies) do
ips
|> Enum.reverse
|> Enum.find(&forwarded?(&1, proxies))
end
defp forwarded?(ip, proxies) do
!proxy?(ip, proxies)
end
defp proxy?(ip, proxies) do
Enum.any?(proxies, fn proxy -> InetCidr.contains?(proxy, ip) end)
end
end
diff --git a/lib/remote_ip/headers.ex b/lib/remote_ip/headers.ex
index 169fc41..662245b 100644
--- a/lib/remote_ip/headers.ex
+++ b/lib/remote_ip/headers.ex
@@ -1,23 +1,48 @@
defmodule RemoteIp.Headers do
+ @moduledoc """
+ Entry point for parsing any type of forwarding header.
+ """
+
+ @doc """
+ Selects the appropriate headers and parses IPs out of them.
+
+ * `headers` - The entire list of the `Plug.Conn` `req_headers`
+ * `allowed` - The list of headers `RemoteIp` is configured to look for,
+ converted to a `MapSet` for efficiency
+
+ The actual parsing is delegated to the `RemoteIp.Headers.*` submodules:
+
+ * `"forwarded"` is parsed by `RemoteIp.Headers.Forwarded`
+ * everything else is parsed by `RemoteIp.Headers.Generic`
+ """
+
+ @type key :: String.t
+ @type value :: String.t
+ @type header :: {key, value}
+ @type allowed :: %MapSet{}
+ @type ip :: :inet.ip_address
+
+ @spec parse([header], allowed) :: [ip]
+
def parse(headers, %MapSet{} = allowed) when is_list(headers) do
headers
|> allow(allowed)
|> parse_each
end
defp allow(headers, allowed) do
Enum.filter(headers, fn {header, _} -> MapSet.member?(allowed, header) end)
end
defp parse_each(headers) do
Enum.flat_map(headers, &parse_ips/1)
end
defp parse_ips({"forwarded", value}) when is_binary(value) do
RemoteIp.Headers.Forwarded.parse(value)
end
defp parse_ips({header, value}) when is_binary(header) and is_binary(value) do
RemoteIp.Headers.Generic.parse(value)
end
end
diff --git a/lib/remote_ip/headers/forwarded.ex b/lib/remote_ip/headers/forwarded.ex
index 106521f..2e8388f 100644
--- a/lib/remote_ip/headers/forwarded.ex
+++ b/lib/remote_ip/headers/forwarded.ex
@@ -1,141 +1,166 @@
defmodule RemoteIp.Headers.Forwarded do
+ @moduledoc """
+ [RFC 7239](https://tools.ietf.org/html/rfc7239) compliant parser for
+ `Forwarded` headers.
+ """
+
use Combine
+ @doc """
+ Given a `Forwarded` header's string value, parses out IP addresses from the
+ `for=` parameter.
+
+ ## Examples
+
+ iex> RemoteIp.Headers.Forwarded.parse("for=1.2.3.4;by=2.3.4.5")
+ [{1, 2, 3, 4}]
+
+ iex> RemoteIp.Headers.Forwarded.parse("for=\\"[::1]\\", for=\\"[::2]\\"")
+ [{0, 0, 0, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 2}]
+
+ iex> RemoteIp.Headers.Forwarded.parse("invalid")
+ []
+ """
+
+ @type header :: String.t
+ @type ip :: :inet.ip_address
+ @spec parse(header) :: [ip]
+
def parse(header) when is_binary(header) do
case Combine.parse(header, forwarded) do
[elements] -> Enum.flat_map(elements, &parse_forwarded_for/1)
_ -> []
end
end
defp parse_forwarded_for(pairs) do
case pairs |> fors do
[string] -> parse_ip(string)
_ -> [] # no `for=`s or multiple `for=`s
end
end
defp fors(pairs) do
for {key, val} <- pairs, String.downcase(key) == "for", do: val
end
defp parse_ip(string) do
case Combine.parse(string, ip_address) do
[ip] -> [ip]
_ -> []
end
end
# https://tools.ietf.org/html/rfc7239#section-4
defp forwarded do
sep_by(forwarded_element, comma) |> eof
end
defp forwarded_element do
sep_by1(forwarded_pair, char(";"))
end
defp forwarded_pair do
pair = [token, ignore(char("=")), value]
pipe(pair, &List.to_tuple/1)
end
defp value do
either(token, quoted_string)
end
# https://tools.ietf.org/html/rfc7230#section-3.2.6
defp token do
word_of(~r/[!#$%&'*+\-.^_`|~0-9a-zA-Z]/)
end
defp quoted_string do
quoted(string_of(either(qdtext, quoted_pair)))
end
defp quoted(parser) do
between(char("\""), parser, char("\""))
end
defp string_of(parser) do
map(many(parser), &Enum.join/1)
end
defp qdtext do
word_of(~r/[\t \x21\x23-\x5B\x5D-\x7E\x80-\xFF]/)
end
@quotable [?\t]
++ Enum.to_list(0x21..0x7E)
++ Enum.to_list(0x80..0xFF)
|> Enum.map(&<<&1::utf8>>)
defp quoted_pair do
ignore(char("\\")) |> one_of(char, @quotable)
end
# https://tools.ietf.org/html/rfc7230#section-7
defp comma do
skip(many(either(space, tab)))
|> char(",")
|> skip(many(either(space, tab)))
end
# https://tools.ietf.org/html/rfc7239#section-6
defp ip_address do
node_name
|> ignore(option(ignore(char(":")) |> node_port))
|> eof
end
defp node_name do
choice [
ipv4_address,
between(char("["), ipv6_address, char("]")),
ignore(string("unknown")),
ignore(obfuscated),
]
end
defp node_port(previous) do
previous |> either(port, obfuscated)
end
defp port do
# Have to try to parse the wider integers first due to greediness. For
# example, the port "12345" would be matched by fixed_integer(1) and the
# remaining "2345" would cause a parse error for the eof in ip_address/0.
choice(Enum.map(5..1, &fixed_integer/1))
end
defp obfuscated do
word_of(~r/^_[a-zA-Z0-9._\-]+/)
end
# Could follow the ABNF described in
# https://tools.ietf.org/html/rfc3986#section-3.2.2, but prefer to lean on
# the existing :inet parser - we want its output anyway.
defp ipv4_address do
map(word_of(~r/[0-9.]/), fn string ->
case :inet.parse_ipv4strict_address(string |> to_char_list) do
{:ok, ip} -> ip
{:error, :einval} -> {:error, "Invalid IPv4 address"}
end
end)
end
defp ipv6_address do
map(word_of(~r/[0-9a-f:.]/i), fn string ->
case :inet.parse_ipv6strict_address(string |> to_char_list) do
{:ok, ip} -> ip
{:error, :einval} -> {:error, "Invalid IPv6 address"}
end
end)
end
end
diff --git a/lib/remote_ip/headers/generic.ex b/lib/remote_ip/headers/generic.ex
index 9b5879c..1431aeb 100644
--- a/lib/remote_ip/headers/generic.ex
+++ b/lib/remote_ip/headers/generic.ex
@@ -1,24 +1,55 @@
defmodule RemoteIp.Headers.Generic do
+ @moduledoc """
+ Generic parser for forwarding headers.
+
+ When there is no other special `RemoteIp.Headers.*` parser submodule,
+ `RemoteIp.Headers.parse/2` will use this module to parse the header value.
+ So, `RemoteIp.Headers.Generic` is used to parse `X-Forwarded-For`,
+ `X-Real-IP`, `X-Client-IP`, and generally unrecognized headers.
+ """
+
+ @doc """
+ Parses a comma-separated list of IPs.
+
+ Any amount of whitespace is allowed before and after the commas, as well as
+ at the beginning/end of the input.
+
+ ## Examples
+
+ iex> RemoteIp.Headers.Generic.parse("1.2.3.4, 5.6.7.8")
+ [{1, 2, 3, 4}, {5, 6, 7, 8}]
+
+ iex> RemoteIp.Headers.Generic.parse(" ::1 ")
+ [{0, 0, 0, 0, 0, 0, 0, 1}]
+
+ iex> RemoteIp.Headers.Generic.parse("invalid")
+ []
+ """
+
+ @type header :: String.t
+ @type ip :: :inet.ip_address
+ @spec parse(header) :: [ip]
+
def parse(header) when is_binary(header) do
header
|> split_commas
|> parse_ips
end
defp split_commas(header) do
header |> String.trim |> String.split(~r/\s*,\s*/)
end
defp parse_ips(strings) do
Enum.reduce(strings, [], fn string, ips ->
case parse_ip(string) do
{:ok, ip} -> [ip | ips]
{:error, :einval} -> ips
end
end) |> Enum.reverse
end
defp parse_ip(string) do
string |> to_char_list |> :inet.parse_strict_address
end
end
diff --git a/test/remote_ip/headers/forwarded_test.exs b/test/remote_ip/headers/forwarded_test.exs
index 6191e60..d4a8154 100644
--- a/test/remote_ip/headers/forwarded_test.exs
+++ b/test/remote_ip/headers/forwarded_test.exs
@@ -1,360 +1,362 @@
defmodule RemoteIp.Headers.ForwardedTest do
use ExUnit.Case, async: true
alias RemoteIp.Headers.Forwarded
+ doctest Forwarded
+
describe "parsing" do
test "RFC 7239 examples" do
parsed = Forwarded.parse(~S'for="_gazonk"')
assert parsed == []
parsed = Forwarded.parse(~S'For="[2001:db8:cafe::17]:4711"')
assert parsed == [{8193, 3512, 51966, 0, 0, 0, 0, 23}]
parsed = Forwarded.parse(~S'for=192.0.2.60;proto=http;by=203.0.113.43')
assert parsed == [{192, 0, 2, 60}]
parsed = Forwarded.parse(~S'for=192.0.2.43, for=198.51.100.17')
assert parsed == [{192, 0, 2, 43}, {198, 51, 100, 17}]
end
test "case insensitivity" do
assert [{0, 0, 0, 0}] == Forwarded.parse(~S'for=0.0.0.0')
assert [{0, 0, 0, 0}] == Forwarded.parse(~S'foR=0.0.0.0')
assert [{0, 0, 0, 0}] == Forwarded.parse(~S'fOr=0.0.0.0')
assert [{0, 0, 0, 0}] == Forwarded.parse(~S'fOR=0.0.0.0')
assert [{0, 0, 0, 0}] == Forwarded.parse(~S'For=0.0.0.0')
assert [{0, 0, 0, 0}] == Forwarded.parse(~S'FoR=0.0.0.0')
assert [{0, 0, 0, 0}] == Forwarded.parse(~S'FOr=0.0.0.0')
assert [{0, 0, 0, 0}] == Forwarded.parse(~S'FOR=0.0.0.0')
assert [{0, 0, 0, 0, 0, 0, 0, 0}] == Forwarded.parse(~S'for="[::]"')
assert [{0, 0, 0, 0, 0, 0, 0, 0}] == Forwarded.parse(~S'foR="[::]"')
assert [{0, 0, 0, 0, 0, 0, 0, 0}] == Forwarded.parse(~S'fOr="[::]"')
assert [{0, 0, 0, 0, 0, 0, 0, 0}] == Forwarded.parse(~S'fOR="[::]"')
assert [{0, 0, 0, 0, 0, 0, 0, 0}] == Forwarded.parse(~S'For="[::]"')
assert [{0, 0, 0, 0, 0, 0, 0, 0}] == Forwarded.parse(~S'FoR="[::]"')
assert [{0, 0, 0, 0, 0, 0, 0, 0}] == Forwarded.parse(~S'FOr="[::]"')
assert [{0, 0, 0, 0, 0, 0, 0, 0}] == Forwarded.parse(~S'FOR="[::]"')
end
test "IPv4" do
assert [] == Forwarded.parse(~S'for=')
assert [] == Forwarded.parse(~S'for=1')
assert [] == Forwarded.parse(~S'for=1.2')
assert [] == Forwarded.parse(~S'for=1.2.3')
assert [] == Forwarded.parse(~S'for=1000.2.3.4')
assert [] == Forwarded.parse(~S'for=1.2000.3.4')
assert [] == Forwarded.parse(~S'for=1.2.3000.4')
assert [] == Forwarded.parse(~S'for=1.2.3.4000')
assert [] == Forwarded.parse(~S'for=1abc.2.3.4')
assert [] == Forwarded.parse(~S'for=1.2abc.3.4')
assert [] == Forwarded.parse(~S'for=1.2.3.4abc')
assert [] == Forwarded.parse(~S'for=1.2.3abc.4')
assert [] == Forwarded.parse(~S'for=1.2.3.4abc')
assert [] == Forwarded.parse(~S'for="1.2.3.4')
assert [] == Forwarded.parse(~S'for=1.2.3.4"')
assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for=1.2.3.4')
assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for="1.2.3.4"')
assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for="\1.2\.3.\4"')
end
test "IPv4 with port" do
assert [] == Forwarded.parse(~S'for=1.2.3.4:')
assert [] == Forwarded.parse(~S'for=1.2.3.4:1')
assert [] == Forwarded.parse(~S'for=1.2.3.4:12')
assert [] == Forwarded.parse(~S'for=1.2.3.4:123')
assert [] == Forwarded.parse(~S'for=1.2.3.4:1234')
assert [] == Forwarded.parse(~S'for=1.2.3.4:12345')
assert [] == Forwarded.parse(~S'for=1.2.3.4:123456')
assert [] == Forwarded.parse(~S'for=1.2.3.4:_underscore')
assert [] == Forwarded.parse(~S'for=1.2.3.4:no_underscore')
assert [] == Forwarded.parse(~S'for="1.2.3.4:"')
assert [] == Forwarded.parse(~S'for="1.2.3.4:123456"')
assert [] == Forwarded.parse(~S'for="1.2.3.4:no_underscore"')
assert [] == Forwarded.parse(~S'for="1.2\.3.4\:no_un\der\score"')
assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for="1.2.3.4:1"')
assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for="1.2.3.4:12"')
assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for="1.2.3.4:123"')
assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for="1.2.3.4:1234"')
assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for="1.2.3.4:12345"')
assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for="1.2.3.4:_underscore"')
assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for="\1.2\.3.4\:_po\r\t"')
end
test "improperly formatted IPv6" do
assert [] == Forwarded.parse(~S'for=[127.0.0.1]')
assert [] == Forwarded.parse(~S'for="[127.0.0.1]"')
assert [] == Forwarded.parse(~S'for=::127.0.0.1')
assert [] == Forwarded.parse(~S'for=[::127.0.0.1]')
assert [] == Forwarded.parse(~S'for="::127.0.0.1"')
assert [] == Forwarded.parse(~S'for="[::127.0.0.1"')
assert [] == Forwarded.parse(~S'for="::127.0.0.1]"')
assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8')
assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]')
assert [] == Forwarded.parse(~S'for="1:2:3:4:5:6:7:8"')
assert [] == Forwarded.parse(~S'for="[1:2:3:4:5:6:7:8"')
assert [] == Forwarded.parse(~S'for="1:2:3:4:5:6:7:8]"')
end
test "IPv6 with port" do
assert [] == Forwarded.parse(~S'for=::1.2.3.4:')
assert [] == Forwarded.parse(~S'for=::1.2.3.4:1')
assert [] == Forwarded.parse(~S'for=::1.2.3.4:12')
assert [] == Forwarded.parse(~S'for=::1.2.3.4:123')
assert [] == Forwarded.parse(~S'for=::1.2.3.4:1234')
assert [] == Forwarded.parse(~S'for=::1.2.3.4:12345')
assert [] == Forwarded.parse(~S'for=::1.2.3.4:123456')
assert [] == Forwarded.parse(~S'for=::1.2.3.4:_underscore')
assert [] == Forwarded.parse(~S'for=::1.2.3.4:no_underscore')
assert [] == Forwarded.parse(~S'for=[::1.2.3.4]:')
assert [] == Forwarded.parse(~S'for=[::1.2.3.4]:1')
assert [] == Forwarded.parse(~S'for=[::1.2.3.4]:12')
assert [] == Forwarded.parse(~S'for=[::1.2.3.4]:123')
assert [] == Forwarded.parse(~S'for=[::1.2.3.4]:1234')
assert [] == Forwarded.parse(~S'for=[::1.2.3.4]:12345')
assert [] == Forwarded.parse(~S'for=[::1.2.3.4]:123456')
assert [] == Forwarded.parse(~S'for=[::1.2.3.4]:_underscore')
assert [] == Forwarded.parse(~S'for=[::1.2.3.4]:no_underscore')
assert [] == Forwarded.parse(~S'for="::1.2.3.4:"')
assert [] == Forwarded.parse(~S'for="::1.2.3.4:123456"')
assert [] == Forwarded.parse(~S'for="::1.2.3.4:no_underscore"')
assert [] == Forwarded.parse(~S'for="::1.2\.3.4\:no_un\der\score"')
assert [] == Forwarded.parse(~S'for="[::1.2.3.4]:"')
assert [] == Forwarded.parse(~S'for="[::1.2.3.4]:123456"')
assert [] == Forwarded.parse(~S'for="[::1.2.3.4]:no_underscore"')
assert [] == Forwarded.parse(~S'for="\[::1.2\.3.4]\:no_un\der\score"')
assert [] == Forwarded.parse(~S'for="::1.2.3.4:1"')
assert [] == Forwarded.parse(~S'for="::1.2.3.4:12"')
assert [] == Forwarded.parse(~S'for="::1.2.3.4:123"')
assert [] == Forwarded.parse(~S'for="::1.2.3.4:1234"')
assert [] == Forwarded.parse(~S'for="::1.2.3.4:12345"')
assert [] == Forwarded.parse(~S'for="::1.2.3.4:_underscore"')
assert [] == Forwarded.parse(~S'for="::\1.2\.3.4\:_po\r\t"')
assert [{0, 0, 0, 0, 0, 0, 258, 772}] == Forwarded.parse(~S'for="[::1.2.3.4]:1"')
assert [{0, 0, 0, 0, 0, 0, 258, 772}] == Forwarded.parse(~S'for="[::1.2.3.4]:12"')
assert [{0, 0, 0, 0, 0, 0, 258, 772}] == Forwarded.parse(~S'for="[::1.2.3.4]:123"')
assert [{0, 0, 0, 0, 0, 0, 258, 772}] == Forwarded.parse(~S'for="[::1.2.3.4]:1234"')
assert [{0, 0, 0, 0, 0, 0, 258, 772}] == Forwarded.parse(~S'for="[::1.2.3.4]:12345"')
assert [{0, 0, 0, 0, 0, 0, 258, 772}] == Forwarded.parse(~S'for="[::1.2.3.4]:_underscore"')
assert [{0, 0, 0, 0, 0, 0, 258, 772}] == Forwarded.parse(~S'for="[::\1.2\.3.4\]\:_po\r\t"')
assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8:')
assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8:1')
assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8:12')
assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8:123')
assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8:1234')
assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8:12345')
assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8:123456')
assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8:_underscore')
assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8:no_underscore')
assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]:')
assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]:1')
assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]:12')
assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]:123')
assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]:1234')
assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]:12345')
assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]:123456')
assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]:_underscore')
assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]:no_underscore')
assert [] == Forwarded.parse(~S'for="1:2:3:4:5:6:7:8:"')
assert [] == Forwarded.parse(~S'for="1:2:3:4:5:6:7:8:123456"')
assert [] == Forwarded.parse(~S'for="1:2:3:4:5:6:7:8:no_underscore"')
assert [] == Forwarded.parse(~S'for="::1.2\.3.4\:no_un\der\score"')
assert [] == Forwarded.parse(~S'for="[1:2:3:4:5:6:7:8]:"')
assert [] == Forwarded.parse(~S'for="[1:2:3:4:5:6:7:8]:123456"')
assert [] == Forwarded.parse(~S'for="[1:2:3:4:5:6:7:8]:no_underscore"')
assert [] == Forwarded.parse(~S'for="\[1:2\:3:4:5:6:7:8]\:no_un\der\score"')
assert [] == Forwarded.parse(~S'for="1:2:3:4:5:6:7:8:1"')
assert [] == Forwarded.parse(~S'for="1:2:3:4:5:6:7:8:12"')
assert [] == Forwarded.parse(~S'for="1:2:3:4:5:6:7:8:123"')
assert [] == Forwarded.parse(~S'for="1:2:3:4:5:6:7:8:1234"')
assert [] == Forwarded.parse(~S'for="1:2:3:4:5:6:7:8:12345"')
assert [] == Forwarded.parse(~S'for="1:2:3:4:5:6:7:8:_underscore"')
assert [] == Forwarded.parse(~S'for="\1:2\:3:4:5:6:7:8\:_po\r\t"')
assert [{1, 2, 3, 4, 5, 6, 7, 8}] == Forwarded.parse(~S'for="[1:2:3:4:5:6:7:8]:1"')
assert [{1, 2, 3, 4, 5, 6, 7, 8}] == Forwarded.parse(~S'for="[1:2:3:4:5:6:7:8]:12"')
assert [{1, 2, 3, 4, 5, 6, 7, 8}] == Forwarded.parse(~S'for="[1:2:3:4:5:6:7:8]:123"')
assert [{1, 2, 3, 4, 5, 6, 7, 8}] == Forwarded.parse(~S'for="[1:2:3:4:5:6:7:8]:1234"')
assert [{1, 2, 3, 4, 5, 6, 7, 8}] == Forwarded.parse(~S'for="[1:2:3:4:5:6:7:8]:12345"')
assert [{1, 2, 3, 4, 5, 6, 7, 8}] == Forwarded.parse(~S'for="[1:2:3:4:5:6:7:8]:_underscore"')
assert [{1, 2, 3, 4, 5, 6, 7, 8}] == Forwarded.parse(~S'for="[1:2:3:4:\5:6\:7:8\]\:_po\r\t"')
end
test "IPv6 without ::" do
assert [{0x0001, 0x0023, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[1:23:456:7890:a:bc:def:d34d]"')
assert [{0x0001, 0x0023, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[1:23:456:7890:a:bc:1.2.3.4]"')
end
test "IPv6 with :: at position 0" do
assert [{0x0000, 0x0023, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[::23:456:7890:a:bc:def:d34d]"')
assert [{0x0000, 0x0023, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[::23:456:7890:a:bc:1.2.3.4]"')
assert [{0x0000, 0x0000, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[::456:7890:a:bc:def:d34d]"')
assert [{0x0000, 0x0000, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[::456:7890:a:bc:1.2.3.4]"')
assert [{0x0000, 0x0000, 0x0000, 0x7890, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[::7890:a:bc:def:d34d]"')
assert [{0x0000, 0x0000, 0x0000, 0x7890, 0x000a, 0x00bc, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[::7890:a:bc:1.2.3.4]"')
assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[::a:bc:def:d34d]"')
assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x000a, 0x00bc, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[::a:bc:1.2.3.4]"')
assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x00bc, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[::bc:def:d34d]"')
assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x00bc, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[::bc:1.2.3.4]"')
assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[::def:d34d]"')
assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[::1.2.3.4]"')
assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xd34d}] == Forwarded.parse(~S'for="[::d34d]"')
assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for="[::]"')
end
test "IPv6 with :: at position 1" do
assert [{0x0001, 0x000, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[1::456:7890:a:bc:def:d34d]"')
assert [{0x0001, 0x000, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[1::456:7890:a:bc:1.2.3.4]"')
assert [{0x0001, 0x000, 0x0000, 0x7890, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[1::7890:a:bc:def:d34d]"')
assert [{0x0001, 0x000, 0x0000, 0x7890, 0x000a, 0x00bc, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[1::7890:a:bc:1.2.3.4]"')
assert [{0x0001, 0x000, 0x0000, 0x0000, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[1::a:bc:def:d34d]"')
assert [{0x0001, 0x000, 0x0000, 0x0000, 0x000a, 0x00bc, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[1::a:bc:1.2.3.4]"')
assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x00bc, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[1::bc:def:d34d]"')
assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x00bc, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[1::bc:1.2.3.4]"')
assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[1::def:d34d]"')
assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[1::1.2.3.4]"')
assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xd34d}] == Forwarded.parse(~S'for="[1::d34d]"')
assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for="[1::]"')
end
test "IPv6 with :: at position 2" do
assert [{0x0001, 0x023, 0x0000, 0x7890, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[1:23::7890:a:bc:def:d34d]"')
assert [{0x0001, 0x023, 0x0000, 0x7890, 0x000a, 0x00bc, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[1:23::7890:a:bc:1.2.3.4]"')
assert [{0x0001, 0x023, 0x0000, 0x0000, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[1:23::a:bc:def:d34d]"')
assert [{0x0001, 0x023, 0x0000, 0x0000, 0x000a, 0x00bc, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[1:23::a:bc:1.2.3.4]"')
assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x00bc, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[1:23::bc:def:d34d]"')
assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x00bc, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[1:23::bc:1.2.3.4]"')
assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x0000, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[1:23::def:d34d]"')
assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x0000, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[1:23::1.2.3.4]"')
assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xd34d}] == Forwarded.parse(~S'for="[1:23::d34d]"')
assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for="[1:23::]"')
end
test "IPv6 with :: at position 3" do
assert [{0x0001, 0x023, 0x0456, 0x0000, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[1:23:456::a:bc:def:d34d]"')
assert [{0x0001, 0x023, 0x0456, 0x0000, 0x000a, 0x00bc, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[1:23:456::a:bc:1.2.3.4]"')
assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x00bc, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[1:23:456::bc:def:d34d]"')
assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x00bc, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[1:23:456::bc:1.2.3.4]"')
assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x0000, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[1:23:456::def:d34d]"')
assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x0000, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[1:23:456::1.2.3.4]"')
assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x0000, 0x0000, 0xd34d}] == Forwarded.parse(~S'for="[1:23:456::d34d]"')
assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for="[1:23:456::]"')
end
test "IPv6 with :: at position 4" do
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x00bc, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[1:23:456:7890::bc:def:d34d]"')
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x00bc, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[1:23:456:7890::bc:1.2.3.4]"')
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x0000, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[1:23:456:7890::def:d34d]"')
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x0000, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[1:23:456:7890::1.2.3.4]"')
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x0000, 0x0000, 0xd34d}] == Forwarded.parse(~S'for="[1:23:456:7890::d34d]"')
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for="[1:23:456:7890::]"')
end
test "IPv6 with :: at position 5" do
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000a, 0x0000, 0x0def, 0xd34d}] == Forwarded.parse(~S'for="[1:23:456:7890:a::def:d34d]"')
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000a, 0x0000, 0x0102, 0x0304}] == Forwarded.parse(~S'for="[1:23:456:7890:a::1.2.3.4]"')
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000a, 0x0000, 0x0000, 0xd34d}] == Forwarded.parse(~S'for="[1:23:456:7890:a::d34d]"')
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000a, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for="[1:23:456:7890:a::]"')
end
test "IPv6 with :: at position 6" do
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0000, 0xd34d}] == Forwarded.parse(~S'for="[1:23:456:7890:a:bc::d34d]"')
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0000, 0x0000}] == Forwarded.parse(~S'for="[1:23:456:7890:a:bc::]"')
end
test "IPv6 with leading zeroes" do
assert [{0x0000, 0x0001, 0x0002, 0x0003, 0x0000, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for="[0:01:002:0003:0000::]"')
assert [{0x000a, 0x001a, 0x002a, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for="[0a:01a:002a::]"')
assert [{0x00ab, 0x01ab, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for="[0ab:01ab::]"')
assert [{0x0abc, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for="[0abc::]"')
end
test "IPv6 with mixed case" do
assert [{0xabcd, 0xabcd, 0xabcd, 0xabcd, 0xabcd, 0xabcd, 0xabcd, 0xabcd}] == Forwarded.parse(~S'for="[abcd:abcD:abCd:abCD:aBcd:aBcD:aBCd:aBCD]"')
assert [{0xabcd, 0xabcd, 0xabcd, 0xabcd, 0xabcd, 0xabcd, 0xabcd, 0xabcd}] == Forwarded.parse(~S'for="[Abcd:AbcD:AbCd:AbCD:ABcd:ABcD:ABCd:ABCD]"')
end
test "semicolons" do
assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for=1.2.3.4;proto=http;by=2.3.4.5')
assert [{1, 2, 3, 4}] == Forwarded.parse(~S'proto=http;for=1.2.3.4;by=2.3.4.5')
assert [{1, 2, 3, 4}] == Forwarded.parse(~S'proto=http;by=2.3.4.5;for=1.2.3.4')
assert [] == Forwarded.parse(~S'for=1.2.3.4proto=http;by=2.3.4.5')
assert [] == Forwarded.parse(~S'proto=httpfor=1.2.3.4;by=2.3.4.5')
assert [] == Forwarded.parse(~S'proto=http;by=2.3.4.5for=1.2.3.4')
assert [] == Forwarded.parse(~S'for=1.2.3.4;proto=http;by=2.3.4.5;')
assert [] == Forwarded.parse(~S'proto=http;for=1.2.3.4;by=2.3.4.5;')
assert [] == Forwarded.parse(~S'proto=http;by=2.3.4.5;for=1.2.3.4;')
assert [] == Forwarded.parse(~S'for=1.2.3.4;proto=http;for=2.3.4.5')
assert [] == Forwarded.parse(~S'for=1.2.3.4;for=2.3.4.5;proto=http')
assert [] == Forwarded.parse(~S'proto=http;for=1.2.3.4;for=2.3.4.5')
end
test "parameters other than `for`" do
assert [] == Forwarded.parse(~S'by=1.2.3.4')
assert [] == Forwarded.parse(~S'host=example.com')
assert [] == Forwarded.parse(~S'proto=http')
assert [] == Forwarded.parse(~S'by=1.2.3.4;proto=http;host=example.com')
end
test "bad whitespace" do
assert [] == Forwarded.parse(~S'for= 1.2.3.4')
assert [] == Forwarded.parse(~S'for = 1.2.3.4')
assert [] == Forwarded.parse(~S'for=1.2.3.4; proto=http')
assert [] == Forwarded.parse(~S'for=1.2.3.4 ;proto=http')
assert [] == Forwarded.parse(~S'for=1.2.3.4 ; proto=http')
assert [] == Forwarded.parse(~S'proto=http; for=1.2.3.4')
assert [] == Forwarded.parse(~S'proto=http ;for=1.2.3.4')
assert [] == Forwarded.parse(~S'proto=http ; for=1.2.3.4')
end
test "commas" do
assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(~S'for=1.2.3.4, for=2.3.4.5')
assert [{1, 2, 3, 4}, {0, 0, 0, 0, 2, 3, 4, 5}] == Forwarded.parse(~S'for=1.2.3.4, for="[::2:3:4:5]"')
assert [{1, 2, 3, 4}, {0, 0, 0, 0, 2, 3, 4, 5}] == Forwarded.parse(~S'for=1.2.3.4, for="[::2:3:4:5]"')
assert [{0, 0, 0, 0, 1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(~S'for="[::1:2:3:4]", for=2.3.4.5')
assert [{0, 0, 0, 0, 1, 2, 3, 4}, {0, 0, 0, 0, 2, 3, 4, 5}] == Forwarded.parse(~S'for="[::1:2:3:4]", for="[::2:3:4:5]"')
end
test "optional whitespace" do
assert [{1, 2, 3, 4}, {2, 3, 4, 5}, {3, 4, 5, 6}, {4, 5, 6, 7}, {5, 6, 7, 8}] == Forwarded.parse("for=1.2.3.4,for=2.3.4.5,\sfor=3.4.5.6\s,for=4.5.6.7\s,\sfor=5.6.7.8")
assert [{1, 2, 3, 4}, {2, 3, 4, 5}, {3, 4, 5, 6}, {4, 5, 6, 7}, {5, 6, 7, 8}] == Forwarded.parse("for=1.2.3.4,for=2.3.4.5,\tfor=3.4.5.6\t,for=4.5.6.7\t,\tfor=5.6.7.8")
assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse("for=1.2.3.4\s\s,\s\sfor=2.3.4.5")
assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse("for=1.2.3.4\s\s,\s\tfor=2.3.4.5")
assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse("for=1.2.3.4\s\s,\t\sfor=2.3.4.5")
assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse("for=1.2.3.4\s\s,\t\tfor=2.3.4.5")
assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse("for=1.2.3.4\s\t,\s\sfor=2.3.4.5")
assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse("for=1.2.3.4\s\t,\s\tfor=2.3.4.5")
assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse("for=1.2.3.4\s\t,\t\sfor=2.3.4.5")
assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse("for=1.2.3.4\s\t,\t\tfor=2.3.4.5")
assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse("for=1.2.3.4\t\s,\s\sfor=2.3.4.5")
assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse("for=1.2.3.4\t\s,\s\tfor=2.3.4.5")
assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse("for=1.2.3.4\t\s,\t\sfor=2.3.4.5")
assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse("for=1.2.3.4\t\s,\t\tfor=2.3.4.5")
assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse("for=1.2.3.4\t\t,\s\sfor=2.3.4.5")
assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse("for=1.2.3.4\t\t,\s\tfor=2.3.4.5")
assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse("for=1.2.3.4\t\t,\t\sfor=2.3.4.5")
assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse("for=1.2.3.4\t\t,\t\tfor=2.3.4.5")
assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse("for=1.2.3.4\t\s\s\s\s\t\s\t\s\t,\t\s\s\t\tfor=2.3.4.5")
end
test "commas and semicolons" do
assert [{1, 2, 3, 4}, {0, 0, 0, 0, 2, 3, 4, 5}, {3, 4, 5, 6}, {0, 0, 0, 0, 4, 5, 6, 7}] == Forwarded.parse(~S'for=1.2.3.4, for="[::2:3:4:5]";proto=http;host=example.com, proto=http;for=3.4.5.6;by=127.0.0.1, proto=http;host=example.com;for="[::4:5:6:7]"')
end
end
end
diff --git a/test/remote_ip/headers/generic_test.exs b/test/remote_ip/headers/generic_test.exs
index d979a99..4c8e102 100644
--- a/test/remote_ip/headers/generic_test.exs
+++ b/test/remote_ip/headers/generic_test.exs
@@ -1,150 +1,152 @@
defmodule RemoteIp.Headers.GenericTest do
use ExUnit.Case, async: true
alias RemoteIp.Headers.Generic
+ doctest Generic
+
describe "parsing" do
test "bad IPs" do
assert [] == Generic.parse("")
assert [] == Generic.parse(" ")
assert [] == Generic.parse("not_an_ip")
assert [] == Generic.parse("unknown")
end
test "bad IPv4" do
assert [] == Generic.parse("1")
assert [] == Generic.parse("1.2")
assert [] == Generic.parse("1.2.3")
assert [] == Generic.parse("1000.2.3.4")
assert [] == Generic.parse("1.2000.3.4")
assert [] == Generic.parse("1.2.3000.4")
assert [] == Generic.parse("1.2.3.4000")
assert [] == Generic.parse("1abc.2.3.4")
assert [] == Generic.parse("1.2abc.3.4")
assert [] == Generic.parse("1.2.3.4abc")
assert [] == Generic.parse("1.2.3abc.4")
assert [] == Generic.parse("1.2.3.4abc")
assert [] == Generic.parse("1.2.3.4.5")
end
test "bad IPv6" do
assert [] == Generic.parse("1:")
assert [] == Generic.parse("1:2")
assert [] == Generic.parse("1:2:3")
assert [] == Generic.parse("1:2:3:4")
assert [] == Generic.parse("1:2:3:4:5")
assert [] == Generic.parse("1:2:3:4:5:6")
assert [] == Generic.parse("1:2:3:4:5:6:7")
assert [] == Generic.parse("1:2:3:4:5:6:7:8:")
assert [] == Generic.parse("1:2:3:4:5:6:7:8:9")
assert [] == Generic.parse("1:::2:3:4:5:6:7:8")
assert [] == Generic.parse("a:b:c:d:e:f::g")
end
test "IPv4" do
assert [{1, 2, 3, 4}] == Generic.parse("1.2.3.4")
assert [{1, 2, 3, 4}] == Generic.parse(" 1.2.3.4 ")
end
test "IPv6 without ::" do
assert [{0x0001, 0x0023, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Generic.parse("1:23:456:7890:a:bc:def:d34d")
assert [{0x0001, 0x0023, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0102, 0x0304}] == Generic.parse("1:23:456:7890:a:bc:1.2.3.4")
end
test "IPv6 with :: at position 0" do
assert [{0x0000, 0x0023, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Generic.parse("::23:456:7890:a:bc:def:d34d")
assert [{0x0000, 0x0023, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0102, 0x0304}] == Generic.parse("::23:456:7890:a:bc:1.2.3.4")
assert [{0x0000, 0x0000, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Generic.parse("::456:7890:a:bc:def:d34d")
assert [{0x0000, 0x0000, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0102, 0x0304}] == Generic.parse("::456:7890:a:bc:1.2.3.4")
assert [{0x0000, 0x0000, 0x0000, 0x7890, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Generic.parse("::7890:a:bc:def:d34d")
assert [{0x0000, 0x0000, 0x0000, 0x7890, 0x000a, 0x00bc, 0x0102, 0x0304}] == Generic.parse("::7890:a:bc:1.2.3.4")
assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Generic.parse("::a:bc:def:d34d")
assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x000a, 0x00bc, 0x0102, 0x0304}] == Generic.parse("::a:bc:1.2.3.4")
assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x00bc, 0x0def, 0xd34d}] == Generic.parse("::bc:def:d34d")
assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x00bc, 0x0102, 0x0304}] == Generic.parse("::bc:1.2.3.4")
assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0def, 0xd34d}] == Generic.parse("::def:d34d")
assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0102, 0x0304}] == Generic.parse("::1.2.3.4")
assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xd34d}] == Generic.parse("::d34d")
assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Generic.parse("::")
end
test "IPv6 with :: at position 1" do
assert [{0x0001, 0x000, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Generic.parse("1::456:7890:a:bc:def:d34d")
assert [{0x0001, 0x000, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0102, 0x0304}] == Generic.parse("1::456:7890:a:bc:1.2.3.4")
assert [{0x0001, 0x000, 0x0000, 0x7890, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Generic.parse("1::7890:a:bc:def:d34d")
assert [{0x0001, 0x000, 0x0000, 0x7890, 0x000a, 0x00bc, 0x0102, 0x0304}] == Generic.parse("1::7890:a:bc:1.2.3.4")
assert [{0x0001, 0x000, 0x0000, 0x0000, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Generic.parse("1::a:bc:def:d34d")
assert [{0x0001, 0x000, 0x0000, 0x0000, 0x000a, 0x00bc, 0x0102, 0x0304}] == Generic.parse("1::a:bc:1.2.3.4")
assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x00bc, 0x0def, 0xd34d}] == Generic.parse("1::bc:def:d34d")
assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x00bc, 0x0102, 0x0304}] == Generic.parse("1::bc:1.2.3.4")
assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0def, 0xd34d}] == Generic.parse("1::def:d34d")
assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0102, 0x0304}] == Generic.parse("1::1.2.3.4")
assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xd34d}] == Generic.parse("1::d34d")
assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Generic.parse("1::")
end
test "IPv6 with :: at position 2" do
assert [{0x0001, 0x023, 0x0000, 0x7890, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Generic.parse("1:23::7890:a:bc:def:d34d")
assert [{0x0001, 0x023, 0x0000, 0x7890, 0x000a, 0x00bc, 0x0102, 0x0304}] == Generic.parse("1:23::7890:a:bc:1.2.3.4")
assert [{0x0001, 0x023, 0x0000, 0x0000, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Generic.parse("1:23::a:bc:def:d34d")
assert [{0x0001, 0x023, 0x0000, 0x0000, 0x000a, 0x00bc, 0x0102, 0x0304}] == Generic.parse("1:23::a:bc:1.2.3.4")
assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x00bc, 0x0def, 0xd34d}] == Generic.parse("1:23::bc:def:d34d")
assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x00bc, 0x0102, 0x0304}] == Generic.parse("1:23::bc:1.2.3.4")
assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x0000, 0x0def, 0xd34d}] == Generic.parse("1:23::def:d34d")
assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x0000, 0x0102, 0x0304}] == Generic.parse("1:23::1.2.3.4")
assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xd34d}] == Generic.parse("1:23::d34d")
assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Generic.parse("1:23::")
end
test "IPv6 with :: at position 3" do
assert [{0x0001, 0x023, 0x0456, 0x0000, 0x000a, 0x00bc, 0x0def, 0xd34d}] == Generic.parse("1:23:456::a:bc:def:d34d")
assert [{0x0001, 0x023, 0x0456, 0x0000, 0x000a, 0x00bc, 0x0102, 0x0304}] == Generic.parse("1:23:456::a:bc:1.2.3.4")
assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x00bc, 0x0def, 0xd34d}] == Generic.parse("1:23:456::bc:def:d34d")
assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x00bc, 0x0102, 0x0304}] == Generic.parse("1:23:456::bc:1.2.3.4")
assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x0000, 0x0def, 0xd34d}] == Generic.parse("1:23:456::def:d34d")
assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x0000, 0x0102, 0x0304}] == Generic.parse("1:23:456::1.2.3.4")
assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x0000, 0x0000, 0xd34d}] == Generic.parse("1:23:456::d34d")
assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Generic.parse("1:23:456::")
end
test "IPv6 with :: at position 4" do
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x00bc, 0x0def, 0xd34d}] == Generic.parse("1:23:456:7890::bc:def:d34d")
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x00bc, 0x0102, 0x0304}] == Generic.parse("1:23:456:7890::bc:1.2.3.4")
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x0000, 0x0def, 0xd34d}] == Generic.parse("1:23:456:7890::def:d34d")
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x0000, 0x0102, 0x0304}] == Generic.parse("1:23:456:7890::1.2.3.4")
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x0000, 0x0000, 0xd34d}] == Generic.parse("1:23:456:7890::d34d")
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x0000, 0x0000, 0x0000}] == Generic.parse("1:23:456:7890::")
end
test "IPv6 with :: at position 5" do
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000a, 0x0000, 0x0def, 0xd34d}] == Generic.parse("1:23:456:7890:a::def:d34d")
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000a, 0x0000, 0x0102, 0x0304}] == Generic.parse("1:23:456:7890:a::1.2.3.4")
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000a, 0x0000, 0x0000, 0xd34d}] == Generic.parse("1:23:456:7890:a::d34d")
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000a, 0x0000, 0x0000, 0x0000}] == Generic.parse("1:23:456:7890:a::")
end
test "IPv6 with :: at position 6" do
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0000, 0xd34d}] == Generic.parse("1:23:456:7890:a:bc::d34d")
assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000a, 0x00bc, 0x0000, 0x0000}] == Generic.parse("1:23:456:7890:a:bc::")
end
test "IPv6 with leading zeroes" do
assert [{0x0000, 0x0001, 0x0002, 0x0003, 0x0000, 0x0000, 0x0000, 0x0000}] == Generic.parse("0:01:002:0003:0000::")
assert [{0x000a, 0x001a, 0x002a, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Generic.parse("0a:01a:002a::")
assert [{0x00ab, 0x01ab, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Generic.parse("0ab:01ab::")
assert [{0x0abc, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Generic.parse("0abc::")
end
test "IPv6 with mixed case" do
assert [{0xabcd, 0xabcd, 0xabcd, 0xabcd, 0xabcd, 0xabcd, 0xabcd, 0xabcd}] == Generic.parse("abcd:abcD:abCd:abCD:aBcd:aBcD:aBCd:aBCD")
assert [{0xabcd, 0xabcd, 0xabcd, 0xabcd, 0xabcd, 0xabcd, 0xabcd, 0xabcd}] == Generic.parse("Abcd:AbcD:AbCd:AbCD:ABcd:ABcD:ABCd:ABCD")
end
test "commas with optional whitespace" do
assert [{127, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 1}] == Generic.parse("127.0.0.1,::1")
assert [{127, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 1}] == Generic.parse("127.0.0.1,\s::1")
assert [{127, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 1}] == Generic.parse("127.0.0.1\s,::1")
assert [{127, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 1}] == Generic.parse("127.0.0.1\s,\s::1")
assert [{127, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 1}] == Generic.parse("\s\t\s\t127.0.0.1\t\t\s\s,\s\t\t\s::1\t")
end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 29, 3:46 PM (1 d, 18 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41251
Default Alt Text
(44 KB)

Event Timeline