Parses SMTP command lines (RFC 5321 §4.1).
The line is given without its CRLF. Verbs and parameter keywords are
case-insensitive. Only printable ASCII is accepted, apart from UTF-8
characters in the arguments of MAIL, RCPT, and VRFY, for
internationalized addresses (SMTPUTF8, RFC 6531): whether they are
allowed depends on the transaction, so the session checks that. Verbs
map to atoms from a fixed table; parameter keywords stay strings,
upper-cased.
iex> Sovite.SMTP.Command.parse("MAIL FROM:<a@example.com> SIZE=1024")
{:ok, {:mail, "a@example.com", [{"SIZE", "1024"}]}}
iex> Sovite.SMTP.Command.parse("RCPT TO:<@relay.example:b@example.net>")
{:ok, {:rcpt, "b@example.net", []}}
iex> Sovite.SMTP.Command.parse("rcpt to:<Postmaster>")
{:ok, {:rcpt, "Postmaster", []}}Paths must be in angle brackets. Whitespace after FROM: and TO: is
tolerated, since common clients send it. Source routes are accepted and
ignored (RFC 5321 §4.1.1.3 and Appendix C). The mailbox is checked with
Sovite.Validators.split_mailbox/2, accepting UTF-8.
Summary
Types
XCLIENT and XFORWARD attributes: upper-cased names and decoded
values, nil for [UNAVAILABLE] and [TEMPUNAVAIL].
Why a line did not parse
What ETRN (RFC 1985) asks for: the mail queued for a domain, for a
domain and its subdomains (@example.com), or in a named queue
(#queue). Domains are lower-cased.
Functions
Parses one command line. Returns the verb, if known, with errors.
Types
XCLIENT and XFORWARD attributes: upper-cased names and decoded
values, nil for [UNAVAILABLE] and [TEMPUNAVAIL].
@type error() ::
:unrecognized
| :not_implemented
| :non_smtp
| :invalid_characters
| :syntax
| :invalid_sender
| :invalid_recipient
| :invalid_parameter
Why a line did not parse:
:unrecognized- unknown verb:not_implemented- a known SMTP verb this server does not support:non_smtp- an HTTP request or header line, as in cross-protocol attacks:invalid_characters- control characters, invalid UTF-8, or non-ASCII characters outside the arguments ofMAIL,RCPT, andVRFY:syntax- wrong arguments for the verb:invalid_sender/:invalid_recipient- bad path or mailbox:invalid_parameter- malformedKEYWORD=valueparameter
What ETRN (RFC 1985) asks for: the mail queued for a domain, for a
domain and its subdomains (@example.com), or in a named queue
(#queue). Domains are lower-cased.
@type t() :: {:ehlo, String.t()} | {:helo, String.t()} | {:lhlo, String.t()} | {:mail, String.t(), params()} | {:rcpt, String.t(), params()} | :data | :rset | :quit | {:noop, String.t()} | {:vrfy, String.t()} | {:help, String.t()} | :starttls | {:auth, mechanism :: String.t(), initial_response :: String.t() | nil} | {:xclient, attributes()} | {:xforward, attributes()} | {:bdat, size :: non_neg_integer(), last :: boolean()} | {:etrn, etrn_node()}