@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.
@@ -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
- * NodeRewriter Transform stream. Updates content for all nodes with specified mime type
17
+ * NodeRewriter Transform stream. Updates content for all nodes selected by the
18
+ * filter function.
10
19
  *
11
- * @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 that receives a MIME node and returns true to rewrite it.
21
+ * @param {Function} [rewriteAction] Optional compatibility hook stored on the instance.
14
22
  */
15
23
  class NodeRewriter extends Transform {
24
+ /**
25
+ * @param {FilterFunc} filterFunc Function that receives a MIME node and returns true to rewrite it.
26
+ * @param {Function} [rewriteAction] Optional compatibility hook stored on the instance.
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,22 +107,32 @@ 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
 
131
+ /**
132
+ * Reads encoded replacement bytes and forwards them as body chunks.
133
+ *
134
+ * @returns {void | NodeJS.Immediate}
135
+ */
94
136
  let readFromEncoder = () => {
95
137
  decoder.$reading = true;
96
138
 
@@ -104,9 +146,6 @@ class NodeRewriter extends Transform {
104
146
  firstChunk = false;
105
147
  if (this.readable) {
106
148
  this.push(node);
107
- if (node.type === 'body') {
108
- lastByte = node.value && node.value.length && node.value[node.value.length - 1];
109
- }
110
149
  }
111
150
  }
112
151
 
@@ -144,9 +183,6 @@ class NodeRewriter extends Transform {
144
183
  firstChunk = false;
145
184
  if (this.readable) {
146
185
  this.push(node);
147
- if (node.type === 'body') {
148
- lastByte = node.value && node.value.length && node.value[node.value.length - 1];
149
- }
150
186
  }
151
187
  }
152
188
 
@@ -164,16 +200,25 @@ class NodeRewriter extends Transform {
164
200
  }
165
201
  });
166
202
 
