@portable/mime-stream
Functions
MimeStream.splitHeaders
Reads a message's header block from a stream and returns it with a stream of the rest. Nothing
is decoded and the body isn't searched for parts, so splitHeaders is the cheap way to get the
bytes that DKIM signs. If the input ends before an empty line, all of it is the header block.
async function splitHeaders(
stream: ReadableStream<Uint8Array>,
options: splitHeaders.Options = {},
): Promise<splitHeaders.ReturnType>;| Option | Type | Default | Description |
|---|---|---|---|
maxHeaderBytes | number | Infinity | Longest header block, counted with the empty line that ends it. Set it, for example to 1 MiB, when the input is untrusted. |
signal | AbortSignal | Rejects with signal.reason while the header block is read, and later errors body with it. Either way the input is cancelled. |
import * as MimeStream from '@portable/mime-stream';
const { headers, body } = await MimeStream.splitHeaders(response.body!);
const bodyHash = await crypto.subtle.digest(
'SHA-256',
await new Response(body).arrayBuffer(),
);Classes
MimeStream.DecoderStream
A TransformStream that splits a raw MIME message into chunks of header blocks, bodies and
multipart structure, without decoding anything. Pipe the chunks through
MimeStream.EncoderStream to get the same bytes back, or change some chunks on the way.
class DecoderStream extends TransformStream<Uint8Array, Chunk> {
constructor(options: DecoderStream.Options = {});
}| Option | Type | Default | Description |
|---|---|---|---|
embedded | boolean | false | Read the message inside a message/rfc822 or message/global part as parts too, when it isn't base64 or quoted-printable. |
maxBoundaryLineBytes | number | Infinity | Longest boundary line, line break included. Only whitespace after the boundary can make one this long. Set it, for example to 1000, when the input is untrusted. |
maxHeaderBytes | number | Infinity | Longest header block of a part, counted with the empty line that ends it. Set it, for example to 1 MiB, when the input is untrusted. |
maxParts | number | Infinity | Most parts in a message, the root included. Set it, for example to 1000, when the input is untrusted. |
signal | AbortSignal | Errors the stream with signal.reason when aborted, which cancels the source it is piped from. |
import * as MimeStream from '@portable/mime-stream';
import * as Streams from '@portable/streams';
const chunks = response.body!.pipeThrough(
new MimeStream.DecoderStream(),
);
for await (const chunk of Streams.toAsyncIterable(chunks)) {
if (chunk.type === 'headers') {
console.log(chunk.part.contentType);
}
}MimeStream.EncoderStream
A TransformStream that writes chunks from MimeStream.DecoderStream back out as bytes. Chunks
that pass through unchanged give back the original message byte for byte.
class EncoderStream extends TransformStream<
EncoderStream.Input,
Uint8Array
> {
constructor(options: EncoderStream.Options = {});
}| Option | Type | Default | Description |
|---|---|---|---|
signal | AbortSignal | Errors the stream with signal.reason when aborted, which cancels the source it is piped from. |
import * as MimeStream from '@portable/mime-stream';
const output = response
.body!.pipeThrough(new MimeStream.DecoderStream())
.pipeThrough(new MimeStream.EncoderStream());Errors
MimeStream.MaxBoundaryLineBytesError
Thrown when a line that matches a boundary grows past maxBoundaryLineBytes, which happens only
when that much whitespace follows the boundary.
class MaxBoundaryLineBytesError extends Errors.MaxBytesError {
constructor(
options: ErrorOptions & {
maxBoundaryLineBytes: number;
},
);
readonly kind: string;
readonly maxBoundaryLineBytes: number;
}import * as MimeStream from '@portable/mime-stream';
const stream = body.pipeThrough(
new MimeStream.DecoderStream({ maxBoundaryLineBytes: 200 }),
);
// A read rejects with MimeStream.MaxBoundaryLineBytesError on '--boundary' plus 200 spaces.MimeStream.MaxHeaderBytesError
Thrown when a header block grows past maxHeaderBytes. The count includes the empty line that
ends the block. It extends Errors.MaxBytesError, so its maxBytes holds the same limit.
class MaxHeaderBytesError extends Errors.MaxBytesError {
constructor(
options: ErrorOptions & {
maxHeaderBytes: number;
},
);
readonly kind: string;
readonly maxHeaderBytes: number;
}import * as MimeStream from '@portable/mime-stream';
try {
await MimeStream.splitHeaders(response.body!, {
maxHeaderBytes: 64 * 1024,
});
} catch (error) {
if (error instanceof MimeStream.MaxHeaderBytesError) {
console.log(error.maxHeaderBytes);
}
}MimeStream.MaxPartsError
Thrown when a message has more than maxParts parts, counting the root.
class MaxPartsError extends Errors.BaseError {
constructor(
options: ErrorOptions & {
maxParts: number;
},
);
readonly kind: string;
readonly maxParts: number;
}import * as MimeStream from '@portable/mime-stream';
const stream = body.pipeThrough(
new MimeStream.DecoderStream({ maxParts: 100 }),
);
// A read rejects with MimeStream.MaxPartsError on the 101st part.Types
MimeStream.Chunk
A run of bytes from the message. Joining every chunk's bytes in order gives back the input.
headers: the header block ofpart, with the empty line that ends it.body: content of a part that has no children, still transfer-encoded.preamble,epilogue: text before the first and after the last delimiter of the multipartpart.delimiter: a boundary line of the multipartpart, with the line break before it and its own.
type Chunk = {
type: | | | | ;
part: Part;
bytes: Uint8Array;
};MimeStream.Part
One part of a message: the root, a part of a multipart, or the message inside a
message/rfc822 part.
type Part = {
parent?: Part | undefined;
headers: readonly MimeHeaders.Header[];
contentType: string;
encoding: string;
boundary?: string | undefined;
};