@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.
package/index.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ /// <reference types="node" />
2
+
3
+ import Splitter = require('./lib/message-splitter');
4
+ import Joiner = require('./lib/message-joiner');
5
+ import Rewriter = require('./lib/node-rewriter');
6
+ import Streamer = require('./lib/node-streamer');
7
+ import ChunkedPassthrough = require('./lib/chunked-passthrough');
8
+ import Headers = require('./lib/headers');
9
+ import MimeNode = require('./lib/mime-node');
10
+
11
+ export {
12
+ Splitter,
13
+ Joiner,
14
+ Rewriter,
15
+ Streamer,
16
+ ChunkedPassthrough,
17
+ Headers,
18
+ MimeNode
19
+ };
20
+
21
+ export type {
22
+ Maybe,
23
+ PartNumberItem,
24
+ PartNumber,
25
+ LibmimeOptions,
26
+ SplitterOptions,
27
+ ChunkedPassthroughOptions,
28
+ FlowedDecoderOptions,
29
+ HeaderLine,
30
+ DecodedHeader,
31
+ MimeNode,
32
+ MessageChunk,
33
+ EmptyChunk,
34
+ SplitterChunk,
35
+ RewriterInput,
36
+ FilterFunc,
37
+ ErrorWithCode,
38
+ ContinueCallback,
39
+ ContentStream,
40
+ DecoderStream,
41
+ SplitterGroup,
42
+ RewriterNode,
43
+ StreamerNode
44
+ } from './lib/types';
package/index.js CHANGED
@@ -6,12 +6,15 @@ const NodeRewriter = require('./lib/node-rewriter');
6
6
  const NodeStreamer = require('./lib/node-streamer');
7
7
  const Headers = require('./lib/headers');
8
8
  const ChunkedPassthrough = require('./lib/chunked-passthrough');
9
+ const MimeNode = require('./lib/mime-node');
9
10
 
11
+ /** @type {typeof import('.')} */
10
12
  module.exports = {
11
13
  Splitter: MessageSplitter,
12
14
  Joiner: MessageJoiner,
13
15
  Rewriter: NodeRewriter,
14
16
  Streamer: NodeStreamer,
15
17
  ChunkedPassthrough,
16
- Headers
18
+ Headers,
19
+ MimeNode
17
20
  };
@@ -0,0 +1,13 @@
1
+ import { Transform } from 'node:stream';
2
+ import type { ChunkedPassthroughOptions } from './types';
3
+
4
+ declare class ChunkedPassthrough extends Transform {
5
+ constructor(options?: ChunkedPassthroughOptions);
6
+ on(event: 'data', listener: (data: Buffer) => void): this;
7
+ once(event: 'data', listener: (data: Buffer) => void): this;
8
+ addListener(event: 'data', listener: (data: Buffer) => void): this;
9
+ prependListener(event: 'data', listener: (data: Buffer) => void): this;
10
+ emit(event: 'data', data: Buffer): boolean;
11
+ }
12
+
13
+ export = ChunkedPassthrough;
@@ -3,6 +3,9 @@
3
3
  const { Transform } = require('stream');
4
4
 
