@zone-eu/mailsplit 5.4.6
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 +214 -0
- package/index.js +17 -0
- package/lib/chunked-passthrough.js +37 -0
- package/lib/flowed-decoder.js +55 -0
- package/lib/headers.js +238 -0
- package/lib/message-joiner.js +30 -0
- package/lib/message-splitter.js +422 -0
- package/lib/mime-node.js +265 -0
- package/lib/node-rewriter.js +194 -0
- package/lib/node-streamer.js +121 -0
- package/package.json +38 -0
package/lib/headers.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const libmime = require('libmime');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Class Headers to parse and handle message headers. Headers instance allows to
|
|
7
|
+
* check existing, delete or add new headers
|
|
8
|
+
*/
|
|
9
|
+
class Headers {
|
|
10
|
+
constructor(headers, config) {
|
|
11
|
+
config = config || {};
|
|
12
|
+
|
|
13
|
+
if (Array.isArray(headers)) {
|
|
14
|
+
// already using parsed headers
|
|
15
|
+
this.changed = true;
|
|
16
|
+
this.headers = false;
|
|
17
|
+
this.parsed = true;
|
|
18
|
+
this.lines = headers;
|
|
19
|
+
} else {
|
|
20
|
+
// using original string/buffer headers
|
|
21
|
+
this.changed = false;
|
|
22
|
+
this.headers = headers;
|
|
23
|
+
this.parsed = false;
|
|
24
|
+
this.lines = false;
|
|
25
|
+
}
|
|
26
|
+
this.mbox = false;
|
|
27
|
+
this.http = false;
|
|
28
|
+
|
|
29
|
+
this.libmime = new libmime.Libmime({ Iconv: config.Iconv });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
hasHeader(key) {
|
|
33
|
+
if (!this.parsed) {
|
|
34
|
+
this._parseHeaders();
|
|
35
|
+
}
|
|
36
|
+
key = this._normalizeHeader(key);
|
|
37
|
+
return typeof this.lines.find(line => line.key === key) === 'object';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get(key) {
|
|
41
|
+
if (!this.parsed) {
|
|
42
|
+
this._parseHeaders();
|
|
43
|
+
}
|
|
44
|
+
key = this._normalizeHeader(key);
|
|
45
|
+
let lines = this.lines.filter(line => line.key === key).map(line => line.line);
|
|
46
|
+
|
|
47
|
+
return lines;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getDecoded(key) {
|
|
51
|
+
return this.get(key)
|
|
52
|
+
.map(line => this.libmime.decodeHeader(line))
|
|
53
|
+
.filter(line => line && line.value);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getFirst(key) {
|
|
57
|
+
if (!this.parsed) {
|
|
58
|
+
this._parseHeaders();
|
|
59
|
+
}
|
|
60
|
+
key = this._normalizeHeader(key);
|
|
61
|
+
let header = this.lines.find(line => line.key === key);
|
|
62
|
+
if (!header) {
|
|
63
|
+
return '';
|
|
64
|
+
}
|
|
65
|
+
return ((this.libmime.decodeHeader(header.line) || {}).value || '').toString().trim();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getList() {
|
|
69
|
+
if (!this.parsed) {
|
|
70
|
+
this._parseHeaders();
|
|
71
|
+
}
|
|
72
|
+
return this.lines;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
add(key, value, index) {
|
|
76
|
+
if (typeof value === 'undefined') {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (typeof value === 'number') {
|
|
81
|
+
value = value.toString();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (typeof value === 'string') {
|
|
85
|
+
value = Buffer.from(value);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
value = value.toString('binary');
|
|
89
|
+
this.addFormatted(key, this.libmime.foldLines(key + ': ' + value.replace(/\r?\n/g, ''), 76, false), index);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
addFormatted(key, line, index) {
|
|
93
|
+
if (!this.parsed) {
|
|
94
|
+
this._parseHeaders();
|
|
95
|
+
}
|
|
96
|
+
index = index || 0;
|
|
97
|
+
this.changed = true;
|
|
98
|
+
|
|
99
|
+
if (!line) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (typeof line !== 'string') {
|
|
104
|
+
line = line.toString('binary');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let header = {
|
|
108
|
+
key: this._normalizeHeader(key),
|
|
109
|
+
line
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
if (index < 1) {
|
|
113
|
+
this.lines.unshift(header);
|
|
114
|
+
} else if (index >= this.lines.length) {
|
|
115
|
+
this.lines.push(header);
|
|
116
|
+
} else {
|
|
117
|
+
this.lines.splice(index, 0, header);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
remove(key) {
|
|
122
|
+
if (!this.parsed) {
|
|
123
|
+
this._parseHeaders();
|
|
124
|
+
}
|
|
125
|
+
key = this._normalizeHeader(key);
|
|
126
|
+
for (let i = this.lines.length - 1; i >= 0; i--) {
|
|
127
|
+
if (this.lines[i].key === key) {
|
|
128
|
+
this.changed = true;
|
|
129
|
+
this.lines.splice(i, 1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
update(key, value, relativeIndex) {
|
|
135
|
+
if (!this.parsed) {
|
|
136
|
+
this._parseHeaders();
|
|
137
|
+
}
|
|
138
|
+
let keyName = key;
|
|
139
|
+
let index = 0;
|
|
140
|
+
key = this._normalizeHeader(key);
|
|
141
|
+
let relativeIndexCount = 0;
|
|
142
|
+
let relativeMatchFound = false;
|
|
143
|
+
for (let i = this.lines.length - 1; i >= 0; i--) {
|
|
144
|
+
if (this.lines[i].key === key) {
|
|
145
|
+
if (relativeIndex && relativeIndex !== relativeIndexCount) {
|
|
146
|
+
relativeIndexCount++;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
index = i;
|
|
150
|
+
this.changed = true;
|
|
151
|
+
this.lines.splice(i, 1);
|
|
152
|
+
if (relativeIndex) {
|
|
153
|
+
relativeMatchFound = true;
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (relativeIndex && !relativeMatchFound) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
this.add(keyName, value, index);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
build(lineEnd) {
|
|
167
|
+
if (!this.changed && !lineEnd) {
|
|
168
|
+
return typeof this.headers === 'string' ? Buffer.from(this.headers, 'binary') : this.headers;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!this.parsed) {
|
|
172
|
+
this._parseHeaders();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
lineEnd = lineEnd || '\r\n';
|
|
176
|
+
|
|
177
|
+
let headers = this.lines.map(line => line.line.replace(/\r?\n/g, lineEnd)).join(lineEnd) + `${lineEnd}${lineEnd}`;
|
|
178
|
+
|
|
179
|
+
if (this.mbox) {
|
|
180
|
+
headers = this.mbox + lineEnd + headers;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (this.http) {
|
|
184
|
+
headers = this.http + lineEnd + headers;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return Buffer.from(headers, 'binary');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
_normalizeHeader(key) {
|
|
191
|
+
return (key || '').toLowerCase().trim();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
_parseHeaders() {
|
|
195
|
+
if (!this.headers) {
|
|
196
|
+
this.lines = [];
|
|
197
|
+
this.parsed = true;
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let lines = this.headers
|
|
202
|
+
.toString('binary')
|
|
203
|
+
.replace(/[\r\n]+$/, '')
|
|
204
|
+
.split(/\r?\n/);
|
|
205
|
+
|
|
206
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
207
|
+
let chr = lines[i].charAt(0);
|
|
208
|
+
if (i && (chr === ' ' || chr === '\t')) {
|
|
209
|
+
lines[i - 1] += '\r\n' + lines[i];
|
|
210
|
+
lines.splice(i, 1);
|
|
211
|
+
} else {
|
|
212
|
+
let line = lines[i];
|
|
213
|
+
if (!i && /^From /i.test(line)) {
|
|
214
|
+
// mbox file
|
|
215
|
+
this.mbox = line;
|
|
216
|
+
lines.splice(i, 1);
|
|
217
|
+
continue;
|
|
218
|
+
} else if (!i && /^POST /i.test(line)) {
|
|
219
|
+
// HTTP POST request
|
|
220
|
+
this.http = line;
|
|
221
|
+
lines.splice(i, 1);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
let key = this._normalizeHeader(line.substr(0, line.indexOf(':')));
|
|
225
|
+
lines[i] = {
|
|
226
|
+
key,
|
|
227
|
+
line
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
this.lines = lines;
|
|
233
|
+
this.parsed = true;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// expose to the world
|
|
238
|
+
module.exports = Headers;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Transform = require('stream').Transform;
|
|
4
|
+
|
|
5
|
+
class MessageJoiner extends Transform {
|
|
6
|
+
constructor() {
|
|
7
|
+
let options = {
|
|
8
|
+
readableObjectMode: false,
|
|
9
|
+
writableObjectMode: true
|
|
10
|
+
};
|
|
11
|
+
super(options);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
_transform(obj, encoding, callback) {
|
|
15
|
+
if (Buffer.isBuffer(obj)) {
|
|
16
|
+
this.push(obj);
|
|
17
|
+
} else if (obj.type === 'node') {
|
|
18
|
+
this.push(obj.getHeaders());
|
|
19
|
+
} else if (obj.value) {
|
|
20
|
+
this.push(obj.value);
|
|
21
|
+
}
|
|
22
|
+
return callback();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
_flush(callback) {
|
|
26
|
+
return callback();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = MessageJoiner;
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Transform = require('stream').Transform;
|
|
4
|
+
const MimeNode = require('./mime-node');
|
|
5
|
+
|
|
6
|
+
const MAX_HEAD_SIZE = 1 * 1024 * 1024;
|
|
7
|
+
const MAX_CHILD_NODES = 1000;
|
|
8
|
+
|
|
9
|
+
const HEAD = 0x01;
|
|
10
|
+
const BODY = 0x02;
|
|
11
|
+
|
|
12
|
+
class MessageSplitter extends Transform {
|
|
13
|
+
constructor(config) {
|
|
14
|
+
let options = {
|
|
15
|
+
readableObjectMode: true,
|
|
16
|
+
writableObjectMode: false
|
|
17
|
+
};
|
|
18
|
+
super(options);
|
|
19
|
+
|
|
20
|
+
this.config = config || {};
|
|
21
|
+
this.maxHeadSize = this.config.maxHeadSize || MAX_HEAD_SIZE;
|
|
22
|
+
this.maxChildNodes = this.config.maxChildNodes || MAX_CHILD_NODES;
|
|
23
|
+
this.tree = [];
|
|
24
|
+
this.nodeCounter = 0;
|
|
25
|
+
this.newNode();
|
|
26
|
+
this.tree.push(this.node);
|
|
27
|
+
this.line = false;
|
|
28
|
+
this.hasFailed = false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_transform(chunk, encoding, callback) {
|
|
32
|
+
// process line by line
|
|
33
|
+
// find next line ending
|
|
34
|
+
let pos = 0;
|
|
35
|
+
let i = 0;
|
|
36
|
+
let group = {
|
|
37
|
+
type: 'none'
|
|
38
|
+
};
|
|
39
|
+
let groupstart = this.line ? -this.line.length : 0;
|
|
40
|
+
let groupend = 0;
|
|
41
|
+
|
|
42
|
+
let checkTrailingLinebreak = data => {
|
|
43
|
+
if (data.type === 'body' && data.node.parentNode && data.value && data.value.length) {
|
|
44
|
+
if (data.value[data.value.length - 1] === 0x0a) {
|
|
45
|
+
groupstart--;
|
|
46
|
+
groupend--;
|
|
47
|
+
pos--;
|
|
48
|
+
if (data.value.length > 1 && data.value[data.value.length - 2] === 0x0d) {
|
|
49
|
+
groupstart--;
|
|
50
|
+
groupend--;
|
|
51
|
+
pos--;
|
|
52
|
+
if (groupstart < 0 && !this.line) {
|
|
53
|
+
// store only <CR> as <LF> should be on the positive side
|
|
54
|
+
this.line = Buffer.allocUnsafe(1);
|
|
55
|
+
this.line[0] = 0x0d;
|
|
56
|
+
}
|
|
57
|
+
data.value = data.value.slice(0, data.value.length - 2);
|
|
58
|
+
} else {
|
|
59
|
+
data.value = data.value.slice(0, data.value.length - 1);
|
|
60
|
+
}
|
|
61
|
+
} else if (data.value[data.value.length - 1] === 0x0d) {
|
|
62
|
+
groupstart--;
|
|
63
|
+
groupend--;
|
|
64
|
+
pos--;
|
|
65
|
+
data.value = data.value.slice(0, data.value.length - 1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
let iterateData = () => {
|
|
71
|
+
for (let len = chunk.length; i < len; i++) {
|
|
72
|
+
// find next <LF>
|
|
73
|
+
if (chunk[i] === 0x0a) {
|
|
74
|
+
// line end
|
|
75
|
+
|
|
76
|
+
let start = Math.max(pos, 0);
|
|
77
|
+
pos = ++i;
|
|
78
|
+
|
|
79
|
+
return this.processLine(chunk.slice(start, i), false, (err, data, flush) => {
|
|
80
|
+
if (err) {
|
|
81
|
+
this.hasFailed = true;
|
|
82
|
+
return setImmediate(() => callback(err));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!data) {
|
|
86
|
+
return setImmediate(iterateData);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (flush) {
|
|
90
|
+
if (group && group.type !== 'none') {
|
|
91
|
+
if (group.type === 'body' && groupend >= groupstart && group.node.parentNode) {
|
|
92
|
+
// do not include the last line ending for body
|
|
93
|
+
if (chunk[groupend - 1] === 0x0a) {
|
|
94
|
+
groupend--;
|
|
95
|
+
if (groupend >= groupstart && chunk[groupend - 1] === 0x0d) {
|
|
96
|
+
groupend--;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (groupstart !== groupend) {
|
|
101
|
+
group.value = chunk.slice(groupstart, groupend);
|
|
102
|
+
if (groupend < i) {
|
|
103
|
+
data.value = chunk.slice(groupend, i);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
this.push(group);
|
|
107
|
+
group = {
|
|
108
|
+
type: 'none'
|
|
109
|
+
};
|
|
110
|
+
groupstart = groupend = i;
|
|
111
|
+
}
|
|
112
|
+
this.push(data);
|
|
113
|
+
groupend = i;
|
|
114
|
+
return setImmediate(iterateData);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (data.type === group.type) {
|
|
118
|
+
// shift slice end position forward
|
|
119
|
+
groupend = i;
|
|
120
|
+
} else {
|
|
121
|
+
if (group.type === 'body' && groupend >= groupstart && group.node.parentNode) {
|
|
122
|
+
// do not include the last line ending for body
|
|
123
|
+
if (chunk[groupend - 1] === 0x0a) {
|
|
124
|
+
groupend--;
|
|
125
|
+
if (groupend >= groupstart && chunk[groupend - 1] === 0x0d) {
|
|
126
|
+
groupend--;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (group.type !== 'none' && group.type !== 'node') {
|
|
132
|
+
// we have a previous data/body chunk to output
|
|
133
|
+
if (groupstart !== groupend) {
|
|
134
|
+
group.value = chunk.slice(groupstart, groupend);
|
|
135
|
+
if (group.value && group.value.length) {
|
|
136
|
+
this.push(group);
|
|
137
|
+
group = {
|
|
138
|
+
type: 'none'
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (data.type === 'node') {
|
|
145
|
+
this.push(data);
|
|
146
|
+
groupstart = i;
|
|
147
|
+
groupend = i;
|
|
148
|
+
} else if (groupstart < 0) {
|
|
149
|
+
groupstart = i;
|
|
150
|
+
groupend = i;
|
|
151
|
+
checkTrailingLinebreak(data);
|
|
152
|
+
if (data.value && data.value.length) {
|
|
153
|
+
this.push(data);
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
// start new body/data chunk
|
|
157
|
+
group = data;
|
|
158
|
+
groupstart = groupend;
|
|
159
|
+
groupend = i;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return setImmediate(iterateData);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// skip last linebreak for body
|
|
168
|
+
if (pos >= groupstart + 1 && group.type === 'body' && group.node.parentNode) {
|
|
169
|
+
// do not include the last line ending for body
|
|
170
|
+
if (chunk[pos - 1] === 0x0a) {
|
|
171
|
+
pos--;
|
|
172
|
+
if (pos >= groupstart && chunk[pos - 1] === 0x0d) {
|
|
173
|
+
pos--;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (group.type !== 'none' && group.type !== 'node' && pos > groupstart) {
|
|
179
|
+
// we have a leftover data/body chunk to push out
|
|
180
|
+
group.value = chunk.slice(groupstart, pos);
|
|
181
|
+
|
|
182
|
+
if (group.value && group.value.length) {
|
|
183
|
+
this.push(group);
|
|
184
|
+
group = {
|
|
185
|
+
type: 'none'
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (pos < chunk.length) {
|
|
191
|
+
if (this.line) {
|
|
192
|
+
this.line = Buffer.concat([this.line, chunk.slice(pos)]);
|
|
193
|
+
} else {
|
|
194
|
+
this.line = chunk.slice(pos);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
callback();
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
setImmediate(iterateData);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
_flush(callback) {
|
|
204
|
+
if (this.hasFailed) {
|
|
205
|
+
return callback();
|
|
206
|
+
}
|
|
207
|
+
this.processLine(false, true, (err, data) => {
|
|
208
|
+
if (err) {
|
|
209
|
+
return setImmediate(() => callback(err));
|
|
210
|
+
}
|
|
211
|
+
if (data && (data.type === 'node' || (data.value && data.value.length))) {
|
|
212
|
+
this.push(data);
|
|
213
|
+
}
|
|
214
|
+
callback();
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
compareBoundary(line, startpos, boundary) {
|
|
219
|
+
// --{boundary}\r\n or --{boundary}--\r\n
|
|
220
|
+
if (line.length < boundary.length + 3 + startpos || line.length > boundary.length + 6 + startpos) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
for (let i = 0; i < boundary.length; i++) {
|
|
224
|
+
if (line[i + 2 + startpos] !== boundary[i]) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let pos = 0;
|
|
230
|
+
for (let i = boundary.length + 2 + startpos; i < line.length; i++) {
|
|
231
|
+
let c = line[i];
|
|
232
|
+
if (pos === 0 && (c === 0x0d || c === 0x0a)) {
|
|
233
|
+
// 1: next node
|
|
234
|
+
return 1;
|
|
235
|
+
}
|
|
236
|
+
if (pos === 0 && c !== 0x2d) {
|
|
237
|
+
// expecting "-"
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
if (pos === 1 && c !== 0x2d) {
|
|
241
|
+
// expecting "-"
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
if (pos === 2 && c !== 0x0d && c !== 0x0a) {
|
|
245
|
+
// expecting line terminator, either <CR> or <LF>
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
if (pos === 3 && c !== 0x0a) {
|
|
249
|
+
// expecting line terminator <LF>
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
pos++;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// 2: multipart end
|
|
256
|
+
return 2;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
checkBoundary(line) {
|
|
260
|
+
let startpos = 0;
|
|
261
|
+
if (line.length >= 1 && (line[0] === 0x0d || line[0] === 0x0a)) {
|
|
262
|
+
startpos++;
|
|
263
|
+
if (line.length >= 2 && (line[0] === 0x0d || line[1] === 0x0a)) {
|
|
264
|
+
startpos++;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (line.length < 4 || line[startpos] !== 0x2d || line[startpos + 1] !== 0x2d) {
|
|
268
|
+
// defnitely not a boundary
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let boundary;
|
|
273
|
+
if (this.node._boundary && (boundary = this.compareBoundary(line, startpos, this.node._boundary))) {
|
|
274
|
+
// 1: next child
|
|
275
|
+
// 2: multipart end
|
|
276
|
+
return boundary;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (this.node._parentBoundary && (boundary = this.compareBoundary(line, startpos, this.node._parentBoundary))) {
|
|
280
|
+
// 3: next sibling
|
|
281
|
+
// 4: parent end
|
|
282
|
+
return boundary + 2;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
processLine(line, final, next) {
|
|
289
|
+
let flush = false;
|
|
290
|
+
|
|
291
|
+
if (this.line && line) {
|
|
292
|
+
line = Buffer.concat([this.line, line]);
|
|
293
|
+
this.line = false;
|
|
294
|
+
} else if (this.line && !line) {
|
|
295
|
+
line = this.line;
|
|
296
|
+
this.line = false;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!line) {
|
|
300
|
+
line = Buffer.alloc(0);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (this.nodeCounter > this.maxChildNodes) {
|
|
304
|
+
let err = new Error('Max allowed child nodes exceeded');
|
|
305
|
+
err.code = 'EMAXLEN';
|
|
306
|
+
return next(err);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// we check boundary outside the HEAD/BODY scope as it may appear anywhere
|
|
310
|
+
let boundary = this.checkBoundary(line);
|
|
311
|
+
if (boundary) {
|
|
312
|
+
// reached boundary, switch context
|
|
313
|
+
switch (boundary) {
|
|
314
|
+
case 1:
|
|
315
|
+
// next child
|
|
316
|
+
this.newNode(this.node);
|
|
317
|
+
flush = true;
|
|
318
|
+
break;
|
|
319
|
+
case 2:
|
|
320
|
+
// reached end of children, keep current node
|
|
321
|
+
break;
|
|
322
|
+
case 3: {
|
|
323
|
+
// next sibling
|
|
324
|
+
let parentNode = this.node.parentNode;
|
|
325
|
+
if (parentNode && parentNode.contentType === 'message/rfc822') {
|
|
326
|
+
// special case where immediate parent is an inline message block
|
|
327
|
+
// move up another step
|
|
328
|
+
parentNode = parentNode.parentNode;
|
|
329
|
+
}
|
|
330
|
+
this.newNode(parentNode);
|
|
331
|
+
flush = true;
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
case 4:
|
|
335
|
+
// special case when boundary close a node with only header.
|
|
336
|
+
if (this.node && this.node._headerlen && !this.node.headers) {
|
|
337
|
+
this.node.parseHeaders();
|
|
338
|
+
this.push(this.node);
|
|
339
|
+
}
|
|
340
|
+
// move up
|
|
341
|
+
if (this.tree.length) {
|
|
342
|
+
this.node = this.tree.pop();
|
|
343
|
+
}
|
|
344
|
+
this.state = BODY;
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return next(
|
|
349
|
+
null,
|
|
350
|
+
{
|
|
351
|
+
node: this.node,
|
|
352
|
+
type: 'data',
|
|
353
|
+
value: line
|
|
354
|
+
},
|
|
355
|
+
flush
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
switch (this.state) {
|
|
360
|
+
case HEAD: {
|
|
361
|
+
this.node.addHeaderChunk(line);
|
|
362
|
+
if (this.node._headerlen > this.maxHeadSize) {
|
|
363
|
+
let err = new Error('Max header size for a MIME node exceeded');
|
|
364
|
+
err.code = 'EMAXLEN';
|
|
365
|
+
return next(err);
|
|
366
|
+
}
|
|
367
|
+
if (final || (line.length === 1 && line[0] === 0x0a) || (line.length === 2 && line[0] === 0x0d && line[1] === 0x0a)) {
|
|
368
|
+
let currentNode = this.node;
|
|
369
|
+
|
|
370
|
+
currentNode.parseHeaders();
|
|
371
|
+
|
|
372
|
+
// if the content is attached message then just continue
|
|
373
|
+
if (
|
|
374
|
+
currentNode.contentType === 'message/rfc822' &&
|
|
375
|
+
!this.config.ignoreEmbedded &&
|
|
376
|
+
(!currentNode.encoding || ['7bit', '8bit', 'binary'].includes(currentNode.encoding)) &&
|
|
377
|
+
(this.config.defaultInlineEmbedded ? currentNode.disposition !== 'attachment' : currentNode.disposition === 'inline')
|
|
378
|
+
) {
|
|
379
|
+
currentNode.messageNode = true;
|
|
380
|
+
this.newNode(currentNode);
|
|
381
|
+
if (currentNode.parentNode) {
|
|
382
|
+
this.node._parentBoundary = currentNode.parentNode._boundary;
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
if (currentNode.contentType === 'message/rfc822') {
|
|
386
|
+
currentNode.messageNode = false;
|
|
387
|
+
}
|
|
388
|
+
this.state = BODY;
|
|
389
|
+
if (currentNode.multipart && currentNode._boundary) {
|
|
390
|
+
this.tree.push(currentNode);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return next(null, currentNode, flush);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return next();
|
|
398
|
+
}
|
|
399
|
+
case BODY: {
|
|
400
|
+
return next(
|
|
401
|
+
null,
|
|
402
|
+
{
|
|
403
|
+
node: this.node,
|
|
404
|
+
type: this.node.multipart ? 'data' : 'body',
|
|
405
|
+
value: line
|
|
406
|
+
},
|
|
407
|
+
flush
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
next(null, false);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
newNode(parent) {
|
|
416
|
+
this.node = new MimeNode(parent || false, this.config);
|
|
417
|
+
this.state = HEAD;
|
|
418
|
+
this.nodeCounter++;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
module.exports = MessageSplitter;
|