@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 +44 -0
- package/index.js +4 -1
- package/lib/chunked-passthrough.d.ts +13 -0
- package/lib/chunked-passthrough.js +13 -0
- package/lib/flowed-decoder.d.ts +13 -0
- package/lib/flowed-decoder.js +21 -3
- package/lib/headers.d.ts +24 -0
- package/lib/headers.js +145 -26
- package/lib/message-joiner.d.ts +12 -0
- package/lib/message-joiner.js +12 -0
- package/lib/message-splitter.d.ts +13 -0
- package/lib/message-splitter.js +55 -9
- package/lib/mime-node.d.ts +38 -0
- package/lib/mime-node.js +126 -47
- package/lib/node-rewriter.d.ts +18 -0
- package/lib/node-rewriter.js +48 -17
- package/lib/node-streamer.d.ts +18 -0
- package/lib/node-streamer.js +42 -8
- package/lib/types.d.ts +105 -0
- package/package.json +16 -6
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;
|
package/lib/flowed-decoder.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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 (
|
|
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);
|
package/lib/headers.d.ts
ADDED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
114
|
-
} else if (index >=
|
|
115
|
-
|
|
164
|
+
lines.unshift(header);
|
|
165
|
+
} else if (index >= lines.length) {
|
|
166
|
+
lines.push(header);
|
|
116
167
|
} else {
|
|
117
|
-
|
|
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 =
|
|
127
|
-
if (
|
|
182
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
183
|
+
if (lines[i].key === key) {
|
|
128
184
|
this.changed = true;
|
|
129
|
-
|
|
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 =
|
|
144
|
-
if (
|
|
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
|
-
|
|
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 =
|
|
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
|
|
258
|
+
headers.unshift(Buffer.from(this.mbox + lineEnd, 'binary'));
|
|
181
259
|
}
|
|
182
260
|
|
|
183
261
|
if (this.http) {
|
|
184
|
-
headers
|
|
262
|
+
headers.unshift(Buffer.from(this.http + lineEnd, 'binary'));
|
|
185
263
|
}
|
|
186
264
|
|
|
187
|
-
return Buffer.
|
|
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
|
|
303
|
+
let currentLine = /** @type {string} */ (lines[i]);
|
|
304
|
+
let chr = currentLine.charAt(0);
|
|
208
305
|
if (i && (chr === ' ' || chr === '\t')) {
|
|
209
|
-
lines[i - 1]
|
|
306
|
+
lines[i - 1] = /** @type {string} */ (lines[i - 1]) + '\r\n' + currentLine;
|
|
210
307
|
lines.splice(i, 1);
|
|
211
308
|
} else {
|
|
212
|
-
let line =
|
|
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;
|
package/lib/message-joiner.js
CHANGED
|
@@ -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;
|