5
5
  class ChunkedPassthrough extends Transform {
6
+ /**
7
+ * @param {import('..').ChunkedPassthroughOptions} [options]
8
+ */
6
9
  constructor(options = {}) {
7
10
  let config = {
8
11
  readableObjectMode: true,
@@ -13,6 +16,12 @@ class ChunkedPassthrough extends Transform {
13
16
  this.buffer = Buffer.alloc(0);
14
17
  }
15
18
 
19
+ /**
20
+ * @param {Buffer} chunk
21
+ * @param {BufferEncoding} encoding
22
+ * @param {import('stream').TransformCallback} callback
23
+ * @returns {void}
24
+ */
16
25
  _transform(chunk, encoding, callback) {
17
26
  this.buffer = Buffer.concat([this.buffer, chunk]);
18
27
 
@@ -24,6 +33,10 @@ class ChunkedPassthrough extends Transform {
24
33
  callback();
25
34
  }
26
35
 
36
+ /**
37
+ * @param {import('stream').TransformCallback} callback
38
+ * @returns {void}
39
+ */
27
40
  _flush(callback) {
28
41
  // Send remaining data
29
42
  if (this.buffer.length > 0) {
@@ -0,0 +1,13 @@
1
+ import { Transform } from 'node:stream';
2
+ import type { FlowedDecoderOptions } from './types';
3
+
4
+ declare class FlowedDecoder extends Transform {
5
+ constructor(config?: FlowedDecoderOptions);
6
+ on(event: 'data', listener: (data: Buffer) => void): this;
7
+ once(event: 'data', listener: (data: Buffer) => void): this;
8
+ addListener(event: 'data', listener: (data: Buffer) => void): this;
9
+ prependListener(event: 'data', listener: (data: Buffer) => void): this;
10
+ emit(event: 'data', data: Buffer): boolean;
11
+ }
12
+
13
+ export = FlowedDecoder;
@@ -5,29 +5,43 @@
5
5
  const Transform = require('stream').Transform;
6
6
  const libmime = require('libmime');
7
7
 
8
+ /** @typedef {import('..').FlowedDecoderOptions} FlowedDecoderOptions */
9
+
10
+ const Libmime = /** @type {any} */ (libmime.Libmime);
11
+
8
12
  /**
9
13
  * Really bad "stream" transform to parse format=flowed content
10
14
  *
11
15
  * @constructor
12
- * @param {String} delSp True if delsp option was used
16
+ * @param {FlowedDecoderOptions} [config]
13
17
  */
14
18
  class FlowedDecoder extends Transform {
19
+ /**
20
+ * @param {FlowedDecoderOptions} [config]
21
+ */
15
22
  constructor(config) {
16
23
  super();
17
24
  this.config = config || {};
18
25
 
26
+ /** @type {Buffer[]} */
19
27
  this.chunks = [];
20
28
  this.chunklen = 0;
21
29
 
22
- this.libmime = new libmime.Libmime({ Iconv: config.Iconv });
30
+ this.libmime = new Libmime({ Iconv: this.config.Iconv });
23
31
  }
24
32
 
33
+ /**
34
+ * @param {Buffer | string} chunk
35
+ * @param {BufferEncoding} encoding
36
+ * @param {import('stream').TransformCallback} callback
37
+ * @returns {void}
38
+ */
25
39
  _transform(chunk, encoding, callback) {
26
40
  if (!chunk || !chunk.length) {
27
41
  return callback();
28
42
  }
29
43
 
30
- if (!encoding !== 'buffer') {
44
+ if (typeof chunk === 'string') {
31
45
  chunk = Buffer.from(chunk, encoding);
32
46
  }
33
47
 
@@ -37,6 +51,10 @@ class FlowedDecoder extends Transform {
37
51
  callback();
38
52
  }
39
53
 
54
+ /**
55
+ * @param {import('stream').TransformCallback} callback
56
+ * @returns {void}
57
+ */
40
58
  _flush(callback) {
41
59
  if (this.chunklen) {
42
60
  let currentBody = Buffer.concat(this.chunks, this.chunklen);
@@ -0,0 +1,24 @@
1
+ import type { DecodedHeader, HeaderLine, LibmimeOptions } from './types';
2
+
3
+ declare class Headers {
4
+ changed: boolean;
5
+ headers: string | Buffer | false;
6
+ parsed: boolean;
7
+ lines: HeaderLine[] | false;
8
+ mbox: string | false;
9
+ http: string | false;
10
+
11
+ constructor(headers?: string | Buffer | HeaderLine[] | false, config?: LibmimeOptions);
12
+ hasHeader(key: string): boolean;
13
+ get(key: string): string[];
14
+ getDecoded(key: string): DecodedHeader[];
15
+ getFirst(key: string): string;
16
+ getList(): HeaderLine[];
17
+ add(key: string, value?: string | number | Buffer, index?: number): void;
18
+ addFormatted(key: string, line?: string | Buffer | false, index?: number): void;
19
+ remove(key: string): void;
20
+ update(key: string, value?: string | number | Buffer, relativeIndex?: number): void;
21
+ build(lineEnd?: string | false): Buffer;
22
+ }
23
+
24
+ export = Headers;
package/lib/headers.js CHANGED
@@ -2,76 +2,120 @@
2
2
 
3
3
  const libmime = require('libmime');
4
4
 
5
+ /** @typedef {import('..').HeaderLine} HeaderLine */
6
+ /** @typedef {import('..').LibmimeOptions} LibmimeOptions */
7
+ /** @typedef {import('..').DecodedHeader} DecodedHeader */
8
+
9
+ const Libmime = /** @type {any} */ (libmime.Libmime);
10
+
5
11
  /**
6
12
  * Class Headers to parse and handle message headers. Headers instance allows to
7
13
  * check existing, delete or add new headers
8
14
  */
9
15
  class Headers {
16
+ /**
17
+ * @param {string | Buffer | HeaderLine[] | false} [headers]
18
+ * @param {LibmimeOptions} [config]
19
+ */
10
20
  constructor(headers, config) {
11
21
  config = config || {};
12
22
 
13
23
  if (Array.isArray(headers)) {
14
24
  // already using parsed headers
15
25
  this.changed = true;
26
+ /** @type {string | Buffer | false} */
16
27
  this.headers = false;
17
28
  this.parsed = true;
29
+ /** @type {HeaderLine[] | false} */
18
30
  this.lines = headers;
19
31
  } else {
20
32
  // using original string/buffer headers
21
33
  this.changed = false;
22
- this.headers = headers;
34
+ /** @type {string | Buffer | false} */
35
+ this.headers = headers || false;
23
36
  this.parsed = false;
37
+ /** @type {HeaderLine[] | false} */
24
38
  this.lines = false;
25
39
  }
40
+ /** @type {string | false} */
26
41
  this.mbox = false;
42
+ /** @type {string | false} */
27
43
  this.http = false;
28
44
 
29
- this.libmime = new libmime.Libmime({ Iconv: config.Iconv });
45
+ this.libmime = new Libmime({ Iconv: config.Iconv });
30
46
  }
31
47
 
48
+ /**
49
+ * @param {string} key
50
+ * @returns {boolean}
51
+ */
32
52
  hasHeader(key) {
33
53
  if (!this.parsed) {
34
54
  this._parseHeaders();
35
55
  }
56
+ let lines = this._getLines();
36
57
  key = this._normalizeHeader(key);
37
- return typeof this.lines.find(line => line.key === key) === 'object';
58
+ return typeof lines.find(line => line.key === key) === 'object';
38
59
  }
39
60
 
61
+ /**
62
+ * @param {string} key
63
+ * @returns {string[]}
64
+ */
40
65
  get(key) {
41
66
  if (!this.parsed) {
42
67
  this._parseHeaders();
43
68
  }
69
+ let headerLines = this._getLines();
44
70
  key = this._normalizeHeader(key);
45
- let lines = this.lines.filter(line => line.key === key).map(line => line.line);
71
+ let lines = headerLines.filter(line => line.key === key).map(line => this._decodeHeaderValue(line.line));
46
72
 
47
73
  return lines;
48
74
  }
49
75
 
76
+ /**
77
+ * @param {string} key
78
+ * @returns {DecodedHeader[]}
79
+ */
50
80
  getDecoded(key) {
51
81
  return this.get(key)
52
82
  .map(line => this.libmime.decodeHeader(line))
53
83
  .filter(line => line && line.value);
54
84
  }
55
85
 
86
+ /**
87
+ * @param {string} key
88
+ * @returns {string}
89
+ */
56
90
  getFirst(key) {
57
91
  if (!this.parsed) {
58
92
  this._parseHeaders();
59
93
  }
94
+ let lines = this._getLines();
60
95
  key = this._normalizeHeader(key);
61
- let header = this.lines.find(line => line.key === key);
96
+ let header = lines.find(line => line.key === key);
62
97
  if (!header) {
63
98
  return '';
64
99
  }
65
- return ((this.libmime.decodeHeader(header.line) || {}).value || '').toString().trim();
100
+ return ((this.libmime.decodeHeader(this._decodeHeaderValue(header.line)) || {}).value || '').toString().trim();
66
101
  }
67
102
 
103
+ /**
104
+ * @returns {HeaderLine[]}
105
+ */
68
106
  getList() {
69
107
  if (!this.parsed) {
70
108
  this._parseHeaders();
71
109
  }
72
- return this.lines;
110
+ return this._getLines();
73
111
  }
74
112
 
113
+ /**
114
+ * @param {string} key
115
+ * @param {string | number | Buffer} [value]
116
+ * @param {number} [index]
117
+ * @returns {void}
118
+ */
75
119
  add(key, value, index) {
76
120
  if (typeof value === 'undefined') {
77
121
  return;
@@ -89,10 +133,17 @@ class Headers {
89
133
  this.addFormatted(key, this.libmime.foldLines(key + ': ' + value.replace(/\r?\n/g, ''), 76, false), index);
90
134
  }
91
135
 
136
+ /**
137
+ * @param {string} key
138
+ * @param {string | Buffer | false} [line]
139
+ * @param {number} [index]
140
+ * @returns {void}
141
+ */
92
142
  addFormatted(key, line, index) {
93
143
  if (!this.parsed) {
94
144
  this._parseHeaders();
95
145
  }
146
+ let lines = this._getLines();
96
147
  index = index || 0;
97
148
  this.changed = true;
98
149
 
@@ -110,45 +161,57 @@ class Headers {
110
161
  };
111
162
 
112
163
  if (index < 1) {
113
- this.lines.unshift(header);
114
- } else if (index >= this.lines.length) {
115
- this.lines.push(header);
164
+ lines.unshift(header);
165
+ } else if (index >= lines.length) {
166
+ lines.push(header);
116
167
  } else {
117
- this.lines.splice(index, 0, header);
168
+ lines.splice(index, 0, header);
118
169
  }
119
170
  }
120
171
 
172
+ /**
173
+ * @param {string} key
174
+ * @returns {void}
175
+ */
121
176
  remove(key) {
122
177
  if (!this.parsed) {
123
178
  this._parseHeaders();
124
179
  }
180
+ let lines = this._getLines();
125
181
  key = this._normalizeHeader(key);
126
- for (let i = this.lines.length - 1; i >= 0; i--) {
127
- if (this.lines[i].key === key) {
182
+ for (let i = lines.length - 1; i >= 0; i--) {
183
+ if (lines[i].key === key) {
128
184
  this.changed = true;
129
- this.lines.splice(i, 1);
185
+ lines.splice(i, 1);
130
186
  }
131
187
  }
132
188
  }
133
189
 
190
+ /**
191
+ * @param {string} key
192
+ * @param {string | number | Buffer} [value]
193
+ * @param {number} [relativeIndex]
194
+ * @returns {void}
195
+ */
134
196
  update(key, value, relativeIndex) {
135
197
  if (!this.parsed) {
136
198
  this._parseHeaders();
137
199
  }
200
+ let lines = this._getLines();
138
201
  let keyName = key;
139
202
  let index = 0;
140
203
  key = this._normalizeHeader(key);
141
204
  let relativeIndexCount = 0;
142
205
  let relativeMatchFound = false;
143
- for (let i = this.lines.length - 1; i >= 0; i--) {
144
- if (this.lines[i].key === key) {
206
+ for (let i = lines.length - 1; i >= 0; i--) {
207
+ if (lines[i].key === key) {
145
208
  if (relativeIndex && relativeIndex !== relativeIndexCount) {
146
209
  relativeIndexCount++;
147
210
  continue;
148
211
  }
149
212
  index = i;
150
213
  this.changed = true;
151
- this.lines.splice(i, 1);
214
+ lines.splice(i, 1);
152
215
  if (relativeIndex) {
153
216
  relativeMatchFound = true;
154
217
  break;
@@ -163,34 +226,66 @@ class Headers {
163
226
  this.add(keyName, value, index);
164
227
  }
165
228
 
229
+ /**
230
+ * @param {string | false} [lineEnd]
231
+ * @returns {Buffer}
232
+ */
166
233
  build(lineEnd) {
167
234
  if (!this.changed && !lineEnd) {
168
- return typeof this.headers === 'string' ? Buffer.from(this.headers, 'binary') : this.headers;
235
+ return typeof this.headers === 'string' ? Buffer.from(this.headers, 'binary') : this.headers || Buffer.alloc(0);
169
236
  }
170
237
 
171
238
  if (!this.parsed) {
172
239
  this._parseHeaders();
173
240
  }
241
+ let lines = this._getLines();
174
242
 
175
243
  lineEnd = lineEnd || '\r\n';
176
244
 
177
- let headers = this.lines.map(line => line.line.replace(/\r?\n/g, lineEnd)).join(lineEnd) + `${lineEnd}${lineEnd}`;
245
+ let headers = lines
246
+ .map(line => this._buildHeaderLine(line.line.replace(/\r?\n/g, lineEnd)))
247
+ .reduce((joined, line, idx) => {
248
+ if (idx) {
249
+ joined.push(Buffer.from(lineEnd, 'binary'));
250
+ }
251
+ joined.push(line);
252
+ return joined;
253
+ }, /** @type {Buffer[]} */ ([]));
254
+
255
+ headers.push(Buffer.from(lineEnd + lineEnd, 'binary'));
178
256
 
179
257
  if (this.mbox) {
180
- headers = this.mbox + lineEnd + headers;
258
+ headers.unshift(Buffer.from(this.mbox + lineEnd, 'binary'));
181
259
  }
182
260
 
183
261
  if (this.http) {
184
- headers = this.http + lineEnd + headers;
262
+ headers.unshift(Buffer.from(this.http + lineEnd, 'binary'));
185
263
  }
186
264
 
187
- return Buffer.from(headers, 'binary');
265
+ return Buffer.concat(headers);
188
266
  }
189
267
 
268
+ /**
269
+ * @param {string} key
270
+ * @returns {string}
271
+ */
190
272
  _normalizeHeader(key) {
191
273
  return (key || '').toLowerCase().trim();
192
274
  }
193
275
 
276
+ /**
277
+ * @returns {HeaderLine[]}
278
+ */
279
+ _getLines() {
280
+ if (!this.lines) {
281
+ this.lines = [];
282
+ }
283
+ return this.lines;
284
+ }
285
+
286
+ /**
287
+ * @returns {void}
288
+ */
194
289
  _parseHeaders() {
195
290
  if (!this.headers) {
196
291
  this.lines = [];
@@ -198,18 +293,20 @@ class Headers {
198
293
  return;
199
294
  }
200
295
 
296
+ /** @type {Array<string | HeaderLine>} */
201
297
  let lines = this.headers
202
298
  .toString('binary')
203
299
  .replace(/[\r\n]+$/, '')
204
300
  .split(/\r?\n/);
205
301
 
206
302
  for (let i = lines.length - 1; i >= 0; i--) {
207
- let chr = lines[i].charAt(0);
303
+ let currentLine = /** @type {string} */ (lines[i]);
304
+ let chr = currentLine.charAt(0);
208
305
  if (i && (chr === ' ' || chr === '\t')) {
209
- lines[i - 1] += '\r\n' + lines[i];
306
+ lines[i - 1] = /** @type {string} */ (lines[i - 1]) + '\r\n' + currentLine;
210
307
  lines.splice(i, 1);
211
308
  } else {
212
- let line = lines[i];
309
+ let line = currentLine;
213
310
  if (!i && /^From /i.test(line)) {
214
311
  // mbox file
215
312
  this.mbox = line;
@@ -229,9 +326,31 @@ class Headers {
229
326
  }
230
327
  }
231
328
 
232
- this.lines = lines;
329
+ this.lines = /** @type {HeaderLine[]} */ (lines);
233
330
  this.parsed = true;
234
331
  }
332
+
333
+ /**
334
+ * @param {string} line
335
+ * @returns {Buffer}
336
+ */
337
+ _buildHeaderLine(line) {
338
+ let value = this._decodeHeaderValue(line);
339
+ return Buffer.from(value, value === line ? 'binary' : 'utf8');
340
+ }
341
+
342
+ /**
343
+ * @param {string} str
344
+ * @returns {string}
345
+ */
346
+ _decodeHeaderValue(str) {
347
+ if (!str) {
348
+ return str;
349
+ }
350
+
351
+ let utf8 = Buffer.from(str, 'binary').toString('utf8');
352
+ return utf8.includes('\uFFFD') ? str : utf8;
353
+ }
235
354
  }
236
355
 
237
356
  // expose to the world
@@ -0,0 +1,12 @@
1
+ import { Transform } from 'node:stream';
2
+
3
+ declare class MessageJoiner extends Transform {
4
+ constructor();
5
+ on(event: 'data', listener: (data: Buffer) => void): this;
6
+ once(event: 'data', listener: (data: Buffer) => void): this;
7
+ addListener(event: 'data', listener: (data: Buffer) => void): this;
8
+ prependListener(event: 'data', listener: (data: Buffer) => void): this;
9
+ emit(event: 'data', data: Buffer): boolean;
10
+ }
11
+
12
+ export = MessageJoiner;
@@ -2,6 +2,8 @@
2
2
 
3
3
  const Transform = require('stream').Transform;
4
4
 
5
+ /** @typedef {import('..').SplitterChunk} SplitterChunk */
6
+
5
7
  class MessageJoiner extends Transform {
6
8
  constructor() {
7
9
  let options = {
@@ -11,6 +13,12 @@ class MessageJoiner extends Transform {
11
13
  super(options);
12
14
  }
13
15
 
16
+ /**
17
+ * @param {SplitterChunk | Buffer} obj
18
+ * @param {BufferEncoding} encoding
19
+ * @param {import('stream').TransformCallback} callback
20
+ * @returns {void}
21
+ */
14
22
  _transform(obj, encoding, callback) {
15
23
  if (Buffer.isBuffer(obj)) {
16
24
  this.push(obj);
@@ -22,6 +30,10 @@ class MessageJoiner extends Transform {
22
30
  return callback();
23
31
  }
24
32
 
33
+ /**
34
+ * @param {import('stream').TransformCallback} callback
35
+ * @returns {void}
36
+ */
25
37
  _flush(callback) {
26
38
  return callback();
27
39
  }
@@ -0,0 +1,13 @@
1
+ import { Transform } from 'node:stream';
2
+ import type { SplitterChunk, SplitterOptions } from './types';
3
+
4
+ declare class MessageSplitter extends Transform {
5
+ constructor(config?: SplitterOptions);
6
+ on(event: 'data', listener: (data: SplitterChunk) => void): this;
7
+ once(event: 'data', listener: (data: SplitterChunk) => void): this;
8
+ addListener(event: 'data', listener: (data: SplitterChunk) => void): this;
9
+ prependListener(event: 'data', listener: (data: SplitterChunk) => void): this;
10
+ emit(event: 'data', data: SplitterChunk): boolean;
11
+ }
12
+
13
+ export = MessageSplitter;