@zone-eu/mailsplit 5.4.10 → 5.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +56 -0
- package/lib/chunked-passthrough.d.ts +46 -0
- package/lib/chunked-passthrough.js +3 -0
- package/lib/flowed-decoder.d.ts +46 -0
- package/lib/flowed-decoder.js +4 -5
- package/lib/headers.d.ts +93 -0
- package/lib/headers.js +4 -4
- package/lib/message-joiner.d.ts +44 -0
- package/lib/message-joiner.js +6 -0
- package/lib/message-splitter.d.ts +46 -0
- package/lib/message-splitter.js +14 -1
- package/lib/mime-node.d.ts +106 -0
- package/lib/mime-node.js +5 -2
- package/lib/node-rewriter.d.ts +87 -0
- package/lib/node-rewriter.js +23 -9
- package/lib/node-streamer.d.ts +87 -0
- package/lib/node-streamer.js +29 -10
- package/lib/types.d.ts +180 -0
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -9,36 +9,92 @@ import Headers = require('./lib/headers');
|
|
|
9
9
|
import MimeNode = require('./lib/mime-node');
|
|
10
10
|
|
|
11
11
|
export {
|
|
12
|
+
/** Splits raw message bytes into MIME node and content chunks. */
|
|
12
13
|
Splitter,
|
|
14
|
+
|
|
15
|
+
/** Joins MIME node and content chunks back into raw message bytes. */
|
|
13
16
|
Joiner,
|
|
17
|
+
|
|
18
|
+
/** Rewrites body content for MIME nodes selected by a filter function. */
|
|
14
19
|
Rewriter,
|
|
20
|
+
|
|
21
|
+
/** Streams decoded body content for MIME nodes selected by a filter function. */
|
|
15
22
|
Streamer,
|
|
23
|
+
|
|
24
|
+
/** Buffers byte input and emits larger Buffer chunks. */
|
|
16
25
|
ChunkedPassthrough,
|
|
26
|
+
|
|
27
|
+
/** Parses, mutates, and rebuilds message header blocks. */
|
|
17
28
|
Headers,
|
|
29
|
+
|
|
30
|
+
/** Represents one parsed MIME node and its header/body metadata. */
|
|
18
31
|
MimeNode
|
|
19
32
|
};
|
|
20
33
|
|
|
21
34
|
export type {
|
|
35
|
+
/** Value that is either present as `T` or explicitly unavailable as `false`. */
|
|
22
36
|
Maybe,
|
|
37
|
+
|
|
38
|
+
/** Single item in an IMAP-style MIME part number. */
|
|
23
39
|
PartNumberItem,
|
|
40
|
+
|
|
41
|
+
/** IMAP-style path to a MIME part. */
|
|
24
42
|
PartNumber,
|
|
43
|
+
|
|
44
|
+
/** Options passed through to libmime instances. */
|
|
25
45
|
LibmimeOptions,
|
|
46
|
+
|
|
47
|
+
/** Configuration for `Splitter` and MIME node parsing. */
|
|
26
48
|
SplitterOptions,
|
|
49
|
+
|
|
50
|
+
/** Configuration for `ChunkedPassthrough`. */
|
|
27
51
|
ChunkedPassthroughOptions,
|
|
52
|
+
|
|
53
|
+
/** Configuration for format=flowed decoding. */
|
|
28
54
|
FlowedDecoderOptions,
|
|
55
|
+
|
|
56
|
+
/** Parsed raw header line with a normalized lookup key. */
|
|
29
57
|
HeaderLine,
|
|
58
|
+
|
|
59
|
+
/** Decoded structured header value. */
|
|
30
60
|
DecodedHeader,
|
|
61
|
+
|
|
62
|
+
/** MIME node shape emitted by `Splitter`. */
|
|
31
63
|
MimeNode,
|
|
64
|
+
|
|
65
|
+
/** Data or body bytes emitted by `Splitter`. */
|
|
32
66
|
MessageChunk,
|
|
67
|
+
|
|
68
|
+
/** Sentinel input used internally by rewriter/streamer transforms. */
|
|
33
69
|
EmptyChunk,
|
|
70
|
+
|
|
71
|
+
/** Object emitted by `Splitter`. */
|
|
34
72
|
SplitterChunk,
|
|
73
|
+
|
|
74
|
+
/** Object accepted by rewriter and streamer transforms. */
|
|
35
75
|
RewriterInput,
|
|
76
|
+
|
|
77
|
+
/** Predicate used to select MIME nodes. */
|
|
36
78
|
FilterFunc,
|
|
79
|
+
|
|
80
|
+
/** Error object that may include a Node-style string code. */
|
|
37
81
|
ErrorWithCode,
|
|
82
|
+
|
|
83
|
+
/** Callback that resumes processing after a selected node stream ends. */
|
|
38
84
|
ContinueCallback,
|
|
85
|
+
|
|
86
|
+
/** Content transform stream used for decoded or encoded node bodies. */
|
|
39
87
|
ContentStream,
|
|
88
|
+
|
|
89
|
+
/** Decoder stream with an internal readable-state guard. */
|
|
40
90
|
DecoderStream,
|
|
91
|
+
|
|
92
|
+
/** Internal splitter grouping state. */
|
|
41
93
|
SplitterGroup,
|
|
94
|
+
|
|
95
|
+
/** Payload emitted with `Rewriter`'s `node` event. */
|
|
42
96
|
RewriterNode,
|
|
97
|
+
|
|
98
|
+
/** Payload emitted with `Streamer`'s `node` event. */
|
|
43
99
|
StreamerNode
|
|
44
100
|
} from './lib/types';
|
|
@@ -1,12 +1,58 @@
|
|
|
1
1
|
import { Transform } from 'node:stream';
|
|
2
2
|
import type { ChunkedPassthroughOptions } from './types';
|
|
3
3
|
|
|
4
|
+
/** Transform stream that buffers byte input and emits larger Buffer chunks. */
|
|
4
5
|
declare class ChunkedPassthrough extends Transform {
|
|
6
|
+
/**
|
|
7
|
+
* Creates a chunking passthrough transform that accepts Buffer input and emits Buffer chunks.
|
|
8
|
+
*
|
|
9
|
+
* @param options Optional chunk size configuration.
|
|
10
|
+
*/
|
|
5
11
|
constructor(options?: ChunkedPassthroughOptions);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Registers a listener for buffered byte chunks.
|
|
15
|
+
*
|
|
16
|
+
* @param event Event name.
|
|
17
|
+
* @param listener Receives each buffered Buffer chunk.
|
|
18
|
+
* @returns This passthrough instance.
|
|
19
|
+
*/
|
|
6
20
|
on(event: 'data', listener: (data: Buffer) => void): this;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Registers a one-time listener for the next buffered byte chunk.
|
|
24
|
+
*
|
|
25
|
+
* @param event Event name.
|
|
26
|
+
* @param listener Receives the next buffered Buffer chunk.
|
|
27
|
+
* @returns This passthrough instance.
|
|
28
|
+
*/
|
|
7
29
|
once(event: 'data', listener: (data: Buffer) => void): this;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Adds a listener for buffered byte chunks.
|
|
33
|
+
*
|
|
34
|
+
* @param event Event name.
|
|
35
|
+
* @param listener Receives each buffered Buffer chunk.
|
|
36
|
+
* @returns This passthrough instance.
|
|
37
|
+
*/
|
|
8
38
|
addListener(event: 'data', listener: (data: Buffer) => void): this;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Prepends a listener for buffered byte chunks.
|
|
42
|
+
*
|
|
43
|
+
* @param event Event name.
|
|
44
|
+
* @param listener Receives each buffered Buffer chunk.
|
|
45
|
+
* @returns This passthrough instance.
|
|
46
|
+
*/
|
|
9
47
|
prependListener(event: 'data', listener: (data: Buffer) => void): this;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Emits a buffered byte chunk.
|
|
51
|
+
*
|
|
52
|
+
* @param event Event name.
|
|
53
|
+
* @param data Buffer chunk to emit.
|
|
54
|
+
* @returns `true` when the event had listeners.
|
|
55
|
+
*/
|
|
10
56
|
emit(event: 'data', data: Buffer): boolean;
|
|
11
57
|
}
|
|
12
58
|
|
package/lib/flowed-decoder.d.ts
CHANGED
|
@@ -1,12 +1,58 @@
|
|
|
1
1
|
import { Transform } from 'node:stream';
|
|
2
2
|
import type { FlowedDecoderOptions } from './types';
|
|
3
3
|
|
|
4
|
+
/** Transform stream that decodes `text/plain; format=flowed` content. */
|
|
4
5
|
declare class FlowedDecoder extends Transform {
|
|
6
|
+
/**
|
|
7
|
+
* Creates a flowed text decoder that accepts encoded text bytes and emits decoded Buffer chunks.
|
|
8
|
+
*
|
|
9
|
+
* @param config Optional flowed text and charset decoding settings.
|
|
10
|
+
*/
|
|
5
11
|
constructor(config?: FlowedDecoderOptions);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Registers a listener for decoded flowed text bytes.
|
|
15
|
+
*
|
|
16
|
+
* @param event Event name.
|
|
17
|
+
* @param listener Receives decoded Buffer chunks.
|
|
18
|
+
* @returns This decoder instance.
|
|
19
|
+
*/
|
|
6
20
|
on(event: 'data', listener: (data: Buffer) => void): this;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Registers a one-time listener for the next decoded flowed text chunk.
|
|
24
|
+
*
|
|
25
|
+
* @param event Event name.
|
|
26
|
+
* @param listener Receives the next decoded Buffer chunk.
|
|
27
|
+
* @returns This decoder instance.
|
|
28
|
+
*/
|
|
7
29
|
once(event: 'data', listener: (data: Buffer) => void): this;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Adds a listener for decoded flowed text bytes.
|
|
33
|
+
*
|
|
34
|
+
* @param event Event name.
|
|
35
|
+
* @param listener Receives decoded Buffer chunks.
|
|
36
|
+
* @returns This decoder instance.
|
|
37
|
+
*/
|
|
8
38
|
addListener(event: 'data', listener: (data: Buffer) => void): this;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Prepends a listener for decoded flowed text bytes.
|
|
42
|
+
*
|
|
43
|
+
* @param event Event name.
|
|
44
|
+
* @param listener Receives decoded Buffer chunks.
|
|
45
|
+
* @returns This decoder instance.
|
|
46
|
+
*/
|
|
9
47
|
prependListener(event: 'data', listener: (data: Buffer) => void): this;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Emits a decoded flowed text chunk.
|
|
51
|
+
*
|
|
52
|
+
* @param event Event name.
|
|
53
|
+
* @param data Buffer chunk to emit.
|
|
54
|
+
* @returns `true` when the event had listeners.
|
|
55
|
+
*/
|
|
10
56
|
emit(event: 'data', data: Buffer): boolean;
|
|
11
57
|
}
|
|
12
58
|
|
package/lib/flowed-decoder.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// Helper class to
|
|
3
|
+
// Helper class to decode format=flowed text nodes
|
|
4
4
|
|
|
5
5
|
const Transform = require('stream').Transform;
|
|
6
6
|
const libmime = require('libmime');
|
|
@@ -10,14 +10,13 @@ const libmime = require('libmime');
|
|
|
10
10
|
const Libmime = /** @type {any} */ (libmime.Libmime);
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* Transform stream that decodes text/plain format=flowed content.
|
|
14
14
|
*
|
|
15
|
-
* @
|
|
16
|
-
* @param {FlowedDecoderOptions} [config]
|
|
15
|
+
* @param {FlowedDecoderOptions} [config] Flowed text and charset decoding settings.
|
|
17
16
|
*/
|
|
18
17
|
class FlowedDecoder extends Transform {
|
|
19
18
|
/**
|
|
20
|
-
* @param {FlowedDecoderOptions} [config]
|
|
19
|
+
* @param {FlowedDecoderOptions} [config] Flowed text and charset decoding settings.
|
|
21
20
|
*/
|
|
22
21
|
constructor(config) {
|
|
23
22
|
super();
|
package/lib/headers.d.ts
CHANGED
|
@@ -1,23 +1,116 @@
|
|
|
1
1
|
import type { DecodedHeader, HeaderLine, LibmimeOptions } from './types';
|
|
2
2
|
|
|
3
|
+
/** Mutable parser and builder for RFC-style message header blocks. */
|
|
3
4
|
declare class Headers {
|
|
5
|
+
/** Whether header lines have been modified after construction. */
|
|
4
6
|
changed: boolean;
|
|
7
|
+
|
|
8
|
+
/** Original unparsed header source, or `false` when constructed from parsed lines. */
|
|
5
9
|
headers: string | Buffer | false;
|
|
10
|
+
|
|
11
|
+
/** Whether `headers` has been parsed into `lines`. */
|
|
6
12
|
parsed: boolean;
|
|
13
|
+
|
|
14
|
+
/** Parsed header lines, or `false` until parsing occurs. */
|
|
7
15
|
lines: HeaderLine[] | false;
|
|
16
|
+
|
|
17
|
+
/** MBOX `From ` prefix line, or `false` when absent. */
|
|
8
18
|
mbox: string | false;
|
|
19
|
+
|
|
20
|
+
/** HTTP request prefix line, or `false` when absent. */
|
|
9
21
|
http: string | false;
|
|
10
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Creates a mutable header collection.
|
|
25
|
+
*
|
|
26
|
+
* @param headers Raw header bytes/string, already parsed header lines, or `false` for an empty collection.
|
|
27
|
+
* @param config Optional libmime configuration.
|
|
28
|
+
*/
|
|
11
29
|
constructor(headers?: string | Buffer | HeaderLine[] | false, config?: LibmimeOptions);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Checks whether at least one header with the requested key exists.
|
|
33
|
+
*
|
|
34
|
+
* @param key Header field name to find, case-insensitively.
|
|
35
|
+
* @returns `true` when the header exists.
|
|
36
|
+
*/
|
|
12
37
|
hasHeader(key: string): boolean;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Gets all raw header lines for a key.
|
|
41
|
+
*
|
|
42
|
+
* @param key Header field name to find, case-insensitively.
|
|
43
|
+
* @returns Full decoded header lines, including field names.
|
|
44
|
+
*/
|
|
13
45
|
get(key: string): string[];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Gets all decoded structured header values for a key.
|
|
49
|
+
*
|
|
50
|
+
* @param key Header field name to decode, case-insensitively.
|
|
51
|
+
* @returns Decoded header entries with key and value fields.
|
|
52
|
+
*/
|
|
14
53
|
getDecoded(key: string): DecodedHeader[];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Gets the first decoded header value for a key.
|
|
57
|
+
*
|
|
58
|
+
* @param key Header field name to find, case-insensitively.
|
|
59
|
+
* @returns Trimmed decoded value, or an empty string when the header is absent.
|
|
60
|
+
*/
|
|
15
61
|
getFirst(key: string): string;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Gets the mutable parsed header list.
|
|
65
|
+
*
|
|
66
|
+
* @returns Parsed header lines in message order.
|
|
67
|
+
*/
|
|
16
68
|
getList(): HeaderLine[];
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Adds a folded header line.
|
|
72
|
+
*
|
|
73
|
+
* @param key Header field name to add.
|
|
74
|
+
* @param value Header value; `undefined` leaves the collection unchanged.
|
|
75
|
+
* @param index Insertion index, where omitted or less than 1 inserts at the top.
|
|
76
|
+
* @returns Nothing.
|
|
77
|
+
*/
|
|
17
78
|
add(key: string, value?: string | number | Buffer, index?: number): void;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Adds a preformatted header line.
|
|
82
|
+
*
|
|
83
|
+
* @param key Header field name used for normalized lookup.
|
|
84
|
+
* @param line Full header line to insert; falsy values leave the collection unchanged.
|
|
85
|
+
* @param index Insertion index, where omitted or less than 1 inserts at the top.
|
|
86
|
+
* @returns Nothing.
|
|
87
|
+
*/
|
|
18
88
|
addFormatted(key: string, line?: string | Buffer | false, index?: number): void;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Removes all headers matching a key.
|
|
92
|
+
*
|
|
93
|
+
* @param key Header field name to remove, case-insensitively.
|
|
94
|
+
* @returns Nothing.
|
|
95
|
+
*/
|
|
19
96
|
remove(key: string): void;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Replaces matching headers with a new folded header value.
|
|
100
|
+
*
|
|
101
|
+
* @param key Header field name to update.
|
|
102
|
+
* @param value Header value to write; `undefined` removes matching values without adding a replacement.
|
|
103
|
+
* @param relativeIndex Optional zero-based index among headers with the same key.
|
|
104
|
+
* @returns Nothing.
|
|
105
|
+
*/
|
|
20
106
|
update(key: string, value?: string | number | Buffer, relativeIndex?: number): void;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Builds a raw header block.
|
|
110
|
+
*
|
|
111
|
+
* @param lineEnd Line ending to use when rebuilding changed headers; defaults to CRLF.
|
|
112
|
+
* @returns Header bytes ending with an empty header/body separator line.
|
|
113
|
+
*/
|
|
21
114
|
build(lineEnd?: string | false): Buffer;
|
|
22
115
|
}
|
|
23
116
|
|
package/lib/headers.js
CHANGED
|
@@ -9,13 +9,13 @@ const libmime = require('libmime');
|
|
|
9
9
|
const Libmime = /** @type {any} */ (libmime.Libmime);
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Parses and builds message headers. A Headers instance allows callers to
|
|
13
|
+
* inspect, delete, update, and add header lines.
|
|
14
14
|
*/
|
|
15
15
|
class Headers {
|
|
16
16
|
/**
|
|
17
|
-
* @param {string | Buffer | HeaderLine[] | false} [headers]
|
|
18
|
-
* @param {LibmimeOptions} [config]
|
|
17
|
+
* @param {string | Buffer | HeaderLine[] | false} [headers] Raw header source or already parsed lines.
|
|
18
|
+
* @param {LibmimeOptions} [config] Optional libmime configuration.
|
|
19
19
|
*/
|
|
20
20
|
constructor(headers, config) {
|
|
21
21
|
config = config || {};
|
package/lib/message-joiner.d.ts
CHANGED
|
@@ -1,11 +1,55 @@
|
|
|
1
1
|
import { Transform } from 'node:stream';
|
|
2
2
|
|
|
3
|
+
/** Transform stream that joins splitter objects back into raw email bytes. */
|
|
3
4
|
declare class MessageJoiner extends Transform {
|
|
5
|
+
/**
|
|
6
|
+
* Creates a joiner that accepts splitter objects and emits Buffer chunks.
|
|
7
|
+
*/
|
|
4
8
|
constructor();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Registers a listener for generated message bytes.
|
|
12
|
+
*
|
|
13
|
+
* @param event Event name.
|
|
14
|
+
* @param listener Receives each generated Buffer chunk.
|
|
15
|
+
* @returns This joiner instance.
|
|
16
|
+
*/
|
|
5
17
|
on(event: 'data', listener: (data: Buffer) => void): this;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Registers a one-time listener for generated message bytes.
|
|
21
|
+
*
|
|
22
|
+
* @param event Event name.
|
|
23
|
+
* @param listener Receives the next generated Buffer chunk.
|
|
24
|
+
* @returns This joiner instance.
|
|
25
|
+
*/
|
|
6
26
|
once(event: 'data', listener: (data: Buffer) => void): this;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Adds a listener for generated message bytes.
|
|
30
|
+
*
|
|
31
|
+
* @param event Event name.
|
|
32
|
+
* @param listener Receives each generated Buffer chunk.
|
|
33
|
+
* @returns This joiner instance.
|
|
34
|
+
*/
|
|
7
35
|
addListener(event: 'data', listener: (data: Buffer) => void): this;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Prepends a listener for generated message bytes.
|
|
39
|
+
*
|
|
40
|
+
* @param event Event name.
|
|
41
|
+
* @param listener Receives each generated Buffer chunk.
|
|
42
|
+
* @returns This joiner instance.
|
|
43
|
+
*/
|
|
8
44
|
prependListener(event: 'data', listener: (data: Buffer) => void): this;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Emits a generated message byte chunk.
|
|
48
|
+
*
|
|
49
|
+
* @param event Event name.
|
|
50
|
+
* @param data Buffer chunk to emit.
|
|
51
|
+
* @returns `true` when the event had listeners.
|
|
52
|
+
*/
|
|
9
53
|
emit(event: 'data', data: Buffer): boolean;
|
|
10
54
|
}
|
|
11
55
|
|
package/lib/message-joiner.js
CHANGED
|
@@ -4,7 +4,13 @@ const Transform = require('stream').Transform;
|
|
|
4
4
|
|
|
5
5
|
/** @typedef {import('..').SplitterChunk} SplitterChunk */
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Transform stream that joins splitter objects back into raw message bytes.
|
|
9
|
+
*/
|
|
7
10
|
class MessageJoiner extends Transform {
|
|
11
|
+
/**
|
|
12
|
+
* Creates a joiner that accepts splitter objects and emits Buffer chunks.
|
|
13
|
+
*/
|
|
8
14
|
constructor() {
|
|
9
15
|
let options = {
|
|
10
16
|
readableObjectMode: false,
|
|
@@ -1,12 +1,58 @@
|
|
|
1
1
|
import { Transform } from 'node:stream';
|
|
2
2
|
import type { SplitterChunk, SplitterOptions } from './types';
|
|
3
3
|
|
|
4
|
+
/** Transform stream that splits raw email bytes into MIME node and content chunks. */
|
|
4
5
|
declare class MessageSplitter extends Transform {
|
|
6
|
+
/**
|
|
7
|
+
* Creates a splitter that accepts Buffer input and emits `SplitterChunk` objects.
|
|
8
|
+
*
|
|
9
|
+
* @param config Optional parser limits and embedded-message behavior.
|
|
10
|
+
*/
|
|
5
11
|
constructor(config?: SplitterOptions);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Registers a listener for parsed splitter chunks.
|
|
15
|
+
*
|
|
16
|
+
* @param event Event name.
|
|
17
|
+
* @param listener Receives each parsed MIME node, data chunk, or body chunk.
|
|
18
|
+
* @returns This splitter instance.
|
|
19
|
+
*/
|
|
6
20
|
on(event: 'data', listener: (data: SplitterChunk) => void): this;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Registers a one-time listener for the next parsed splitter chunk.
|
|
24
|
+
*
|
|
25
|
+
* @param event Event name.
|
|
26
|
+
* @param listener Receives the next parsed MIME node, data chunk, or body chunk.
|
|
27
|
+
* @returns This splitter instance.
|
|
28
|
+
*/
|
|
7
29
|
once(event: 'data', listener: (data: SplitterChunk) => void): this;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Adds a listener for parsed splitter chunks.
|
|
33
|
+
*
|
|
34
|
+
* @param event Event name.
|
|
35
|
+
* @param listener Receives each parsed MIME node, data chunk, or body chunk.
|
|
36
|
+
* @returns This splitter instance.
|
|
37
|
+
*/
|
|
8
38
|
addListener(event: 'data', listener: (data: SplitterChunk) => void): this;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Prepends a listener for parsed splitter chunks.
|
|
42
|
+
*
|
|
43
|
+
* @param event Event name.
|
|
44
|
+
* @param listener Receives each parsed MIME node, data chunk, or body chunk.
|
|
45
|
+
* @returns This splitter instance.
|
|
46
|
+
*/
|
|
9
47
|
prependListener(event: 'data', listener: (data: SplitterChunk) => void): this;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Emits a parsed splitter chunk.
|
|
51
|
+
*
|
|
52
|
+
* @param event Event name.
|
|
53
|
+
* @param data MIME node, data chunk, or body chunk to emit.
|
|
54
|
+
* @returns `true` when the event had listeners.
|
|
55
|
+
*/
|
|
10
56
|
emit(event: 'data', data: SplitterChunk): boolean;
|
|
11
57
|
}
|
|
12
58
|
|
package/lib/message-splitter.js
CHANGED
|
@@ -16,6 +16,9 @@ const MAX_CHILD_NODES = 1000;
|
|
|
16
16
|
const HEAD = 0x01;
|
|
17
17
|
const BODY = 0x02;
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Transform stream that splits raw email bytes into MIME node and content chunks.
|
|
21
|
+
*/
|
|
19
22
|
class MessageSplitter extends Transform {
|
|
20
23
|
/**
|
|
21
24
|
* @param {SplitterOptions} [config]
|
|
@@ -59,7 +62,12 @@ class MessageSplitter extends Transform {
|
|
|
59
62
|
let groupstart = this.line ? -this.line.length : 0;
|
|
60
63
|
let groupend = 0;
|
|
61
64
|
|
|
62
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Removes a pending line break from body data that belongs to a following boundary.
|
|
67
|
+
*
|
|
68
|
+
* @param {MessageChunk} data Body chunk to adjust in place.
|
|
69
|
+
* @returns {void}
|
|
70
|
+
*/
|
|
63
71
|
let checkTrailingLinebreak = data => {
|
|
64
72
|
if (data.type === 'body' && data.node.parentNode && data.value && data.value.length) {
|
|
65
73
|
if (data.value[data.value.length - 1] === 0x0a) {
|
|
@@ -88,6 +96,11 @@ class MessageSplitter extends Transform {
|
|
|
88
96
|
}
|
|
89
97
|
};
|
|
90
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Iterates the current input chunk line by line and emits parsed groups.
|
|
101
|
+
*
|
|
102
|
+
* @returns {void}
|
|
103
|
+
*/
|
|
91
104
|
let iterateData = () => {
|
|
92
105
|
for (let len = chunk.length; i < len; i++) {
|
|
93
106
|
// find next <LF>
|
package/lib/mime-node.d.ts
CHANGED
|
@@ -1,37 +1,143 @@
|
|
|
1
1
|
import type { ContentStream, MimeNode as MimeNodeShape, PartNumber, PartNumberItem, SplitterOptions } from './types';
|
|
2
2
|
import type Headers = require('./headers');
|
|
3
3
|
|
|
4
|
+
/** Parsed MIME node with mutable headers and content encoding helpers. */
|
|
4
5
|
declare class MimeNode implements MimeNodeShape {
|
|
6
|
+
/** Discriminator identifying this chunk as a MIME node. */
|
|
5
7
|
type: 'node';
|
|
8
|
+
|
|
9
|
+
/** Whether this node is the root message node. */
|
|
6
10
|
root: boolean;
|
|
11
|
+
|
|
12
|
+
/** Parent MIME node, or `false` for the root node. */
|
|
7
13
|
parentNode: MimeNodeShape | false;
|
|
14
|
+
|
|
15
|
+
/** Boundary used by this multipart node, or `false` when not multipart. */
|
|
8
16
|
_boundary: Buffer | false;
|
|
17
|
+
|
|
18
|
+
/** Boundary inherited from the parent multipart node, or `false` when absent. */
|
|
9
19
|
_parentBoundary: Buffer | false;
|
|
20
|
+
|
|
21
|
+
/** Length, in bytes, of the raw header block collected for this node. */
|
|
10
22
|
_headerlen: number;
|
|
23
|
+
|
|
24
|
+
/** Multipart subtype such as `mixed` or `alternative`, or `false` for leaf nodes. */
|
|
11
25
|
multipart: string | false;
|
|
26
|
+
|
|
27
|
+
/** Content-Transfer-Encoding value, normalized to lower case, or `false` when absent. */
|
|
12
28
|
encoding: string | false;
|
|
29
|
+
|
|
30
|
+
/** Parsed and mutable header collection, available after headers are parsed. */
|
|
13
31
|
headers: Headers | false;
|
|
32
|
+
|
|
33
|
+
/** MIME content type such as `text/plain`, or `false` when unavailable. */
|
|
14
34
|
contentType: string | false;
|
|
35
|
+
|
|
36
|
+
/** Charset parameter from Content-Type, or `false` when absent. */
|
|
15
37
|
charset: string | false;
|
|
38
|
+
|
|
39
|
+
/** Content-Disposition value such as `inline` or `attachment`, or `false` when absent. */
|
|
16
40
|
disposition: string | false;
|
|
41
|
+
|
|
42
|
+
/** Decoded filename from Content-Disposition or Content-Type parameters, or `false` when absent. */
|
|
17
43
|
filename: string | false;
|
|
44
|
+
|
|
45
|
+
/** Whether this node is `text/*` with `format=flowed`. */
|
|
18
46
|
flowed: boolean;
|
|
47
|
+
|
|
48
|
+
/** Whether flowed text uses `delsp=yes`. */
|
|
19
49
|
delSp: boolean;
|
|
50
|
+
|
|
51
|
+
/** Splitter configuration used when parsing this node. */
|
|
20
52
|
config: SplitterOptions;
|
|
53
|
+
|
|
54
|
+
/** Resolved IMAP-style part number for this node, or `false` before resolution. */
|
|
21
55
|
partNr: PartNumber | false;
|
|
56
|
+
|
|
57
|
+
/** Number of child part numbers allocated by this node. */
|
|
22
58
|
childPartNumbers: number;
|
|
59
|
+
|
|
60
|
+
/** Whether this node's content type is `message/rfc822`. */
|
|
23
61
|
rfc822: boolean;
|
|
62
|
+
|
|
63
|
+
/** Whether an embedded `message/rfc822` node was parsed as a nested message. */
|
|
24
64
|
messageNode?: boolean;
|
|
25
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Creates a MIME node with empty header state.
|
|
68
|
+
*
|
|
69
|
+
* @param parentNode Parent node, or `false`/omitted for the root node.
|
|
70
|
+
* @param config Optional splitter and libmime configuration.
|
|
71
|
+
*/
|
|
26
72
|
constructor(parentNode?: MimeNodeShape | false, config?: SplitterOptions);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Builds the next child part number for this node.
|
|
76
|
+
*
|
|
77
|
+
* @param provided Optional explicit part number item to append.
|
|
78
|
+
* @returns Resolved MIME part number.
|
|
79
|
+
*/
|
|
27
80
|
getPartNr(provided?: PartNumberItem): PartNumber;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Appends one raw header line to this node while parsing.
|
|
84
|
+
*
|
|
85
|
+
* @param line Raw header line bytes; falsy values are ignored.
|
|
86
|
+
* @returns Nothing.
|
|
87
|
+
*/
|
|
28
88
|
addHeaderChunk(line?: Buffer | false): void;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Parses collected header bytes and populates MIME metadata fields.
|
|
92
|
+
*
|
|
93
|
+
* @returns Nothing.
|
|
94
|
+
*/
|
|
29
95
|
parseHeaders(): void;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Builds this node's header block.
|
|
99
|
+
*
|
|
100
|
+
* @returns Header bytes ending with an empty header/body separator line.
|
|
101
|
+
*/
|
|
30
102
|
getHeaders(): Buffer;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Sets or updates the Content-Type header value.
|
|
106
|
+
*
|
|
107
|
+
* @param contentType MIME content type to set; falsy keeps the current type.
|
|
108
|
+
* @returns Nothing.
|
|
109
|
+
*/
|
|
31
110
|
setContentType(contentType?: string | false): void;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Sets, updates, or removes the Content-Type charset parameter.
|
|
114
|
+
*
|
|
115
|
+
* @param charset Charset to set; falsy removes it when possible.
|
|
116
|
+
* @returns Nothing.
|
|
117
|
+
*/
|
|
32
118
|
setCharset(charset?: string | false): void;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Sets, updates, or removes the filename parameter.
|
|
122
|
+
*
|
|
123
|
+
* @param filename Filename to set; falsy removes it when possible.
|
|
124
|
+
* @returns Nothing.
|
|
125
|
+
*/
|
|
33
126
|
setFilename(filename?: string | false): void;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Creates a decoder stream for this node's transfer encoding.
|
|
130
|
+
*
|
|
131
|
+
* @returns Transform stream that outputs decoded content bytes.
|
|
132
|
+
*/
|
|
34
133
|
getDecoder(): ContentStream;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Creates an encoder stream and updates the Content-Transfer-Encoding header when needed.
|
|
137
|
+
*
|
|
138
|
+
* @param encoding Target transfer encoding; defaults to the node's current encoding.
|
|
139
|
+
* @returns Transform stream that outputs encoded content bytes.
|
|
140
|
+
*/
|
|
35
141
|
getEncoder(encoding?: string | false): ContentStream;
|
|
36
142
|
}
|
|
37
143
|
|
package/lib/mime-node.js
CHANGED
|
@@ -18,10 +18,13 @@ const pathlib = require('path');
|
|
|
18
18
|
|
|
19
19
|
const Libmime = /** @type {any} */ (libmime.Libmime);
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Parsed MIME node with mutable headers and transfer-encoding helpers.
|
|
23
|
+
*/
|
|
21
24
|
class MimeNode {
|
|
22
25
|
/**
|
|
23
|
-
* @param {MimeNodeType | false} parentNode
|
|
24
|
-
* @param {SplitterOptions} [config]
|
|
26
|
+
* @param {MimeNodeType | false} parentNode Parent node, or false for the root node.
|
|
27
|
+
* @param {SplitterOptions} [config] Splitter and libmime configuration.
|
|
25
28
|
*/
|
|
26
29
|
constructor(parentNode, config) {
|
|
27
30
|
/** @type {'node'} */
|
package/lib/node-rewriter.d.ts
CHANGED
|
@@ -1,17 +1,104 @@
|
|
|
1
1
|
import { Transform } from 'node:stream';
|
|
2
2
|
import type { FilterFunc, RewriterNode, SplitterChunk } from './types';
|
|
3
3
|
|
|
4
|
+
/** Transform stream that replaces the body content of selected MIME nodes. */
|
|
4
5
|
declare class NodeRewriter extends Transform {
|
|
6
|
+
/**
|
|
7
|
+
* Creates a node rewriter that accepts splitter chunks and emits rewritten splitter chunks.
|
|
8
|
+
*
|
|
9
|
+
* @param filterFunc Predicate that selects MIME nodes to rewrite.
|
|
10
|
+
* @param rewriteAction Optional compatibility hook stored on the instance; consumers usually handle the `node` event.
|
|
11
|
+
*/
|
|
5
12
|
constructor(filterFunc: FilterFunc, rewriteAction?: Function);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Registers a listener for rewritten splitter chunks.
|
|
16
|
+
*
|
|
17
|
+
* @param event Event name.
|
|
18
|
+
* @param listener Receives each outgoing MIME node, data chunk, or body chunk.
|
|
19
|
+
* @returns This rewriter instance.
|
|
20
|
+
*/
|
|
6
21
|
on(event: 'data', listener: (data: SplitterChunk) => void): this;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Registers a listener for selected nodes.
|
|
25
|
+
*
|
|
26
|
+
* @param event Event name.
|
|
27
|
+
* @param listener Receives decoder and encoder streams for a selected node.
|
|
28
|
+
* @returns This rewriter instance.
|
|
29
|
+
*/
|
|
7
30
|
on(event: 'node', listener: (data: RewriterNode) => void): this;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Registers a one-time listener for the next rewritten splitter chunk.
|
|
34
|
+
*
|
|
35
|
+
* @param event Event name.
|
|
36
|
+
* @param listener Receives the next outgoing MIME node, data chunk, or body chunk.
|
|
37
|
+
* @returns This rewriter instance.
|
|
38
|
+
*/
|
|
8
39
|
once(event: 'data', listener: (data: SplitterChunk) => void): this;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Registers a one-time listener for the next selected node.
|
|
43
|
+
*
|
|
44
|
+
* @param event Event name.
|
|
45
|
+
* @param listener Receives decoder and encoder streams for the next selected node.
|
|
46
|
+
* @returns This rewriter instance.
|
|
47
|
+
*/
|
|
9
48
|
once(event: 'node', listener: (data: RewriterNode) => void): this;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Adds a listener for rewritten splitter chunks.
|
|
52
|
+
*
|
|
53
|
+
* @param event Event name.
|
|
54
|
+
* @param listener Receives each outgoing MIME node, data chunk, or body chunk.
|
|
55
|
+
* @returns This rewriter instance.
|
|
56
|
+
*/
|
|
10
57
|
addListener(event: 'data', listener: (data: SplitterChunk) => void): this;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Adds a listener for selected nodes.
|
|
61
|
+
*
|
|
62
|
+
* @param event Event name.
|
|
63
|
+
* @param listener Receives decoder and encoder streams for a selected node.
|
|
64
|
+
* @returns This rewriter instance.
|
|
65
|
+
*/
|
|
11
66
|
addListener(event: 'node', listener: (data: RewriterNode) => void): this;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Prepends a listener for rewritten splitter chunks.
|
|
70
|
+
*
|
|
71
|
+
* @param event Event name.
|
|
72
|
+
* @param listener Receives each outgoing MIME node, data chunk, or body chunk.
|
|
73
|
+
* @returns This rewriter instance.
|
|
74
|
+
*/
|
|
12
75
|
prependListener(event: 'data', listener: (data: SplitterChunk) => void): this;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Prepends a listener for selected nodes.
|
|
79
|
+
*
|
|
80
|
+
* @param event Event name.
|
|
81
|
+
* @param listener Receives decoder and encoder streams for a selected node.
|
|
82
|
+
* @returns This rewriter instance.
|
|
83
|
+
*/
|
|
13
84
|
prependListener(event: 'node', listener: (data: RewriterNode) => void): this;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Emits a rewritten splitter chunk.
|
|
88
|
+
*
|
|
89
|
+
* @param event Event name.
|
|
90
|
+
* @param data MIME node, data chunk, or body chunk to emit.
|
|
91
|
+
* @returns `true` when the event had listeners.
|
|
92
|
+
*/
|
|
14
93
|
emit(event: 'data', data: SplitterChunk): boolean;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Emits a selected-node payload.
|
|
97
|
+
*
|
|
98
|
+
* @param event Event name.
|
|
99
|
+
* @param data Selected node payload containing decoder and encoder streams.
|
|
100
|
+
* @returns `true` when the event had listeners.
|
|
101
|
+
*/
|
|
15
102
|
emit(event: 'node', data: RewriterNode): boolean;
|
|
16
103
|
}
|
|
17
104
|
|
package/lib/node-rewriter.js
CHANGED
|
@@ -14,16 +14,16 @@ const FlowedDecoder = require('./flowed-decoder');
|
|
|
14
14
|
/** @typedef {import('..').ContinueCallback} ContinueCallback */
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* NodeRewriter Transform stream. Updates content for all nodes
|
|
17
|
+
* NodeRewriter Transform stream. Updates content for all nodes selected by the
|
|
18
|
+
* filter function.
|
|
18
19
|
*
|
|
19
|
-
* @
|
|
20
|
-
* @param {
|
|
21
|
-
* @param {Function} [rewriteAction] Function to run with the node content
|
|
20
|
+
* @param {FilterFunc} filterFunc Function that receives a MIME node and returns true to rewrite it.
|
|
21
|
+
* @param {Function} [rewriteAction] Optional compatibility hook stored on the instance.
|
|
22
22
|
*/
|
|
23
23
|
class NodeRewriter extends Transform {
|
|
24
24
|
/**
|
|
25
|
-
* @param {FilterFunc} filterFunc
|
|
26
|
-
* @param {Function} [rewriteAction]
|
|
25
|
+
* @param {FilterFunc} filterFunc Function that receives a MIME node and returns true to rewrite it.
|
|
26
|
+
* @param {Function} [rewriteAction] Optional compatibility hook stored on the instance.
|
|
27
27
|
*/
|
|
28
28
|
constructor(filterFunc, rewriteAction) {
|
|
29
29
|
let options = {
|
|
@@ -128,6 +128,11 @@ class NodeRewriter extends Transform {
|
|
|
128
128
|
let firstChunk = true;
|
|
129
129
|
decoder.$reading = false;
|
|
130
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Reads encoded replacement bytes and forwards them as body chunks.
|
|
133
|
+
*
|
|
134
|
+
* @returns {void | NodeJS.Immediate}
|
|
135
|
+
*/
|
|
131
136
|
let readFromEncoder = () => {
|
|
132
137
|
decoder.$reading = true;
|
|
133
138
|
|
|
@@ -202,9 +207,18 @@ class NodeRewriter extends Transform {
|
|
|
202
207
|
delSp: node.delSp,
|
|
203
208
|
encoding: node.encoding || false
|
|
204
209
|
});
|
|
205
|
-
flowDecoder.on(
|
|
206
|
-
|
|
207
|
-
|
|
210
|
+
flowDecoder.on(
|
|
211
|
+
'error',
|
|
212
|
+
/**
|
|
213
|
+
* Forwards flowed decoder errors to the replacement decoder.
|
|
214
|
+
*
|
|
215
|
+
* @param {Error} err Decoder error to forward.
|
|
216
|
+
* @returns {void}
|
|
217
|
+
*/
|
|
218
|
+
err => {
|
|
219
|
+
decoder.emit('error', err);
|
|
220
|
+
}
|
|
221
|
+
);
|
|
208
222
|
flowDecoder.pipe(decoder);
|
|
209
223
|
|
|
210
224
|
// we don't know what kind of data we are going to get, does it comply with the
|
package/lib/node-streamer.d.ts
CHANGED
|
@@ -1,17 +1,104 @@
|
|
|
1
1
|
import { Transform } from 'node:stream';
|
|
2
2
|
import type { FilterFunc, SplitterChunk, StreamerNode } from './types';
|
|
3
3
|
|
|
4
|
+
/** Transform stream that exposes decoded body streams for selected MIME nodes without replacing them. */
|
|
4
5
|
declare class NodeStreamer extends Transform {
|
|
6
|
+
/**
|
|
7
|
+
* Creates a node streamer that accepts splitter chunks and passes them through unchanged.
|
|
8
|
+
*
|
|
9
|
+
* @param filterFunc Predicate that selects MIME nodes to stream.
|
|
10
|
+
* @param streamAction Optional compatibility hook stored on the instance; consumers usually handle the `node` event.
|
|
11
|
+
*/
|
|
5
12
|
constructor(filterFunc: FilterFunc, streamAction?: Function);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Registers a listener for passed-through splitter chunks.
|
|
16
|
+
*
|
|
17
|
+
* @param event Event name.
|
|
18
|
+
* @param listener Receives each outgoing MIME node, data chunk, or body chunk.
|
|
19
|
+
* @returns This streamer instance.
|
|
20
|
+
*/
|
|
6
21
|
on(event: 'data', listener: (data: SplitterChunk) => void): this;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Registers a listener for selected nodes.
|
|
25
|
+
*
|
|
26
|
+
* @param event Event name.
|
|
27
|
+
* @param listener Receives a decoder stream and completion callback for a selected node.
|
|
28
|
+
* @returns This streamer instance.
|
|
29
|
+
*/
|
|
7
30
|
on(event: 'node', listener: (data: StreamerNode) => void): this;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Registers a one-time listener for the next passed-through splitter chunk.
|
|
34
|
+
*
|
|
35
|
+
* @param event Event name.
|
|
36
|
+
* @param listener Receives the next outgoing MIME node, data chunk, or body chunk.
|
|
37
|
+
* @returns This streamer instance.
|
|
38
|
+
*/
|
|
8
39
|
once(event: 'data', listener: (data: SplitterChunk) => void): this;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Registers a one-time listener for the next selected node.
|
|
43
|
+
*
|
|
44
|
+
* @param event Event name.
|
|
45
|
+
* @param listener Receives a decoder stream and completion callback for the next selected node.
|
|
46
|
+
* @returns This streamer instance.
|
|
47
|
+
*/
|
|
9
48
|
once(event: 'node', listener: (data: StreamerNode) => void): this;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Adds a listener for passed-through splitter chunks.
|
|
52
|
+
*
|
|
53
|
+
* @param event Event name.
|
|
54
|
+
* @param listener Receives each outgoing MIME node, data chunk, or body chunk.
|
|
55
|
+
* @returns This streamer instance.
|
|
56
|
+
*/
|
|
10
57
|
addListener(event: 'data', listener: (data: SplitterChunk) => void): this;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Adds a listener for selected nodes.
|
|
61
|
+
*
|
|
62
|
+
* @param event Event name.
|
|
63
|
+
* @param listener Receives a decoder stream and completion callback for a selected node.
|
|
64
|
+
* @returns This streamer instance.
|
|
65
|
+
*/
|
|
11
66
|
addListener(event: 'node', listener: (data: StreamerNode) => void): this;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Prepends a listener for passed-through splitter chunks.
|
|
70
|
+
*
|
|
71
|
+
* @param event Event name.
|
|
72
|
+
* @param listener Receives each outgoing MIME node, data chunk, or body chunk.
|
|
73
|
+
* @returns This streamer instance.
|
|
74
|
+
*/
|
|
12
75
|
prependListener(event: 'data', listener: (data: SplitterChunk) => void): this;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Prepends a listener for selected nodes.
|
|
79
|
+
*
|
|
80
|
+
* @param event Event name.
|
|
81
|
+
* @param listener Receives a decoder stream and completion callback for a selected node.
|
|
82
|
+
* @returns This streamer instance.
|
|
83
|
+
*/
|
|
13
84
|
prependListener(event: 'node', listener: (data: StreamerNode) => void): this;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Emits a passed-through splitter chunk.
|
|
88
|
+
*
|
|
89
|
+
* @param event Event name.
|
|
90
|
+
* @param data MIME node, data chunk, or body chunk to emit.
|
|
91
|
+
* @returns `true` when the event had listeners.
|
|
92
|
+
*/
|
|
14
93
|
emit(event: 'data', data: SplitterChunk): boolean;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Emits a selected-node payload.
|
|
97
|
+
*
|
|
98
|
+
* @param event Event name.
|
|
99
|
+
* @param data Selected node payload containing decoder stream and completion callback.
|
|
100
|
+
* @returns `true` when the event had listeners.
|
|
101
|
+
*/
|
|
15
102
|
emit(event: 'node', data: StreamerNode): boolean;
|
|
16
103
|
}
|
|
17
104
|
|
package/lib/node-streamer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// Helper class to
|
|
3
|
+
// Helper class to stream selected nodes by MIME metadata
|
|
4
4
|
|
|
5
5
|
const Transform = require('stream').Transform;
|
|
6
6
|
const FlowedDecoder = require('./flowed-decoder');
|
|
@@ -13,16 +13,16 @@ const FlowedDecoder = require('./flowed-decoder');
|
|
|
13
13
|
/** @typedef {import('..').ContinueCallback} ContinueCallback */
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* NodeStreamer Transform stream. Exposes decoded content for nodes selected by
|
|
17
|
+
* the filter function while passing the original message through unchanged.
|
|
17
18
|
*
|
|
18
|
-
* @
|
|
19
|
-
* @param {
|
|
20
|
-
* @param {Function} [streamAction] Function to run with the node content
|
|
19
|
+
* @param {FilterFunc} filterFunc Function that receives a MIME node and returns true to stream it.
|
|
20
|
+
* @param {Function} [streamAction] Optional compatibility hook stored on the instance.
|
|
21
21
|
*/
|
|
22
22
|
class NodeStreamer extends Transform {
|
|
23
23
|
/**
|
|
24
|
-
* @param {FilterFunc} filterFunc
|
|
25
|
-
* @param {Function} [streamAction]
|
|
24
|
+
* @param {FilterFunc} filterFunc Function that receives a MIME node and returns true to stream it.
|
|
25
|
+
* @param {Function} [streamAction] Optional compatibility hook stored on the instance.
|
|
26
26
|
*/
|
|
27
27
|
constructor(filterFunc, streamAction) {
|
|
28
28
|
let options = {
|
|
@@ -89,6 +89,11 @@ class NodeStreamer extends Transform {
|
|
|
89
89
|
// the parsed data is completely processed, so we store a reference to the
|
|
90
90
|
// continue callback
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Resumes processing with the first chunk after the streamed node.
|
|
94
|
+
*
|
|
95
|
+
* @returns {void}
|
|
96
|
+
*/
|
|
92
97
|
let doContinue = () => {
|
|
93
98
|
this.continue = false;
|
|
94
99
|
this.decoder = false;
|
|
@@ -130,15 +135,29 @@ class NodeStreamer extends Transform {
|
|
|
130
135
|
decoder = new FlowedDecoder({
|
|
131
136
|
delSp: node.delSp
|
|
132
137
|
});
|
|
133
|
-
flowDecoder.on(
|
|
134
|
-
|
|
135
|
-
|
|
138
|
+
flowDecoder.on(
|
|
139
|
+
'error',
|
|
140
|
+
/**
|
|
141
|
+
* Forwards flowed decoder errors to the output decoder.
|
|
142
|
+
*
|
|
143
|
+
* @param {Error} err Decoder error to forward.
|
|
144
|
+
* @returns {void}
|
|
145
|
+
*/
|
|
146
|
+
err => {
|
|
147
|
+
decoder.emit('error', err);
|
|
148
|
+
}
|
|
149
|
+
);
|
|
136
150
|
flowDecoder.pipe(decoder);
|
|
137
151
|
}
|
|
138
152
|
|
|
139
153
|
return {
|
|
140
154
|
node,
|
|
141
155
|
decoder,
|
|
156
|
+
/**
|
|
157
|
+
* Marks the selected node stream as consumed so the passthrough can continue.
|
|
158
|
+
*
|
|
159
|
+
* @returns {void}
|
|
160
|
+
*/
|
|
142
161
|
done: () => {
|
|
143
162
|
if (typeof this.continue === 'function') {
|
|
144
163
|
// called once input stream is processed
|
package/lib/types.d.ts
CHANGED
|
@@ -1,105 +1,285 @@
|
|
|
1
1
|
import type { PassThrough, Transform } from 'node:stream';
|
|
2
2
|
import type Headers = require('./headers');
|
|
3
3
|
|
|
4
|
+
/** Value that is either present as `T` or explicitly unavailable as `false`. */
|
|
4
5
|
export type Maybe<T> = T | false;
|
|
6
|
+
|
|
7
|
+
/** Single item in an IMAP-style MIME part number. */
|
|
5
8
|
export type PartNumberItem = number | 'TEXT';
|
|
9
|
+
|
|
10
|
+
/** IMAP-style path to a MIME part, for example `[1, 2, 'TEXT']`. */
|
|
6
11
|
export type PartNumber = PartNumberItem[];
|
|
7
12
|
|
|
13
|
+
/** Options passed through to libmime instances created by this package. */
|
|
8
14
|
export interface LibmimeOptions {
|
|
15
|
+
/** Optional iconv-compatible implementation used by libmime for charset conversion. */
|
|
9
16
|
Iconv?: unknown;
|
|
10
17
|
}
|
|
11
18
|
|
|
19
|
+
/** Configuration for `MessageSplitter` and MIME node parsing. */
|
|
12
20
|
export interface SplitterOptions extends LibmimeOptions {
|
|
21
|
+
/** Treat `message/rfc822` parts as leaf nodes instead of parsing embedded messages. */
|
|
13
22
|
ignoreEmbedded?: boolean;
|
|
23
|
+
|
|
24
|
+
/** Parse embedded messages as inline unless their disposition is `attachment`. */
|
|
14
25
|
defaultInlineEmbedded?: boolean;
|
|
26
|
+
|
|
27
|
+
/** Maximum header block size, in bytes, allowed for a single MIME node. */
|
|
15
28
|
maxHeadSize?: number;
|
|
29
|
+
|
|
30
|
+
/** Maximum number of MIME child nodes accepted before parsing fails. */
|
|
16
31
|
maxChildNodes?: number;
|
|
17
32
|
}
|
|
18
33
|
|
|
34
|
+
/** Configuration for `ChunkedPassthrough`. */
|
|
19
35
|
export interface ChunkedPassthroughOptions {
|
|
36
|
+
/** Buffered byte threshold for non-final chunks. Defaults to 64 KiB. */
|
|
20
37
|
chunkSize?: number;
|
|
21
38
|
}
|
|
22
39
|
|
|
40
|
+
/** Configuration for `FlowedDecoder`. */
|
|
23
41
|
export interface FlowedDecoderOptions extends LibmimeOptions {
|
|
42
|
+
/** Whether format=flowed uses RFC 3676 `DelSp=yes` space deletion semantics. */
|
|
24
43
|
delSp?: boolean;
|
|
44
|
+
|
|
45
|
+
/** Source Content-Transfer-Encoding hint used during format=flowed handling. */
|
|
25
46
|
encoding?: string | false;
|
|
26
47
|
}
|
|
27
48
|
|
|
49
|
+
/** Parsed raw header line with a normalized lookup key. */
|
|
28
50
|
export interface HeaderLine {
|
|
51
|
+
/** Lower-case header key used for comparisons and lookups. */
|
|
29
52
|
key: string;
|
|
53
|
+
|
|
54
|
+
/** Full header line, including the original field name, value, and any folded continuations. */
|
|
30
55
|
line: string;
|
|
31
56
|
}
|
|
32
57
|
|
|
58
|
+
/** Decoded structured header value returned by libmime. */
|
|
33
59
|
export interface DecodedHeader {
|
|
60
|
+
/** Header key returned by libmime for the decoded value. */
|
|
34
61
|
key: string;
|
|
62
|
+
|
|
63
|
+
/** Unicode decoded header value. */
|
|
35
64
|
value: string;
|
|
36
65
|
}
|
|
37
66
|
|
|
67
|
+
/** MIME node emitted by `MessageSplitter` and accepted by `MessageJoiner`. */
|
|
38
68
|
export interface MimeNode {
|
|
69
|
+
/** Discriminator identifying this chunk as a MIME node. */
|
|
39
70
|
type: 'node';
|
|
71
|
+
|
|
72
|
+
/** Whether this node is the root message node. */
|
|
40
73
|
root: boolean;
|
|
74
|
+
|
|
75
|
+
/** Parent MIME node, or `false` for the root node. */
|
|
41
76
|
parentNode: MimeNode | false;
|
|
77
|
+
|
|
78
|
+
/** Boundary used by this multipart node, or `false` when not multipart. */
|
|
42
79
|
_boundary: Buffer | false;
|
|
80
|
+
|
|
81
|
+
/** Boundary inherited from the parent multipart node, or `false` when absent. */
|
|
43
82
|
_parentBoundary: Buffer | false;
|
|
83
|
+
|
|
84
|
+
/** Length, in bytes, of the raw header block collected for this node. */
|
|
44
85
|
_headerlen: number;
|
|
86
|
+
|
|
87
|
+
/** Multipart subtype such as `mixed` or `alternative`, or `false` for leaf nodes. */
|
|
45
88
|
multipart: string | false;
|
|
89
|
+
|
|
90
|
+
/** Content-Transfer-Encoding value, normalized to lower case, or `false` when absent. */
|
|
46
91
|
encoding: string | false;
|
|
92
|
+
|
|
93
|
+
/** Parsed and mutable header collection, available after headers are parsed. */
|
|
47
94
|
headers: Headers | false;
|
|
95
|
+
|
|
96
|
+
/** MIME content type such as `text/plain`, or `false` when unavailable. */
|
|
48
97
|
contentType: string | false;
|
|
98
|
+
|
|
99
|
+
/** Charset parameter from Content-Type, or `false` when absent. */
|
|
49
100
|
charset: string | false;
|
|
101
|
+
|
|
102
|
+
/** Content-Disposition value such as `inline` or `attachment`, or `false` when absent. */
|
|
50
103
|
disposition: string | false;
|
|
104
|
+
|
|
105
|
+
/** Decoded filename from Content-Disposition or Content-Type parameters, or `false` when absent. */
|
|
51
106
|
filename: string | false;
|
|
107
|
+
|
|
108
|
+
/** Whether this node is `text/*` with `format=flowed`. */
|
|
52
109
|
flowed: boolean;
|
|
110
|
+
|
|
111
|
+
/** Whether flowed text uses `delsp=yes`. */
|
|
53
112
|
delSp: boolean;
|
|
113
|
+
|
|
114
|
+
/** Splitter configuration used when parsing this node. */
|
|
54
115
|
config: SplitterOptions;
|
|
116
|
+
|
|
117
|
+
/** Resolved IMAP-style part number for this node, or `false` before resolution. */
|
|
55
118
|
partNr: PartNumber | false;
|
|
119
|
+
|
|
120
|
+
/** Number of child part numbers allocated by this node. */
|
|
56
121
|
childPartNumbers: number;
|
|
122
|
+
|
|
123
|
+
/** Whether this node's content type is `message/rfc822`. */
|
|
57
124
|
rfc822: boolean;
|
|
125
|
+
|
|
126
|
+
/** Whether an embedded `message/rfc822` node was parsed as a nested message. */
|
|
58
127
|
messageNode?: boolean;
|
|
59
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Builds the next child part number for this node.
|
|
131
|
+
*
|
|
132
|
+
* @param provided Optional explicit part number item to append.
|
|
133
|
+
* @returns Resolved MIME part number.
|
|
134
|
+
*/
|
|
60
135
|
getPartNr(provided?: PartNumberItem): PartNumber;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Appends one raw header line to this node while parsing.
|
|
139
|
+
*
|
|
140
|
+
* @param line Raw header line bytes; falsy values are ignored.
|
|
141
|
+
* @returns Nothing.
|
|
142
|
+
*/
|
|
61
143
|
addHeaderChunk(line?: Buffer | false): void;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Parses collected header bytes and populates MIME metadata fields.
|
|
147
|
+
*
|
|
148
|
+
* @returns Nothing.
|
|
149
|
+
*/
|
|
62
150
|
parseHeaders(): void;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Builds this node's header block.
|
|
154
|
+
*
|
|
155
|
+
* @returns Header bytes ending with an empty header/body separator line.
|
|
156
|
+
*/
|
|
63
157
|
getHeaders(): Buffer;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Sets or updates the Content-Type header value.
|
|
161
|
+
*
|
|
162
|
+
* @param contentType MIME content type to set; falsy keeps the current type.
|
|
163
|
+
* @returns Nothing.
|
|
164
|
+
*/
|
|
64
165
|
setContentType(contentType?: string | false): void;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Sets, updates, or removes the Content-Type charset parameter.
|
|
169
|
+
*
|
|
170
|
+
* @param charset Charset to set; falsy removes it when possible.
|
|
171
|
+
* @returns Nothing.
|
|
172
|
+
*/
|
|
65
173
|
setCharset(charset?: string | false): void;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Sets, updates, or removes the filename parameter.
|
|
177
|
+
*
|
|
178
|
+
* @param filename Filename to set; falsy removes it when possible.
|
|
179
|
+
* @returns Nothing.
|
|
180
|
+
*/
|
|
66
181
|
setFilename(filename?: string | false): void;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Creates a decoder stream for this node's transfer encoding.
|
|
185
|
+
*
|
|
186
|
+
* @returns Transform stream that outputs decoded content bytes.
|
|
187
|
+
*/
|
|
67
188
|
getDecoder(): Transform | PassThrough;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Creates an encoder stream and updates the Content-Transfer-Encoding header when needed.
|
|
192
|
+
*
|
|
193
|
+
* @param encoding Target transfer encoding; defaults to the node's current encoding.
|
|
194
|
+
* @returns Transform stream that outputs encoded content bytes.
|
|
195
|
+
*/
|
|
68
196
|
getEncoder(encoding?: string | false): Transform | PassThrough;
|
|
69
197
|
}
|
|
70
198
|
|
|
199
|
+
/** Data or body bytes emitted by `MessageSplitter`. */
|
|
71
200
|
export interface MessageChunk {
|
|
201
|
+
/** MIME node that owns or precedes this chunk. */
|
|
72
202
|
node: MimeNode;
|
|
203
|
+
|
|
204
|
+
/** Chunk kind: multipart structure bytes (`data`) or leaf content bytes (`body`). */
|
|
73
205
|
type: 'data' | 'body';
|
|
206
|
+
|
|
207
|
+
/** Raw chunk bytes. */
|
|
74
208
|
value: Buffer;
|
|
75
209
|
}
|
|
76
210
|
|
|
211
|
+
/** Sentinel input used internally to finish a pending rewriter or streamer node. */
|
|
77
212
|
export interface EmptyChunk {
|
|
213
|
+
/** Discriminator for an empty control chunk. */
|
|
78
214
|
type: 'none';
|
|
79
215
|
}
|
|
80
216
|
|
|
217
|
+
/** Object emitted by `MessageSplitter`: either a MIME node or a data/body byte chunk. */
|
|
81
218
|
export type SplitterChunk = MimeNode | MessageChunk;
|
|
219
|
+
|
|
220
|
+
/** Object accepted by rewriter and streamer transforms. */
|
|
82
221
|
export type RewriterInput = SplitterChunk | EmptyChunk;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Predicate used to select MIME nodes.
|
|
225
|
+
*
|
|
226
|
+
* @param node MIME node being inspected.
|
|
227
|
+
* @returns `true` to process the node, otherwise `false`.
|
|
228
|
+
*/
|
|
83
229
|
export type FilterFunc = (node: MimeNode) => boolean;
|
|
230
|
+
|
|
231
|
+
/** Error object that may include a Node-style string error code. */
|
|
84
232
|
export type ErrorWithCode = Error & { code?: string };
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Callback that resumes processing after a selected node stream has ended.
|
|
236
|
+
*
|
|
237
|
+
* @returns Nothing.
|
|
238
|
+
*/
|
|
85
239
|
export type ContinueCallback = () => void;
|
|
240
|
+
|
|
241
|
+
/** Content transform stream used for decoded or encoded node bodies. */
|
|
86
242
|
export type ContentStream = Transform | PassThrough;
|
|
243
|
+
|
|
244
|
+
/** Decoder stream with an internal readable-state guard used by rewriter/streamer. */
|
|
87
245
|
export type DecoderStream = ContentStream & { $reading?: boolean };
|
|
88
246
|
|
|
247
|
+
/** Internal grouping state used while splitter coalesces adjacent chunks. */
|
|
89
248
|
export interface SplitterGroup {
|
|
249
|
+
/** MIME node associated with the group, when one exists. */
|
|
90
250
|
node?: MimeNode;
|
|
251
|
+
|
|
252
|
+
/** Group kind currently being accumulated. */
|
|
91
253
|
type: 'none' | 'node' | 'data' | 'body';
|
|
254
|
+
|
|
255
|
+
/** Buffered raw bytes for `data` or `body` groups. */
|
|
92
256
|
value?: Buffer;
|
|
93
257
|
}
|
|
94
258
|
|
|
259
|
+
/** Payload emitted with `NodeRewriter`'s `node` event. */
|
|
95
260
|
export interface RewriterNode {
|
|
261
|
+
/** Selected MIME node whose body can be rewritten. */
|
|
96
262
|
node: MimeNode;
|
|
263
|
+
|
|
264
|
+
/** Stream that yields decoded original body bytes. */
|
|
97
265
|
decoder: Transform;
|
|
266
|
+
|
|
267
|
+
/** Stream that accepts replacement decoded bytes and emits properly encoded body bytes. */
|
|
98
268
|
encoder: Transform;
|
|
99
269
|
}
|
|
100
270
|
|
|
271
|
+
/** Payload emitted with `NodeStreamer`'s `node` event. */
|
|
101
272
|
export interface StreamerNode {
|
|
273
|
+
/** Selected MIME node whose body is being streamed. */
|
|
102
274
|
node: MimeNode;
|
|
275
|
+
|
|
276
|
+
/** Stream that yields decoded original body bytes. */
|
|
103
277
|
decoder: Transform;
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Signals that the consumer has finished reading the selected node.
|
|
281
|
+
*
|
|
282
|
+
* @returns Nothing.
|
|
283
|
+
*/
|
|
104
284
|
done: () => void;
|
|
105
285
|
}
|