@depup/zone-eu__mailsplit 5.4.16-depup.0
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/LICENSE.EUPL-1.2 +153 -0
- package/LICENSE.MIT +21 -0
- package/README.md +25 -0
- package/changes.json +5 -0
- package/index.d.ts +97 -0
- package/index.js +20 -0
- package/lib/chunked-passthrough.d.ts +59 -0
- package/lib/chunked-passthrough.js +53 -0
- package/lib/flowed-decoder.d.ts +59 -0
- package/lib/flowed-decoder.js +72 -0
- package/lib/headers.d.ts +117 -0
- package/lib/headers.js +428 -0
- package/lib/message-joiner.d.ts +56 -0
- package/lib/message-joiner.js +48 -0
- package/lib/message-splitter.d.ts +59 -0
- package/lib/message-splitter.js +641 -0
- package/lib/mime-node.d.ts +147 -0
- package/lib/mime-node.js +351 -0
- package/lib/node-rewriter.d.ts +105 -0
- package/lib/node-rewriter.js +239 -0
- package/lib/node-streamer.d.ts +105 -0
- package/lib/node-streamer.js +174 -0
- package/lib/types.d.ts +288 -0
- package/package.json +66 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Helper class to rewrite nodes with specific mime type
|
|
4
|
+
|
|
5
|
+
const Transform = require('stream').Transform;
|
|
6
|
+
const FlowedDecoder = require('./flowed-decoder');
|
|
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
|
+
|
|
16
|
+
/**
|
|
17
|
+
* NodeRewriter Transform stream. Updates content for all nodes selected by the
|
|
18
|
+
* filter function.
|
|
19
|
+
*
|
|
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.
|
|
22
|
+
*/
|
|
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
|
+
*/
|
|
28
|
+
constructor(filterFunc, rewriteAction) {
|
|
29
|
+
let options = {
|
|
30
|
+
readableObjectMode: true,
|
|
31
|
+
writableObjectMode: true
|
|
32
|
+
};
|
|
33
|
+
super(options);
|
|
34
|
+
|
|
35
|
+
this.filterFunc = filterFunc;
|
|
36
|
+
this.rewriteAction = rewriteAction;
|
|
37
|
+
|
|
38
|
+
/** @type {DecoderStream | false} */
|
|
39
|
+
this.decoder = false;
|
|
40
|
+
/** @type {ContentStream | false} */
|
|
41
|
+
this.encoder = false;
|
|
42
|
+
/** @type {ContinueCallback | false} */
|
|
43
|
+
this.continue = false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {RewriterInput} data
|
|
48
|
+
* @param {BufferEncoding} encoding
|
|
49
|
+
* @param {import('stream').TransformCallback} callback
|
|
50
|
+
* @returns {void}
|
|
51
|
+
*/
|
|
52
|
+
_transform(data, encoding, callback) {
|
|
53
|
+
this.processIncoming(data, callback);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {import('stream').TransformCallback} callback
|
|
58
|
+
* @returns {void}
|
|
59
|
+
*/
|
|
60
|
+
_flush(callback) {
|
|
61
|
+
if (this.decoder) {
|
|
62
|
+
// emit an empty node just in case there is pending data to end
|
|
63
|
+
return this.processIncoming(
|
|
64
|
+
{
|
|
65
|
+
type: 'none'
|
|
66
|
+
},
|
|
67
|
+
callback
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return callback();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {RewriterInput} data
|
|
75
|
+
* @param {import('stream').TransformCallback} callback
|
|
76
|
+
* @returns {void}
|
|
77
|
+
*/
|
|
78
|
+
processIncoming(data, callback) {
|
|
79
|
+
if (this.decoder && data.type === 'body') {
|
|
80
|
+
// data to parse
|
|
81
|
+
if (!this.decoder.write(data.value)) {
|
|
82
|
+
this.decoder.once('drain', callback);
|
|
83
|
+
return;
|
|
84
|
+
} else {
|
|
85
|
+
return callback();
|
|
86
|
+
}
|
|
87
|
+
} else if (this.decoder && data.type !== 'body') {
|
|
88
|
+
// stop decoding.
|
|
89
|
+
// we can not process the current data chunk as we need to wait until
|
|
90
|
+
// the parsed data is completely processed, so we store a reference to the
|
|
91
|
+
// continue callback
|
|
92
|
+
this.continue = () => {
|
|
93
|
+
this.continue = false;
|
|
94
|
+
this.decoder = false;
|
|
95
|
+
this.encoder = false;
|
|
96
|
+
this.processIncoming(data, callback);
|
|
97
|
+
};
|
|
98
|
+
this.decoder.end();
|
|
99
|
+
return;
|
|
100
|
+
} else if (data.type === 'node' && this.filterFunc(data)) {
|
|
101
|
+
// found matching node, create new handler
|
|
102
|
+
this.emit('node', this.createDecodePair(data));
|
|
103
|
+
} else if (this.readable && data.type !== 'none') {
|
|
104
|
+
// we don't care about this data, just pass it over to the joiner
|
|
105
|
+
this.push(data);
|
|
106
|
+
}
|
|
107
|
+
callback();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @param {MimeNode} node
|
|
112
|
+
* @returns {RewriterNode}
|
|
113
|
+
*/
|
|
114
|
+
createDecodePair(node) {
|
|
115
|
+
this.decoder = /** @type {DecoderStream} */ (node.getDecoder());
|
|
116
|
+
|
|
117
|
+
if (['base64', 'quoted-printable'].includes(node.encoding || '')) {
|
|
118
|
+
this.encoder = node.getEncoder();
|
|
119
|
+
} else {
|
|
120
|
+
this.encoder = node.getEncoder('quoted-printable');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** @type {number | false} */
|
|
124
|
+
let lastByte = false;
|
|
125
|
+
|
|
126
|
+
let decoder = /** @type {DecoderStream} */ (this.decoder);
|
|
127
|
+
let encoder = /** @type {ContentStream} */ (this.encoder);
|
|
128
|
+
let firstChunk = true;
|
|
129
|
+
decoder.$reading = false;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Reads encoded replacement bytes and forwards them as body chunks.
|
|
133
|
+
*
|
|
134
|
+
* @returns {void | NodeJS.Immediate}
|
|
135
|
+
*/
|
|
136
|
+
let readFromEncoder = () => {
|
|
137
|
+
decoder.$reading = true;
|
|
138
|
+
|
|
139
|
+
let data = encoder.read();
|
|
140
|
+
if (data === null) {
|
|
141
|
+
decoder.$reading = false;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (firstChunk) {
|
|
146
|
+
firstChunk = false;
|
|
147
|
+
if (this.readable) {
|
|
148
|
+
this.push(node);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let writeMore = true;
|
|
153
|
+
if (this.readable) {
|
|
154
|
+
writeMore = this.push({
|
|
155
|
+
node,
|
|
156
|
+
type: 'body',
|
|
157
|
+
value: data
|
|
158
|
+
});
|
|
159
|
+
lastByte = data && data.length && data[data.length - 1];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (writeMore) {
|
|
163
|
+
return setImmediate(readFromEncoder);
|
|
164
|
+
} else {
|
|
165
|
+
encoder.pause();
|
|
166
|
+
// no idea how to catch drain? use timeout for now as poor man's substitute
|
|
167
|
+
// this.once('drain', () => encoder.resume());
|
|
168
|
+
setTimeout(() => {
|
|
169
|
+
encoder.resume();
|
|
170
|
+
setImmediate(readFromEncoder);
|
|
171
|
+
}, 100);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
encoder.on('readable', () => {
|
|
176
|
+
if (!decoder.$reading) {
|
|
177
|
+
return readFromEncoder();
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
encoder.on('end', () => {
|
|
182
|
+
if (firstChunk) {
|
|
183
|
+
firstChunk = false;
|
|
184
|
+
if (this.readable) {
|
|
185
|
+
this.push(node);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (lastByte !== 0x0a) {
|
|
190
|
+
// make sure there is a terminating line break
|
|
191
|
+
this.push({
|
|
192
|
+
node,
|
|
193
|
+
type: 'body',
|
|
194
|
+
value: Buffer.from([0x0a])
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (this.continue) {
|
|
199
|
+
return this.continue();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (/^text\//.test(node.contentType || '') && node.flowed) {
|
|
204
|
+
// text/plain; format=flowed is a special case
|
|
205
|
+
let flowDecoder = decoder;
|
|
206
|
+
decoder = new FlowedDecoder({
|
|
207
|
+
delSp: node.delSp,
|
|
208
|
+
encoding: node.encoding || false
|
|
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
|
+
);
|
|
222
|
+
flowDecoder.pipe(decoder);
|
|
223
|
+
|
|
224
|
+
// we don't know what kind of data we are going to get, does it comply with the
|
|
225
|
+
// requirements of format=flowed, so we just cancel it
|
|
226
|
+
node.flowed = false;
|
|
227
|
+
node.delSp = false;
|
|
228
|
+
node.setContentType();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
node,
|
|
233
|
+
decoder,
|
|
234
|
+
encoder
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
module.exports = NodeRewriter;
|
|
@@ -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;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Helper class to stream selected nodes by MIME metadata
|
|
4
|
+
|
|
5
|
+
const Transform = require('stream').Transform;
|
|
6
|
+
const FlowedDecoder = require('./flowed-decoder');
|
|
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
|
+
|
|
15
|
+
/**
|
|
16
|
+
* NodeStreamer Transform stream. Exposes decoded content for nodes selected by
|
|
17
|
+
* the filter function while passing the original message through unchanged.
|
|
18
|
+
*
|
|
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.
|
|
21
|
+
*/
|
|
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
|
+
*/
|
|
27
|
+
constructor(filterFunc, streamAction) {
|
|
28
|
+
let options = {
|
|
29
|
+
readableObjectMode: true,
|
|
30
|
+
writableObjectMode: true
|
|
31
|
+
};
|
|
32
|
+
super(options);
|
|
33
|
+
|
|
34
|
+
this.filterFunc = filterFunc;
|
|
35
|
+
this.streamAction = streamAction;
|
|
36
|
+
|
|
37
|
+
/** @type {DecoderStream | false} */
|
|
38
|
+
this.decoder = false;
|
|
39
|
+
this.canContinue = false;
|
|
40
|
+
/** @type {ContinueCallback | false} */
|
|
41
|
+
this.continue = false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {RewriterInput} data
|
|
46
|
+
* @param {BufferEncoding} encoding
|
|
47
|
+
* @param {import('stream').TransformCallback} callback
|
|
48
|
+
* @returns {void}
|
|
49
|
+
*/
|
|
50
|
+
_transform(data, encoding, callback) {
|
|
51
|
+
this.processIncoming(data, callback);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {import('stream').TransformCallback} callback
|
|
56
|
+
* @returns {void}
|
|
57
|
+
*/
|
|
58
|
+
_flush(callback) {
|
|
59
|
+
if (this.decoder) {
|
|
60
|
+
// emit an empty node just in case there is pending data to end
|
|
61
|
+
return this.processIncoming(
|
|
62
|
+
{
|
|
63
|
+
type: 'none'
|
|
64
|
+
},
|
|
65
|
+
callback
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return callback();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {RewriterInput} data
|
|
73
|
+
* @param {import('stream').TransformCallback} callback
|
|
74
|
+
* @returns {void}
|
|
75
|
+
*/
|
|
76
|
+
processIncoming(data, callback) {
|
|
77
|
+
if (this.decoder && data.type === 'body') {
|
|
78
|
+
// data to parse
|
|
79
|
+
this.push(data);
|
|
80
|
+
if (!this.decoder.write(data.value)) {
|
|
81
|
+
this.decoder.once('drain', callback);
|
|
82
|
+
return;
|
|
83
|
+
} else {
|
|
84
|
+
return callback();
|
|
85
|
+
}
|
|
86
|
+
} else if (this.decoder && data.type !== 'body') {
|
|
87
|
+
// stop decoding.
|
|
88
|
+
// we can not process the current data chunk as we need to wait until
|
|
89
|
+
// the parsed data is completely processed, so we store a reference to the
|
|
90
|
+
// continue callback
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Resumes processing with the first chunk after the streamed node.
|
|
94
|
+
*
|
|
95
|
+
* @returns {void}
|
|
96
|
+
*/
|
|
97
|
+
let doContinue = () => {
|
|
98
|
+
this.continue = false;
|
|
99
|
+
this.decoder = false;
|
|
100
|
+
this.canContinue = false;
|
|
101
|
+
this.processIncoming(data, callback);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
if (this.canContinue) {
|
|
105
|
+
setImmediate(doContinue);
|
|
106
|
+
} else {
|
|
107
|
+
this.continue = () => doContinue();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.decoder.end();
|
|
111
|
+
return;
|
|
112
|
+
} else if (data.type === 'node' && this.filterFunc(data)) {
|
|
113
|
+
this.push(data);
|
|
114
|
+
// found matching node, create new handler
|
|
115
|
+
this.emit('node', this.createDecoder(data));
|
|
116
|
+
} else if (this.readable && data.type !== 'none') {
|
|
117
|
+
// we don't care about this data, just pass it over to the joiner
|
|
118
|
+
this.push(data);
|
|
119
|
+
}
|
|
120
|
+
callback();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @param {MimeNode} node
|
|
125
|
+
* @returns {StreamerNode}
|
|
126
|
+
*/
|
|
127
|
+
createDecoder(node) {
|
|
128
|
+
this.decoder = /** @type {DecoderStream} */ (node.getDecoder());
|
|
129
|
+
|
|
130
|
+
let decoder = /** @type {DecoderStream} */ (this.decoder);
|
|
131
|
+
decoder.$reading = false;
|
|
132
|
+
|
|
133
|
+
if (/^text\//.test(node.contentType || '') && node.flowed) {
|
|
134
|
+
let flowDecoder = decoder;
|
|
135
|
+
decoder = new FlowedDecoder({
|
|
136
|
+
delSp: node.delSp
|
|
137
|
+
});
|
|
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
|
+
);
|
|
150
|
+
flowDecoder.pipe(decoder);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
node,
|
|
155
|
+
decoder,
|
|
156
|
+
/**
|
|
157
|
+
* Marks the selected node stream as consumed so the passthrough can continue.
|
|
158
|
+
*
|
|
159
|
+
* @returns {void}
|
|
160
|
+
*/
|
|
161
|
+
done: () => {
|
|
162
|
+
if (typeof this.continue === 'function') {
|
|
163
|
+
// called once input stream is processed
|
|
164
|
+
this.continue();
|
|
165
|
+
} else {
|
|
166
|
+
// called before input stream is processed
|
|
167
|
+
this.canContinue = true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
module.exports = NodeStreamer;
|