MailglassInbound.MIME (MailglassInbound v0.2.0)

Copy Markdown View Source

Standalone, never-raising RFC 5322 MIME parser.

parse/1 turns a canonical raw MIME body into a stable internal representation, or returns a structured MailglassInbound.MIMEError. It never raises (MIME-04): the underlying gen_smtp mimemail decoder escapes through three mechanisms (erlang:error, throw, and :exit/:undef from iconv), all of which are absorbed by the Mailglass.OptionalDeps.GenSmtp gateway seam and translated into {:error, %MailglassInbound.MIMEError{}}.

Contract

@spec parse(binary()) :: {:ok, repr} | {:error, MailglassInbound.MIMEError.t()}
  • {:ok, repr}repr is %{headers: ..., parts: ..., attachments: ..., inline: ...} (see Internal representation below).
  • {:error, %MIMEError{type: :inbound_mime_invalid}} — the raw source could not be parsed (any of the three escape mechanisms, or the representation exceeded :max_depth). :cause carries the tagged gateway failure; :context carries %{byte_size: byte_size(raw)}.
  • {:error, %MIMEError{type: :gen_smtp_unavailable}} — the optional gen_smtp dependency is not loaded; MIME parsing is unavailable (MIME-02 degraded fallback).

Note

The :max_depth option bounds the depth of the internal representation walkcollect_leaves/3 re-walking the already-decoded tree — and gives a deterministic structured ceiling on what the pipeline iterates. It does not limit the underlying :mimemail decoder recursion: decode_and_build/2 calls the decoder first, which fully parses to any depth before the guard ever runs. So :max_depth does not by itself defend against provider-fed deep-nesting (boundary-bomb) DoS.

Provider-fed DoS hardening — a real decoder-level recursion limit — is a Phase 46 concern that will plug into this same seam. The guard is kept because it is that seam and because it bounds the representation the pipeline iterates.

Internal representation

parse/1 returns a map with four keys:

  • :headers — the top-level decoded header proplist ([{binary, binary}]).
  • :parts — a flattened list of non-attachment, non-inline leaf parts. Each part is %{type, subtype, headers, params, body} where body is the raw (untranscoded) leaf bytes.
  • :attachments — leaf parts whose Content-Disposition is attachment. Each carries a resolved :filename (disposition_params["filename"] || content_type_params["name"]).
  • :inline — leaf parts whose Content-Disposition is explicitly inline and which carry a filename (typically Content-ID images). Inline text leaves without a filename land in :parts.

Standalone — not wired into any provider path

This parser is the producer only (D-45-18). It is NOT wired into the working JSON-based Postmark/SendGrid normalize paths in this phase. Phase 46 (Mailgun/SES raw-MIME ingress) is the first consumer.

Encoding note

The gateway passes {:encoding, :none} to the decoder (gen_smtp does not bundle iconv), so leaf :body bytes are not transcoded to UTF-8. A consumer that needs UTF-8 text must transcode using the part's declared charset (content_type_params["charset"]). Flagged for Phase 46.

Summary

Types

A single decoded leaf or container part in the internal representation.

The stable internal representation returned by parse/1.

Functions

Parses a raw RFC 5322 MIME body. See the moduledoc for the full contract.

Parses a raw RFC 5322 MIME body with options.

Types

part()

@type part() :: %{
  type: binary(),
  subtype: binary(),
  headers: [{binary(), binary()}],
  params: map(),
  body: term()
}

A single decoded leaf or container part in the internal representation.

repr()

@type repr() :: %{
  headers: [{binary(), binary()}],
  parts: [part()],
  attachments: [part()],
  inline: [part()]
}

The stable internal representation returned by parse/1.

Functions

parse(raw)

(since 0.2.0)
@spec parse(binary()) :: {:ok, repr()} | {:error, MailglassInbound.MIMEError.t()}

Parses a raw RFC 5322 MIME body. See the moduledoc for the full contract.

Equivalent to parse(raw, []).

parse(raw, opts)

(since 0.2.0)
@spec parse(
  binary(),
  keyword()
) :: {:ok, repr()} | {:error, MailglassInbound.MIMEError.t()}

Parses a raw RFC 5322 MIME body with options.

Options

  • :max_depth — maximum multipart nesting depth before the representation-depth guard trips and returns :inbound_mime_invalid (default 100). See the moduledoc note on what this guard does and does not bound.
  • :gen_smtp_available? — overrides the gateway availability check (testing seam for the MIME-02 degraded path). Defaults to Mailglass.OptionalDeps.GenSmtp.available?/0.