@depup/zone-eu__mailsplit 5.4.16-depup.0

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.
@@ -0,0 +1,147 @@
1
+ import type { ContentStream, MimeNode as MimeNodeShape, PartNumber, PartNumberItem, SplitterOptions } from './types';
2
+ import type Headers = require('./headers');
3
+
4
+ /** Parsed MIME node with mutable headers and content encoding helpers. */
5
+ declare class MimeNode implements MimeNodeShape {
6
+ /** Discriminator identifying this chunk as a MIME node. */
7
+ type: 'node';
8
+
9
+ /** Whether this node is the root message node. */
10
+ root: boolean;
11
+
12
+ /** Parent MIME node, or `false` for the root node. */
13
+ parentNode: MimeNodeShape | false;
14
+
15
+ /** Boundary used by this multipart node, or `false` when not multipart. */
16
+ _boundary: Buffer | false;
17
+
18
+ /** Boundary inherited from the parent multipart node, or `false` when absent. */
19
+ _parentBoundary: Buffer | false;
20
+
21
+ /** Node whose boundary `_parentBoundary` was copied from. */
22
+ _parentBoundaryOwner: MimeNode | false;
23
+
24
+ /** Length, in bytes, of the raw header block collected for this node. */
25
+ _headerlen: number;
26
+
27
+ /** Multipart subtype such as `mixed` or `alternative`, or `false` for leaf nodes. */
28
+ multipart: string | false;
29
+
30
+ /** Content-Transfer-Encoding value, normalized to lower case, or `false` when absent. */
31
+ encoding: string | false;
32
+
33
+ /** Parsed and mutable header collection, available after headers are parsed. */
34
+ headers: Headers | false;
35
+
36
+ /** MIME content type such as `text/plain`, or `false` when unavailable. */
37
+ contentType: string | false;
38
+
39
+ /** Charset parameter from Content-Type, or `false` when absent. */
40
+ charset: string | false;
41
+
42
+ /** Content-Disposition value such as `inline` or `attachment`, or `false` when absent. */
43
+ disposition: string | false;
44
+
45
+ /** Decoded filename from Content-Disposition or Content-Type parameters, or `false` when absent. */
46
+ filename: string | false;
47
+
48
+ /** Whether this node is `text/*` with `format=flowed`. */
49
+ flowed: boolean;
50
+
51
+ /** Whether flowed text uses `delsp=yes`. */
52
+ delSp: boolean;
53
+
54
+ /** Splitter configuration used when parsing this node. */
55
+ config: SplitterOptions;
56
+
57
+ /** Resolved IMAP-style part number for this node, or `false` before resolution. */
58
+ partNr: PartNumber | false;
59
+
60
+ /** Number of child part numbers allocated by this node. */
61
+ childPartNumbers: number;
62
+
63
+ /** Whether this node's content type is `message/rfc822`. */
64
+ rfc822: boolean;
65
+
66
+ /** Whether an embedded `message/rfc822` node was parsed as a nested message. */
67
+ messageNode?: boolean;
68
+
69
+ /**
70
+ * Creates a MIME node with empty header state.
71
+ *
72
+ * @param parentNode Parent node, or `false`/omitted for the root node.
73
+ * @param config Optional splitter and libmime configuration.
74
+ */
75
+ constructor(parentNode?: MimeNodeShape | false, config?: SplitterOptions);
76
+
77
+ /**
78
+ * Builds the next child part number for this node.
79
+ *
80
+ * @param provided Optional explicit part number item to append.
81
+ * @returns Resolved MIME part number.
82
+ */
83
+ getPartNr(provided?: PartNumberItem): PartNumber;
84
+
85
+ /**
86
+ * Appends one raw header line to this node while parsing.
87
+ *
88
+ * @param line Raw header line bytes; falsy values are ignored.
89
+ * @returns Nothing.
90
+ */
91
+ addHeaderChunk(line?: Buffer | false): void;
92
+
93
+ /**
94
+ * Parses collected header bytes and populates MIME metadata fields.
95
+ *
96
+ * @returns Nothing.
97
+ */
98
+ parseHeaders(): void;
99
+
100
+ /**
101
+ * Builds this node's header block.
102
+ *
103
+ * @returns Header bytes ending with an empty header/body separator line.
104
+ */
105
+ getHeaders(): Buffer;
106
+
107
+ /**
108
+ * Sets or updates the Content-Type header value.
109
+ *
110
+ * @param contentType MIME content type to set; falsy keeps the current type.
111
+ * @returns Nothing.
112
+ */
113
+ setContentType(contentType?: string | false): void;
114
+
115
+ /**
116
+ * Sets, updates, or removes the Content-Type charset parameter.
117
+ *
118
+ * @param charset Charset to set; falsy removes it when possible.
119
+ * @returns Nothing.
120
+ */
121
+ setCharset(charset?: string | false): void;
122
+
123
+ /**
124
+ * Sets, updates, or removes the filename parameter.
125
+ *
126
+ * @param filename Filename to set; falsy removes it when possible.
127
+ * @returns Nothing.
128
+ */
129
+ setFilename(filename?: string | false): void;
130
+
131
+ /**
132
+ * Creates a decoder stream for this node's transfer encoding.
133
+ *
134
+ * @returns Transform stream that outputs decoded content bytes.
135
+ */
136
+ getDecoder(): ContentStream;
137
+
138
+ /**
139
+ * Creates an encoder stream and updates the Content-Transfer-Encoding header when needed.
140
+ *
141
+ * @param encoding Target transfer encoding; defaults to the node's current encoding.
142
+ * @returns Transform stream that outputs encoded content bytes.
143
+ */
144
+ getEncoder(encoding?: string | false): ContentStream;
145
+ }
146
+
147
+ export = MimeNode;
@@ -0,0 +1,351 @@
1
+ 'use strict';
2
+
3
+ const Headers = require('./headers');
4
+ const libmime = require('libmime');
5
+ const libqp = require('libqp');
6
+ // @ts-ignore
7
+ const libbase64 = require('libbase64');
8
+ const PassThrough = require('stream').PassThrough;
9
+ const pathlib = require('path');
10
+
11
+ /** @typedef {import('..').Headers} HeadersType */
12
+ /** @typedef {import('..').PartNumberItem} PartNumberItem */
13
+ /** @typedef {import('..').PartNumber} PartNumber */
14
+ /** @typedef {import('..').SplitterOptions} SplitterOptions */
15
+ /** @typedef {import('libmime').StructuredHeader} StructuredHeader */
16
+ /** @typedef {import('stream').Transform | import('stream').PassThrough} ContentStream */
17
+ /** @typedef {import('..').MimeNode} MimeNodeType */
18
+
19
+ const Libmime = /** @type {any} */ (libmime.Libmime);
20
+
21
+ /**
22
+ * Parsed MIME node with mutable headers and transfer-encoding helpers.
23
+ */
24
+ class MimeNode {
25
+ /**
26
+ * @param {MimeNodeType | false} parentNode Parent node, or false for the root node.
27
+ * @param {SplitterOptions} [config] Splitter and libmime configuration.
28
+ */
29
+ constructor(parentNode, config) {
30
+ /** @type {'node'} */
31
+ this.type = 'node';
32
+ this.root = !parentNode;
33
+ /** @type {MimeNodeType | false} */
34
+ this.parentNode = parentNode;
35
+
36
+ /** @type {Buffer | false} */
37
+ this._parentBoundary = this.parentNode && this.parentNode._boundary;
38
+ // the node whose boundary _parentBoundary was copied from, recorded here so that
39
+ // a delimiter for it never has to be matched back to an owner by walking the tree
40
+ /** @type {MimeNodeType | false} */
41
+ this._parentBoundaryOwner = this.parentNode || false;
42
+ /** @type {Buffer[]} */
43
+ this._headersLines = [];
44
+ this._headerlen = 0;
45
+
46
+ /** @type {StructuredHeader | false} */
47
+ this._parsedContentType = false;
48
+ /** @type {StructuredHeader | false} */
49
+ this._parsedContentDisposition = false;
50
+ /** @type {Buffer | false} */
51
+ this._boundary = false;
52
+
53
+ /** @type {string | false} */
54
+ this.multipart = false;
55
+ /** @type {string | false} */
56
+ this.encoding = false;
57
+ /** @type {HeadersType | false} */
58
+ this.headers = false;
59
+ /** @type {string | false} */
60
+ this.contentType = false;
61
+ /** @type {string | false} */
62
+ this.charset = false;
63
+ /** @type {string | false} */
64
+ this.disposition = false;
65
+ /** @type {string | false} */
66
+ this.filename = false;
67
+ this.flowed = false;
68
+ this.delSp = false;
69
+
70
+ this.config = config || {};
71
+ this.libmime = new Libmime({ Iconv: this.config.Iconv });
72
+
73
+ /** @type {PartNumber} */
74
+ this.parentPartNumber = [];
75
+ /** @type {PartNumber | false} */
76
+ this.partNr = false; // resolved later
77
+ this.childPartNumbers = 0;
78
+ this.rfc822 = false;
79
+ /** @type {boolean | undefined} */
80
+ this.messageNode = undefined;
81
+ }
82
+
83
+ /**
84
+ * @param {PartNumberItem} [provided]
85
+ * @returns {PartNumber}
86
+ */
87
+ getPartNr(provided) {
88
+ /** @type {PartNumber} */
89
+ let partNr = this.partNr || [];
90
+ if (provided) {
91
+ return partNr.filter(nr => !isNaN(Number(nr))).concat(provided);
92
+ }
93
+ let childPartNr = ++this.childPartNumbers;
94
+ return partNr.filter(nr => !isNaN(Number(nr))).concat(childPartNr);
95
+ }
96
+
97
+ /**
98
+ * @param {Buffer | false} [line]
99
+ * @returns {void}
100
+ */
101
+ addHeaderChunk(line) {
102
+ if (!line) {
103
+ return;
104
+ }
105
+ this._headersLines.push(line);
106
+ this._headerlen += line.length;
107
+ }
108
+
109
+ /**
110
+ * @returns {void}
111
+ */
112
+ parseHeaders() {
113
+ if (this.headers) {
114
+ return;
115
+ }
116
+ this.headers = new Headers(Buffer.concat(this._headersLines, this._headerlen), this.config);
117
+ let headers = this.headers;
118
+
119
+ this._parsedContentDisposition = this.libmime.parseHeaderValue(headers.getFirst('Content-Disposition'));
120
+ let parsedContentDisposition = /** @type {StructuredHeader} */ (this._parsedContentDisposition);
121
+
122
+ // if content-type is missing default to plaintext
123
+ let contentHeader;
124
+ if (headers.get('Content-Type').length) {
125
+ contentHeader = headers.getFirst('Content-Type');
126
+ } else {
127
+ if (parsedContentDisposition.params.filename) {
128
+ let extension = pathlib.parse(parsedContentDisposition.params.filename).ext.replace(/^\./, '');
129
+ if (extension) {
130
+ contentHeader = libmime.detectMimeType(extension);
131
+ }
132
+ }
133
+ if (!contentHeader) {
134
+ if (/^attachment$/i.test(parsedContentDisposition.value)) {
135
+ contentHeader = 'application/octet-stream';
136
+ } else {
137
+ contentHeader = 'text/plain';
138
+ }
139
+ }
140
+ }
141
+
142
+ this._parsedContentType = this.libmime.parseHeaderValue(contentHeader);
143
+ let parsedContentType = /** @type {StructuredHeader} */ (this._parsedContentType);
144
+
145
+ this.encoding = headers
146
+ .getFirst('Content-Transfer-Encoding')
147
+ .replace(/\(.*\)/g, '')
148
+ .toLowerCase()
149
+ .trim();
150
+ this.contentType = (parsedContentType.value || '').toLowerCase().trim() || false;
151
+ this.charset = parsedContentType.params.charset || false;
152
+ this.disposition = (parsedContentDisposition.value || '').toLowerCase().trim() || false;
153
+
154
+ // fix invalidly encoded disposition values
155
+ if (this.disposition) {
156
+ try {
157
+ this.disposition = this.libmime.decodeWords(this.disposition);
158
+ } catch (E) {
159
+ // failed to parse disposition, keep as is (most probably an unknown charset is used)
160
+ }
161
+ }
162
+
163
+ this.filename = parsedContentDisposition.params.filename || parsedContentType.params.name || false;
164
+
165
+ if (parsedContentType.params.format && parsedContentType.params.format.toLowerCase().trim() === 'flowed') {
166
+ this.flowed = true;
167
+ if (parsedContentType.params.delsp && parsedContentType.params.delsp.toLowerCase().trim() === 'yes') {
168
+ this.delSp = true;
169
+ }
170
+ }
171
+
172
+ if (this.filename) {
173
+ try {
174
+ this.filename = this.libmime.decodeWords(this.filename);
175
+ } catch (E) {
176
+ // failed to parse filename, keep as is (most probably an unknown charset is used)
177
+ }
178
+ }
179
+
180
+ this.multipart =
181
+ (this.contentType &&
182
+ this.contentType.substr(0, this.contentType.indexOf('/')) === 'multipart' &&
183
+ this.contentType.substr(this.contentType.indexOf('/') + 1)) ||
184
+ false;
185
+ this._boundary = (parsedContentType.params.boundary && Buffer.from(parsedContentType.params.boundary)) || false;
186
+
187
+ this.rfc822 = this.contentType === 'message/rfc822';
188
+
189
+ if (!this.parentNode || this.parentNode.rfc822) {
190
+ this.partNr = this.parentNode ? this.parentNode.getPartNr('TEXT') : ['TEXT'];
191
+ } else {
192
+ this.partNr = this.parentNode ? this.parentNode.getPartNr() : [];
193
+ }
194
+ }
195
+
196
+ /**
197
+ * @returns {Buffer}
198
+ */
199
+ getHeaders() {
200
+ if (!this.headers) {
201
+ this.parseHeaders();
202
+ }
203
+ let headers = /** @type {HeadersType} */ (this.headers);
204
+ return headers.build();
205
+ }
206
+
207
+ /**
208
+ * @param {string | false} [contentType]
209
+ * @returns {void}
210
+ */
211
+ setContentType(contentType) {
212
+ if (!this.headers) {
213
+ this.parseHeaders();
214
+ }
215
+ let headers = /** @type {HeadersType} */ (this.headers);
216
+ let parsedContentType = /** @type {StructuredHeader} */ (this._parsedContentType);
217
+
218
+ contentType = (contentType || '').toLowerCase().trim();
219
+ if (contentType) {
220
+ parsedContentType.value = contentType;
221
+ }
222
+
223
+ if (!this.flowed && parsedContentType.params.format) {
224
+ delete parsedContentType.params.format;
225
+ }
226
+
227
+ if (!this.delSp && parsedContentType.params.delsp) {
228
+ delete parsedContentType.params.delsp;
229
+ }
230
+
231
+ headers.update('Content-Type', this.libmime.buildHeaderValue(parsedContentType));
232
+ }
233
+
234
+ /**
235
+ * @param {string | false} [charset]
236
+ * @returns {void}
237
+ */
238
+ setCharset(charset) {
239
+ if (!this.headers) {
240
+ this.parseHeaders();
241
+ }
242
+ let headers = /** @type {HeadersType} */ (this.headers);
243
+ let parsedContentType = /** @type {StructuredHeader} */ (this._parsedContentType);
244
+
245
+ charset = (charset || '').toLowerCase().trim();
246
+
247
+ if (charset === 'ascii') {
248
+ charset = '';
249
+ }
250
+
251
+ if (!charset) {
252
+ if (!parsedContentType.value) {
253
+ // nothing to set or update
254
+ return;
255
+ }
256
+ delete parsedContentType.params.charset;
257
+ } else {
258
+ parsedContentType.params.charset = charset;
259
+ }
260
+
261
+ if (!parsedContentType.value) {
262
+ parsedContentType.value = 'text/plain';
263
+ }
264
+
265
+ headers.update('Content-Type', this.libmime.buildHeaderValue(parsedContentType));
266
+ }
267
+
268
+ /**
269
+ * @param {string | false} [filename]
270
+ * @returns {void}
271
+ */
272
+ setFilename(filename) {
273
+ if (!this.headers) {
274
+ this.parseHeaders();
275
+ }
276
+ let headers = /** @type {HeadersType} */ (this.headers);
277
+ let parsedContentType = /** @type {StructuredHeader} */ (this._parsedContentType);
278
+ let parsedContentDisposition = /** @type {StructuredHeader} */ (this._parsedContentDisposition);
279
+
280
+ this.filename = (filename || '').toLowerCase().trim();
281
+
282
+ if (parsedContentType.params.name) {
283
+ delete parsedContentType.params.name;
284
+ headers.update('Content-Type', this.libmime.buildHeaderValue(parsedContentType));
285
+ }
286
+
287
+ if (!this.filename) {
288
+ if (!parsedContentDisposition.value) {
289
+ // nothing to set or update
290
+ return;
291
+ }
292
+ delete parsedContentDisposition.params.filename;
293
+ } else {
294
+ parsedContentDisposition.params.filename = this.filename;
295
+ }
296
+
297
+ if (!parsedContentDisposition.value) {
298
+ parsedContentDisposition.value = 'attachment';
299
+ }
300
+
301
+ headers.update('Content-Disposition', this.libmime.buildHeaderValue(parsedContentDisposition));
302
+ }
303
+
304
+ /**
305
+ * @returns {import('stream').Transform | import('stream').PassThrough}
306
+ */
307
+ getDecoder() {
308
+ if (!this.headers) {
309
+ this.parseHeaders();
310
+ }
311
+
312
+ switch (this.encoding) {
313
+ case 'base64':
314
+ return new libbase64.Decoder();
315
+ case 'quoted-printable':
316
+ return new libqp.Decoder();
317
+ default:
318
+ return new PassThrough();
319
+ }
320
+ }
321
+
322
+ /**
323
+ * @param {string | false} [encoding]
324
+ * @returns {import('stream').Transform | import('stream').PassThrough}
325
+ */
326
+ getEncoder(encoding) {
327
+ if (!this.headers) {
328
+ this.parseHeaders();
329
+ }
330
+ let headers = /** @type {HeadersType} */ (this.headers);
331
+
332
+ encoding = (encoding || '').toString().toLowerCase().trim();
333
+
334
+ if (encoding && encoding !== this.encoding) {
335
+ headers.update('Content-Transfer-Encoding', encoding);
336
+ } else {
337
+ encoding = this.encoding;
338
+ }
339
+
340
+ switch (encoding) {
341
+ case 'base64':
342
+ return new libbase64.Encoder();
343
+ case 'quoted-printable':
344
+ return new libqp.Encoder();
345
+ default:
346
+ return new PassThrough();
347
+ }
348
+ }
349
+ }
350
+
351
+ module.exports = MimeNode;
@@ -0,0 +1,105 @@
1
+ import { Transform } from 'node:stream';
2
+ import type { FilterFunc, RewriterNode, SplitterChunk } from './types';
3
+
4
+ /** Transform stream that replaces the body content of selected MIME nodes. */
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
+ */
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
+ */
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
+ */
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
+ */
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
+ */
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
+ */
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
+ */
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
+ */
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
+ */
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
+ */
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
+ */
102
+ emit(event: 'node', data: RewriterNode): boolean;
103
+ }
104
+
105
+ export = NodeRewriter;