Skip to content
LogoLogo

@portable/mime-headers

Functions

MimeHeaders.formatAddresses

Formats mailboxes and groups as an address list, the reverse of parseAddresses. A name that needs quoting is quoted, or written as encoded words when it contains characters outside printable ASCII. Fold the whole header line afterwards.

function formatAddresses(
  addresses: readonly Address[],
  options: formatAddresses.Options = {},
): string;
OptionTypeDefaultDescription
charsetimport("./words.js").CharsetEncoderUTF-8Charset of the encoded bytes.
wordEncodingimport("./words.js").WordEncoding'Q'

Throws

  • InvalidInputError When an address doesn't parse as one addr-spec.
import * as MimeHeaders from '@portable/mime-headers';
 
MimeHeaders.formatAddresses([
  { name: 'Reinman, Andris', address: 'andris@example.com' },
  { name: 'Jõgeva', address: 'j@example.com' },
]);
// '"Reinman, Andris" <andris@example.com>, =?UTF-8?Q?J=C3=B5geva?= <j@example.com>'

MimeHeaders.parseAddresses

Parses an address list, such as the value of a To or From header. Display names are unquoted and their encoded words decoded, and a comment is used as the name when the mailbox has no other name. Without strict, malformed input never throws: each entry gets the first address written in it, and the rest of the entry's text becomes the name.

function parseAddresses(
  input: string | Uint8Array,
  options: parseAddresses.Options = {},
): Address[];
OptionTypeDefaultDescription
strictbooleanfalseThrow InvalidInputError unless the input is a valid RFC 5322 address list, with the obsolete syntax of section 4 and the UTF-8 of RFC 6532 allowed.

Throws

  • InvalidInputError With strict: true, when the input isn't a valid address list.
import * as MimeHeaders from '@portable/mime-headers';
 
MimeHeaders.parseAddresses(
  '"Reinman, Andris" <andris@example.com>, team: a@example.com;',
);
// [
//   { name: 'Reinman, Andris', address: 'andris@example.com' },
//   { name: 'team', group: [{ name: '', address: 'a@example.com' }] },
// ]

MimeHeaders.parseMailboxes

Parses an address list as parseAddresses does, and returns the members of each group in place of the group.

function parseMailboxes(
  input: string | Uint8Array,
  options: parseMailboxes.Options = {},
): Mailbox[];
OptionTypeDefaultDescription
strictbooleanfalseThrow InvalidInputError unless the input is a valid RFC 5322 address list, with the obsolete syntax of section 4 and the UTF-8 of RFC 6532 allowed.

Throws

  • InvalidInputError With strict: true, when the input isn't a valid address list.
import * as MimeHeaders from '@portable/mime-headers';
 
MimeHeaders.parseMailboxes(
  'andris@example.com, team: a@example.com, b@example.com;',
);
// [
//   { name: '', address: 'andris@example.com' },
//   { name: '', address: 'a@example.com' },
//   { name: '', address: 'b@example.com' },
// ]

MimeHeaders.fold

Folds a header line (RFC 5322 section 2.2.3) by inserting CRLF before whitespace, so that each line fits in lineLength where the text has whitespace to break at. A word longer than a line is kept whole, and line breaks already in the text are kept. unfold reverses it exactly.

function fold(
  input: string | Uint8Array,
  options: fold.Options = {},
): string;
OptionTypeDefaultDescription
lineLengthnumber76Longest line, not counting the line break. RFC 5322 recommends 78 at most.
import * as MimeHeaders from '@portable/mime-headers';
 
MimeHeaders.fold('Subject: ' + MimeHeaders.encodeWords(subject));
MimeHeaders.fold('To: a@example.com, b@example.com', {
  lineLength: 20,
});
// 'To: a@example.com,\r\n b@example.com'

MimeHeaders.parseHeaders

Splits a header block into fields. Reading stops at the first empty line, so a whole message can be passed in, and the body isn't read. Each value is unfolded, with the whitespace around it removed, and its encoded words are left as they are, because whether to decode them depends on the field. Names keep their case, and a line without a colon is skipped.

