@n8n/imap 0.7.0 → 0.8.19

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.
Files changed (44) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +754 -0
  3. package/deps/encoding/encoding-indexes.js +73 -0
  4. package/deps/encoding/encoding.js +2340 -0
  5. package/deps/test/utf7-imap.js +48 -0
  6. package/deps/test/utf7-rfc2152.js +78 -0
  7. package/deps/utf7.js +104 -0
  8. package/index.d.ts +396 -0
  9. package/lib/Connection.js +2131 -0
  10. package/lib/Parser.js +1026 -0
  11. package/package.json +18 -41
  12. package/test/test-connection-fetch-dup.js +92 -0
  13. package/test/test-connection-fetch-frag.js +92 -0
  14. package/test/test-connection-fetch-spillover.js +176 -0
  15. package/test/test-connection-fetch-stringbody.js +104 -0
  16. package/test/test-connection-idle-normal.js +144 -0
  17. package/test/test-connection-idle-order.js +138 -0
  18. package/test/test-parse-bodystructure.js +83 -0
  19. package/test/test-parse-envelope-addrs.js +117 -0
  20. package/test/test-parse-expr.js +92 -0
  21. package/test/test-parse-header.js +109 -0
  22. package/test/test-parser.js +543 -0
  23. package/test/test.js +4 -0
  24. package/LICENSE.md +0 -86
  25. package/LICENSE_EE.md +0 -27
  26. package/dist/ImapSimple.d.ts +0 -16
  27. package/dist/ImapSimple.js +0 -120
  28. package/dist/ImapSimple.js.map +0 -1
  29. package/dist/PartData.d.ts +0 -24
  30. package/dist/PartData.js +0 -99
  31. package/dist/PartData.js.map +0 -1
  32. package/dist/build.tsbuildinfo +0 -1
  33. package/dist/errors.d.ts +0 -12
  34. package/dist/errors.js +0 -30
  35. package/dist/errors.js.map +0 -1
  36. package/dist/helpers/getMessage.d.ts +0 -3
  37. package/dist/helpers/getMessage.js +0 -37
  38. package/dist/helpers/getMessage.js.map +0 -1
  39. package/dist/index.d.ts +0 -7
  40. package/dist/index.js +0 -85
  41. package/dist/index.js.map +0 -1
  42. package/dist/types.d.ts +0 -30
  43. package/dist/types.js +0 -3
  44. package/dist/types.js.map +0 -1
