@zone-eu/mailsplit 5.4.9 → 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 +100 -0
- package/index.js +4 -1
- package/lib/chunked-passthrough.d.ts +59 -0
- package/lib/chunked-passthrough.js +16 -0
- package/lib/flowed-decoder.d.ts +59 -0
- package/lib/flowed-decoder.js +23 -6
- package/lib/headers.d.ts +117 -0
- package/lib/headers.js +120 -25
- package/lib/message-joiner.d.ts +56 -0
- package/lib/message-joiner.js +18 -0
- package/lib/message-splitter.d.ts +59 -0
- package/lib/message-splitter.js +68 -9
- package/lib/mime-node.d.ts +144 -0
- package/lib/mime-node.js +129 -47
- package/lib/node-rewriter.d.ts +105 -0
- package/lib/node-rewriter.js +66 -21
- package/lib/node-streamer.d.ts +105 -0
- package/lib/node-streamer.js +66 -13
- package/lib/types.d.ts +285 -0
- package/package.json +15 -5
|
@@ -0,0 +1,144 @@
|
|
|
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
|
+
/** Length, in bytes, of the raw header block collected for this node. */
|
|
22
|
+
_headerlen: number;
|
|
23
|
+
|
|
24
|
+
/** Multipart subtype such as `mixed` or `alternative`, or `false` for leaf nodes. */
|
|
25
|
+
multipart: string | false;
|
|
26
|
+
|
|
27
|
+
/** Content-Transfer-Encoding value, normalized to lower case, or `false` when absent. */
|
|
28
|
+
encoding: string | false;
|
|
29
|
+
|
|
30
|
+
/** Parsed and mutable header collection, available after headers are parsed. */
|
|
31
|
+
headers: Headers | false;
|
|
32
|
+
|
|
33
|
+
/** MIME content type such as `text/plain`, or `false` when unavailable. */
|
|
34
|
+
contentType: string | false;
|
|
35
|
+
|
|
36
|
+
/** Charset parameter from Content-Type, or `false` when absent. */
|
|
37
|
+
charset: string | false;
|
|
38
|
+
|
|
39
|
+
/** Content-Disposition value such as `inline` or `attachment`, or `false` when absent. */
|
|
40
|
+
disposition: string | false;
|
|
41
|
+
|
|
42
|
+
/** Decoded filename from Content-Disposition or Content-Type parameters, or `false` when absent. */
|
|
43
|
+
filename: string | false;
|
|
44
|
+
|
|
45
|
+
/** Whether this node is `text/*` with `format=flowed`. */
|
|
46
|
+
flowed: boolean;
|
|
47
|
+
|
|
48
|
+
/** Whether flowed text uses `delsp=yes`. */
|
|
49
|
+
delSp: boolean;
|
|
50
|
+
|
|
51
|
+
/** Splitter configuration used when parsing this node. */
|
|
52
|
+
config: SplitterOptions;
|
|
53
|
+
|
|
54
|
+
/** Resolved IMAP-style part number for this node, or `false` before resolution. */
|
|
55
|
+
partNr: PartNumber | false;
|
|
56
|
+
|
|
57
|
+
/** Number of child part numbers allocated by this node. */
|
|
58
|
+
childPartNumbers: number;
|
|
59
|
+
|
|
60
|
+
/** Whether this node's content type is `message/rfc822`. */
|
|
61
|
+
rfc822: boolean;
|
|
62
|
+
|
|
63
|
+
/** Whether an embedded `message/rfc822` node was parsed as a nested message. */
|
|
64
|
+
messageNode?: boolean;
|
|
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
|
+
*/
|
|
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
|
+
*/
|
|
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
|
+
*/
|
|
88
|
+
addHeaderChunk(line?: Buffer | false): void;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Parses collected header bytes and populates MIME metadata fields.
|
|
92
|
+
*
|
|
93
|
+
* @returns Nothing.
|
|
94
|
+
*/
|
|
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
|
+
*/
|
|
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
|
+
*/
|
|
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
|
+
*/
|
|
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
|
+
*/
|
|
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
|
+
*/
|
|
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
|
+
*/
|
|
141
|
+
getEncoder(encoding?: string | false): ContentStream;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export = MimeNode;
|
package/lib/mime-node.js
CHANGED
|
@@ -3,52 +3,97 @@
|
|
|
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
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parsed MIME node with mutable headers and transfer-encoding helpers.
|
|
23
|
+
*/
|
|
10
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
|
+
*/
|
|
11
29
|
constructor(parentNode, config) {
|
|
30
|
+
/** @type {'node'} */
|
|
12
31
|
this.type = 'node';
|
|
13
32
|
this.root = !parentNode;
|
|
33
|
+
/** @type {MimeNodeType | false} */
|
|
14
34
|
this.parentNode = parentNode;
|
|
15
35
|
|
|
36
|
+
/** @type {Buffer | false} */
|
|
16
37
|
this._parentBoundary = this.parentNode && this.parentNode._boundary;
|
|
38
|
+
/** @type {Buffer[]} */
|
|
17
39
|
this._headersLines = [];
|
|
18
40
|
this._headerlen = 0;
|
|
19
41
|
|
|
42
|
+
/** @type {StructuredHeader | false} */
|
|
20
43
|
this._parsedContentType = false;
|
|
44
|
+
/** @type {StructuredHeader | false} */
|
|
45
|
+
this._parsedContentDisposition = false;
|
|
46
|
+
/** @type {Buffer | false} */
|
|
21
47
|
this._boundary = false;
|
|
22
48
|
|
|
49
|
+
/** @type {string | false} */
|
|
23
50
|
this.multipart = false;
|
|
51
|
+
/** @type {string | false} */
|
|
24
52
|
this.encoding = false;
|
|
53
|
+
/** @type {HeadersType | false} */
|
|
25
54
|
this.headers = false;
|
|
55
|
+
/** @type {string | false} */
|
|
26
56
|
this.contentType = false;
|
|
57
|
+
/** @type {string | false} */
|
|
58
|
+
this.charset = false;
|
|
59
|
+
/** @type {string | false} */
|
|
60
|
+
this.disposition = false;
|
|
61
|
+
/** @type {string | false} */
|
|
62
|
+
this.filename = false;
|
|
27
63
|
this.flowed = false;
|
|
28
64
|
this.delSp = false;
|
|
29
65
|
|
|
30
66
|
this.config = config || {};
|
|
31
|
-
this.libmime = new
|
|
67
|
+
this.libmime = new Libmime({ Iconv: this.config.Iconv });
|
|
32
68
|
|
|
33
|
-
|
|
69
|
+
/** @type {PartNumber} */
|
|
70
|
+
this.parentPartNumber = [];
|
|
71
|
+
/** @type {PartNumber | false} */
|
|
34
72
|
this.partNr = false; // resolved later
|
|
35
73
|
this.childPartNumbers = 0;
|
|
74
|
+
this.rfc822 = false;
|
|
75
|
+
/** @type {boolean | undefined} */
|
|
76
|
+
this.messageNode = undefined;
|
|
36
77
|
}
|
|
37
78
|
|
|
79
|
+
/**
|
|
80
|
+
* @param {PartNumberItem} [provided]
|
|
81
|
+
* @returns {PartNumber}
|
|
82
|
+
*/
|
|
38
83
|
getPartNr(provided) {
|
|
84
|
+
/** @type {PartNumber} */
|
|
85
|
+
let partNr = this.partNr || [];
|
|
39
86
|
if (provided) {
|
|
40
|
-
return
|
|
41
|
-
.concat(this.partNr || [])
|
|
42
|
-
.filter(nr => !isNaN(nr))
|
|
43
|
-
.concat(provided);
|
|
87
|
+
return partNr.filter(nr => !isNaN(Number(nr))).concat(provided);
|
|
44
88
|
}
|
|
45
89
|
let childPartNr = ++this.childPartNumbers;
|
|
46
|
-
return
|
|
47
|
-
.concat(this.partNr || [])
|
|
48
|
-
.filter(nr => !isNaN(nr))
|
|
49
|
-
.concat(childPartNr);
|
|
90
|
+
return partNr.filter(nr => !isNaN(Number(nr))).concat(childPartNr);
|
|
50
91
|
}
|
|
51
92
|
|
|
93
|
+
/**
|
|
94
|
+
* @param {Buffer | false} [line]
|
|
95
|
+
* @returns {void}
|
|
96
|
+
*/
|
|
52
97
|
addHeaderChunk(line) {
|
|
53
98
|
if (!line) {
|
|
54
99
|
return;
|
|
@@ -57,27 +102,32 @@ class MimeNode {
|
|
|
57
102
|
this._headerlen += line.length;
|
|
58
103
|
}
|
|
59
104
|
|
|
105
|
+
/**
|
|
106
|
+
* @returns {void}
|
|
107
|
+
*/
|
|
60
108
|
parseHeaders() {
|
|
61
109
|
if (this.headers) {
|
|
62
110
|
return;
|
|
63
111
|
}
|
|
64
112
|
this.headers = new Headers(Buffer.concat(this._headersLines, this._headerlen), this.config);
|
|
113
|
+
let headers = this.headers;
|
|
65
114
|
|
|
66
|
-
this._parsedContentDisposition = this.libmime.parseHeaderValue(
|
|
115
|
+
this._parsedContentDisposition = this.libmime.parseHeaderValue(headers.getFirst('Content-Disposition'));
|
|
116
|
+
let parsedContentDisposition = /** @type {StructuredHeader} */ (this._parsedContentDisposition);
|
|
67
117
|
|
|
68
118
|
// if content-type is missing default to plaintext
|
|
69
119
|
let contentHeader;
|
|
70
|
-
if (
|
|
71
|
-
contentHeader =
|
|
120
|
+
if (headers.get('Content-Type').length) {
|
|
121
|
+
contentHeader = headers.getFirst('Content-Type');
|
|
72
122
|
} else {
|
|
73
|
-
if (
|
|
74
|
-
let extension = pathlib.parse(
|
|
123
|
+
if (parsedContentDisposition.params.filename) {
|
|
124
|
+
let extension = pathlib.parse(parsedContentDisposition.params.filename).ext.replace(/^\./, '');
|
|
75
125
|
if (extension) {
|
|
76
126
|
contentHeader = libmime.detectMimeType(extension);
|
|
77
127
|
}
|
|
78
128
|
}
|
|
79
129
|
if (!contentHeader) {
|
|
80
|
-
if (/^attachment$/i.test(
|
|
130
|
+
if (/^attachment$/i.test(parsedContentDisposition.value)) {
|
|
81
131
|
contentHeader = 'application/octet-stream';
|
|
82
132
|
} else {
|
|
83
133
|
contentHeader = 'text/plain';
|
|
@@ -86,15 +136,16 @@ class MimeNode {
|
|
|
86
136
|
}
|
|
87
137
|
|
|
88
138
|
this._parsedContentType = this.libmime.parseHeaderValue(contentHeader);
|
|
139
|
+
let parsedContentType = /** @type {StructuredHeader} */ (this._parsedContentType);
|
|
89
140
|
|
|
90
|
-
this.encoding =
|
|
141
|
+
this.encoding = headers
|
|
91
142
|
.getFirst('Content-Transfer-Encoding')
|
|
92
143
|
.replace(/\(.*\)/g, '')
|
|
93
144
|
.toLowerCase()
|
|
94
145
|
.trim();
|
|
95
|
-
this.contentType = (
|
|
96
|
-
this.charset =
|
|
97
|
-
this.disposition = (
|
|
146
|
+
this.contentType = (parsedContentType.value || '').toLowerCase().trim() || false;
|
|
147
|
+
this.charset = parsedContentType.params.charset || false;
|
|
148
|
+
this.disposition = (parsedContentDisposition.value || '').toLowerCase().trim() || false;
|
|
98
149
|
|
|
99
150
|
// fix invalidly encoded disposition values
|
|
100
151
|
if (this.disposition) {
|
|
@@ -105,11 +156,11 @@ class MimeNode {
|
|
|
105
156
|
}
|
|
106
157
|
}
|
|
107
158
|
|
|
108
|
-
this.filename =
|
|
159
|
+
this.filename = parsedContentDisposition.params.filename || parsedContentType.params.name || false;
|
|
109
160
|
|
|
110
|
-
if (
|
|
161
|
+
if (parsedContentType.params.format && parsedContentType.params.format.toLowerCase().trim() === 'flowed') {
|
|
111
162
|
this.flowed = true;
|
|
112
|
-
if (
|
|
163
|
+
if (parsedContentType.params.delsp && parsedContentType.params.delsp.toLowerCase().trim() === 'yes') {
|
|
113
164
|
this.delSp = true;
|
|
114
165
|
}
|
|
115
166
|
}
|
|
@@ -127,7 +178,7 @@ class MimeNode {
|
|
|
127
178
|
this.contentType.substr(0, this.contentType.indexOf('/')) === 'multipart' &&
|
|
128
179
|
this.contentType.substr(this.contentType.indexOf('/') + 1)) ||
|
|
129
180
|
false;
|
|
130
|
-
this._boundary = (
|
|
181
|
+
this._boundary = (parsedContentType.params.boundary && Buffer.from(parsedContentType.params.boundary)) || false;
|
|
131
182
|
|
|
132
183
|
this.rfc822 = this.contentType === 'message/rfc822';
|
|
133
184
|
|
|
@@ -138,38 +189,54 @@ class MimeNode {
|
|
|
138
189
|
}
|
|
139
190
|
}
|
|
140
191
|
|
|
192
|
+
/**
|
|
193
|
+
* @returns {Buffer}
|
|
194
|
+
*/
|
|
141
195
|
getHeaders() {
|
|
142
196
|
if (!this.headers) {
|
|
143
197
|
this.parseHeaders();
|
|
144
198
|
}
|
|
145
|
-
|
|
199
|
+
let headers = /** @type {HeadersType} */ (this.headers);
|
|
200
|
+
return headers.build();
|
|
146
201
|
}
|
|
147
202
|
|
|
203
|
+
/**
|
|
204
|
+
* @param {string | false} [contentType]
|
|
205
|
+
* @returns {void}
|
|
206
|
+
*/
|
|
148
207
|
setContentType(contentType) {
|
|
149
208
|
if (!this.headers) {
|
|
150
209
|
this.parseHeaders();
|
|
151
210
|
}
|
|
211
|
+
let headers = /** @type {HeadersType} */ (this.headers);
|
|
212
|
+
let parsedContentType = /** @type {StructuredHeader} */ (this._parsedContentType);
|
|
152
213
|
|
|
153
214
|
contentType = (contentType || '').toLowerCase().trim();
|
|
154
215
|
if (contentType) {
|
|
155
|
-
|
|
216
|
+
parsedContentType.value = contentType;
|
|
156
217
|
}
|
|
157
218
|
|
|
158
|
-
if (!this.flowed &&
|
|
159
|
-
delete
|
|
219
|
+
if (!this.flowed && parsedContentType.params.format) {
|
|
220
|
+
delete parsedContentType.params.format;
|
|
160
221
|
}
|
|
161
222
|
|
|
162
|
-
if (!this.delSp &&
|
|
163
|
-
delete
|
|
223
|
+
if (!this.delSp && parsedContentType.params.delsp) {
|
|
224
|
+
delete parsedContentType.params.delsp;
|
|
164
225
|
}
|
|
165
226
|
|
|
166
|
-
|
|
227
|
+
headers.update('Content-Type', this.libmime.buildHeaderValue(parsedContentType));
|
|
167
228
|
}
|
|
168
229
|
|
|
230
|
+
/**
|
|
231
|
+
* @param {string | false} [charset]
|
|
232
|
+
* @returns {void}
|
|
233
|
+
*/
|
|
169
234
|
setCharset(charset) {
|
|
170
235
|
if (!this.headers) {
|
|
171
236
|
this.parseHeaders();
|
|
172
237
|
}
|
|
238
|
+
let headers = /** @type {HeadersType} */ (this.headers);
|
|
239
|
+
let parsedContentType = /** @type {StructuredHeader} */ (this._parsedContentType);
|
|
173
240
|
|
|
174
241
|
charset = (charset || '').toLowerCase().trim();
|
|
175
242
|
|
|
@@ -178,51 +245,61 @@ class MimeNode {
|
|
|
178
245
|
}
|
|
179
246
|
|
|
180
247
|
if (!charset) {
|
|
181
|
-
if (!
|
|
248
|
+
if (!parsedContentType.value) {
|
|
182
249
|
// nothing to set or update
|
|
183
250
|
return;
|
|
184
251
|
}
|
|
185
|
-
delete
|
|
252
|
+
delete parsedContentType.params.charset;
|
|
186
253
|
} else {
|
|
187
|
-
|
|
254
|
+
parsedContentType.params.charset = charset;
|
|
188
255
|
}
|
|
189
256
|
|
|
190
|
-
if (!
|
|
191
|
-
|
|
257
|
+
if (!parsedContentType.value) {
|
|
258
|
+
parsedContentType.value = 'text/plain';
|
|
192
259
|
}
|
|
193
260
|
|
|
194
|
-
|
|
261
|
+
headers.update('Content-Type', this.libmime.buildHeaderValue(parsedContentType));
|
|
195
262
|
}
|
|
196
263
|
|
|
264
|
+
/**
|
|
265
|
+
* @param {string | false} [filename]
|
|
266
|
+
* @returns {void}
|
|
267
|
+
*/
|
|
197
268
|
setFilename(filename) {
|
|
198
269
|
if (!this.headers) {
|
|
199
270
|
this.parseHeaders();
|
|
200
271
|
}
|
|
272
|
+
let headers = /** @type {HeadersType} */ (this.headers);
|
|
273
|
+
let parsedContentType = /** @type {StructuredHeader} */ (this._parsedContentType);
|
|
274
|
+
let parsedContentDisposition = /** @type {StructuredHeader} */ (this._parsedContentDisposition);
|
|
201
275
|
|
|
202
276
|
this.filename = (filename || '').toLowerCase().trim();
|
|
203
277
|
|
|
204
|
-
if (
|
|
205
|
-
delete
|
|
206
|
-
|
|
278
|
+
if (parsedContentType.params.name) {
|
|
279
|
+
delete parsedContentType.params.name;
|
|
280
|
+
headers.update('Content-Type', this.libmime.buildHeaderValue(parsedContentType));
|
|
207
281
|
}
|
|
208
282
|
|
|
209
283
|
if (!this.filename) {
|
|
210
|
-
if (!
|
|
284
|
+
if (!parsedContentDisposition.value) {
|
|
211
285
|
// nothing to set or update
|
|
212
286
|
return;
|
|
213
287
|
}
|
|
214
|
-
delete
|
|
288
|
+
delete parsedContentDisposition.params.filename;
|
|
215
289
|
} else {
|
|
216
|
-
|
|
290
|
+
parsedContentDisposition.params.filename = this.filename;
|
|
217
291
|
}
|
|
218
292
|
|
|
219
|
-
if (!
|
|
220
|
-
|
|
293
|
+
if (!parsedContentDisposition.value) {
|
|
294
|
+
parsedContentDisposition.value = 'attachment';
|
|
221
295
|
}
|
|
222
296
|
|
|
223
|
-
|
|
297
|
+
headers.update('Content-Disposition', this.libmime.buildHeaderValue(parsedContentDisposition));
|
|
224
298
|
}
|
|
225
299
|
|
|
300
|
+
/**
|
|
301
|
+
* @returns {import('stream').Transform | import('stream').PassThrough}
|
|
302
|
+
*/
|
|
226
303
|
getDecoder() {
|
|
227
304
|
if (!this.headers) {
|
|
228
305
|
this.parseHeaders();
|
|
@@ -238,15 +315,20 @@ class MimeNode {
|
|
|
238
315
|
}
|
|
239
316
|
}
|
|
240
317
|
|
|
318
|
+
/**
|
|
319
|
+
* @param {string | false} [encoding]
|
|
320
|
+
* @returns {import('stream').Transform | import('stream').PassThrough}
|
|
321
|
+
*/
|
|
241
322
|
getEncoder(encoding) {
|
|
242
323
|
if (!this.headers) {
|
|
243
324
|
this.parseHeaders();
|
|
244
325
|
}
|
|
326
|
+
let headers = /** @type {HeadersType} */ (this.headers);
|
|
245
327
|
|
|
246
328
|
encoding = (encoding || '').toString().toLowerCase().trim();
|
|
247
329
|
|
|
248
330
|
if (encoding && encoding !== this.encoding) {
|
|
249
|
-
|
|
331
|
+
headers.update('Content-Transfer-Encoding', encoding);
|
|
250
332
|
} else {
|
|
251
333
|
encoding = this.encoding;
|
|
252
334
|
}
|
|
@@ -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;
|