167
- if (/^text\//.test(node.contentType) && node.flowed) {
203
+ if (/^text\//.test(node.contentType || '') && node.flowed) {
168
204
  // text/plain; format=flowed is a special case
169
205
  let flowDecoder = decoder;
170
206
  decoder = new FlowedDecoder({
171
207
  delSp: node.delSp,
172
- encoding: node.encoding
173
- });
174
- flowDecoder.on('error', err => {
175
- decoder.emit('error', err);
208
+ encoding: node.encoding || false
176
209
  });
210
+ flowDecoder.on(
211
+ 'error',
212
+ /**
213
+ * Forwards flowed decoder errors to the replacement decoder.
214
+ *
215
+ * @param {Error} err Decoder error to forward.
216
+ * @returns {void}
217
+ */
218
+ err => {
219
+ decoder.emit('error', err);
220
+ }
221
+ );
177
222
  flowDecoder.pipe(decoder);
178
223
 
179
224
  // we don't know what kind of data we are going to get, does it comply with the
@@ -0,0 +1,105 @@
1
+ import { Transform } from 'node:stream';
2
+ import type { FilterFunc, SplitterChunk, StreamerNode } from './types';
3
+
4
+ /** Transform stream that exposes decoded body streams for selected MIME nodes without replacing them. */
5
+ declare class NodeStreamer extends Transform {
6
+ /**
7
+ * Creates a node streamer that accepts splitter chunks and passes them through unchanged.
8
+ *
9
+ * @param filterFunc Predicate that selects MIME nodes to stream.
10
+ * @param streamAction Optional compatibility hook stored on the instance; consumers usually handle the `node` event.
11
+ */
12
+ constructor(filterFunc: FilterFunc, streamAction?: Function);
13
+
14
+ /**
15
+ * Registers a listener for passed-through splitter chunks.
16
+ *
17
+ * @param event Event name.
18
+ * @param listener Receives each outgoing MIME node, data chunk, or body chunk.
19
+ * @returns This streamer 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 a decoder stream and completion callback for a selected node.
28
+ * @returns This streamer instance.
29
+ */
30
+ on(event: 'node', listener: (data: StreamerNode) => void): this;
31
+
32
+ /**
33
+ * Registers a one-time listener for the next passed-through 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 streamer 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 a decoder stream and completion callback for the next selected node.
46
+ * @returns This streamer instance.
47
+ */
48
+ once(event: 'node', listener: (data: StreamerNode) => void): this;
49
+
50
+ /**
51
+ * Adds a listener for passed-through splitter chunks.
52
+ *
53
+ * @param event Event name.
54
+ * @param listener Receives each outgoing MIME node, data chunk, or body chunk.
55
+ * @returns This streamer 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 a decoder stream and completion callback for a selected node.
64
+ * @returns This streamer instance.
65
+ */
66
+ addListener(event: 'node', listener: (data: StreamerNode) => void): this;
67
+
68
+ /**
69
+ * Prepends a listener for passed-through splitter chunks.
70
+ *
71
+ * @param event Event name.
72
+ * @param listener Receives each outgoing MIME node, data chunk, or body chunk.
73
+ * @returns This streamer 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 a decoder stream and completion callback for a selected node.
82
+ * @returns This streamer instance.
83
+ */
84
+ prependListener(event: 'node', listener: (data: StreamerNode) => void): this;
85
+
86
+ /**
87
+ * Emits a passed-through 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 stream and completion callback.
100
+ * @returns `true` when the event had listeners.
101
+ */
102
+ emit(event: 'node', data: StreamerNode): boolean;
103
+ }
104
+
105
+ export = NodeStreamer;
@@ -1,18 +1,29 @@
1
1
  'use strict';
2
2
 
3
- // Helper class to rewrite nodes with specific mime type
3
+ // Helper class to stream selected nodes by MIME metadata
4
4
 
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
- * NodeRewriter Transform stream. Updates content for all nodes with specified mime type
16
+ * NodeStreamer Transform stream. Exposes decoded content for nodes selected by
17
+ * the filter function while passing the original message through unchanged.
10
18
  *
11
- * @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 that receives a MIME node and returns true to stream it.
20
+ * @param {Function} [streamAction] Optional compatibility hook stored on the instance.
14
21
  */
15
22
  class NodeStreamer extends Transform {
23
+ /**
24
+ * @param {FilterFunc} filterFunc Function that receives a MIME node and returns true to stream it.
25
+ * @param {Function} [streamAction] Optional compatibility hook stored on the instance.
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
  }
@@ -60,6 +89,11 @@ class NodeStreamer extends Transform {
60
89
  // the parsed data is completely processed, so we store a reference to the
61
90
  // continue callback
62
91
 
92
+ /**
93
+ * Resumes processing with the first chunk after the streamed node.
94
+ *
95
+ * @returns {void}
96
+ */
63
97
  let doContinue = () => {
64
98
  this.continue = false;
65
99
  this.decoder = false;
@@ -73,7 +107,8 @@ class NodeStreamer extends Transform {
73
107
  this.continue = () => doContinue();
74
108
  }
75
109
 
76
- return this.decoder.end();
110
+ this.decoder.end();
111
+ return;
77
112
  } else if (data.type === 'node' && this.filterFunc(data)) {
78
113
  this.push(data);
79
114
  // found matching node, create new handler
@@ -85,26 +120,44 @@ class NodeStreamer extends Transform {
85
120
  callback();
86
121
  }
87
122
 
123
+ /**
124
+ * @param {MimeNode} node
125
+ * @returns {StreamerNode}
126
+ */
88
127
  createDecoder(node) {
89
- this.decoder = node.getDecoder();
128
+ this.decoder = /** @type {DecoderStream} */ (node.getDecoder());
90
129
 
91
- let decoder = this.decoder;
130
+ let decoder = /** @type {DecoderStream} */ (this.decoder);
92
131
  decoder.$reading = false;
93
132
 
94
- if (/^text\//.test(node.contentType) && node.flowed) {
133
+ if (/^text\//.test(node.contentType || '') && node.flowed) {
95
134
  let flowDecoder = decoder;
96
135
  decoder = new FlowedDecoder({
97
136
  delSp: node.delSp
98
137
  });
99
- flowDecoder.on('error', err => {
100
- decoder.emit('error', err);
101
- });
138
+ flowDecoder.on(
139
+ 'error',
140
+ /**
141
+ * Forwards flowed decoder errors to the output decoder.
142
+ *
143
+ * @param {Error} err Decoder error to forward.
144
+ * @returns {void}
145
+ */
146
+ err => {
147
+ decoder.emit('error', err);
148
+ }
149
+ );
102
150
  flowDecoder.pipe(decoder);
103
151
  }
104
152
 
105
153
  return {
106
154
  node,
107
155
  decoder,
156
+ /**
157
+ * Marks the selected node stream as consumed so the passthrough can continue.
158
+ *
159
+ * @returns {void}
160
+ */
108
161
  done: () => {
109
162
  if (typeof this.continue === 'function') {
110
163
  // called once input stream is processed