Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F115921
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
44 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Nov 29, 3:46 PM (1 d, 21 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41251
Default Alt Text
(44 KB)
Attached To
Mode
R27 remote_ip
Attached
Detach File
Event Timeline
Log In to Comment