lib/exonerate/formats/email.ex

defmodule Exonerate.Formats.Email do
  @moduledoc """
  Module which provides a macro that generates special code for a duration
  filter.

  the format is governed by section 4.1.2 of RFC 5321:
  https://www.rfc-editor.org/rfc/rfc5321.txt
  """

  alias Exonerate.Cache

  @doc """
  Creates a `NimbleParsec` parser `~email/1`.

  This function returns `{:ok, ...}` if the passed string is a valid email, or
  `{:error, reason, ...}` if it is not.  See `NimbleParsec` for more
  information on the return tuples.

  The function will only be created once per module, and it is safe to call
  the macro more than once.

  ## Options:
  - `:name` (atom): the name of the function to create.  Defaults to
    `:"~email"`
  """
  defmacro filter(opts \\ []) do
    name = Keyword.get(opts, :name, :"~email")

    if Cache.register_context(__CALLER__.module, name) do
      quote do
        require Pegasus
        import NimbleParsec

        Pegasus.parser_from_string(~S"""
        EM_DIGIT <- [0-9]
        EM_HEXDIG <- [0-9A-Fa-f]
        EM_ALPHA <- [A-Za-z]

        EM_Snum <-  EM_DIGIT EM_DIGIT EM_DIGIT

        EM_IPv4_address_literal <- EM_Snum "." EM_Snum "." EM_Snum "." EM_Snum

        EM_IPv6_address_literal <- "IPv6:" EM_IPv6_addr

        EM_IPv6_addr <- EM_IPv6_full / EM_IPv6_comp / EM_IPv6v4_full / EM_IPv6v4_comp

        EM_IPv6_hex <- EM_HEXDIG EM_HEXDIG EM_HEXDIG EM_HEXDIG

        EM_IPv6_full <- EM_IPv6_hex ":" EM_IPv6_hex  ":" EM_IPv6_hex  ":" EM_IPv6_hex  ":" EM_IPv6_hex  ":" EM_IPv6_hex  ":" EM_IPv6_hex  ":" EM_IPv6_hex

        EM_IPv6_comp <- (EM_IPv6_hex (":" EM_IPv6_hex)? (":" EM_IPv6_hex)? (":" EM_IPv6_hex)? (":" EM_IPv6_hex)? (":" EM_IPv6_hex)?)? "::"
                  (EM_IPv6_hex (":" EM_IPv6_hex)? (":" EM_IPv6_hex)? (":" EM_IPv6_hex)? (":" EM_IPv6_hex)? (":" EM_IPv6_hex)?)?

        EM_IPv6v4_full <- EM_IPv6_hex ":" EM_IPv6_hex ":" EM_IPv6_hex ":" EM_IPv6_hex ":" EM_IPv6_hex ":" EM_IPv6_hex ":" EM_IPv4_address_literal

        EM_IPv6v4_comp <- (EM_IPv6_hex (":" EM_IPv6_hex)? (":" EM_IPv6_hex)? (":" EM_IPv6_hex)?)? "::"
                  (EM_IPv6_hex (":" EM_IPv6_hex)? (":" EM_IPv6_hex)? (":" EM_IPv6_hex)? ":")?
                  EM_IPv4_address_literal

        EM_Let_dig <- EM_ALPHA / EM_DIGIT

        EM_Ldh_str <- EM_Let_dig EM_Ldh_str / "-" EM_Ldh_str / EM_Let_dig

        EM_Standardized_tag <- EM_Ldh_str

        EM_dcontent <- [!-Z^-~]

        EM_General_address_literal <- EM_Standardized_tag ":" EM_dcontent?

        EM_sub_domain <- EM_Let_dig EM_Ldh_str?

        EM_Domain <- EM_sub_domain ("." EM_sub_domain)*

        EM_address_literal  <- "[" ( EM_IPv4_address_literal /
                                      EM_IPv6_address_literal /
                                      EM_General_address_literal ) "]"

        EM_atext <-   EM_ALPHA / EM_DIGIT / "!" / "#" /  "$" / "%" /  "&" / "'" / "*" / "+" / "-" / "/" / "=" / "?" / "^" / "_" / "`" / "{" / "|" / "}" / "~"

        EM_Atom           <- EM_atext+

        EM_Dot_string <- EM_Atom ("." EM_Atom)*

        EM_quoted_pairSMTP  <- "\\" / " " / [!-~]

        EM_qtextSMTP      <- " " / "!" / [#-Z] / "[" / "]" /  [^-~]

        EM_QcontentSMTP   <- EM_qtextSMTP / EM_quoted_pairSMTP

        EM_Quoted_string  <- "\"" EM_QcontentSMTP* "\""

        EM_Local_part <- EM_Dot_string

        EM_Mailbox  <-  EM_Local_part "@" ( EM_Domain / EM_address_literal )
        """)

        defparsec(unquote(name), parsec(:EM_Mailbox) |> eos)
      end
    end
  end
end