eml-parser-qaap 1.1.15
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 +21 -0
- package/README.md +55 -0
- package/dist/addressparser.d.ts +46 -0
- package/dist/charset.d.ts +24 -0
- package/dist/index.d.ts +76 -0
- package/dist/interface.d.ts +103 -0
- package/dist/utils.d.ts +32 -0
- package/lib/bundle.amd.js +1427 -0
- package/lib/bundle.cjs.js +1428 -0
- package/lib/bundle.esm.js +1404 -0
- package/lib/bundle.iife.js +1431 -0
- package/lib/bundle.umd.js +1431 -0
- package/package.json +107 -0
- package/src/addressparser.ts +317 -0
- package/src/charset.ts +72 -0
- package/src/index.ts +983 -0
- package/src/interface.ts +118 -0
- package/src/utils.ts +198 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,983 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author superchow
|
|
3
|
+
* @emil superchow@live.cn
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Base64 } from 'js-base64';
|
|
7
|
+
|
|
8
|
+
import { convert, decode, encode } from './charset';
|
|
9
|
+
import { GB2312UTF8, getCharsetName, guid, mimeDecode, wrap, getBoundary } from './utils';
|
|
10
|
+
import {
|
|
11
|
+
KeyValue,
|
|
12
|
+
EmailAddress,
|
|
13
|
+
ParsedEmlJson,
|
|
14
|
+
ReadedEmlJson,
|
|
15
|
+
Attachment,
|
|
16
|
+
EmlHeaders,
|
|
17
|
+
Options,
|
|
18
|
+
BuildOptions,
|
|
19
|
+
CallbackFn,
|
|
20
|
+
OptionOrNull,
|
|
21
|
+
BoundaryRawData,
|
|
22
|
+
BoundaryConvertedData,
|
|
23
|
+
} from './interface';
|
|
24
|
+
import { addressparser } from './addressparser';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* log for test
|
|
28
|
+
*/
|
|
29
|
+
let verbose: boolean = false;
|
|
30
|
+
const defaultCharset = 'utf-8';
|
|
31
|
+
const fileExtensions: KeyValue = {
|
|
32
|
+
'text/plain': '.txt',
|
|
33
|
+
'text/html': '.html',
|
|
34
|
+
'image/png': '.png',
|
|
35
|
+
'image/jpg': '.jpg',
|
|
36
|
+
'image/jpeg': '.jpg',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Gets file extension by mime type
|
|
41
|
+
* @param {String} mimeType
|
|
42
|
+
* @returns {String}
|
|
43
|
+
*/
|
|
44
|
+
// eslint-disable-next-line no-unused-vars
|
|
45
|
+
function getFileExtension(mimeType: string): string {
|
|
46
|
+
return fileExtensions[mimeType] || '';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* create a boundary
|
|
51
|
+
*/
|
|
52
|
+
function createBoundary(): string {
|
|
53
|
+
return '----=' + guid();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Builds e-mail address string, e.g. { name: 'PayPal', email: 'noreply@paypal.com' } => 'PayPal' <noreply@paypal.com>
|
|
57
|
+
* @param {String|EmailAddress|EmailAddress[]|null} data
|
|
58
|
+
*/
|
|
59
|
+
function toEmailAddress(data?: string | EmailAddress | EmailAddress[] | null): string {
|
|
60
|
+
let email = '';
|
|
61
|
+
if (typeof data === 'undefined') {
|
|
62
|
+
//No e-mail address
|
|
63
|
+
} else if (typeof data === 'string') {
|
|
64
|
+
email = data;
|
|
65
|
+
} else if (typeof data === 'object') {
|
|
66
|
+
if (Array.isArray(data)) {
|
|
67
|
+
email += data
|
|
68
|
+
.map(item => {
|
|
69
|
+
let str = '';
|
|
70
|
+
if (item.name) {
|
|
71
|
+
str += '"' + item.name.replace(/^"|"\s*$/g, '') + '" ';
|
|
72
|
+
}
|
|
73
|
+
if (item.email) {
|
|
74
|
+
str += '<' + item.email + '>';
|
|
75
|
+
}
|
|
76
|
+
return str;
|
|
77
|
+
})
|
|
78
|
+
.filter(a => a)
|
|
79
|
+
.join(', ');
|
|
80
|
+
} else {
|
|
81
|
+
if (data) {
|
|
82
|
+
if (data.name) {
|
|
83
|
+
email += '"' + data.name.replace(/^"|"\s*$/g, '') + '" ';
|
|
84
|
+
}
|
|
85
|
+
if (data.email) {
|
|
86
|
+
email += '<' + data.email + '>';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return email;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Gets character set name, e.g. contentType='.....charset='iso-8859-2'....'
|
|
96
|
+
* @param {String} contentType
|
|
97
|
+
* @returns {String|undefined}
|
|
98
|
+
*/
|
|
99
|
+
function getCharset(contentType: string) {
|
|
100
|
+
const match = /charset\s*=\W*([\w\-]+)/g.exec(contentType);
|
|
101
|
+
return match ? match[1] : undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Gets name and e-mail address from a string, e.g. 'PayPal' <noreply@paypal.com> => { name: 'PayPal', email: 'noreply@paypal.com' }
|
|
106
|
+
* @param {String} raw
|
|
107
|
+
* @returns { EmailAddress | EmailAddress[] | null}
|
|
108
|
+
*/
|
|
109
|
+
function getEmailAddress(rawStr: string): EmailAddress | EmailAddress[] | null {
|
|
110
|
+
const raw = unquoteString(rawStr);
|
|
111
|
+
const parseList = addressparser(raw);
|
|
112
|
+
const list = parseList.map(v => ({ name: v.name, email: v.address } as EmailAddress));
|
|
113
|
+
|
|
114
|
+
//Return result
|
|
115
|
+
if (list.length === 0) {
|
|
116
|
+
return null; //No e-mail address
|
|
117
|
+
}
|
|
118
|
+
if (list.length === 1) {
|
|
119
|
+
return list[0]; //Only one record, return as object, required to preserve backward compatibility
|
|
120
|
+
}
|
|
121
|
+
return list; //Multiple e-mail addresses as array
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* decode one joint
|
|
126
|
+
* @param {String} str
|
|
127
|
+
* @returns {String}
|
|
128
|
+
*/
|
|
129
|
+
function decodeJoint(str: string) {
|
|
130
|
+
const match = /=\?([^?]+)\?(B|Q)\?(.+?)(\?=)/gi.exec(str);
|
|
131
|
+
if (match) {
|
|
132
|
+
const charset = getCharsetName(match[1] || defaultCharset); //eq. match[1] = 'iso-8859-2'; charset = 'iso88592'
|
|
133
|
+
const type = match[2].toUpperCase();
|
|
134
|
+
const value = match[3];
|
|
135
|
+
if (type === 'B') {
|
|
136
|
+
//Base64
|
|
137
|
+
if (charset === 'utf8') {
|
|
138
|
+
return decode(encode(Base64.fromBase64(value.replace(/\r?\n/g, ''))), 'utf8');
|
|
139
|
+
} else {
|
|
140
|
+
return decode(Base64.toUint8Array(value.replace(/\r?\n/g, '')), charset);
|
|
141
|
+
}
|
|
142
|
+
} else if (type === 'Q') {
|
|
143
|
+
//Quoted printable
|
|
144
|
+
return unquotePrintable(value, charset, true);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return str;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* decode section
|
|
152
|
+
* @param {String} str
|
|
153
|
+
* @returns {String}
|
|
154
|
+
*/
|
|
155
|
+
function unquoteString(str: string): string {
|
|
156
|
+
const regex = /=\?([^?]+)\?(B|Q)\?(.+?)(\?=)/gi;
|
|
157
|
+
let decodedString = str || '';
|
|
158
|
+
const spinOffMatch = decodedString.match(regex);
|
|
159
|
+
if (spinOffMatch) {
|
|
160
|
+
spinOffMatch.forEach(spin => {
|
|
161
|
+
decodedString = decodedString.replace(spin, decodeJoint(spin));
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return decodedString.replace(/\r?\n/g, '');
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Decodes 'quoted-printable'
|
|
169
|
+
* @param {String} value
|
|
170
|
+
* @param {String} charset
|
|
171
|
+
* @param {String} qEncoding whether the encoding is RFC-2047’s Q-encoding, meaning special handling of underscores.
|
|
172
|
+
* @returns {String}
|
|
173
|
+
*/
|
|
174
|
+
function unquotePrintable(value: string, charset?: string, qEncoding = false): string {
|
|
175
|
+
//Convert =0D to '\r', =20 to ' ', etc.
|
|
176
|
+
// if (!charset || charset == "utf8" || charset == "utf-8") {
|
|
177
|
+
// return value
|
|
178
|
+
// .replace(/=([\w\d]{2})=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, p3, offset, string) {
|
|
179
|
+
|
|
180
|
+
// })
|
|
181
|
+
// .replace(/=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, offset, string) {
|
|
182
|
+
|
|
183
|
+
// })
|
|
184
|
+
// .replace(/=([\w\d]{2})/gi, function (matcher, p1, offset, string) { return String.fromCharCode(parseInt(p1, 16)); })
|
|
185
|
+
// .replace(/=\r?\n/gi, ""); //Join line
|
|
186
|
+
// } else {
|
|
187
|
+
// return value
|
|
188
|
+
// .replace(/=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, offset, string) {
|
|
189
|
+
|
|
190
|
+
// })
|
|
191
|
+
// .replace(/=([\w\d]{2})/gi, function (matcher, p1, offset, string) {
|
|
192
|
+
|
|
193
|
+
// })
|
|
194
|
+
// .replace(/=\r?\n/gi, ''); //Join line
|
|
195
|
+
// }
|
|
196
|
+
let rawString = value
|
|
197
|
+
.replace(/[\t ]+$/gm, '') // remove invalid whitespace from the end of lines
|
|
198
|
+
.replace(/=(?:\r?\n|$)/g, ''); // remove soft line breaks
|
|
199
|
+
|
|
200
|
+
if (qEncoding) {
|
|
201
|
+
rawString = rawString.replace(/_/g, decode(new Uint8Array([0x20]), charset));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return mimeDecode(rawString, charset);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Parses EML file content and returns object-oriented representation of the content.
|
|
209
|
+
* @param {String} eml
|
|
210
|
+
* @param {OptionOrNull | CallbackFn<ParsedEmlJson>} options
|
|
211
|
+
* @param {CallbackFn<ParsedEmlJson>} callback
|
|
212
|
+
* @returns {string | Error | ParsedEmlJson}
|
|
213
|
+
*/
|
|
214
|
+
function parse(
|
|
215
|
+
eml: string,
|
|
216
|
+
options?: OptionOrNull | CallbackFn<ParsedEmlJson>,
|
|
217
|
+
callback?: CallbackFn<ParsedEmlJson>
|
|
218
|
+
): string | Error | ParsedEmlJson {
|
|
219
|
+
//Shift arguments
|
|
220
|
+
if (typeof options === 'function' && typeof callback === 'undefined') {
|
|
221
|
+
callback = options;
|
|
222
|
+
options = null;
|
|
223
|
+
}
|
|
224
|
+
if (typeof options !== 'object') {
|
|
225
|
+
options = { headersOnly: false };
|
|
226
|
+
}
|
|
227
|
+
let error: string | Error | undefined;
|
|
228
|
+
let result: ParsedEmlJson | undefined = {} as ParsedEmlJson;
|
|
229
|
+
try {
|
|
230
|
+
if (typeof eml !== 'string') {
|
|
231
|
+
throw new Error('Argument "eml" expected to be string!');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const lines = eml.split(/\r?\n/);
|
|
235
|
+
result = parseRecursive(lines, 0, result, options as Options) as ParsedEmlJson;
|
|
236
|
+
} catch (e) {
|
|
237
|
+
error = e as string;
|
|
238
|
+
}
|
|
239
|
+
callback && callback(error, result);
|
|
240
|
+
return error || result || new Error('read EML failed!');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Parses EML file content.
|
|
245
|
+
* @param {String[]} lines
|
|
246
|
+
* @param {Number} start
|
|
247
|
+
* @param {Options} options
|
|
248
|
+
* @returns {ParsedEmlJson}
|
|
249
|
+
*/
|
|
250
|
+
function parseRecursive(lines: string[], start: number, parent: any, options: Options) {
|
|
251
|
+
let boundary: any = null;
|
|
252
|
+
let lastHeaderName = '';
|
|
253
|
+
let findBoundary = '';
|
|
254
|
+
let insideBody = false;
|
|
255
|
+
let insideBoundary = false;
|
|
256
|
+
let isMultiHeader = false;
|
|
257
|
+
let isMultipart = false;
|
|
258
|
+
let checkedForCt = false;
|
|
259
|
+
let ctInBody = false;
|
|
260
|
+
|
|
261
|
+
parent.headers = {};
|
|
262
|
+
//parent.body = null;
|
|
263
|
+
|
|
264
|
+
function complete(boundary: any) {
|
|
265
|
+
//boundary.part = boundary.lines.join("\r\n");
|
|
266
|
+
boundary.part = {};
|
|
267
|
+
parseRecursive(boundary.lines, 0, boundary.part, options);
|
|
268
|
+
delete boundary.lines;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
//Read line by line
|
|
272
|
+
for (let i = start; i < lines.length; i++) {
|
|
273
|
+
let line = lines[i];
|
|
274
|
+
|
|
275
|
+
//Header
|
|
276
|
+
if (!insideBody) {
|
|
277
|
+
//Search for empty line
|
|
278
|
+
if (line == '') {
|
|
279
|
+
insideBody = true;
|
|
280
|
+
|
|
281
|
+
if (options && options.headersOnly) {
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
//Expected boundary
|
|
286
|
+
let ct = parent.headers['Content-Type'] || parent.headers['Content-type'];
|
|
287
|
+
if (!ct) {
|
|
288
|
+
if (checkedForCt) {
|
|
289
|
+
insideBody = !ctInBody;
|
|
290
|
+
} else {
|
|
291
|
+
checkedForCt = true;
|
|
292
|
+
const lineClone = Array.from(lines);
|
|
293
|
+
const string = lineClone.splice(i).join('\r\n');
|
|
294
|
+
const trimmedStrin = string.trim();
|
|
295
|
+
if (trimmedStrin.indexOf('Content-Type') === 0 || trimmedStrin.indexOf('Content-type') === 0) {
|
|
296
|
+
insideBody = false;
|
|
297
|
+
ctInBody = true;
|
|
298
|
+
} else {
|
|
299
|
+
console.warn('Warning: undefined Content-Type');
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
} else if (/^multipart\//g.test(ct)) {
|
|
303
|
+
let b = getBoundary(ct);
|
|
304
|
+
if (b && b.length) {
|
|
305
|
+
findBoundary = b;
|
|
306
|
+
isMultipart = true;
|
|
307
|
+
parent.body = [];
|
|
308
|
+
} else {
|
|
309
|
+
if (verbose) {
|
|
310
|
+
console.warn('Multipart without boundary! ' + ct.replace(/\r?\n/g, ' '));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
//Header value with new line
|
|
319
|
+
let match = /^\s+([^\r\n]+)/g.exec(line);
|
|
320
|
+
if (match) {
|
|
321
|
+
if (isMultiHeader) {
|
|
322
|
+
parent.headers[lastHeaderName][parent.headers[lastHeaderName].length - 1] += '\r\n' + match[1];
|
|
323
|
+
} else {
|
|
324
|
+
parent.headers[lastHeaderName] += '\r\n' + match[1];
|
|
325
|
+
}
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
//Header name and value
|
|
330
|
+
match = /^([\w\d\-]+):\s*([^\r\n]*)/gi.exec(line);
|
|
331
|
+
if (match) {
|
|
332
|
+
lastHeaderName = match[1];
|
|
333
|
+
if (parent.headers[lastHeaderName]) {
|
|
334
|
+
//Multiple headers with the same name
|
|
335
|
+
isMultiHeader = true;
|
|
336
|
+
if (typeof parent.headers[lastHeaderName] == 'string') {
|
|
337
|
+
parent.headers[lastHeaderName] = [parent.headers[lastHeaderName]];
|
|
338
|
+
}
|
|
339
|
+
parent.headers[lastHeaderName].push(match[2]);
|
|
340
|
+
} else {
|
|
341
|
+
//Header first appeared here
|
|
342
|
+
isMultiHeader = false;
|
|
343
|
+
parent.headers[lastHeaderName] = match[2];
|
|
344
|
+
}
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
//Body
|
|
349
|
+
else {
|
|
350
|
+
//Multipart body
|
|
351
|
+
if (isMultipart) {
|
|
352
|
+
//Search for boundary start
|
|
353
|
+
|
|
354
|
+
//Updated on 2019-10-12: A line before the boundary marker is not required to be an empty line
|
|
355
|
+
//if (lines[i - 1] == "" && line.indexOf("--" + findBoundary) == 0 && !/\-\-(\r?\n)?$/g.test(line)) {
|
|
356
|
+
if (line.indexOf('--' + findBoundary) == 0 && !/\-\-(\r?\n)?$/g.test(line)) {
|
|
357
|
+
insideBoundary = true;
|
|
358
|
+
|
|
359
|
+
//Complete the previous boundary
|
|
360
|
+
if (boundary && boundary.lines) {
|
|
361
|
+
complete(boundary);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
//Start a new boundary
|
|
365
|
+
let match = /^\-\-([^\r\n]+)(\r?\n)?$/g.exec(line) as RegExpExecArray;
|
|
366
|
+
boundary = { boundary: match[1], lines: [] as any[] };
|
|
367
|
+
parent.body.push(boundary);
|
|
368
|
+
|
|
369
|
+
if (verbose) {
|
|
370
|
+
console.log('Found boundary: ' + boundary.boundary);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (insideBoundary) {
|
|
377
|
+
//Search for boundary end
|
|
378
|
+
if (boundary?.boundary && lines[i - 1] == '' && line.indexOf('--' + findBoundary + '--') == 0) {
|
|
379
|
+
insideBoundary = false;
|
|
380
|
+
complete(boundary);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (boundary?.boundary && line.indexOf('--' + findBoundary + '--') == 0) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
boundary?.lines.push(line);
|
|
387
|
+
}
|
|
388
|
+
} else {
|
|
389
|
+
//Solid string body
|
|
390
|
+
parent.body = lines.splice(i).join('\r\n');
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
//Complete the last boundary
|
|
397
|
+
if (parent.body && parent.body.length && parent.body[parent.body.length - 1].lines) {
|
|
398
|
+
complete(parent.body[parent.body.length - 1]);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return parent;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Convert BoundaryRawData to BoundaryConvertedData
|
|
406
|
+
* @param {BoundaryRawData} boundary
|
|
407
|
+
* @returns {BoundaryConvertedData} Obj
|
|
408
|
+
*/
|
|
409
|
+
function completeBoundary(boundary: BoundaryRawData): BoundaryConvertedData | null {
|
|
410
|
+
if (!boundary || !boundary.boundary) {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
const lines = boundary.lines || [];
|
|
414
|
+
const result = {
|
|
415
|
+
boundary: boundary.boundary,
|
|
416
|
+
part: {
|
|
417
|
+
headers: {},
|
|
418
|
+
},
|
|
419
|
+
} as BoundaryConvertedData;
|
|
420
|
+
let lastHeaderName = '';
|
|
421
|
+
let insideBody = false;
|
|
422
|
+
let childBoundary: BoundaryRawData | undefined;
|
|
423
|
+
for (let index = 0; index < lines.length; index++) {
|
|
424
|
+
const line = lines[index];
|
|
425
|
+
if (!insideBody) {
|
|
426
|
+
if (line === '') {
|
|
427
|
+
insideBody = true;
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
const match = /^([\w\d\-]+):\s*([^\r\n]*)/gi.exec(line);
|
|
431
|
+
if (match) {
|
|
432
|
+
lastHeaderName = match[1];
|
|
433
|
+
result.part.headers[lastHeaderName] = match[2];
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
//Header value with new line
|
|
437
|
+
const lineMatch = /^\s+([^\r\n]+)/g.exec(line);
|
|
438
|
+
if (lineMatch) {
|
|
439
|
+
result.part.headers[lastHeaderName] += '\r\n' + lineMatch[1];
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
} else {
|
|
443
|
+
// part.body
|
|
444
|
+
const match = /^\-\-([^\r\n]+)(\r?\n)?$/g.exec(line);
|
|
445
|
+
const childBoundaryStr = getBoundary(result.part.headers['Content-Type'] || result.part.headers['Content-type']);
|
|
446
|
+
if (verbose) {
|
|
447
|
+
if (match) {
|
|
448
|
+
console.log(`line 568: line is ${line}, ${'--' + childBoundaryStr}`, `${line.indexOf('--' + childBoundaryStr)}`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (match && line.indexOf('--' + childBoundaryStr) === 0 && !childBoundary) {
|
|
452
|
+
childBoundary = { boundary: match ? match[1] : '', lines: [] };
|
|
453
|
+
continue;
|
|
454
|
+
} else if (!!childBoundary && childBoundary.boundary) {
|
|
455
|
+
if (lines[index - 1] === '' && line.indexOf('--' + childBoundary.boundary) === 0) {
|
|
456
|
+
const child = completeBoundary(childBoundary);
|
|
457
|
+
if (verbose) {
|
|
458
|
+
console.info(`578: ${JSON.stringify(child)}`);
|
|
459
|
+
}
|
|
460
|
+
if (child) {
|
|
461
|
+
if (Array.isArray(result.part.body)) {
|
|
462
|
+
result.part.body.push(child);
|
|
463
|
+
} else {
|
|
464
|
+
result.part.body = [child];
|
|
465
|
+
}
|
|
466
|
+
} else {
|
|
467
|
+
result.part.body = childBoundary.lines.join('\r\n');
|
|
468
|
+
}
|
|
469
|
+
// next line child
|
|
470
|
+
if (!!lines[index + 1]) {
|
|
471
|
+
childBoundary.lines = [];
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
// end line child And this boundary's end
|
|
475
|
+
if (line.indexOf('--' + childBoundary.boundary + '--') === 0 && lines[index + 1] === '') {
|
|
476
|
+
if (verbose) {
|
|
477
|
+
console.info('line 601 childBoundary is over line is 534');
|
|
478
|
+
}
|
|
479
|
+
childBoundary = undefined;
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
childBoundary.lines.push(line);
|
|
484
|
+
} else {
|
|
485
|
+
if (verbose) {
|
|
486
|
+
console.warn('body is string');
|
|
487
|
+
}
|
|
488
|
+
result.part.body = lines.splice(index).join('\r\n');
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return result;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* buid EML file by ReadedEmlJson or EML file content
|
|
498
|
+
* @param {ReadedEmlJson} data
|
|
499
|
+
* @param {BuildOptions | CallbackFn<string> | null} options
|
|
500
|
+
* @param {CallbackFn<string>} callback
|
|
501
|
+
*/
|
|
502
|
+
function build(
|
|
503
|
+
data: ReadedEmlJson | string,
|
|
504
|
+
options?: BuildOptions | CallbackFn<string> | null,
|
|
505
|
+
callback?: CallbackFn<string>
|
|
506
|
+
): string | Error {
|
|
507
|
+
//Shift arguments
|
|
508
|
+
if (typeof options === 'function' && typeof callback === 'undefined') {
|
|
509
|
+
callback = options;
|
|
510
|
+
options = null;
|
|
511
|
+
}
|
|
512
|
+
let error: Error | string | undefined;
|
|
513
|
+
let eml = '';
|
|
514
|
+
const EOL = '\r\n'; //End-of-line
|
|
515
|
+
|
|
516
|
+
try {
|
|
517
|
+
if (!data) {
|
|
518
|
+
throw new Error('Argument "data" expected to be an object! or string');
|
|
519
|
+
}
|
|
520
|
+
if (typeof data === 'string') {
|
|
521
|
+
const readResult = read(data);
|
|
522
|
+
if (typeof readResult === 'string') {
|
|
523
|
+
throw new Error(readResult);
|
|
524
|
+
} else if (readResult instanceof Error) {
|
|
525
|
+
throw readResult;
|
|
526
|
+
} else {
|
|
527
|
+
data = readResult;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (!data.headers) {
|
|
532
|
+
throw new Error('Argument "data" expected to be has headers');
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (typeof data.subject === 'string') {
|
|
536
|
+
data.headers['Subject'] = data.subject;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (typeof data.from !== 'undefined') {
|
|
540
|
+
data.headers['From'] = toEmailAddress(data.from);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (typeof data.to !== 'undefined') {
|
|
544
|
+
data.headers['To'] = toEmailAddress(data.to);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (typeof data.cc !== 'undefined') {
|
|
548
|
+
data.headers['Cc'] = toEmailAddress(data.cc);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// if (!data.headers['To']) {
|
|
552
|
+
// throw new Error('Missing "To" e-mail address!');
|
|
553
|
+
// }
|
|
554
|
+
|
|
555
|
+
const emlBoundary = getBoundary(data.headers['Content-Type'] || data.headers['Content-type'] || '');
|
|
556
|
+
let hasBoundary = false;
|
|
557
|
+
let boundary = createBoundary();
|
|
558
|
+
let multipartBoundary = '';
|
|
559
|
+
if (data.multipartAlternative) {
|
|
560
|
+
multipartBoundary = '' + (getBoundary(data.multipartAlternative['Content-Type']) || '');
|
|
561
|
+
hasBoundary = true;
|
|
562
|
+
}
|
|
563
|
+
if (emlBoundary) {
|
|
564
|
+
boundary = emlBoundary;
|
|
565
|
+
hasBoundary = true;
|
|
566
|
+
} else {
|
|
567
|
+
data.headers['Content-Type'] = data.headers['Content-type'] || 'multipart/mixed;' + EOL + 'boundary="' + boundary + '"';
|
|
568
|
+
// Restrained
|
|
569
|
+
// hasBoundary = true;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
//Build headers
|
|
573
|
+
const keys = Object.keys(data.headers);
|
|
574
|
+
for (let i = 0; i < keys.length; i++) {
|
|
575
|
+
const key = keys[i];
|
|
576
|
+
const value: string | string[] = data.headers[key];
|
|
577
|
+
if (typeof value === 'undefined') {
|
|
578
|
+
continue; //Skip missing headers
|
|
579
|
+
} else if (typeof value === 'string') {
|
|
580
|
+
eml += key + ': ' + value.replace(/\r?\n/g, EOL + ' ') + EOL;
|
|
581
|
+
} else {
|
|
582
|
+
//Array
|
|
583
|
+
for (let j = 0; j < value.length; j++) {
|
|
584
|
+
eml += key + ': ' + value[j].replace(/\r?\n/g, EOL + ' ') + EOL;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (data.multipartAlternative) {
|
|
590
|
+
eml += EOL;
|
|
591
|
+
eml += '--' + emlBoundary + EOL;
|
|
592
|
+
eml += 'Content-Type: ' + data.multipartAlternative['Content-Type'].replace(/\r?\n/g, EOL + ' ') + EOL;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
//Start the body
|
|
596
|
+
eml += EOL;
|
|
597
|
+
|
|
598
|
+
//Plain text content
|
|
599
|
+
if (data.text) {
|
|
600
|
+
// Encode opened and self headers keeped
|
|
601
|
+
if (typeof options === 'object' && !!options && options.encode && data.textheaders) {
|
|
602
|
+
eml += '--' + boundary + EOL;
|
|
603
|
+
for (const key in data.textheaders) {
|
|
604
|
+
if (data.textheaders.hasOwnProperty(key)) {
|
|
605
|
+
eml += `${key}: ${data.textheaders[key].replace(/\r?\n/g, EOL + ' ')}`;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
} else if (hasBoundary) {
|
|
609
|
+
// else Assembly
|
|
610
|
+
eml += '--' + (multipartBoundary ? multipartBoundary : boundary) + EOL;
|
|
611
|
+
eml += 'Content-Type: text/plain; charset="utf-8"' + EOL;
|
|
612
|
+
}
|
|
613
|
+
eml += EOL + data.text;
|
|
614
|
+
eml += EOL;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
//HTML content
|
|
618
|
+
if (data.html) {
|
|
619
|
+
// Encode opened and self headers keeped
|
|
620
|
+
if (typeof options === 'object' && !!options && options.encode && data.textheaders) {
|
|
621
|
+
eml += '--' + boundary + EOL;
|
|
622
|
+
for (const key in data.textheaders) {
|
|
623
|
+
if (data.textheaders.hasOwnProperty(key)) {
|
|
624
|
+
eml += `${key}: ${data.textheaders[key].replace(/\r?\n/g, EOL + ' ')}`;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
} else if (hasBoundary) {
|
|
628
|
+
eml += '--' + (multipartBoundary ? multipartBoundary : boundary) + EOL;
|
|
629
|
+
eml += 'Content-Type: text/html; charset="utf-8"' + EOL;
|
|
630
|
+
}
|
|
631
|
+
if (verbose) {
|
|
632
|
+
console.info(
|
|
633
|
+
`line 765 ${hasBoundary}, emlBoundary: ${emlBoundary}, multipartBoundary: ${multipartBoundary}, boundary: ${boundary}`
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
eml += EOL + data.html;
|
|
637
|
+
eml += EOL;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
//Append attachments
|
|
641
|
+
if (data.attachments) {
|
|
642
|
+
for (let i = 0; i < data.attachments.length; i++) {
|
|
643
|
+
const attachment = data.attachments[i];
|
|
644
|
+
eml += '--' + boundary + EOL;
|
|
645
|
+
eml += 'Content-Type: ' + (attachment.contentType.replace(/\r?\n/g, EOL + ' ') || 'application/octet-stream') + EOL;
|
|
646
|
+
eml += 'Content-Transfer-Encoding: base64' + EOL;
|
|
647
|
+
eml +=
|
|
648
|
+
'Content-Disposition: ' +
|
|
649
|
+
(attachment.inline ? 'inline' : 'attachment') +
|
|
650
|
+
'; filename="' +
|
|
651
|
+
(attachment.filename || attachment.name || 'attachment_' + (i + 1)) +
|
|
652
|
+
'"' +
|
|
653
|
+
EOL;
|
|
654
|
+
if (attachment.cid) {
|
|
655
|
+
eml += 'Content-ID: <' + attachment.cid + '>' + EOL;
|
|
656
|
+
}
|
|
657
|
+
eml += EOL;
|
|
658
|
+
if (typeof attachment.data === 'string') {
|
|
659
|
+
const content = Base64.toBase64(attachment.data);
|
|
660
|
+
eml += wrap(content, 72) + EOL;
|
|
661
|
+
} else {
|
|
662
|
+
//Buffer
|
|
663
|
+
// Uint8Array to string by new TextEncoder
|
|
664
|
+
const content = decode(attachment.data);
|
|
665
|
+
eml += wrap(content, 72) + EOL;
|
|
666
|
+
}
|
|
667
|
+
eml += EOL;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
//Finish the boundary
|
|
672
|
+
if (hasBoundary) {
|
|
673
|
+
eml += '--' + boundary + '--' + EOL;
|
|
674
|
+
}
|
|
675
|
+
} catch (e) {
|
|
676
|
+
error = e as string;
|
|
677
|
+
}
|
|
678
|
+
callback && callback(error, eml);
|
|
679
|
+
return error || eml;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Parses EML file content and return user-friendly object.
|
|
684
|
+
* @param {String | ParsedEmlJson} eml EML file content or object from 'parse'
|
|
685
|
+
* @param { OptionOrNull | CallbackFn<ReadedEmlJson>} options EML parse options
|
|
686
|
+
* @param {CallbackFn<ReadedEmlJson>} callback Callback function(error, data)
|
|
687
|
+
*/
|
|
688
|
+
function read(
|
|
689
|
+
eml: string | ParsedEmlJson,
|
|
690
|
+
options?: OptionOrNull | CallbackFn<ReadedEmlJson>,
|
|
691
|
+
callback?: CallbackFn<ReadedEmlJson>
|
|
692
|
+
): ReadedEmlJson | Error | string {
|
|
693
|
+
//Shift arguments
|
|
694
|
+
if (typeof options === 'function' && typeof callback === 'undefined') {
|
|
695
|
+
callback = options;
|
|
696
|
+
options = null;
|
|
697
|
+
}
|
|
698
|
+
let error: Error | string | undefined;
|
|
699
|
+
let result: ReadedEmlJson | undefined;
|
|
700
|
+
|
|
701
|
+
//Appends the boundary to the result
|
|
702
|
+
function _append(headers: EmlHeaders, content: string | Uint8Array | Attachment, result: ReadedEmlJson) {
|
|
703
|
+
const contentType = headers['Content-Type'] || headers['Content-type'];
|
|
704
|
+
const contentDisposition = headers['Content-Disposition'];
|
|
705
|
+
|
|
706
|
+
const charset = getCharsetName(getCharset(contentType as string) || defaultCharset);
|
|
707
|
+
let encoding = headers['Content-Transfer-Encoding'] || headers['Content-transfer-encoding'];
|
|
708
|
+
if (typeof encoding === 'string') {
|
|
709
|
+
encoding = encoding.toLowerCase();
|
|
710
|
+
}
|
|
711
|
+
if (encoding === 'base64') {
|
|
712
|
+
if (contentType && contentType.indexOf('gbk') >= 0) {
|
|
713
|
+
// is work? I'm not sure
|
|
714
|
+
content = encode(GB2312UTF8.GB2312ToUTF8((content as string).replace(/\r?\n/g, '')));
|
|
715
|
+
} else {
|
|
716
|
+
// string to Uint8Array by TextEncoder
|
|
717
|
+
content = encode((content as string).replace(/\r?\n/g, ''));
|
|
718
|
+
}
|
|
719
|
+
} else if (encoding === 'quoted-printable') {
|
|
720
|
+
content = unquotePrintable(content as string, charset);
|
|
721
|
+
} else if (encoding && charset !== 'utf8' && encoding.search(/binary|8bit/) === 0) {
|
|
722
|
+
//'8bit', 'binary', '8bitmime', 'binarymime'
|
|
723
|
+
content = decode(content as Uint8Array, charset);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (!contentDisposition && contentType && contentType.indexOf('text/html') >= 0) {
|
|
727
|
+
if (typeof content !== 'string') {
|
|
728
|
+
content = decode(content as Uint8Array, charset);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
let htmlContent = content.replace(/\r\n|(")/g, '').replace(/\"/g, `"`);
|
|
732
|
+
|
|
733
|
+
try {
|
|
734
|
+
if (encoding === 'base64') {
|
|
735
|
+
htmlContent = Base64.decode(htmlContent);
|
|
736
|
+
} else if (Base64.btoa(Base64.atob(htmlContent)) == htmlContent) {
|
|
737
|
+
htmlContent = Base64.atob(htmlContent);
|
|
738
|
+
}
|
|
739
|
+
} catch (error) {
|
|
740
|
+
console.error(error);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if (result.html) {
|
|
744
|
+
result.html += htmlContent;
|
|
745
|
+
} else {
|
|
746
|
+
result.html = htmlContent;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
result.htmlheaders = {
|
|
750
|
+
'Content-Type': contentType,
|
|
751
|
+
'Content-Transfer-Encoding': encoding || '',
|
|
752
|
+
};
|
|
753
|
+
// self boundary Not used at conversion
|
|
754
|
+
} else if (!contentDisposition && contentType && contentType.indexOf('text/plain') >= 0) {
|
|
755
|
+
if (typeof content !== 'string') {
|
|
756
|
+
content = decode(content as Uint8Array, charset);
|
|
757
|
+
}
|
|
758
|
+
if (encoding === 'base64') {
|
|
759
|
+
content = Base64.decode(content);
|
|
760
|
+
}
|
|
761
|
+
//Plain text message
|
|
762
|
+
|
|
763
|
+
if (result.text) {
|
|
764
|
+
result.text += content;
|
|
765
|
+
} else {
|
|
766
|
+
result.text = content;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
result.textheaders = {
|
|
770
|
+
'Content-Type': contentType,
|
|
771
|
+
'Content-Transfer-Encoding': encoding || '',
|
|
772
|
+
};
|
|
773
|
+
// self boundary Not used at conversion
|
|
774
|
+
} else {
|
|
775
|
+
//Get the attachment
|
|
776
|
+
if (!result.attachments) {
|
|
777
|
+
result.attachments = [];
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const attachment = {} as Attachment;
|
|
781
|
+
|
|
782
|
+
const id = headers['Content-ID'] || headers['Content-Id'];
|
|
783
|
+
if (id) {
|
|
784
|
+
attachment.id = id;
|
|
785
|
+
}
|
|
786
|
+
const qaaapId = headers['X-Qaap-Object-Id'];
|
|
787
|
+
if (qaaapId) {
|
|
788
|
+
attachment.qaapId = qaaapId;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const NameContainer = ['Content-Disposition', 'Content-Type', 'Content-type'];
|
|
792
|
+
|
|
793
|
+
let result_name;
|
|
794
|
+
for (const key of NameContainer) {
|
|
795
|
+
const name: string = headers[key];
|
|
796
|
+
if (name) {
|
|
797
|
+
result_name = name
|
|
798
|
+
.replace(/(\s|'|utf-8|\*[0-9]\*)/g, '')
|
|
799
|
+
.split(';')
|
|
800
|
+
.map(v => /name[\*]?="?(.+?)"?$/gi.exec(v))
|
|
801
|
+
.reduce((a, b) => {
|
|
802
|
+
if (b && b[1]) {
|
|
803
|
+
a += b[1];
|
|
804
|
+
}
|
|
805
|
+
return a;
|
|
806
|
+
}, '');
|
|
807
|
+
if (result_name) {
|
|
808
|
+
break;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (result_name) {
|
|
813
|
+
attachment.name = decodeURI(result_name);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const ct = headers['Content-Type'] || headers['Content-type'];
|
|
817
|
+
if (ct) {
|
|
818
|
+
attachment.contentType = ct;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const cd = headers['Content-Disposition'];
|
|
822
|
+
if (cd) {
|
|
823
|
+
attachment.inline = /^\s*inline/g.test(cd);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
attachment.data = content as Uint8Array;
|
|
827
|
+
attachment.data64 = decode(content as Uint8Array, charset);
|
|
828
|
+
result.attachments.push(attachment);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function _read(data: ParsedEmlJson): ReadedEmlJson | Error | string {
|
|
833
|
+
if (!data) {
|
|
834
|
+
return 'no data';
|
|
835
|
+
}
|
|
836
|
+
try {
|
|
837
|
+
const result = {} as ReadedEmlJson;
|
|
838
|
+
if (!data.headers) {
|
|
839
|
+
throw new Error("data does't has headers");
|
|
840
|
+
}
|
|
841
|
+
if (data.headers['Date']) {
|
|
842
|
+
result.date = new Date(data.headers['Date']);
|
|
843
|
+
}
|
|
844
|
+
if (data.headers['Subject']) {
|
|
845
|
+
result.subject = unquoteString(data.headers['Subject']);
|
|
846
|
+
}
|
|
847
|
+
if (data.headers['From']) {
|
|
848
|
+
result.from = getEmailAddress(data.headers['From']);
|
|
849
|
+
}
|
|
850
|
+
if (data.headers['To']) {
|
|
851
|
+
result.to = getEmailAddress(data.headers['To']);
|
|
852
|
+
}
|
|
853
|
+
if (data.headers['CC']) {
|
|
854
|
+
result.cc = getEmailAddress(data.headers['CC']);
|
|
855
|
+
}
|
|
856
|
+
if (data.headers['Cc']) {
|
|
857
|
+
result.cc = getEmailAddress(data.headers['Cc']);
|
|
858
|
+
}
|
|
859
|
+
result.headers = data.headers;
|
|
860
|
+
|
|
861
|
+
//Content mime type
|
|
862
|
+
let boundary: any = null;
|
|
863
|
+
const ct = data.headers['Content-Type'] || data.headers['Content-type'];
|
|
864
|
+
if (ct && /^multipart\//g.test(ct)) {
|
|
865
|
+
const b = getBoundary(ct);
|
|
866
|
+
if (b && b.length) {
|
|
867
|
+
boundary = b;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
if (boundary && Array.isArray(data.body)) {
|
|
872
|
+
for (let i = 0; i < data.body.length; i++) {
|
|
873
|
+
const boundaryBlock = data.body[i];
|
|
874
|
+
if (!boundaryBlock) {
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
//Get the message content
|
|
878
|
+
if (typeof boundaryBlock.part === 'undefined') {
|
|
879
|
+
verbose && console.warn('Warning: undefined b.part');
|
|
880
|
+
} else if (typeof boundaryBlock.part === 'string') {
|
|
881
|
+
result.data = boundaryBlock.part;
|
|
882
|
+
} else {
|
|
883
|
+
if (typeof boundaryBlock.part.body === 'undefined') {
|
|
884
|
+
verbose && console.warn('Warning: undefined b.part.body');
|
|
885
|
+
} else if (typeof boundaryBlock.part.body === 'string') {
|
|
886
|
+
_append(boundaryBlock.part.headers, boundaryBlock.part.body, result);
|
|
887
|
+
} else {
|
|
888
|
+
// keep multipart/alternative
|
|
889
|
+
const currentHeaders = boundaryBlock.part.headers;
|
|
890
|
+
const currentHeadersContentType = currentHeaders['Content-Type'] || currentHeaders['Content-type'];
|
|
891
|
+
if (verbose) {
|
|
892
|
+
console.log(`line 969 currentHeadersContentType: ${currentHeadersContentType}`);
|
|
893
|
+
}
|
|
894
|
+
// Hasmore ?
|
|
895
|
+
if (currentHeadersContentType && currentHeadersContentType.indexOf('multipart') >= 0 && !result.multipartAlternative) {
|
|
896
|
+
result.multipartAlternative = {
|
|
897
|
+
'Content-Type': currentHeadersContentType,
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
for (let j = 0; j < boundaryBlock.part.body.length; j++) {
|
|
901
|
+
const selfBoundary = boundaryBlock.part.body[j];
|
|
902
|
+
if (typeof selfBoundary === 'string') {
|
|
903
|
+
result.data = selfBoundary;
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
const headers = selfBoundary.part.headers;
|
|
908
|
+
const content = selfBoundary.part.body;
|
|
909
|
+
if (Array.isArray(content)) {
|
|
910
|
+
(content as any).forEach((bound: any) => {
|
|
911
|
+
_append(bound.part.headers, bound.part.body, result);
|
|
912
|
+
});
|
|
913
|
+
} else {
|
|
914
|
+
_append(headers, content, result);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
} else if (typeof data.body === 'string') {
|
|
921
|
+
_append(data.headers, data.body, result);
|
|
922
|
+
}
|
|
923
|
+
return result;
|
|
924
|
+
} catch (e) {
|
|
925
|
+
return e as any;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
if (typeof eml === 'string') {
|
|
930
|
+
const parseResult = parse(eml, options as OptionOrNull);
|
|
931
|
+
if (typeof parseResult === 'string' || parseResult instanceof Error) {
|
|
932
|
+
error = parseResult;
|
|
933
|
+
} else {
|
|
934
|
+
const readResult = _read(parseResult);
|
|
935
|
+
if (typeof readResult === 'string' || readResult instanceof Error) {
|
|
936
|
+
error = readResult;
|
|
937
|
+
} else {
|
|
938
|
+
result = readResult;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
} else if (typeof eml === 'object') {
|
|
942
|
+
const readResult = _read(eml);
|
|
943
|
+
if (typeof readResult === 'string' || readResult instanceof Error) {
|
|
944
|
+
error = readResult;
|
|
945
|
+
} else {
|
|
946
|
+
result = readResult;
|
|
947
|
+
}
|
|
948
|
+
} else {
|
|
949
|
+
error = new Error('Missing EML file content!');
|
|
950
|
+
}
|
|
951
|
+
callback && callback(error, result);
|
|
952
|
+
return error || result || new Error('read EML failed!');
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* if you need
|
|
957
|
+
* eml-format all api
|
|
958
|
+
*/
|
|
959
|
+
export {
|
|
960
|
+
getEmailAddress,
|
|
961
|
+
toEmailAddress,
|
|
962
|
+
createBoundary,
|
|
963
|
+
getBoundary,
|
|
964
|
+
getCharset,
|
|
965
|
+
unquoteString,
|
|
966
|
+
unquotePrintable,
|
|
967
|
+
mimeDecode,
|
|
968
|
+
Base64,
|
|
969
|
+
convert,
|
|
970
|
+
encode,
|
|
971
|
+
decode,
|
|
972
|
+
completeBoundary,
|
|
973
|
+
parse as parseEml,
|
|
974
|
+
read as readEml,
|
|
975
|
+
build as buildEml,
|
|
976
|
+
GB2312UTF8 as GBKUTF8,
|
|
977
|
+
};
|
|
978
|
+
|
|
979
|
+
// const GBKUTF8 = GB2312UTF8;
|
|
980
|
+
|
|
981
|
+
// const parseEml = parse;
|
|
982
|
+
// const readEml = read;
|
|
983
|
+
// const buildEml = build;
|