@zone-eu/mailsplit 5.4.9 → 5.4.10
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 +44 -0
- package/index.js +4 -1
- package/lib/chunked-passthrough.d.ts +13 -0
- package/lib/chunked-passthrough.js +13 -0
- package/lib/flowed-decoder.d.ts +13 -0
- package/lib/flowed-decoder.js +21 -3
- package/lib/headers.d.ts +24 -0
- package/lib/headers.js +118 -23
- package/lib/message-joiner.d.ts +12 -0
- package/lib/message-joiner.js +12 -0
- package/lib/message-splitter.d.ts +13 -0
- package/lib/message-splitter.js +55 -9
- package/lib/mime-node.d.ts +38 -0
- package/lib/mime-node.js +126 -47
- package/lib/node-rewriter.d.ts +18 -0
- package/lib/node-rewriter.js +48 -17
- package/lib/node-streamer.d.ts +18 -0
- package/lib/node-streamer.js +42 -8
- package/lib/types.d.ts +105 -0
- package/package.json +15 -5
package/lib/message-splitter.js
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
const Transform = require('stream').Transform;
|
|
4
4
|
const MimeNode = require('./mime-node');
|
|
5
5
|
|
|
6
|
+
/** @typedef {import('..').MimeNode} MimeNodeType */
|
|
7
|
+
/** @typedef {import('..').MessageChunk} MessageChunk */
|
|
8
|
+
/** @typedef {import('..').SplitterChunk} SplitterChunk */
|
|
9
|
+
/** @typedef {import('..').SplitterGroup} SplitterGroup */
|
|
10
|
+
/** @typedef {import('..').SplitterOptions} SplitterOptions */
|
|
11
|
+
/** @typedef {(err?: (Error & {code?: string}) | null, data?: SplitterChunk | MessageChunk | false, flush?: boolean) => void} ProcessLineCallback */
|
|
12
|
+
|
|
6
13
|
const MAX_HEAD_SIZE = 1 * 1024 * 1024;
|
|
7
14
|
const MAX_CHILD_NODES = 1000;
|
|
8
15
|
|
|
@@ -10,6 +17,9 @@ const HEAD = 0x01;
|
|
|
10
17
|
const BODY = 0x02;
|
|
11
18
|
|
|
12
19
|
class MessageSplitter extends Transform {
|
|
20
|
+
/**
|
|
21
|
+
* @param {SplitterOptions} [config]
|
|
22
|
+
*/
|
|
13
23
|
constructor(config) {
|
|
14
24
|
let options = {
|
|
15
25
|
readableObjectMode: true,
|
|
@@ -20,25 +30,36 @@ class MessageSplitter extends Transform {
|
|
|
20
30
|
this.config = config || {};
|
|
21
31
|
this.maxHeadSize = this.config.maxHeadSize || MAX_HEAD_SIZE;
|
|
22
32
|
this.maxChildNodes = this.config.maxChildNodes || MAX_CHILD_NODES;
|
|
33
|
+
/** @type {MimeNodeType[]} */
|
|
23
34
|
this.tree = [];
|
|
24
35
|
this.nodeCounter = 0;
|
|
36
|
+
this.node = /** @type {MimeNodeType} */ (/** @type {unknown} */ (null));
|
|
25
37
|
this.newNode();
|
|
26
38
|
this.tree.push(this.node);
|
|
39
|
+
/** @type {Buffer | false} */
|
|
27
40
|
this.line = false;
|
|
28
41
|
this.hasFailed = false;
|
|
29
42
|
}
|
|
30
43
|
|
|
44
|
+
/**
|
|
45
|
+
* @param {Buffer} chunk
|
|
46
|
+
* @param {BufferEncoding} encoding
|
|
47
|
+
* @param {import('stream').TransformCallback} callback
|
|
48
|
+
* @returns {void}
|
|
49
|
+
*/
|
|
31
50
|
_transform(chunk, encoding, callback) {
|
|
32
51
|
// process line by line
|
|
33
52
|
// find next line ending
|
|
34
53
|
let pos = 0;
|
|
35
54
|
let i = 0;
|
|
55
|
+
/** @type {SplitterGroup} */
|
|
36
56
|
let group = {
|
|
37
57
|
type: 'none'
|
|
38
58
|
};
|
|
39
59
|
let groupstart = this.line ? -this.line.length : 0;
|
|
40
60
|
let groupend = 0;
|
|
41
61
|
|
|
62
|
+
/** @param {MessageChunk} data */
|
|
42
63
|
let checkTrailingLinebreak = data => {
|
|
43
64
|
if (data.type === 'body' && data.node.parentNode && data.value && data.value.length) {
|
|
44
65
|
if (data.value[data.value.length - 1] === 0x0a) {
|
|
@@ -88,7 +109,7 @@ class MessageSplitter extends Transform {
|
|
|
88
109
|
|
|
89
110
|
if (flush) {
|
|
90
111
|
if (group && group.type !== 'none') {
|
|
91
|
-
if (group.type === 'body' && groupend >= groupstart && group.node.parentNode) {
|
|
112
|
+
if (group.type === 'body' && groupend >= groupstart && group.node && group.node.parentNode) {
|
|
92
113
|
// do not include the last line ending for body
|
|
93
114
|
if (chunk[groupend - 1] === 0x0a) {
|
|
94
115
|
groupend--;
|
|
@@ -99,7 +120,7 @@ class MessageSplitter extends Transform {
|
|
|
99
120
|
}
|
|
100
121
|
if (groupstart !== groupend) {
|
|
101
122
|
group.value = chunk.slice(groupstart, groupend);
|
|
102
|
-
if (groupend < i) {
|
|
123
|
+
if (groupend < i && 'value' in data) {
|
|
103
124
|
data.value = chunk.slice(groupend, i);
|
|
104
125
|
}
|
|
105
126
|
}
|
|
@@ -118,7 +139,7 @@ class MessageSplitter extends Transform {
|
|
|
118
139
|
// shift slice end position forward
|
|
119
140
|
groupend = i;
|
|
120
141
|
} else {
|
|
121
|
-
if (group.type === 'body' && groupend >= groupstart && group.node.parentNode) {
|
|
142
|
+
if (group.type === 'body' && groupend >= groupstart && group.node && group.node.parentNode) {
|
|
122
143
|
// do not include the last line ending for body
|
|
123
144
|
if (chunk[groupend - 1] === 0x0a) {
|
|
124
145
|
groupend--;
|
|
@@ -165,7 +186,7 @@ class MessageSplitter extends Transform {
|
|
|
165
186
|
}
|
|
166
187
|
|
|
167
188
|
// skip last linebreak for body
|
|
168
|
-
if (pos >= groupstart + 1 && group.type === 'body' && group.node.parentNode) {
|
|
189
|
+
if (pos >= groupstart + 1 && group.type === 'body' && group.node && group.node.parentNode) {
|
|
169
190
|
// do not include the last line ending for body
|
|
170
191
|
if (chunk[pos - 1] === 0x0a) {
|
|
171
192
|
pos--;
|
|
@@ -200,6 +221,10 @@ class MessageSplitter extends Transform {
|
|
|
200
221
|
setImmediate(iterateData);
|
|
201
222
|
}
|
|
202
223
|
|
|
224
|
+
/**
|
|
225
|
+
* @param {import('stream').TransformCallback} callback
|
|
226
|
+
* @returns {void}
|
|
227
|
+
*/
|
|
203
228
|
_flush(callback) {
|
|
204
229
|
if (this.hasFailed) {
|
|
205
230
|
return callback();
|
|
@@ -215,6 +240,12 @@ class MessageSplitter extends Transform {
|
|
|
215
240
|
});
|
|
216
241
|
}
|
|
217
242
|
|
|
243
|
+
/**
|
|
244
|
+
* @param {Buffer} line
|
|
245
|
+
* @param {number} startpos
|
|
246
|
+
* @param {Buffer} boundary
|
|
247
|
+
* @returns {1 | 2 | false}
|
|
248
|
+
*/
|
|
218
249
|
compareBoundary(line, startpos, boundary) {
|
|
219
250
|
// --{boundary}\r\n or --{boundary}--\r\n
|
|
220
251
|
if (line.length < boundary.length + 3 + startpos || line.length > boundary.length + 6 + startpos) {
|
|
@@ -256,6 +287,10 @@ class MessageSplitter extends Transform {
|
|
|
256
287
|
return 2;
|
|
257
288
|
}
|
|
258
289
|
|
|
290
|
+
/**
|
|
291
|
+
* @param {Buffer} line
|
|
292
|
+
* @returns {1 | 2 | 3 | 4 | false}
|
|
293
|
+
*/
|
|
259
294
|
checkBoundary(line) {
|
|
260
295
|
let startpos = 0;
|
|
261
296
|
if (line.length >= 1 && (line[0] === 0x0d || line[0] === 0x0a)) {
|
|
@@ -269,6 +304,7 @@ class MessageSplitter extends Transform {
|
|
|
269
304
|
return false;
|
|
270
305
|
}
|
|
271
306
|
|
|
307
|
+
/** @type {1 | 2 | false} */
|
|
272
308
|
let boundary;
|
|
273
309
|
if (this.node._boundary && (boundary = this.compareBoundary(line, startpos, this.node._boundary))) {
|
|
274
310
|
// 1: next child
|
|
@@ -279,12 +315,18 @@ class MessageSplitter extends Transform {
|
|
|
279
315
|
if (this.node._parentBoundary && (boundary = this.compareBoundary(line, startpos, this.node._parentBoundary))) {
|
|
280
316
|
// 3: next sibling
|
|
281
317
|
// 4: parent end
|
|
282
|
-
return boundary + 2;
|
|
318
|
+
return /** @type {3 | 4} */ (boundary + 2);
|
|
283
319
|
}
|
|
284
320
|
|
|
285
321
|
return false;
|
|
286
322
|
}
|
|
287
323
|
|
|
324
|
+
/**
|
|
325
|
+
* @param {Buffer | false} line
|
|
326
|
+
* @param {boolean} final
|
|
327
|
+
* @param {ProcessLineCallback} next
|
|
328
|
+
* @returns {void}
|
|
329
|
+
*/
|
|
288
330
|
processLine(line, final, next) {
|
|
289
331
|
let flush = false;
|
|
290
332
|
|
|
@@ -301,7 +343,7 @@ class MessageSplitter extends Transform {
|
|
|
301
343
|
}
|
|
302
344
|
|
|
303
345
|
if (this.nodeCounter > this.maxChildNodes) {
|
|
304
|
-
let err = new Error('Max allowed child nodes exceeded');
|
|
346
|
+
let err = /** @type {Error & {code?: string}} */ (new Error('Max allowed child nodes exceeded'));
|
|
305
347
|
err.code = 'EMAXLEN';
|
|
306
348
|
return next(err);
|
|
307
349
|
}
|
|
@@ -339,7 +381,7 @@ class MessageSplitter extends Transform {
|
|
|
339
381
|
}
|
|
340
382
|
// move up
|
|
341
383
|
if (this.tree.length) {
|
|
342
|
-
this.node = this.tree.pop();
|
|
384
|
+
this.node = /** @type {MimeNodeType} */ (this.tree.pop());
|
|
343
385
|
}
|
|
344
386
|
this.state = BODY;
|
|
345
387
|
break;
|
|
@@ -360,7 +402,7 @@ class MessageSplitter extends Transform {
|
|
|
360
402
|
case HEAD: {
|
|
361
403
|
this.node.addHeaderChunk(line);
|
|
362
404
|
if (this.node._headerlen > this.maxHeadSize) {
|
|
363
|
-
let err = new Error('Max header size for a MIME node exceeded');
|
|
405
|
+
let err = /** @type {Error & {code?: string}} */ (new Error('Max header size for a MIME node exceeded'));
|
|
364
406
|
err.code = 'EMAXLEN';
|
|
365
407
|
return next(err);
|
|
366
408
|
}
|
|
@@ -412,8 +454,12 @@ class MessageSplitter extends Transform {
|
|
|
412
454
|
next(null, false);
|
|
413
455
|
}
|
|
414
456
|
|
|
457
|
+
/**
|
|
458
|
+
* @param {MimeNodeType | false} [parent]
|
|
459
|
+
* @returns {void}
|
|
460
|
+
*/
|
|
415
461
|
newNode(parent) {
|
|
416
|
-
this.node = new MimeNode(parent || false, this.config);
|
|
462
|
+
this.node = /** @type {MimeNodeType} */ (new MimeNode(parent || false, this.config));
|
|
417
463
|
this.state = HEAD;
|
|
418
464
|
this.nodeCounter++;
|
|
419
465
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ContentStream, MimeNode as MimeNodeShape, PartNumber, PartNumberItem, SplitterOptions } from './types';
|
|
2
|
+
import type Headers = require('./headers');
|
|
3
|
+
|
|
4
|
+
declare class MimeNode implements MimeNodeShape {
|
|
5
|
+
type: 'node';
|
|
6
|
+
root: boolean;
|
|
7
|
+
parentNode: MimeNodeShape | false;
|
|
8
|
+
_boundary: Buffer | false;
|
|
9
|
+
_parentBoundary: Buffer | false;
|
|
10
|
+
_headerlen: number;
|
|
11
|
+
multipart: string | false;
|
|
12
|
+
encoding: string | false;
|
|
13
|
+
headers: Headers | false;
|
|
14
|
+
contentType: string | false;
|
|
15
|
+
charset: string | false;
|
|
16
|
+
disposition: string | false;
|
|
17
|
+
filename: string | false;
|
|
18
|
+
flowed: boolean;
|
|
19
|
+
delSp: boolean;
|
|
20
|
+
config: SplitterOptions;
|
|
21
|
+
partNr: PartNumber | false;
|
|
22
|
+
childPartNumbers: number;
|
|
23
|
+
rfc822: boolean;
|
|
24
|
+
messageNode?: boolean;
|
|
25
|
+
|
|
26
|
+
constructor(parentNode?: MimeNodeShape | false, config?: SplitterOptions);
|
|
27
|
+
getPartNr(provided?: PartNumberItem): PartNumber;
|
|
28
|
+
addHeaderChunk(line?: Buffer | false): void;
|
|
29
|
+
parseHeaders(): void;
|
|
30
|
+
getHeaders(): Buffer;
|
|
31
|
+
setContentType(contentType?: string | false): void;
|
|
32
|
+
setCharset(charset?: string | false): void;
|
|
33
|
+
setFilename(filename?: string | false): void;
|
|
34
|
+
getDecoder(): ContentStream;
|
|
35
|
+
getEncoder(encoding?: string | false): ContentStream;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export = MimeNode;
|
package/lib/mime-node.js
CHANGED
|
@@ -3,52 +3,94 @@
|
|
|
3
3
|
const Headers = require('./headers');
|
|
4
4
|
const libmime = require('libmime');
|
|
5
5
|
const libqp = require('libqp');
|
|
6
|
+
// @ts-ignore
|
|
6
7
|
const libbase64 = require('libbase64');
|
|
7
8
|
const PassThrough = require('stream').PassThrough;
|
|
8
9
|
const pathlib = require('path');
|
|
9
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
|
+
|
|
10
21
|
class MimeNode {
|
|
22
|
+
/**
|
|
23
|
+
* @param {MimeNodeType | false} parentNode
|
|
24
|
+
* @param {SplitterOptions} [config]
|
|
25
|
+
*/
|
|
11
26
|
constructor(parentNode, config) {
|
|
27
|
+
/** @type {'node'} */
|
|
12
28
|
this.type = 'node';
|
|
13
29
|
this.root = !parentNode;
|
|
30
|
+
/** @type {MimeNodeType | false} */
|
|
14
31
|
this.parentNode = parentNode;
|
|
15
32
|
|
|
33
|
+
/** @type {Buffer | false} */
|
|
16
34
|
this._parentBoundary = this.parentNode && this.parentNode._boundary;
|
|
35
|
+
/** @type {Buffer[]} */
|
|
17
36
|
this._headersLines = [];
|
|
18
37
|
this._headerlen = 0;
|
|
19
38
|
|
|
39
|
+
/** @type {StructuredHeader | false} */
|
|
20
40
|
this._parsedContentType = false;
|
|
41
|
+
/** @type {StructuredHeader | false} */
|
|
42
|
+
this._parsedContentDisposition = false;
|
|
43
|
+
/** @type {Buffer | false} */
|
|
21
44
|
this._boundary = false;
|
|
22
45
|
|
|
46
|
+
/** @type {string | false} */
|
|
23
47
|
this.multipart = false;
|
|
48
|
+
/** @type {string | false} */
|
|
24
49
|
this.encoding = false;
|
|
50
|
+
/** @type {HeadersType | false} */
|
|
25
51
|
this.headers = false;
|
|
52
|
+
/** @type {string | false} */
|
|
26
53
|
this.contentType = false;
|
|
54
|
+
/** @type {string | false} */
|
|
55
|
+
this.charset = false;
|
|
56
|
+
/** @type {string | false} */
|
|
57
|
+
this.disposition = false;
|
|
58
|
+
/** @type {string | false} */
|
|
59
|
+
this.filename = false;
|
|
27
60
|
this.flowed = false;
|
|
28
61
|
this.delSp = false;
|
|
29
62
|
|
|
30
63
|
this.config = config || {};
|
|
31
|
-
this.libmime = new
|
|
64
|
+
this.libmime = new Libmime({ Iconv: this.config.Iconv });
|
|
32
65
|
|
|
33
|
-
|
|
66
|
+
/** @type {PartNumber} */
|
|
67
|
+
this.parentPartNumber = [];
|
|
68
|
+
/** @type {PartNumber | false} */
|
|
34
69
|
this.partNr = false; // resolved later
|
|
35
70
|
this.childPartNumbers = 0;
|
|
71
|
+
this.rfc822 = false;
|
|
72
|
+
/** @type {boolean | undefined} */
|
|
73
|
+
this.messageNode = undefined;
|
|
36
74
|
}
|
|
37
75
|
|
|
76
|
+
/**
|
|
77
|
+
* @param {PartNumberItem} [provided]
|
|
78
|
+
* @returns {PartNumber}
|
|
79
|
+
*/
|
|
38
80
|
getPartNr(provided) {
|
|
81
|
+
/** @type {PartNumber} */
|
|
82
|
+
let partNr = this.partNr || [];
|
|
39
83
|
if (provided) {
|
|
40
|
-
return
|
|
41
|
-
.concat(this.partNr || [])
|
|
42
|
-
.filter(nr => !isNaN(nr))
|
|
43
|
-
.concat(provided);
|
|
84
|
+
return partNr.filter(nr => !isNaN(Number(nr))).concat(provided);
|
|
44
85
|
}
|
|
45
86
|
let childPartNr = ++this.childPartNumbers;
|
|
46
|
-
return
|
|
47
|
-
.concat(this.partNr || [])
|
|
48
|
-
.filter(nr => !isNaN(nr))
|
|
49
|
-
.concat(childPartNr);
|
|
87
|
+
return partNr.filter(nr => !isNaN(Number(nr))).concat(childPartNr);
|
|
50
88
|
}
|
|
51
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @param {Buffer | false} [line]
|
|
92
|
+
* @returns {void}
|
|
93
|
+
*/
|
|
52
94
|
addHeaderChunk(line) {
|
|
53
95
|
if (!line) {
|
|
54
96
|
return;
|
|
@@ -57,27 +99,32 @@ class MimeNode {
|
|
|
57
99
|
this._headerlen += line.length;
|
|
58
100
|
}
|
|
59
101
|
|
|
102
|
+
/**
|
|
103
|
+
* @returns {void}
|
|
104
|
+
*/
|
|
60
105
|
parseHeaders() {
|
|
61
106
|
if (this.headers) {
|
|
62
107
|
return;
|
|
63
108
|
}
|
|
64
109
|
this.headers = new Headers(Buffer.concat(this._headersLines, this._headerlen), this.config);
|
|
110
|
+
let headers = this.headers;
|
|
65
111
|
|
|
66
|
-
this._parsedContentDisposition = this.libmime.parseHeaderValue(
|
|
112
|
+
this._parsedContentDisposition = this.libmime.parseHeaderValue(headers.getFirst('Content-Disposition'));
|
|
113
|
+
let parsedContentDisposition = /** @type {StructuredHeader} */ (this._parsedContentDisposition);
|
|
67
114
|
|
|
68
115
|
// if content-type is missing default to plaintext
|
|
69
116
|
let contentHeader;
|
|
70
|
-
if (
|
|
71
|
-
contentHeader =
|
|
117
|
+
if (headers.get('Content-Type').length) {
|
|
118
|
+
contentHeader = headers.getFirst('Content-Type');
|
|
72
119
|
} else {
|
|
73
|
-
if (
|
|
74
|
-
let extension = pathlib.parse(
|
|
120
|
+
if (parsedContentDisposition.params.filename) {
|
|
121
|
+
let extension = pathlib.parse(parsedContentDisposition.params.filename).ext.replace(/^\./, '');
|
|
75
122
|
if (extension) {
|
|
76
123
|
contentHeader = libmime.detectMimeType(extension);
|
|
77
124
|
}
|
|
78
125
|
}
|
|
79
126
|
if (!contentHeader) {
|
|
80
|
-
if (/^attachment$/i.test(
|
|
127
|
+
if (/^attachment$/i.test(parsedContentDisposition.value)) {
|
|
81
128
|
contentHeader = 'application/octet-stream';
|
|
82
129
|
} else {
|
|
83
130
|
contentHeader = 'text/plain';
|
|
@@ -86,15 +133,16 @@ class MimeNode {
|
|
|
86
133
|
}
|
|
87
134
|
|
|
88
135
|
this._parsedContentType = this.libmime.parseHeaderValue(contentHeader);
|
|
136
|
+
let parsedContentType = /** @type {StructuredHeader} */ (this._parsedContentType);
|
|
89
137
|
|
|
90
|
-
this.encoding =
|
|
138
|
+
this.encoding = headers
|
|
91
139
|
.getFirst('Content-Transfer-Encoding')
|
|
92
140
|
.replace(/\(.*\)/g, '')
|
|
93
141
|
.toLowerCase()
|
|
94
142
|
.trim();
|
|
95
|
-
this.contentType = (
|
|
96
|
-
this.charset =
|
|
97
|
-
this.disposition = (
|
|
143
|
+
this.contentType = (parsedContentType.value || '').toLowerCase().trim() || false;
|
|
144
|
+
this.charset = parsedContentType.params.charset || false;
|
|
145
|
+
this.disposition = (parsedContentDisposition.value || '').toLowerCase().trim() || false;
|
|
98
146
|
|
|
99
147
|
// fix invalidly encoded disposition values
|
|
100
148
|
if (this.disposition) {
|
|
@@ -105,11 +153,11 @@ class MimeNode {
|
|
|
105
153
|
}
|
|
106
154
|
}
|
|
107
155
|
|
|
108
|
-
this.filename =
|
|
156
|
+
this.filename = parsedContentDisposition.params.filename || parsedContentType.params.name || false;
|
|
109
157
|
|
|
110
|
-
if (
|
|
158
|
+
if (parsedContentType.params.format && parsedContentType.params.format.toLowerCase().trim() === 'flowed') {
|
|
111
159
|
this.flowed = true;
|
|
112
|
-
if (
|
|
160
|
+
if (parsedContentType.params.delsp && parsedContentType.params.delsp.toLowerCase().trim() === 'yes') {
|
|
113
161
|
this.delSp = true;
|
|
114
162
|
}
|
|
115
163
|
}
|
|
@@ -127,7 +175,7 @@ class MimeNode {
|
|
|
127
175
|
this.contentType.substr(0, this.contentType.indexOf('/')) === 'multipart' &&
|
|
128
176
|
this.contentType.substr(this.contentType.indexOf('/') + 1)) ||
|
|
129
177
|
false;
|
|
130
|
-
this._boundary = (
|
|
178
|
+
this._boundary = (parsedContentType.params.boundary && Buffer.from(parsedContentType.params.boundary)) || false;
|
|
131
179
|
|
|
132
180
|
this.rfc822 = this.contentType === 'message/rfc822';
|
|
133
181
|
|
|
@@ -138,38 +186,54 @@ class MimeNode {
|
|
|
138
186
|
}
|
|
139
187
|
}
|
|
140
188
|
|
|
189
|
+
/**
|
|
190
|
+
* @returns {Buffer}
|
|
191
|
+
*/
|
|
141
192
|
getHeaders() {
|
|
142
193
|
if (!this.headers) {
|
|
143
194
|
this.parseHeaders();
|
|
144
195
|
}
|
|
145
|
-
|
|
196
|
+
let headers = /** @type {HeadersType} */ (this.headers);
|
|
197
|
+
return headers.build();
|
|
146
198
|
}
|
|
147
199
|
|
|
200
|
+
/**
|
|
201
|
+
* @param {string | false} [contentType]
|
|
202
|
+
* @returns {void}
|
|
203
|
+
*/
|
|
148
204
|
setContentType(contentType) {
|
|
149
205
|
if (!this.headers) {
|
|
150
206
|
this.parseHeaders();
|
|
151
207
|
}
|
|
208
|
+
let headers = /** @type {HeadersType} */ (this.headers);
|
|
209
|
+
let parsedContentType = /** @type {StructuredHeader} */ (this._parsedContentType);
|
|
152
210
|
|
|
153
211
|
contentType = (contentType || '').toLowerCase().trim();
|
|
154
212
|
if (contentType) {
|
|
155
|
-
|
|
213
|
+
parsedContentType.value = contentType;
|
|
156
214
|
}
|
|
157
215
|
|
|
158
|
-
if (!this.flowed &&
|
|
159
|
-
delete
|
|
216
|
+
if (!this.flowed && parsedContentType.params.format) {
|
|
217
|
+
delete parsedContentType.params.format;
|
|
160
218
|
}
|
|
161
219
|
|
|
162
|
-
if (!this.delSp &&
|
|
163
|
-
delete
|
|
220
|
+
if (!this.delSp && parsedContentType.params.delsp) {
|
|
221
|
+
delete parsedContentType.params.delsp;
|
|
164
222
|
}
|
|
165
223
|
|
|
166
|
-
|
|
224
|
+
headers.update('Content-Type', this.libmime.buildHeaderValue(parsedContentType));
|
|
167
225
|
}
|
|
168
226
|
|
|
227
|
+
/**
|
|
228
|
+
* @param {string | false} [charset]
|
|
229
|
+
* @returns {void}
|
|
230
|
+
*/
|
|
169
231
|
setCharset(charset) {
|
|
170
232
|
if (!this.headers) {
|
|
171
233
|
this.parseHeaders();
|
|
172
234
|
}
|
|
235
|
+
let headers = /** @type {HeadersType} */ (this.headers);
|
|
236
|
+
let parsedContentType = /** @type {StructuredHeader} */ (this._parsedContentType);
|
|
173
237
|
|
|
174
238
|
charset = (charset || '').toLowerCase().trim();
|
|
175
239
|
|
|
@@ -178,51 +242,61 @@ class MimeNode {
|
|
|
178
242
|
}
|
|
179
243
|
|
|
180
244
|
if (!charset) {
|
|
181
|
-
if (!
|
|
245
|
+
if (!parsedContentType.value) {
|
|
182
246
|
// nothing to set or update
|
|
183
247
|
return;
|
|
184
248
|
}
|
|
185
|
-
delete
|
|
249
|
+
delete parsedContentType.params.charset;
|
|
186
250
|
} else {
|
|
187
|
-
|
|
251
|
+
parsedContentType.params.charset = charset;
|
|
188
252
|
}
|
|
189
253
|
|
|
190
|
-
if (!
|
|
191
|
-
|
|
254
|
+
if (!parsedContentType.value) {
|
|
255
|
+
parsedContentType.value = 'text/plain';
|
|
192
256
|
}
|
|
193
257
|
|
|
194
|
-
|
|
258
|
+
headers.update('Content-Type', this.libmime.buildHeaderValue(parsedContentType));
|
|
195
259
|
}
|
|
196
260
|
|
|
261
|
+
/**
|
|
262
|
+
* @param {string | false} [filename]
|
|
263
|
+
* @returns {void}
|
|
264
|
+
*/
|
|
197
265
|
setFilename(filename) {
|
|
198
266
|
if (!this.headers) {
|
|
199
267
|
this.parseHeaders();
|
|
200
268
|
}
|
|
269
|
+
let headers = /** @type {HeadersType} */ (this.headers);
|
|
270
|
+
let parsedContentType = /** @type {StructuredHeader} */ (this._parsedContentType);
|
|
271
|
+
let parsedContentDisposition = /** @type {StructuredHeader} */ (this._parsedContentDisposition);
|
|
201
272
|
|
|
202
273
|
this.filename = (filename || '').toLowerCase().trim();
|
|
203
274
|
|
|
204
|
-
if (
|
|
205
|
-
delete
|
|
206
|
-
|
|
275
|
+
if (parsedContentType.params.name) {
|
|
276
|
+
delete parsedContentType.params.name;
|
|
277
|
+
headers.update('Content-Type', this.libmime.buildHeaderValue(parsedContentType));
|
|
207
278
|
}
|
|
208
279
|
|
|
209
280
|
if (!this.filename) {
|
|
210
|
-
if (!
|
|
281
|
+
if (!parsedContentDisposition.value) {
|
|
211
282
|
// nothing to set or update
|
|
212
283
|
return;
|
|
213
284
|
}
|
|
214
|
-
delete
|
|
285
|
+
delete parsedContentDisposition.params.filename;
|
|
215
286
|
} else {
|
|
216
|
-
|
|
287
|
+
parsedContentDisposition.params.filename = this.filename;
|
|
217
288
|
}
|
|
218
289
|
|
|
219
|
-
if (!
|
|
220
|
-
|
|
290
|
+
if (!parsedContentDisposition.value) {
|
|
291
|
+
parsedContentDisposition.value = 'attachment';
|
|
221
292
|
}
|
|
222
293
|
|
|
223
|
-
|
|
294
|
+
headers.update('Content-Disposition', this.libmime.buildHeaderValue(parsedContentDisposition));
|
|
224
295
|
}
|
|
225
296
|
|
|
297
|
+
/**
|
|
298
|
+
* @returns {import('stream').Transform | import('stream').PassThrough}
|
|
299
|
+
*/
|
|
226
300
|
getDecoder() {
|
|
227
301
|
if (!this.headers) {
|
|
228
302
|
this.parseHeaders();
|
|
@@ -238,15 +312,20 @@ class MimeNode {
|
|
|
238
312
|
}
|
|
239
313
|
}
|
|
240
314
|
|
|
315
|
+
/**
|
|
316
|
+
* @param {string | false} [encoding]
|
|
317
|
+
* @returns {import('stream').Transform | import('stream').PassThrough}
|
|
318
|
+
*/
|
|
241
319
|
getEncoder(encoding) {
|
|
242
320
|
if (!this.headers) {
|
|
243
321
|
this.parseHeaders();
|
|
244
322
|
}
|
|
323
|
+
let headers = /** @type {HeadersType} */ (this.headers);
|
|
245
324
|
|
|
246
325
|
encoding = (encoding || '').toString().toLowerCase().trim();
|
|
247
326
|
|
|
248
327
|
if (encoding && encoding !== this.encoding) {
|
|
249
|
-
|
|
328
|
+
headers.update('Content-Transfer-Encoding', encoding);
|
|
250
329
|
} else {
|
|
251
330
|
encoding = this.encoding;
|
|
252
331
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Transform } from 'node:stream';
|
|
2
|
+
import type { FilterFunc, RewriterNode, SplitterChunk } from './types';
|
|
3
|
+
|
|
4
|
+
declare class NodeRewriter extends Transform {
|
|
5
|
+
constructor(filterFunc: FilterFunc, rewriteAction?: Function);
|
|
6
|
+
on(event: 'data', listener: (data: SplitterChunk) => void): this;
|
|
7
|
+
on(event: 'node', listener: (data: RewriterNode) => void): this;
|
|
8
|
+
once(event: 'data', listener: (data: SplitterChunk) => void): this;
|
|
9
|
+
once(event: 'node', listener: (data: RewriterNode) => void): this;
|
|
10
|
+
addListener(event: 'data', listener: (data: SplitterChunk) => void): this;
|
|
11
|
+
addListener(event: 'node', listener: (data: RewriterNode) => void): this;
|
|
12
|
+
prependListener(event: 'data', listener: (data: SplitterChunk) => void): this;
|
|
13
|
+
prependListener(event: 'node', listener: (data: RewriterNode) => void): this;
|
|
14
|
+
emit(event: 'data', data: SplitterChunk): boolean;
|
|
15
|
+
emit(event: 'node', data: RewriterNode): boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export = NodeRewriter;
|