Sovite.SMTP.DataDecoder (sovite v0.6.0)

Copy Markdown View Source

Streaming decoder for SMTP DATA content (RFC 5321 §4.1.1.4, §4.5.2).

It removes dot-stuffing and finds the end of data without buffering the message: feed it bytes as they arrive and it returns the decoded content as iodata.

Only <CRLF>.<CRLF> ends the data. A bare LF or bare CR (one not part of a CRLF pair) is handled by the policy, so it can never be read as a line ending by one MTA and as content by another (SMTP smuggling):

  • :reject - stop with {:error, :bare_lf | :bare_cr}.

  • :normalize - convert it to CRLF in the output. The line it ends cannot end the data, even if it is a single dot.

The decoded content keeps CRLF line endings, and the final CRLF before the terminating dot is part of the content.

BDAT

With dots: false it checks the content of BDAT chunks (RFC 3030) instead: there is no dot-stuffing and no terminator, the chunk sizes delimit the message, but bare line endings get the same policy. Call finish/1 after the last chunk. BINARYMIME content is binary and needs no decoder.

Summary

Functions

Decodes bytes.

Ends BDAT content: a CR left at the end is a bare one. Returns the content still held back.

Returns a decoder for content right after the 354 reply, or with dots: false for BDAT content.

Types

policy()

@type policy() :: :reject | :normalize

t()

@opaque t()

Functions

decode(decoder, bytes)

@spec decode(t(), binary()) ::
  {:more, iodata(), t()}
  | {:done, iodata(), binary()}
  | {:error, :bare_lf | :bare_cr, iodata()}

Decodes bytes.

  • {:more, content, decoder} - the data has not ended yet.
  • {:done, content, rest} - the data ended; rest is the input after the terminating .<CRLF>, such as pipelined commands.
  • {:error, reason, content} - a bare line ending under :reject. content is what was decoded before it.

finish(data_decoder)

@spec finish(t()) :: {:ok, iodata()} | {:error, :bare_cr, iodata()}

Ends BDAT content: a CR left at the end is a bare one. Returns the content still held back.

new(policy \\ :reject, opts \\ [])

@spec new(policy(), keyword()) :: t()

Returns a decoder for content right after the 354 reply, or with dots: false for BDAT content.