@zone-eu/mailsplit 5.4.8 → 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.
@@ -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 libmime.Libmime({ Iconv: this.config.Iconv });
64
+ this.libmime = new Libmime({ Iconv: this.config.Iconv });
32
65
 
33
- this.parentPartNumber = (parentNode && this.partNr) || [];
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(this.headers.getFirst('Content-Disposition'));
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 (this.headers.get('Content-Type').length) {
71
- contentHeader = this.headers.getFirst('Content-Type');
117
+ if (headers.get('Content-Type').length) {
118
+ contentHeader = headers.getFirst('Content-Type');
72
119
  } else {
73
- if (this._parsedContentDisposition.params.filename) {
74
- let extension = pathlib.parse(this._parsedContentDisposition.params.filename).ext.replace(/^\./, '');
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(this._parsedContentDisposition.value)) {
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 = this.headers
138
+ this.encoding = headers
91
139
  .getFirst('Content-Transfer-Encoding')
92
140
  .replace(/\(.*\)/g, '')
93
141
  .toLowerCase()
94
142
  .trim();
95
- this.contentType = (this._parsedContentType.value || '').toLowerCase().trim() || false;
96
- this.charset = this._parsedContentType.params.charset || false;
97
- this.disposition = (this._parsedContentDisposition.value || '').toLowerCase().trim() || false;
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 = this._parsedContentDisposition.params.filename || this._parsedContentType.params.name || false;
156
+ this.filename = parsedContentDisposition.params.filename || parsedContentType.params.name || false;
109
157
 
110
- if (this._parsedContentType.params.format && this._parsedContentType.params.format.toLowerCase().trim() === 'flowed') {
158
+ if (parsedContentType.params.format && parsedContentType.params.format.toLowerCase().trim() === 'flowed') {
111
159
  this.flowed = true;
112
- if (this._parsedContentType.params.delsp && this._parsedContentType.params.delsp.toLowerCase().trim() === 'yes') {
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 = (this._parsedContentType.params.boundary && Buffer.from(this._parsedContentType.params.boundary)) || false;
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
- return this.headers.build();
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
- this._parsedContentType.value = contentType;
213
+ parsedContentType.value = contentType;
156
214
  }
157
215
 
158
- if (!this.flowed && this._parsedContentType.params.format) {
159
- delete this._parsedContentType.params.format;
216
+ if (!this.flowed && parsedContentType.params.format) {
217
+ delete parsedContentType.params.format;
160
218
  }
161
219
 
162
- if (!this.delSp && this._parsedContentType.params.delsp) {
163
- delete this._parsedContentType.params.delsp;
220
+ if (!this.delSp && parsedContentType.params.delsp) {
221
+ delete parsedContentType.params.delsp;
164
222
  }
165
223
 
166
- this.headers.update('Content-Type', this.libmime.buildHeaderValue(this._parsedContentType));
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 (!this._parsedContentType.value) {
245
+ if (!parsedContentType.value) {
182
246
  // nothing to set or update
183
247
  return;
184
248
  }
185
- delete this._parsedContentType.params.charset;
249
+ delete parsedContentType.params.charset;
186
250
  } else {
187
- this._parsedContentType.params.charset = charset;
251
+ parsedContentType.params.charset = charset;
188
252
  }
189
253
 
190
- if (!this._parsedContentType.value) {
191
- this._parsedContentType.value = 'text/plain';
254
+ if (!parsedContentType.value) {
255
+ parsedContentType.value = 'text/plain';
192
256
  }
193
257
 
194
- this.headers.update('Content-Type', this.libmime.buildHeaderValue(this._parsedContentType));
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 (this._parsedContentType.params.name) {
205
- delete this._parsedContentType.params.name;
206
- this.headers.update('Content-Type', this.libmime.buildHeaderValue(this._parsedContentType));
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 (!this._parsedContentDisposition.value) {
281
+ if (!parsedContentDisposition.value) {
211
282
  // nothing to set or update
212
283
  return;
213
284
  }
214
- delete this._parsedContentDisposition.params.filename;
285
+ delete parsedContentDisposition.params.filename;
215
286
  } else {
216
- this._parsedContentDisposition.params.filename = this.filename;
287
+ parsedContentDisposition.params.filename = this.filename;
217
288
  }
218
289
 
219
- if (!this._parsedContentDisposition.value) {
220
- this._parsedContentDisposition.value = 'attachment';
290
+ if (!parsedContentDisposition.value) {
291
+ parsedContentDisposition.value = 'attachment';
221
292
  }
222
293
 
223
- this.headers.update('Content-Disposition', this.libmime.buildHeaderValue(this._parsedContentDisposition));
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
- this.headers.update('Content-Transfer-Encoding', encoding);
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;