Current section
Files
Jump to
Current section
Files
lib/domainatrex.ex
defmodule Domainatrex do
require Logger
@moduledoc """
Documentation for Domainatrex.
"""
@fallback_local_copy Application.compile_env(
:domainatrex,
:fallback_local_copy,
"lib/public_suffix_list.dat"
)
@fetch_latest Application.compile_env(:domainatrex, :fetch_latest, true)
@public_suffix_list nil
:inets.start()
:ssl.start()
with true <- @fetch_latest,
public_suffix_list_url <-
Application.compile_env(
:domainatrex,
:public_suffix_list_url,
'https://raw.githubusercontent.com/publicsuffix/list/master/public_suffix_list.dat'
),
{:ok, {_, _, string}} <- :httpc.request(:get, {public_suffix_list_url, []}, [], []) do
@public_suffix_list to_string(string)
else
_ ->
case File.read(@fallback_local_copy) do
{:ok, string} ->
if @fetch_latest do
Logger.error(
"[Domainatrex] Could not read the public suffix list from the internet, trying to read from the backup at #{@fallback_local_copy}"
)
end
@public_suffix_list string
_ ->
Logger.error(
"[Domainatrex] Could not read the public suffix list, please make sure that you either have an internet connection or #{@fallback_local_copy} exists"
)
@public_suffix_list nil
end
end
custom_suffixes = Application.compile_env(:domainatrex, :custom_suffixes, [])
string =
if Application.compile_env(:domainatrex, :icann_only, false) or
Application.compile_env(:domainatrex, :include_private, true) == false do
@public_suffix_list
|> String.split("// ===END ICANN DOMAINS===")
|> List.first()
else
@public_suffix_list
end
suffixes =
string
|> String.split("\n")
|> Enum.reject(&(&1 == ""))
|> Enum.reject(&String.contains?(&1, "//"))
|> Enum.concat(custom_suffixes)
|> Enum.map(&String.split(&1, "."))
|> Enum.map(&Enum.reverse/1)
|> Enum.sort_by(&length/1)
|> Enum.reverse()
Enum.each(suffixes, fn suffix ->
if List.last(suffix) == "*" do
case length(suffix) do
2 ->
defp match([unquote(Enum.at(suffix, 0)), a]) do
format_response([unquote(Enum.at(suffix, 0))], [a])
end
defp match([unquote(Enum.at(suffix, 0)), a, b]) do
format_response([unquote(Enum.at(suffix, 0))], [a, b])
end
defp match([unquote(Enum.at(suffix, 0)) | _] = args) do
format_response(Enum.slice(args, 0, 2), Enum.slice(args, 2, 10))
end
3 ->
defp match([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1)), a]) do
format_response([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1))], [a])
end
defp match([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1)), a, b]) do
format_response([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1))], [a, b])
end
defp match([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1)) | _] = args) do
format_response(Enum.slice(args, 0, 3), Enum.slice(args, 3, 10))
end
4 ->
defp match([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1)), a]) do
format_response([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1))], [a])
end
defp match([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1)), a, b]) do
format_response([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1))], [a, b])
end
defp match([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1)) | _] = args) do
format_response(Enum.slice(args, 0, 4), Enum.slice(args, 4, 10))
end
5 ->
defp match([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1)), a]) do
format_response([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1))], [a])
end
defp match([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1)), a, b]) do
format_response([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1))], [a, b])
end
defp match([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1)) | _] = args) do
format_response(Enum.slice(args, 0, 5), Enum.slice(args, 5, 10))
end
6 ->
defp match([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1)), a]) do
format_response([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1))], [a])
end
defp match([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1)), a, b]) do
format_response([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1))], [a, b])
end
defp match([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1)) | _] = args) do
format_response(Enum.slice(args, 0, 6), Enum.slice(args, 6, 10))
end
end
else
case length(suffix) do
1 ->
defp match([unquote(Enum.at(suffix, 0)) | tail] = args) do
format_response([Enum.at(args, 0)], tail)
end
2 ->
defp match([unquote(Enum.at(suffix, 0)), unquote(Enum.at(suffix, 1)) | tail] = args) do
format_response([Enum.at(args, 0), Enum.at(args, 1)], tail)
end
3 ->
defp match(
[
unquote(Enum.at(suffix, 0)),
unquote(Enum.at(suffix, 1)),
unquote(Enum.at(suffix, 2)) | tail
] = args
) do
format_response([Enum.at(args, 0), Enum.at(args, 1), Enum.at(args, 2)], tail)
end
4 ->
defp match(
[
unquote(Enum.at(suffix, 0)),
unquote(Enum.at(suffix, 1)),
unquote(Enum.at(suffix, 2)),
unquote(Enum.at(suffix, 3)) | tail
] = args
) do
format_response(
[Enum.at(args, 0), Enum.at(args, 1), Enum.at(args, 2), Enum.at(args, 3)],
tail
)
end
5 ->
defp match(
[
unquote(Enum.at(suffix, 0)),
unquote(Enum.at(suffix, 1)),
unquote(Enum.at(suffix, 2)),
unquote(Enum.at(suffix, 3)),
unquote(Enum.at(suffix, 4)) | tail
] = args
) do
format_response(
[
Enum.at(args, 0),
Enum.at(args, 1),
Enum.at(args, 2),
Enum.at(args, 3),
Enum.at(args, 4)
],
tail
)
end
6 ->
defp match(
[
unquote(Enum.at(suffix, 0)),
unquote(Enum.at(suffix, 1)),
unquote(Enum.at(suffix, 2)),
unquote(Enum.at(suffix, 3)),
unquote(Enum.at(suffix, 4)),
unquote(Enum.at(suffix, 5)) | tail
] = args
) do
format_response(
[
Enum.at(args, 0),
Enum.at(args, 1),
Enum.at(args, 2),
Enum.at(args, 3),
Enum.at(args, 4),
Enum.at(args, 5)
],
tail
)
end
_ ->
{:error, "There exists a domain in the list which contains more than 5 dots: #{suffix}"}
end
end
end)
defp format_response(tld, domain) do
with [domain | subdomains] <- domain do
tld = tld |> Enum.reverse() |> Enum.join(".")
subdomains = subdomains |> Enum.reverse() |> Enum.join(".")
{:ok, %{domain: domain, subdomain: subdomains, tld: tld}}
else
_ -> {:error, "Cannot parse: invalid domain"}
end
end
@doc """
## Examples
iex> Domainatrex.parse("someone.com")
{:ok, %{domain: "someone", subdomain: "", tld: "com"}}
iex> Domainatrex.parse("blog.someone.id.au")
{:ok, %{domain: "someone", subdomain: "blog", tld: "id.au"}}
iex> Domainatrex.parse("zen.s3.amazonaws.com")
{:ok, %{domain: "s3", subdomain: "zen", tld: "amazonaws.com"}}
"""
def parse(url) do
case String.length(url) > 1 && String.contains?(url, ".") do
true ->
adjusted_url = url |> String.split(".") |> Enum.reverse()
match(adjusted_url)
_ ->
{:error, "Cannot parse: invalid domain"}
end
end
defp match(_it) do
{:error, "Cannot match: invalid domain"}
end
end