function parseHeaders(
  input: string | Uint8Array,
  options: parseHeaders.Options = {},
): Header[];
OptionTypeDefaultDescription
maxLengthnumberInfinityLongest header block, in UTF-16 code units, up to the end of its last line. A longer block throws MaxLengthError.

Throws

  • MaxLengthError When the header block is longer than maxLength.
import * as MimeHeaders from '@portable/mime-headers';
 
MimeHeaders.parseHeaders(
  'Subject: Hello\r\n world\r\nTo: a@example.com\r\n\r\nBody',
);
// [{ name: 'Subject', value: 'Hello world' }, { name: 'To', value: 'a@example.com' }]

MimeHeaders.unfold

Removes the line breaks that fold a header (RFC 5322 section 2.2.3): every CRLF followed by a space or a tab. The whitespace stays, so unfold(fold(line)) is line. A bare LF or CR in front of whitespace is removed as well, since mail stored on disk often uses them.

function unfold(input: string | Uint8Array): string;
import * as MimeHeaders from '@portable/mime-headers';
 
MimeHeaders.unfold('Subject: line one\r\n line two'); // 'Subject: line one line two'

MimeHeaders.formatValue

Formats a header value with parameters, the reverse of parseValue. A parameter value is quoted when it contains a special character. A value with characters outside printable ASCII, or one that would read as an encoded word, is percent-encoded as UTF-8 (RFC 2231), and a value too long for one line is split into numbered sections. Fold the whole header line afterwards.

function formatValue(input: Value): string;

Throws

  • InvalidInputError When value contains a control character or a character outside ASCII, or a parameter name isn't an RFC 2231 attribute (a token without *, ' or %).
import * as MimeHeaders from '@portable/mime-headers';
 
MimeHeaders.formatValue({
  value: 'attachment',
  params: { filename: 'report 2024.pdf' },
});
// 'attachment; filename="report 2024.pdf"'
MimeHeaders.formatValue({
  value: 'attachment',
  params: { filename: 'Jõgeva.txt' },
});
// "attachment; filename*0*=utf-8''J%C3%B5geva.txt"

MimeHeaders.parseValue

Parses a header value with parameters (RFC 2045 section 5.1), such as text/plain; charset=utf-8. Parameter names are lowercased. Values are unquoted and fully decoded: RFC 2231 continuations (name*0*=) are joined and decoded from their charset, and RFC 2047 encoded words in plain values are decoded, as mail clients send them in file names. When a parameter has both forms, the RFC 2231 value is used (RFC 6266 section 4.3). Never throws.

function parseValue(input: string | Uint8Array): Value;
import * as MimeHeaders from '@portable/mime-headers';
 
MimeHeaders.parseValue('text/plain; CHARSET="utf-8"; format=flowed');
// { value: 'text/plain', params: { charset: 'utf-8', format: 'flowed' } }
MimeHeaders.parseValue("attachment; filename*=UTF-8''J%C3%B5geva.txt");
// { value: 'attachment', params: { filename: 'Jõgeva.txt' } }

MimeHeaders.decodeWords

Decodes the RFC 2047 encoded words in a header value and leaves other text as it is. Words separated only by whitespace are joined, and adjacent words in the same charset are decoded together, so a character split across two words still decodes. A charset that TextDecoder doesn't know is read as UTF-8. Malformed input never throws.

function decodeWords(input: string | Uint8Array): string;
import * as MimeHeaders from '@portable/mime-headers';
 
MimeHeaders.decodeWords('Hello =?UTF-8?Q?J=C3=B5geva?='); // 'Hello Jõgeva'
MimeHeaders.decodeWords('=?UTF-8?B?w7U=?= =?UTF-8?B?w7U=?='); // 'õõ'

MimeHeaders.encodeWord

Encodes all of text as RFC 2047 encoded words, separated by spaces. The result is safe in any header, including inside a display name.

function encodeWord(
  input: string | Uint8Array,
  options: encodeWord.Options = {},
): string;
OptionTypeDefaultDescription
charsetCharsetEncoderUTF-8Charset of the encoded bytes.
wordEncodingWordEncoding'Q'
wordLengthnumber75Longest encoded word, =? and ?= included. Longer text is split into several words that each contain whole characters. RFC 2047 allows 75. A word with a single character may be longer when the limit is too small for it. Infinity writes one word.
import * as MimeHeaders from '@portable/mime-headers';
 
