@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.
@@ -5,14 +5,26 @@
5
5
  const Transform = require('stream').Transform;
6
6
  const FlowedDecoder = require('./flowed-decoder');
7
7
 
8
+ /** @typedef {import('..').MimeNode} MimeNode */
9
+ /** @typedef {import('..').RewriterInput} RewriterInput */
10
+ /** @typedef {import('..').FilterFunc} FilterFunc */
11
+ /** @typedef {import('..').RewriterNode} RewriterNode */
12
+ /** @typedef {import('..').DecoderStream} DecoderStream */
13
+ /** @typedef {import('..').ContentStream} ContentStream */
14
+ /** @typedef {import('..').ContinueCallback} ContinueCallback */
15
+
8
16
  /**
9
17
  * NodeRewriter Transform stream. Updates content for all nodes with specified mime type
10
18
  *
11
19
  * @constructor
12
- * @param {String} mimeType Define the Mime-Type to look for
13
- * @param {Function} rewriteAction Function to run with the node content
20
+ * @param {FilterFunc} filterFunc Function to select nodes to rewrite
21
+ * @param {Function} [rewriteAction] Function to run with the node content
14
22
  */
15
23
  class NodeRewriter extends Transform {
24
+ /**
25
+ * @param {FilterFunc} filterFunc
26
+ * @param {Function} [rewriteAction]
27
+ */
16
28
  constructor(filterFunc, rewriteAction) {
17
29
  let options = {
18
30
  readableObjectMode: true,
@@ -23,15 +35,28 @@ class NodeRewriter extends Transform {
23
35
  this.filterFunc = filterFunc;
24
36
  this.rewriteAction = rewriteAction;
25
37
 
38
+ /** @type {DecoderStream | false} */
26
39
  this.decoder = false;
40
+ /** @type {ContentStream | false} */
27
41
  this.encoder = false;
42
+ /** @type {ContinueCallback | false} */
28
43
  this.continue = false;
29
44
  }
30
45
 
46
+ /**
47
+ * @param {RewriterInput} data
48
+ * @param {BufferEncoding} encoding
49
+ * @param {import('stream').TransformCallback} callback
50
+ * @returns {void}
51
+ */
31
52
  _transform(data, encoding, callback) {
32
53
  this.processIncoming(data, callback);
33
54
  }
34
55
 
56
+ /**
57
+ * @param {import('stream').TransformCallback} callback
58
+ * @returns {void}
59
+ */
35
60
  _flush(callback) {
36
61
  if (this.decoder) {
37
62
  // emit an empty node just in case there is pending data to end
@@ -45,11 +70,17 @@ class NodeRewriter extends Transform {
45
70
  return callback();
46
71
  }
47
72
 
73
+ /**
74
+ * @param {RewriterInput} data
75
+ * @param {import('stream').TransformCallback} callback
76
+ * @returns {void}
77
+ */
48
78
  processIncoming(data, callback) {
49
79
  if (this.decoder && data.type === 'body') {
50
80
  // data to parse
51
81
  if (!this.decoder.write(data.value)) {
52
- return this.decoder.once('drain', callback);
82
+ this.decoder.once('drain', callback);
83
+ return;
53
84
  } else {
54
85
  return callback();
55
86
  }
@@ -64,7 +95,8 @@ class NodeRewriter extends Transform {
64
95
  this.encoder = false;
65
96
  this.processIncoming(data, callback);
66
97
  };
67
- return this.decoder.end();
98
+ this.decoder.end();
99
+ return;
68
100
  } else if (data.type === 'node' && this.filterFunc(data)) {
69
101
  // found matching node, create new handler
70
102
  this.emit('node', this.createDecodePair(data));
@@ -75,19 +107,24 @@ class NodeRewriter extends Transform {
75
107
  callback();
76
108
  }
77
109
 
110
+ /**
111
+ * @param {MimeNode} node
112
+ * @returns {RewriterNode}
113
+ */
78
114
  createDecodePair(node) {
79
- this.decoder = node.getDecoder();
115
+ this.decoder = /** @type {DecoderStream} */ (node.getDecoder());
80
116
 
81
- if (['base64', 'quoted-printable'].includes(node.encoding)) {
117
+ if (['base64', 'quoted-printable'].includes(node.encoding || '')) {
82
118
  this.encoder = node.getEncoder();
83
119
  } else {
84
120
  this.encoder = node.getEncoder('quoted-printable');
85
121
  }
86
122
 
123
+ /** @type {number | false} */
87
124
  let lastByte = false;
88
125
 
89
- let decoder = this.decoder;
90
- let encoder = this.encoder;
126
+ let decoder = /** @type {DecoderStream} */ (this.decoder);
127
+ let encoder = /** @type {ContentStream} */ (this.encoder);
91
128
  let firstChunk = true;
92
129
  decoder.$reading = false;
93
130
 
@@ -104,9 +141,6 @@ class NodeRewriter extends Transform {
104
141
  firstChunk = false;
105
142
  if (this.readable) {
106
143
  this.push(node);
107
- if (node.type === 'body') {
108
- lastByte = node.value && node.value.length && node.value[node.value.length - 1];
109
- }
110
144
  }
111
145
  }
112
146
 
@@ -144,9 +178,6 @@ class NodeRewriter extends Transform {
144
178
  firstChunk = false;
145
179
  if (this.readable) {
146
180
  this.push(node);
147
- if (node.type === 'body') {
148
- lastByte = node.value && node.value.length && node.value[node.value.length - 1];
149
- }
150
181
  }
151
182
  }
152
183
 
@@ -164,14 +195,14 @@ class NodeRewriter extends Transform {
164
195
  }
165
196
  });
166
197
 
167
- if (/^text\//.test(node.contentType) && node.flowed) {
198
+ if (/^text\//.test(node.contentType || '') && node.flowed) {
168
199
  // text/plain; format=flowed is a special case
169
200
  let flowDecoder = decoder;
170
201
  decoder = new FlowedDecoder({
171
202
  delSp: node.delSp,
172
- encoding: node.encoding
203
+ encoding: node.encoding || false
173
204
  });
174
- flowDecoder.on('error', err => {
205
+ flowDecoder.on('error', /** @param {Error} err */ err => {
175
206
  decoder.emit('error', err);
176
207
  });
177
208
  flowDecoder.pipe(decoder);
@@ -0,0 +1,18 @@
1
+ import { Transform } from 'node:stream';
2
+ import type { FilterFunc, SplitterChunk, StreamerNode } from './types';
3
+
4
+ declare class NodeStreamer extends Transform {
5
+ constructor(filterFunc: FilterFunc, streamAction?: Function);
6
+ on(event: 'data', listener: (data: SplitterChunk) => void): this;
7
+ on(event: 'node', listener: (data: StreamerNode) => void): this;
8
+ once(event: 'data', listener: (data: SplitterChunk) => void): this;
9
+ once(event: 'node', listener: (data: StreamerNode) => void): this;
10
+ addListener(event: 'data', listener: (data: SplitterChunk) => void): this;
11
+ addListener(event: 'node', listener: (data: StreamerNode) => void): this;
12
+ prependListener(event: 'data', listener: (data: SplitterChunk) => void): this;
13
+ prependListener(event: 'node', listener: (data: StreamerNode) => void): this;
14
+ emit(event: 'data', data: SplitterChunk): boolean;
15
+ emit(event: 'node', data: StreamerNode): boolean;
16
+ }
17
+
18
+ export = NodeStreamer;
@@ -5,14 +5,25 @@
5
5
  const Transform = require('stream').Transform;
6
6
  const FlowedDecoder = require('./flowed-decoder');
7
7
 
8
+ /** @typedef {import('..').MimeNode} MimeNode */
9
+ /** @typedef {import('..').RewriterInput} RewriterInput */
10
+ /** @typedef {import('..').FilterFunc} FilterFunc */
11
+ /** @typedef {import('..').StreamerNode} StreamerNode */
12
+ /** @typedef {import('..').DecoderStream} DecoderStream */
13
+ /** @typedef {import('..').ContinueCallback} ContinueCallback */
14
+
8
15
  /**
9
16
  * NodeRewriter Transform stream. Updates content for all nodes with specified mime type
10
17
  *
11
18
  * @constructor
12
- * @param {String} mimeType Define the Mime-Type to look for
13
- * @param {Function} streamAction Function to run with the node content
19
+ * @param {FilterFunc} filterFunc Function to select nodes to stream
20
+ * @param {Function} [streamAction] Function to run with the node content
14
21
  */
15
22
  class NodeStreamer extends Transform {
23
+ /**
24
+ * @param {FilterFunc} filterFunc
25
+ * @param {Function} [streamAction]
26
+ */
16
27
  constructor(filterFunc, streamAction) {
17
28
  let options = {
18
29
  readableObjectMode: true,
@@ -23,15 +34,27 @@ class NodeStreamer extends Transform {
23
34
  this.filterFunc = filterFunc;
24
35
  this.streamAction = streamAction;
25
36
 
37
+ /** @type {DecoderStream | false} */
26
38
  this.decoder = false;
27
39
  this.canContinue = false;
40
+ /** @type {ContinueCallback | false} */
28
41
  this.continue = false;
29
42
  }
30
43
 
44
+ /**
45
+ * @param {RewriterInput} data
46
+ * @param {BufferEncoding} encoding
47
+ * @param {import('stream').TransformCallback} callback
48
+ * @returns {void}
49
+ */
31
50
  _transform(data, encoding, callback) {
32
51
  this.processIncoming(data, callback);
33
52
  }
34
53
 
54
+ /**
55
+ * @param {import('stream').TransformCallback} callback
56
+ * @returns {void}
57
+ */
35
58
  _flush(callback) {
36
59
  if (this.decoder) {
37
60
  // emit an empty node just in case there is pending data to end
@@ -45,12 +68,18 @@ class NodeStreamer extends Transform {
45
68
  return callback();
46
69
  }
47
70
 
71
+ /**
72
+ * @param {RewriterInput} data
73
+ * @param {import('stream').TransformCallback} callback
74
+ * @returns {void}
75
+ */
48
76
  processIncoming(data, callback) {
49
77
  if (this.decoder && data.type === 'body') {
50
78
  // data to parse
51
79
  this.push(data);
52
80
  if (!this.decoder.write(data.value)) {
53
- return this.decoder.once('drain', callback);
81
+ this.decoder.once('drain', callback);
82
+ return;
54
83
  } else {
55
84
  return callback();
56
85
  }
@@ -73,7 +102,8 @@ class NodeStreamer extends Transform {
73
102
  this.continue = () => doContinue();
74
103
  }
75
104
 
76
- return this.decoder.end();
105
+ this.decoder.end();
106
+ return;
77
107
  } else if (data.type === 'node' && this.filterFunc(data)) {
78
108
  this.push(data);
79
109
  // found matching node, create new handler
@@ -85,18 +115,22 @@ class NodeStreamer extends Transform {
85
115
  callback();
86
116
  }
87
117
 
118
+ /**
119
+ * @param {MimeNode} node
120
+ * @returns {StreamerNode}
121
+ */
88
122
  createDecoder(node) {
89
- this.decoder = node.getDecoder();
123
+ this.decoder = /** @type {DecoderStream} */ (node.getDecoder());
90
124
 
91
- let decoder = this.decoder;
125
+ let decoder = /** @type {DecoderStream} */ (this.decoder);
92
126
  decoder.$reading = false;
93
127
 
94
- if (/^text\//.test(node.contentType) && node.flowed) {
128
+ if (/^text\//.test(node.contentType || '') && node.flowed) {
95
129
  let flowDecoder = decoder;
96
130
  decoder = new FlowedDecoder({
97
131
  delSp: node.delSp
98
132
  });
99
- flowDecoder.on('error', err => {
133
+ flowDecoder.on('error', /** @param {Error} err */ err => {
100
134
  decoder.emit('error', err);
101
135
  });
102
136
  flowDecoder.pipe(decoder);
package/lib/types.d.ts ADDED
@@ -0,0 +1,105 @@
1
+ import type { PassThrough, Transform } from 'node:stream';
2
+ import type Headers = require('./headers');
3
+
4
+ export type Maybe<T> = T | false;
5
+ export type PartNumberItem = number | 'TEXT';
6
+ export type PartNumber = PartNumberItem[];
7
+
8
+ export interface LibmimeOptions {
9
+ Iconv?: unknown;
10
+ }
11
+
12
+ export interface SplitterOptions extends LibmimeOptions {
13
+ ignoreEmbedded?: boolean;
14
+ defaultInlineEmbedded?: boolean;
15
+ maxHeadSize?: number;
16
+ maxChildNodes?: number;
17
+ }
18
+
19
+ export interface ChunkedPassthroughOptions {
20
+ chunkSize?: number;
21
+ }
22
+
23
+ export interface FlowedDecoderOptions extends LibmimeOptions {
24
+ delSp?: boolean;
25
+ encoding?: string | false;
26
+ }
27
+
28
+ export interface HeaderLine {
29
+ key: string;
30
+ line: string;
31
+ }
32
+
33
+ export interface DecodedHeader {
34
+ key: string;
35
+ value: string;
36
+ }
37
+
38
+ export interface MimeNode {
39
+ type: 'node';
40
+ root: boolean;
41
+ parentNode: MimeNode | false;
42
+ _boundary: Buffer | false;
43
+ _parentBoundary: Buffer | false;
44
+ _headerlen: number;
45
+ multipart: string | false;
46
+ encoding: string | false;
47
+ headers: Headers | false;
48
+ contentType: string | false;
49
+ charset: string | false;
50
+ disposition: string | false;
51
+ filename: string | false;
52
+ flowed: boolean;
53
+ delSp: boolean;
54
+ config: SplitterOptions;
55
+ partNr: PartNumber | false;
56
+ childPartNumbers: number;
57
+ rfc822: boolean;
58
+ messageNode?: boolean;
59
+
60
+ getPartNr(provided?: PartNumberItem): PartNumber;
61
+ addHeaderChunk(line?: Buffer | false): void;
62
+ parseHeaders(): void;
63
+ getHeaders(): Buffer;
64
+ setContentType(contentType?: string | false): void;
65
+ setCharset(charset?: string | false): void;
66
+ setFilename(filename?: string | false): void;
67
+ getDecoder(): Transform | PassThrough;
68
+ getEncoder(encoding?: string | false): Transform | PassThrough;
69
+ }
70
+
71
+ export interface MessageChunk {
72
+ node: MimeNode;
73
+ type: 'data' | 'body';
74
+ value: Buffer;
75
+ }
76
+
77
+ export interface EmptyChunk {
78
+ type: 'none';
79
+ }
80
+
81
+ export type SplitterChunk = MimeNode | MessageChunk;
82
+ export type RewriterInput = SplitterChunk | EmptyChunk;
83
+ export type FilterFunc = (node: MimeNode) => boolean;
84
+ export type ErrorWithCode = Error & { code?: string };
85
+ export type ContinueCallback = () => void;
86
+ export type ContentStream = Transform | PassThrough;
87
+ export type DecoderStream = ContentStream & { $reading?: boolean };
88
+
89
+ export interface SplitterGroup {
90
+ node?: MimeNode;
91
+ type: 'none' | 'node' | 'data' | 'body';
92
+ value?: Buffer;
93
+ }
94
+
95
+ export interface RewriterNode {
96
+ node: MimeNode;
97
+ decoder: Transform;
98
+ encoder: Transform;
99
+ }
100
+
101
+ export interface StreamerNode {
102
+ node: MimeNode;
103
+ decoder: Transform;
104
+ done: () => void;
105
+ }
package/package.json CHANGED
@@ -1,35 +1,45 @@
1
1
  {
2
2
  "name": "@zone-eu/mailsplit",
3
- "version": "5.4.8",
3
+ "version": "5.4.10",
4
4
  "description": "Split email messages into an object stream",
5
5
  "main": "index.js",
6
+ "types": "index.d.ts",
6
7
  "directories": {
7
8
  "test": "test"
8
9
  },
9
10
  "scripts": {
10
11
  "test": "grunt",
11
- "update": "rm -rf node_modules package-lock.json && ncu -u && npm install"
12
+ "update": "rm -rf node_modules package-lock.json && ncu -u && npm install",
13
+ "typecheck": "tsc -p tsconfig.json"
12
14
  },
13
15
  "author": "Andris Reinman",
14
16
  "license": "(MIT OR EUPL-1.1+)",
15
17
  "dependencies": {
16
18
  "libbase64": "1.3.0",
17
- "libmime": "5.3.7",
19
+ "libmime": "5.3.8",
18
20
  "libqp": "2.1.1"
19
21
  },
20
22
  "devDependencies": {
23
+ "@types/eslint": "9.6.1",
24
+ "@types/eslint-config-prettier": "6.11.3",
25
+ "@types/grunt": "0.4.32",
26
+ "@types/libmime": "5.3.0",
27
+ "@types/libqp": "1.1.3",
28
+ "@types/node": "25.7.0",
21
29
  "eslint": "8.29.0",
22
30
  "eslint-config-nodemailer": "1.2.0",
23
31
  "eslint-config-prettier": "9.1.0",
24
- "grunt": "1.6.1",
32
+ "grunt": "1.6.2",
25
33
  "grunt-cli": "1.5.0",
26
34
  "grunt-contrib-nodeunit": "5.0.0",
27
35
  "grunt-eslint": "24.0.1",
28
- "random-message": "1.1.0"
36
+ "random-message": "1.1.0",
37
+ "typescript": "6.0.3"
29
38
  },
30
39
  "files": [
31
40
  "lib",
32
- "index.js"
41
+ "index.js",
42
+ "index.d.ts"
33
43
  ],
34
44
  "repository": {
35
45
  "type": "git",