package/lib/Parser.js ADDED
@@ -0,0 +1,1026 @@
1
+ var EventEmitter = require('events').EventEmitter,
2
+ ReadableStream = require('stream').Readable
3
+ || require('readable-stream').Readable,
4
+ inherits = require('util').inherits,
5
+ inspect = require('util').inspect;
6
+
7
+ var utf7 = require('../deps/utf7').imap,
8
+ jsencoding; // lazy-loaded
9
+
10
+ var CH_LF = 10,
11
+ LITPLACEHOLDER = String.fromCharCode(0),
12
+ EMPTY_READCB = function(n) {},
13
+ RE_INTEGER = /^\d+$/,
14
+ RE_PRECEDING = /^(?:\* |A\d+ |\+ ?)/,
15
+ RE_BODYLITERAL = /BODY\[(.*)\] \{(\d+)\}$/i,
16
+ RE_BODYINLINEKEY = /^BODY\[(.*)\]$/i,
17
+ RE_SEQNO = /^\* (\d+)/,
18
+ RE_LISTCONTENT = /^\((.*)\)$/,
19
+ RE_LITERAL = /\{(\d+)\}$/,
20
+ RE_UNTAGGED = /^\* (?:(OK|NO|BAD|BYE|FLAGS|ID|LIST|XLIST|LSUB|SEARCH|STATUS|CAPABILITY|NAMESPACE|PREAUTH|SORT|THREAD|ESEARCH|QUOTA|QUOTAROOT)|(\d+) (EXPUNGE|FETCH|RECENT|EXISTS))(?:(?: \[([^\]]+)\])?(?: (.+))?)?$/i,
21
+ RE_TAGGED = /^A(\d+) (OK|NO|BAD) ?(?:\[([^\]]+)\] )?(.*)$/i,
22
+ RE_CONTINUE = /^\+(?: (?:\[([^\]]+)\] )?(.+))?$/i,
23
+ RE_CRLF = /\r\n/g,
24
+ RE_HDR = /^([^:]+):[ \t]?(.+)?$/,
25
+ RE_ENCWORD = /=\?([^?*]*?)(?:\*.*?)?\?([qb])\?(.*?)\?=/gi,
26
+ RE_ENCWORD_END = /=\?([^?*]*?)(?:\*.*?)?\?([qb])\?(.*?)\?=$/i,
27
+ RE_ENCWORD_BEGIN = /^[ \t]=\?([^?*]*?)(?:\*.*?)?\?([qb])\?(.*?)\?=/i,
28
+ RE_QENC = /(?:=([a-fA-F0-9]{2}))|_/g,
29
+ RE_SEARCH_MODSEQ = /^(.+) \(MODSEQ (.+?)\)$/i,
30
+ RE_LWS_ONLY = /^[ \t]*$/;
31
+
32
+ function Parser(stream, debug) {
33
+ if (!(this instanceof Parser))
34
+ return new Parser(stream, debug);
35
+
36
+ EventEmitter.call(this);
37
+
38
+ this._stream = undefined;
39
+ this._body = undefined;
40
+ this._literallen = 0;
41
+ this._literals = [];
42
+ this._buffer = '';
43
+ this._ignoreReadable = false;
44
+ this.debug = debug;
45
+
46
+ var self = this;
47
+ this._cbReadable = function() {
48
+ if (self._ignoreReadable)
49
+ return;
50
+ if (self._literallen > 0 && !self._body)
51
+ self._tryread(self._literallen);
52
+ else
53
+ self._tryread();
54
+ };
55
+
56
+ this.setStream(stream);
57
+
58
+ process.nextTick(this._cbReadable);
59
+ }
60
+ inherits(Parser, EventEmitter);
61
+
62
+ Parser.prototype.setStream = function(stream) {
63
+ if (this._stream)
64
+ this._stream.removeListener('readable', this._cbReadable);
65
+
66
+ if (/^v0\.8\./.test(process.version)) {
67
+ this._stream = (new ReadableStream()).wrap(stream);
68
+
69
+ // since Readable.wrap() proxies events, we need to remove at least the
70
+ // proxied 'error' event since this can cause problems and Parser doesn't
71
+ // care about such events
72
+ stream._events.error.pop();
73
+ } else
74
+ this._stream = stream;
75
+
76
+ this._stream.on('readable', this._cbReadable);
77
+ };
78
+
79
+ Parser.prototype._tryread = function(n) {
80
+ if (this._stream.readable) {
81
+ var r = this._stream.read(n);
82
+ r && this._parse(r);
83
+ }
84
+ };
85
+
86
+ Parser.prototype._parse = function(data) {
87
+ var i = 0, datalen = data.length, idxlf;
88
+
89
+ if (this._literallen > 0) {
90
+ if (this._body) {
91
+ var body = this._body;
92
+ if (datalen >= this._literallen) {
93
+ var litlen = this._literallen;
94
+ i = litlen;
95
+ this._literallen = 0;
96
+ this._body = undefined;
97
+ body._read = EMPTY_READCB;
98
+ if (datalen > litlen)
99
+ body.push(data.slice(0, litlen));
100
+ else
101
+ body.push(data);
102
+ body.push(null);
103
+ } else {
104
+ this._literallen -= datalen;
105
+ var r = body.push(data);
106
+ if (!r) {
107
+ body._read = this._cbReadable;
108
+ return;
109
+ }
110
+ i = datalen;
111
+ }
112
+ } else {
113
+ if (datalen > this._literallen)
114
+ this._literals.push(data.slice(0, this._literallen));
115
+ else
116
+ this._literals.push(data);
117
+ i = this._literallen;
118
+ this._literallen = 0;
119
+ }
120
+ }
121
+
122
+ while (i < datalen) {
123
+ idxlf = indexOfCh(data, datalen, i, CH_LF);
124
+ if (idxlf === -1) {
125
+ this._buffer += data.toString('utf8', i);
126
+ break;
127
+ } else {
128
+ this._buffer += data.toString('utf8', i, idxlf);
129
+ this._buffer = this._buffer.trim();
130
+ i = idxlf + 1;
131
+
132
+ this.debug && this.debug('<= ' + inspect(this._buffer));
133
+
134
+ if (RE_PRECEDING.test(this._buffer)) {
135
+ var firstChar = this._buffer[0];
136
+ if (firstChar === '*')
137
+ this._resUntagged();
138
+ else if (firstChar === 'A')
139
+ this._resTagged();
140
+ else if (firstChar === '+')
141
+ this._resContinue();
142
+
143
+ if (this._literallen > 0 && i < datalen) {
144
+ this._ignoreReadable = true;
145
+ // literal data included in this chunk -- put it back onto stream
146
+ this._stream.unshift(data.slice(i));
147
+ this._ignoreReadable = false;
148
+ i = datalen;
149
+ if (!this._body) {
150
+ // check if unshifted contents satisfies non-body literal length
151
+ this._tryread(this._literallen);
152
+ }
153
+ }
154
+ } else {
155
+ this.emit('other', this._buffer);
156
+ this._buffer = '';
157
+ }
158
+ }
159
+ }
160
+
161
+ if (this._literallen === 0 || this._body)
162
+ this._tryread();
163
+ };
164
+
165
+ Parser.prototype._resTagged = function() {
166
+ var m;
167
+ if (m = RE_LITERAL.exec(this._buffer)) {
168
+ // non-BODY literal -- buffer it
169
+ this._buffer = this._buffer.replace(RE_LITERAL, LITPLACEHOLDER);
170
+ this._literallen = parseInt(m[1], 10);
171
+ } else if (m = RE_TAGGED.exec(this._buffer)) {
172
+ this._buffer = '';
173
+ this._literals = [];
174
+
175
+ this.emit('tagged', {
176
+ type: m[2].toLowerCase(),
177
+ tagnum: parseInt(m[1], 10),
178
+ textCode: (m[3] ? parseTextCode(m[3], this._literals) : m[3]),
179
+ text: m[4]
180
+ });
181
+ } else
182
+ this._buffer = '';
183
+ };
184
+
185
+ Parser.prototype._resUntagged = function() {
186
+ var m;
187
+ if (m = RE_BODYLITERAL.exec(this._buffer)) {
188
+ // BODY literal -- stream it
189
+ var which = m[1], size = parseInt(m[2], 10);
190
+ this._literallen = size;
191
+ this._body = new ReadableStream();
192
+ this._body._readableState.sync = false;
193
+ this._body._read = EMPTY_READCB;
194
+ m = RE_SEQNO.exec(this._buffer);
195
+ this._buffer = this._buffer.replace(RE_BODYLITERAL, '');
196
+ this.emit('body', this._body, {
197
+ seqno: parseInt(m[1], 10),
198
+ which: which,
199
+ size: size
200
+ });
201
+ } else if (m = RE_LITERAL.exec(this._buffer)) {
202
+ // non-BODY literal -- buffer it
203
+ this._buffer = this._buffer.replace(RE_LITERAL, LITPLACEHOLDER);
204
+ this._literallen = parseInt(m[1], 10);
205
+ } else if (m = RE_UNTAGGED.exec(this._buffer)) {
206
+ this._buffer = '';
207
+ // normal single line response
208
+
209
+ // m[1] or m[3] = response type
210
+ // if m[3] is set, m[2] = sequence number (for FETCH) or count
211
+ // m[4] = response text code (optional)
212
+ // m[5] = response text (optional)
213
+
214
+ var type, num, textCode, val;
215
+ if (m[2] !== undefined)
216
+ num = parseInt(m[2], 10);
217
+ if (m[4] !== undefined)
218
+ textCode = parseTextCode(m[4], this._literals);
219
+
220
+ type = (m[1] || m[3]).toLowerCase();
221
+
222
+ if (type === 'flags'
223
+ || type === 'search'
224
+ || type === 'capability'
225
+ || type === 'sort') {
226
+ if (m[5]) {
227
+ if (type === 'search' && RE_SEARCH_MODSEQ.test(m[5])) {
228
+ // CONDSTORE search response
229
+ var p = RE_SEARCH_MODSEQ.exec(m[5]);
230
+ val = {
231
+ results: p[1].split(' '),
232
+ modseq: p[2]
233
+ };
234
+ } else {
235
+ if (m[5][0] === '(')
236
+ val = RE_LISTCONTENT.exec(m[5])[1].split(' ');
237
+ else
238
+ val = m[5].split(' ');
239
+
240
+ if (type === 'search' || type === 'sort')
241
+ val = val.map(function(v) { return parseInt(v, 10); });
242
+ }
243
+ } else
244
+ val = [];
245
+ } else if (type === 'thread') {
246
+ if (m[5])
247
+ val = parseExpr(m[5], this._literals);
248
+ else
249
+ val = [];
250
+ } else if (type === 'list' || type === 'lsub' || type === 'xlist')
251
+ val = parseBoxList(m[5], this._literals);
252
+ else if (type === 'id')
253
+ val = parseId(m[5], this._literals);
254
+ else if (type === 'status')
255
+ val = parseStatus(m[5], this._literals);
256
+ else if (type === 'fetch')
257
+ val = parseFetch.call(this, m[5], this._literals, num);
258
+ else if (type === 'namespace')
259
+ val = parseNamespaces(m[5], this._literals);
260
+ else if (type === 'esearch')
261
+ val = parseESearch(m[5], this._literals);
262
+ else if (type === 'quota')
263
+ val = parseQuota(m[5], this._literals);
264
+ else if (type === 'quotaroot')
265
+ val = parseQuotaRoot(m[5], this._literals);
266
+ else
267
+ val = m[5];
268
+
269
+ this._literals = [];
270
+
271
+ this.emit('untagged', {
272
+ type: type,
273
+ num: num,
274
+ textCode: textCode,
275
+ text: val
276
+ });
277
+ } else
278
+ this._buffer = '';
279
+ };
280
+
281
+ Parser.prototype._resContinue = function() {
282
+ var m = RE_CONTINUE.exec(this._buffer),
283
+ textCode,
284
+ text;
285
+
286
+ this._buffer = '';
287
+
288
+ if (!m)
289
+ return;
290
+
291
+ text = m[2];
292
+
293
+ if (m[1] !== undefined)
294
+ textCode = parseTextCode(m[1], this._literals);
295
+
296
+ this.emit('continue', {
297
+ textCode: textCode,
298
+ text: text
299
+ });
300
+ };
301
+
302
+ function indexOfCh(buffer, len, i, ch) {
303
+ var r = -1;
304
+ for (; i < len; ++i) {
305
+ if (buffer[i] === ch) {
306
+ r = i;
307
+ break;
308
+ }
309
+ }
310
+ return r;
311
+ }
312
+
313
+ function parseTextCode(text, literals) {
314
+ var r = parseExpr(text, literals);
315
+ if (r.length === 1)
316
+ return r[0];
317
+ else
318
+ return { key: r[0], val: r.length === 2 ? r[1] : r.slice(1) };
319
+ }
320
+
321
+ function parseESearch(text, literals) {
322
+ var r = parseExpr(text.toUpperCase().replace('UID', ''), literals),
323
+ attrs = {};
324
+
325
+ // RFC4731 unfortunately is lacking on documentation, so we're going to
326
+ // assume that the response text always begins with (TAG "A123") and skip that
327
+ // part ...
328
+
329
+ for (var i = 1, len = r.length, key, val; i < len; i += 2) {
330
+ key = r[i].toLowerCase();
331
+ val = r[i + 1];
332
+ if (key === 'all')
333
+ val = val.toString().split(',');
334
+ attrs[key] = val;
335
+ }
336
+
337
+ return attrs;
338
+ }
339
+
340
+ function parseId(text, literals) {
341
+ var r = parseExpr(text, literals),
342
+ id = {};
343
+ if (r[0] === null)
344
+ return null;
345
+ for (var i = 0, len = r[0].length; i < len; i += 2)
346
+ id[r[0][i].toLowerCase()] = r[0][i + 1];
347
+
348
+ return id;
349
+ }
350
+
351
+ function parseQuota(text, literals) {
352
+ var r = parseExpr(text, literals),
353
+ resources = {};
354
+
355
+ for (var i = 0, len = r[1].length; i < len; i += 3) {
356
+ resources[r[1][i].toLowerCase()] = {
357
+ usage: r[1][i + 1],
358
+ limit: r[1][i + 2]
359
+ };
360
+ }
361
+
362
+ return {
363
+ root: r[0],
364
+ resources: resources
365
+ };
366
+ }
367
+
368
+ function parseQuotaRoot(text, literals) {
369
+ var r = parseExpr(text, literals);
370
+
371
+ return {
372
+ roots: r.slice(1),
373
+ mailbox: r[0]
374
+ };
375
+ }
376
+
377
+ function parseBoxList(text, literals) {
378
+ var r = parseExpr(text, literals);
379
+ return {
380
+ flags: r[0],
381
+ delimiter: r[1],
382
+ name: utf7.decode(''+r[2])
383
+ };
384
+ }
385
+
386
+ function parseNamespaces(text, literals) {
387
+ var r = parseExpr(text, literals), i, len, j, len2, ns, nsobj, namespaces, n;
388
+
389
+ for (n = 0; n < 3; ++n) {
390
+ if (r[n]) {
391
+ namespaces = [];
392
+ for (i = 0, len = r[n].length; i < len; ++i) {
393
+ ns = r[n][i];
394
+ nsobj = {
395
+ prefix: ns[0],
396
+ delimiter: ns[1],
397
+ extensions: undefined
398
+ };
399
+ if (ns.length > 2)
400
+ nsobj.extensions = {};
401
+ for (j = 2, len2 = ns.length; j < len2; j += 2)
402
+ nsobj.extensions[ns[j]] = ns[j + 1];
403
+ namespaces.push(nsobj);
404
+ }
405
+ r[n] = namespaces;
406
+ }
407
+ }
408
+
409
+ return {
410
+ personal: r[0],
411
+ other: r[1],
412
+ shared: r[2]
413
+ };
414
+ }
415
+
416
+ function parseStatus(text, literals) {
417
+ var r = parseExpr(text, literals), attrs = {};
418
+ // r[1] is [KEY1, VAL1, KEY2, VAL2, .... KEYn, VALn]
419
+ for (var i = 0, len = r[1].length; i < len; i += 2)
420
+ attrs[r[1][i].toLowerCase()] = r[1][i + 1];
421
+ return {
422
+ name: utf7.decode(''+r[0]),
423
+ attrs: attrs
424
+ };
425
+ }
426
+
427
+ function parseFetch(text, literals, seqno) {
428
+ var list = parseExpr(text, literals)[0], attrs = {}, m, body;
429
+ // list is [KEY1, VAL1, KEY2, VAL2, .... KEYn, VALn]
430
+ for (var i = 0, len = list.length, key, val; i < len; i += 2) {
431
+ key = list[i].toLowerCase();
432
+ val = list[i + 1];
433
+ if (key === 'envelope')
434
+ val = parseFetchEnvelope(val);
435
+ else if (key === 'internaldate')
436
+ val = new Date(val);
437
+ else if (key === 'modseq') // always a list of one value
438
+ val = ''+val[0];
439
+ else if (key === 'body' || key === 'bodystructure')
440
+ val = parseBodyStructure(val);
441
+ else if (m = RE_BODYINLINEKEY.exec(list[i])) {
442
+ // a body was sent as a non-literal
443
+ val = Buffer.from(''+val);
444
+ body = new ReadableStream();
445
+ body._readableState.sync = false;
446
+ body._read = EMPTY_READCB;
447
+ this.emit('body', body, {
448
+ seqno: seqno,
449
+ which: m[1],
450
+ size: val.length
451
+ });
452
+ body.push(val);
453
+ body.push(null);
454
+ continue;
455
+ }
456
+ attrs[key] = val;
457
+ }
458
+ return attrs;
459
+ }
460
+
461
+ function parseBodyStructure(cur, literals, prefix, partID) {
462
+ var ret = [], i, len;
463
+ if (prefix === undefined) {
464
+ var result = (Array.isArray(cur) ? cur : parseExpr(cur, literals));
465
+ if (result.length)
466
+ ret = parseBodyStructure(result, literals, '', 1);
467
+ } else {
468
+ var part, partLen = cur.length, next;
469
+ if (Array.isArray(cur[0])) { // multipart
470
+ next = -1;
471
+ while (Array.isArray(cur[++next])) {
472
+ ret.push(parseBodyStructure(cur[next],
473
+ literals,
474
+ prefix + (prefix !== '' ? '.' : '')
475
+ + (partID++).toString(), 1));
476
+ }
477
+ part = { type: cur[next++].toLowerCase() };
478
+ if (partLen > next) {
479
+ if (Array.isArray(cur[next])) {
480
+ part.params = {};
481
+ for (i = 0, len = cur[next].length; i < len; i += 2)
482
+ part.params[cur[next][i].toLowerCase()] = cur[next][i + 1];
483
+ } else
484
+ part.params = cur[next];
485
+ ++next;
486
+ }
487
+ } else { // single part
488
+ next = 7;
489
+ if (typeof cur[1] === 'string') {
490
+ part = {
491
+ // the path identifier for this part, useful for fetching specific
492
+ // parts of a message
493
+ partID: (prefix !== '' ? prefix : '1'),
494
+
495
+ // required fields as per RFC 3501 -- null or otherwise
496
+ type: cur[0].toLowerCase(), subtype: cur[1].toLowerCase(),
497
+ params: null, id: cur[3], description: cur[4], encoding: cur[5],
498
+ size: cur[6]
499
+ };
500
+ } else {
501
+ // type information for malformed multipart body
502
+ part = { type: cur[0] ? cur[0].toLowerCase() : null, params: null };
503
+ cur.splice(1, 0, null);
504
+ ++partLen;
505
+ next = 2;
506
+ }
507
+ if (Array.isArray(cur[2])) {
508
+ part.params = {};
509
+ for (i = 0, len = cur[2].length; i < len; i += 2)
510
+ part.params[cur[2][i].toLowerCase()] = cur[2][i + 1];
511
+ if (cur[1] === null)
512
+ ++next;
513
+ }
514
+ if (part.type === 'message' && part.subtype === 'rfc822') {
515
+ // envelope
516
+ if (partLen > next && Array.isArray(cur[next]))
517
+ part.envelope = parseFetchEnvelope(cur[next]);
518
+ else
519
+ part.envelope = null;
520
+ ++next;
521
+
522
+ // body
523
+ if (partLen > next && Array.isArray(cur[next]))
524
+ part.body = parseBodyStructure(cur[next], literals, prefix, 1);
525
+ else
526
+ part.body = null;
527
+ ++next;
528
+ }
529
+ if ((part.type === 'text'
530
+ || (part.type === 'message' && part.subtype === 'rfc822'))
531
+ && partLen > next)
532
+ part.lines = cur[next++];
533
+ if (typeof cur[1] === 'string' && partLen > next)
534
+ part.md5 = cur[next++];
535
+ }
536
+ // add any extra fields that may or may not be omitted entirely
537
+ parseStructExtra(part, partLen, cur, next);
538
+ ret.unshift(part);
539
+ }
540
+ return ret;
541
+ }
542
+
543
+ function parseStructExtra(part, partLen, cur, next) {
544
+ if (partLen > next) {
545
+ // disposition
546
+ // null or a special k/v list with these kinds of values:
547
+ // e.g.: ['Foo', null]
548
+ // ['Foo', ['Bar', 'Baz']]
549
+ // ['Foo', ['Bar', 'Baz', 'Bam', 'Pow']]
550
+ var disposition = { type: null, params: null };
551
+ if (Array.isArray(cur[next])) {
552
+ disposition.type = cur[next][0];
553
+ if (Array.isArray(cur[next][1])) {
554
+ disposition.params = {};
555
+ for (var i = 0, len = cur[next][1].length, key; i < len; i += 2) {
556
+ key = cur[next][1][i].toLowerCase();
557
+ disposition.params[key] = cur[next][1][i + 1];
558
+ }
559
+ }
560
+ } else if (cur[next] !== null)
561
+ disposition.type = cur[next];
562
+
563
+ if (disposition.type === null)
564
+ part.disposition = null;
565
+ else
566
+ part.disposition = disposition;
567
+
568
+ ++next;
569
+ }
570
+ if (partLen > next) {
571
+ // language can be a string or a list of one or more strings, so let's
572
+ // make this more consistent ...
573
+ if (cur[next] !== null)
574
+ part.language = (Array.isArray(cur[next]) ? cur[next] : [cur[next]]);
575
+ else
576
+ part.language = null;
577
+ ++next;
578
+ }
579
+ if (partLen > next)
580
+ part.location = cur[next++];
581
+ if (partLen > next) {
582
+ // extension stuff introduced by later RFCs
583
+ // this can really be any value: a string, number, or (un)nested list
584
+ // let's not parse it for now ...
585
+ part.extensions = cur[next];
586
+ }
587
+ }
588
+
589
+ function parseFetchEnvelope(list) {
590
+ return {
591
+ date: new Date(list[0]),
592
+ subject: decodeWords(list[1]),
593
+ from: parseEnvelopeAddresses(list[2]),
594
+ sender: parseEnvelopeAddresses(list[3]),
595
+ replyTo: parseEnvelopeAddresses(list[4]),
596
+ to: parseEnvelopeAddresses(list[5]),
597
+ cc: parseEnvelopeAddresses(list[6]),
598
+ bcc: parseEnvelopeAddresses(list[7]),
599
+ inReplyTo: list[8],
600
+ messageId: list[9]
601
+ };
602
+ }
603
+
604
+ function parseEnvelopeAddresses(list) {
605
+ var addresses = null;
606
+ if (Array.isArray(list)) {
607
+ addresses = [];
608
+ var inGroup = false, curGroup;
609
+ for (var i = 0, len = list.length, addr; i < len; ++i) {
610
+ addr = list[i];
611
+ if (addr[2] === null) { // end of group addresses
612
+ inGroup = false;
613
+ if (curGroup) {
614
+ addresses.push(curGroup);
615
+ curGroup = undefined;
616
+ }
617
+ } else if (addr[3] === null) { // start of group addresses
618
+ inGroup = true;
619
+ curGroup = {
620
+ group: addr[2],
621
+ addresses: []
622
+ };
623
+ } else { // regular user address
624
+ var info = {
625
+ name: decodeWords(addr[0]),
626
+ mailbox: addr[2],
627
+ host: addr[3]
628
+ };
629
+ if (inGroup)
630
+ curGroup.addresses.push(info);
631
+ else if (!inGroup)
632
+ addresses.push(info);
633
+ }
634
+ list[i] = addr;
635
+ }
636
+ if (inGroup) {
637
+ // no end of group found, assume implicit end
638
+ addresses.push(curGroup);
639
+ }
640
+ }
641
+ return addresses;
642
+ }
643
+
644
+ function parseExpr(o, literals, result, start, useBrackets) {
645
+ start = start || 0;
646
+ var inQuote = false,
647
+ lastPos = start - 1,
648
+ isTop = false,
649
+ isBody = false,
650
+ escaping = false,
651
+ val;
652
+
653
+ if (useBrackets === undefined)
654
+ useBrackets = true;
655
+ if (!result)
656
+ result = [];
657
+ if (typeof o === 'string') {
658
+ o = { str: o };
659
+ isTop = true;
660
+ }
661
+ for (var i = start, len = o.str.length; i < len; ++i) {
662
+ if (!inQuote) {
663
+ if (isBody) {
664
+ if (o.str[i] === ']') {
665
+ val = convStr(o.str.substring(lastPos + 1, i + 1), literals);
666
+ result.push(val);
667
+ lastPos = i;
668
+ isBody = false;
669
+ }
670
+ } else if (o.str[i] === '"')
671
+ inQuote = true;
672
+ else if (o.str[i] === ' '
673
+ || o.str[i] === ')'
674
+ || (useBrackets && o.str[i] === ']')) {
675
+ if (i - (lastPos + 1) > 0) {
676
+ val = convStr(o.str.substring(lastPos + 1, i), literals);
677
+ result.push(val);
678
+ }
679
+ if ((o.str[i] === ')' || (useBrackets && o.str[i] === ']')) && !isTop)
680
+ return i;
681
+ lastPos = i;
682
+ } else if ((o.str[i] === '(' || (useBrackets && o.str[i] === '['))) {
683
+ if (o.str[i] === '['
684
+ && i - 4 >= start
685
+ && o.str.substring(i - 4, i).toUpperCase() === 'BODY') {
686
+ isBody = true;
687
+ lastPos = i - 5;
688
+ } else {
689
+ var innerResult = [];
690
+ i = parseExpr(o, literals, innerResult, i + 1, useBrackets);
691
+ lastPos = i;
692
+ result.push(innerResult);
693
+ }
694
+ }
695
+ } else if (o.str[i] === '\\')
696
+ escaping = !escaping;
697
+ else if (o.str[i] === '"') {
698
+ if (!escaping)
699
+ inQuote = false;
700
+ escaping = false;
701
+ }
702
+ if (i + 1 === len && len - (lastPos + 1) > 0)
703
+ result.push(convStr(o.str.substring(lastPos + 1), literals));
704
+ }
705
+ return (isTop ? result : start);
706
+ }
707
+
708
+ function convStr(str, literals) {
709
+ if (str[0] === '"') {
710
+ str = str.substring(1, str.length - 1);
711
+ var newstr = '', isEscaping = false, p = 0;
712
+ for (var i = 0, len = str.length; i < len; ++i) {
713
+ if (str[i] === '\\') {
714
+ if (!isEscaping)
715
+ isEscaping = true;
716
+ else {
717
+ isEscaping = false;
718
+ newstr += str.substring(p, i - 1);
719
+ p = i;
720
+ }
721
+ } else if (str[i] === '"') {
722
+ if (isEscaping) {
723
+ isEscaping = false;
724
+ newstr += str.substring(p, i - 1);
725
+ p = i;
726
+ }
727
+ }
728
+ }
729
+ if (p === 0)
730
+ return str;
731
+ else {
732
+ newstr += str.substring(p);
733
+ return newstr;
734
+ }
735
+ } else if (str === 'NIL')
736
+ return null;
737
+ else if (RE_INTEGER.test(str)) {
738
+ // some IMAP extensions utilize large (64-bit) integers, which JavaScript
739
+ // can't handle natively, so we'll just keep it as a string if it's too big
740
+ var val = parseInt(str, 10);
741
+ return (val.toString() === str ? val : str);
742
+ } else if (literals && literals.length && str === LITPLACEHOLDER) {
743
+ var l = literals.shift();
744
+ if (Buffer.isBuffer(l))
745
+ l = l.toString('utf8');
746
+ return l;
747
+ }
748
+
749
+ return str;
750
+ }
751
+
752
+ function repeat(chr, len) {
753
+ var s = '';
754
+ for (var i = 0; i < len; ++i)
755
+ s += chr;
756
+ return s;
757
+ }
758
+
759
+ function decodeBytes(buf, encoding, offset, mlen, pendoffset, state, nextBuf) {
760
+ if (!jsencoding)
761
+ jsencoding = require('../deps/encoding/encoding');
762
+ if (jsencoding.encodingExists(encoding)) {
763
+ if (state.buffer !== undefined) {
764
+ if (state.encoding === encoding && state.consecutive) {
765
+ // concatenate buffer + current bytes in hopes of finally having
766
+ // something that's decodable
767
+ var newbuf = Buffer.allocUnsafe(state.buffer.length + buf.length);
768
+ state.buffer.copy(newbuf, 0);
769
+ buf.copy(newbuf, state.buffer.length);
770
+ buf = newbuf;
771
+ } else {
772
+ // either:
773
+ // - the current encoded word is not separated by the previous partial
774
+ // encoded word by linear whitespace, OR
775
+ // - the current encoded word and the previous partial encoded word
776
+ // use different encodings
777
+ state.buffer = state.encoding = undefined;
778
+ state.curReplace = undefined;
779
+ }
780
+ }
781
+ var ret, isPartial = false;
782
+ if (state.remainder !== undefined) {
783
+ // use cached remainder from the previous lookahead
784
+ ret = state.remainder;
785
+ state.remainder = undefined;
786
+ } else {
787
+ try {
788
+ ret = jsencoding.TextDecoder(encoding).decode(buf);
789
+ } catch (e) {
790
+ if (e.message.indexOf('Seeking') === 0)
791
+ isPartial = true;
792
+ }
793
+ }
794
+ if (!isPartial && nextBuf) {
795
+ // try to decode a lookahead buffer (current buffer + next buffer)
796
+ // and see if it starts with the decoded value of the current buffer.
797
+ // if not, the current buffer is partial
798
+ var lookahead, lookaheadBuf =
799
+ Buffer.allocUnsafe(buf.length + nextBuf.length);
800
+ buf.copy(lookaheadBuf);
801
+ nextBuf.copy(lookaheadBuf, buf.length);
802
+ try {
803
+ lookahead = jsencoding.TextDecoder(encoding).decode(lookaheadBuf);
804
+ } catch(e) {
805
+ // cannot decode the lookahead, do nothing
806
+ }
807
+ if (lookahead !== undefined) {
808
+ if (lookahead.indexOf(ret) === 0) {
809
+ // the current buffer is whole, cache the lookahead's remainder
810
+ state.remainder = lookahead.substring(ret.length);
811
+ } else {
812
+ isPartial = true;
813
+ ret = undefined;
814
+ }
815
+ }
816
+ }
817
+ if (ret !== undefined) {
818
+ if (state.curReplace) {
819
+ // we have some previous partials which were finally "satisfied" by the
820
+ // current encoded word, so replace from the beginning of the first
821
+ // partial to the end of the current encoded word
822
+ state.replaces.push({
823
+ fromOffset: state.curReplace[0].fromOffset,
824
+ toOffset: offset + mlen,
825
+ val: ret
826
+ });
827
+ state.replaces.splice(state.replaces.indexOf(state.curReplace), 1);
828
+ state.curReplace = undefined;
829
+ } else {
830
+ // normal case where there are no previous partials and we successfully
831
+ // decoded a single encoded word
832
+ state.replaces.push({
833
+ // we ignore linear whitespace between consecutive encoded words
834
+ fromOffset: state.consecutive ? pendoffset : offset,
835
+ toOffset: offset + mlen,
836
+ val: ret
837
+ });
838
+ }
839
+ state.buffer = state.encoding = undefined;
840
+ return;
841
+ } else if (isPartial) {
842
+ // RFC2047 says that each decoded encoded word "MUST represent an integral
843
+ // number of characters. A multi-octet character may not be split across
844
+ // adjacent encoded-words." However, some MUAs appear to go against this,
845
+ // so we join broken encoded words separated by linear white space until
846
+ // we can successfully decode or we see a change in encoding
847
+ state.encoding = encoding;
848
+ state.buffer = buf;
849
+ if (!state.curReplace)
850
+ state.replaces.push(state.curReplace = []);
851
+ state.curReplace.push({
852
+ fromOffset: offset,
853
+ toOffset: offset + mlen,
854
+ // the value we replace this encoded word with if it doesn't end up
855
+ // becoming part of a successful decode
856
+ val: repeat('\uFFFD', buf.length)
857
+ });
858
+ return;
859
+ }
860
+ }
861
+ // in case of unexpected error or unsupported encoding, just substitute the
862
+ // raw bytes
863
+ state.replaces.push({
864
+ fromOffset: offset,
865
+ toOffset: offset + mlen,
866
+ val: buf.toString('latin1')
867
+ });
868
+ }
869
+
870
+ function qEncReplacer(match, byte) {
871
+ if (match === '_')
872
+ return ' ';
873
+ else
874
+ return String.fromCharCode(parseInt(byte, 16));
875
+ }
876
+ function decodeWords(str, state) {
877
+ var pendoffset = -1;
878
+
879
+ if (!state) {
880
+ state = {
881
+ buffer: undefined,
882
+ encoding: undefined,
883
+ consecutive: false,
884
+ replaces: undefined,
885
+ curReplace: undefined,
886
+ remainder: undefined
887
+ };
888
+ }
889
+
890
+ state.replaces = [];
891
+
892
+ var bytes, m, next, i, j, leni, lenj, seq, replaces = [], lastReplace = {};
893
+
894
+ // join consecutive q-encoded words that have the same charset first
895
+ while (m = RE_ENCWORD.exec(str)) {
896
+ seq = {
897
+ consecutive: (pendoffset > -1
898
+ ? RE_LWS_ONLY.test(str.substring(pendoffset, m.index))
899
+ : false),
900
+ charset: m[1].toLowerCase(),
901
+ encoding: m[2].toLowerCase(),
902
+ chunk: m[3],
903
+ index: m.index,
904
+ length: m[0].length,
905
+ pendoffset: pendoffset,
906
+ buf: undefined
907
+ };
908
+ lastReplace = replaces.length && replaces[replaces.length - 1];
909
+ if (seq.consecutive
910
+ && seq.charset === lastReplace.charset
911
+ && seq.encoding === lastReplace.encoding
912
+ && seq.encoding === 'q') {
913
+ lastReplace.length += seq.length + seq.index - pendoffset;
914
+ lastReplace.chunk += seq.chunk;
915
+ } else {
916
+ replaces.push(seq);
917
+ lastReplace = seq;
918
+ }
919
+ pendoffset = m.index + m[0].length;
920
+ }
921
+
922
+ // generate replacement substrings and their positions
923
+ for (i = 0, leni = replaces.length; i < leni; ++i) {
924
+ m = replaces[i];
925
+ state.consecutive = m.consecutive;
926
+ if (m.encoding === 'q') {
927
+ // q-encoding, similar to quoted-printable
928
+ bytes = Buffer.from(m.chunk.replace(RE_QENC, qEncReplacer), 'latin1');
929
+ next = undefined;
930
+ } else {
931
+ // base64
932
+ bytes = m.buf || Buffer.from(m.chunk, 'base64');
933
+ next = replaces[i + 1];
934
+ if (next && next.consecutive && next.encoding === m.encoding
935
+ && next.charset === m.charset) {
936
+ // we use the next base64 chunk, if any, to determine the integrity
937
+ // of the current chunk
938
+ next.buf = Buffer.from(next.chunk, 'base64');
939
+ }
940
+ }
941
+ decodeBytes(bytes, m.charset, m.index, m.length, m.pendoffset, state,
942
+ next && next.buf);
943
+ }
944
+
945
+ // perform the actual replacements
946
+ for (i = state.replaces.length - 1; i >= 0; --i) {
947
+ seq = state.replaces[i];
948
+ if (Array.isArray(seq)) {
949
+ for (j = 0, lenj = seq.length; j < lenj; ++j) {
950
+ str = str.substring(0, seq[j].fromOffset)
951
+ + seq[j].val
952
+ + str.substring(seq[j].toOffset);
953
+ }
954
+ } else {
955
+ str = str.substring(0, seq.fromOffset)
956
+ + seq.val
957
+ + str.substring(seq.toOffset);
958
+ }
959
+ }
960
+
961
+ return str;
962
+ }
963
+
964
+ function parseHeader(str, noDecode) {
965
+ var lines = str.split(RE_CRLF),
966
+ len = lines.length,
967
+ header = {},
968
+ state = {
969
+ buffer: undefined,
970
+ encoding: undefined,
971
+ consecutive: false,
972
+ replaces: undefined,
973
+ curReplace: undefined,
974
+ remainder: undefined
975
+ },
976
+ m, h, i, val;
977
+
978
+ for (i = 0; i < len; ++i) {
979
+ if (lines[i].length === 0)
980
+ break; // empty line separates message's header and body
981
+ if (lines[i][0] === '\t' || lines[i][0] === ' ') {
982
+ if (!Array.isArray(header[h]))
983
+ continue; // ignore invalid first line
984
+ // folded header content
985
+ val = lines[i];
986
+ if (!noDecode) {
987
+ if (RE_ENCWORD_END.test(lines[i - 1])
988
+ && RE_ENCWORD_BEGIN.test(val)) {
989
+ // RFC2047 says to *ignore* leading whitespace in folded header values
990
+ // for adjacent encoded-words ...
991
+ val = val.substring(1);
992
+ }
993
+ }
994
+ header[h][header[h].length - 1] += val;
995
+ } else {
996
+ m = RE_HDR.exec(lines[i]);
997
+ if (m) {
998
+ h = m[1].toLowerCase().trim();
999
+ if (m[2]) {
1000
+ if (header[h] === undefined)
1001
+ header[h] = [m[2]];
1002
+ else
1003
+ header[h].push(m[2]);
1004
+ } else
1005
+ header[h] = [''];
1006
+ } else
1007
+ break;
1008
+ }
1009
+ }
1010
+ if (!noDecode) {
1011
+ var hvs;
1012
+ for (h in header) {
1013
+ hvs = header[h];
1014
+ for (i = 0, len = header[h].length; i < len; ++i)
1015
+ hvs[i] = decodeWords(hvs[i], state);
1016
+ }
1017
+ }
1018
+
1019
+ return header;
1020
+ }
1021
+
1022
+ exports.Parser = Parser;
1023
+ exports.parseExpr = parseExpr;
1024
+ exports.parseEnvelopeAddresses = parseEnvelopeAddresses;
1025
+ exports.parseBodyStructure = parseBodyStructure;
1026
+ exports.parseHeader = parseHeader;