@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;| Option | Type | Default | Description |
|---|---|---|---|
charset | import("./words.js").CharsetEncoder | UTF-8 | Charset of the encoded bytes. |
wordEncoding | import("./words.js").WordEncoding | 'Q' |
Throws
InvalidInputErrorWhen 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[];| Option | Type | Default | Description |
|---|---|---|---|
strict | boolean | false | Throw 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
InvalidInputErrorWithstrict: 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[];| Option | Type | Default | Description |
|---|---|---|---|
strict | boolean | false | Throw 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
InvalidInputErrorWithstrict: 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;| Option | Type | Default | Description |
|---|---|---|---|
lineLength | number | 76 | Longest 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[];| Option | Type | Default | Description |
|---|---|---|---|
maxLength | number | Infinity | Longest header block, in UTF-16 code units, up to the end of its last line. A longer block throws MaxLengthError. |
Throws
MaxLengthErrorWhen the header block is longer thanmaxLength.
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
InvalidInputErrorWhenvaluecontains 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;| Option | Type | Default | Description |
|---|---|---|---|
charset | CharsetEncoder | UTF-8 | Charset of the encoded bytes. |
wordEncoding | WordEncoding | 'Q' | |
wordLength | number | 75 | Longest 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;| Option | Type | Default | Description |
|---|---|---|---|
charset | CharsetEncoder | UTF-8 | Charset of the encoded bytes. |
wordEncoding | WordEncoding | 'Q' | |
wordLength | number | 75 | Longest 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 = | ;