MimeHeaders.encodeWord('See on õhin test'); // '=?UTF-8?Q?See_on_=C3=B5hin_test?='
MimeHeaders.encodeWord('See on õhin test', { wordEncoding: 'B' }); // '=?UTF-8?B?U2VlIG9uIMO1aGluIHRlc3Q=?='

MimeHeaders.encodeWords

Encodes the part of text that can't appear in a header as is, and leaves the rest readable. The encoded part runs from the first word that needs encoding to the last one: a word with a character outside printable ASCII, or one that would read as an encoded word.

function encodeWords(
  input: string | Uint8Array,
  options: encodeWords.Options = {},
): string;
OptionTypeDefaultDescription
charsetCharsetEncoderUTF-8Charset of the encoded bytes.
wordEncodingWordEncoding'Q'
wordLengthnumber75Longest encoded word, =? and ?= included. Longer text is split into several words that each contain whole characters. RFC 2047 allows 75. A word with a single character may be longer when the limit is too small for it. Infinity writes one word.
import * as MimeHeaders from '@portable/mime-headers';
 
MimeHeaders.encodeWords('Tere õhtust, Mari'); // 'Tere =?UTF-8?Q?=C3=B5htust=2C?= Mari'
MimeHeaders.encodeWords('plain ascii'); // 'plain ascii'

Errors

MimeHeaders.InvalidInputError

Thrown by parseAddresses with strict: true when the input isn't a valid RFC 5322 address list, and by formatValue and formatAddresses when given something that can't be written in a header, such as a line break in a parameter name or an address that doesn't parse.

class InvalidInputError extends Errors.InvalidInputError {
  readonly kind: string;
}
import * as MimeHeaders from '@portable/mime-headers';
 
try {
  MimeHeaders.parseAddresses('not an address', { strict: true });
} catch (error) {
  if (error instanceof MimeHeaders.InvalidInputError) {
    console.log(error.message);
  }
}

MimeHeaders.MaxLengthError

Thrown by parseHeaders when the header block is longer than maxLength.

class MaxLengthError extends Errors.MaxLengthError {
  constructor(options: Errors.MaxLengthError.Options);
  readonly kind: string;
}
import * as MimeHeaders from '@portable/mime-headers';
 
try {
  MimeHeaders.parseHeaders(message, { maxLength: 64 * 1024 });
} catch (error) {
  if (error instanceof MimeHeaders.MaxLengthError) {
    console.log(error.maxLength);
  }
}

Types

MimeHeaders.Address

One entry of an address list: a mailbox or a group.

type Address = Mailbox | Group;

MimeHeaders.Group

A named group of mailboxes (RFC 5322 section 3.4), as in Team: a@example.com, b@example.com;.

type Group = {
  readonly name: string;
  readonly group: readonly Mailbox[];
};

MimeHeaders.Mailbox

One mailbox. address is '' when the input has no address, as in Undisclosed recipients.

type Mailbox = {
  readonly name: string;
  readonly address: string;
};

MimeHeaders.Header

One header field: its name as written and its unfolded value.

type Header = {
  readonly name: string;
  readonly value: string;
};

MimeHeaders.Value

A header value with parameters, such as Content-Type or Content-Disposition.

type Value = {
  readonly value: string;
  readonly params: Readonly<Record<string, string>>;
};

MimeHeaders.CharsetEncoder

A charset to write encoded words in, for mail that has to use one other than UTF-8. encode is called once per encoded word, so the bytes from each call must decode on their own. For a stateful charset such as ISO-2022-JP, each call's output has to return to the initial state before it ends. Every charset from @portable/charset is a CharsetEncoder.

type CharsetEncoder = {
  readonly name: string;
  readonly encode: (text: string) => Uint8Array;
};

MimeHeaders.WordEncoding

'Q' keeps ASCII letters and digits readable. 'B' is base64 and shorter for non-Latin text.

type WordEncoding =  | ;