imapflow 1.0.95 → 1.0.98
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/CHANGELOG.md +12 -0
- package/lib/handler/imap-formal-syntax.js +1 -1
- package/lib/handler/parser-instance.js +7 -7
- package/lib/handler/token-parser.js +32 -27
- package/lib/imap-flow.js +1 -1
- package/lib/tools.js +20 -7
- package/package.json +7 -7
- package/test/imap-parser-test.js +28 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
- 1.0.98 - 2022-05-30
|
|
4
|
+
|
|
5
|
+
- Fixed an issue with envelope parsing where literal values were returned as Buffers
|
|
6
|
+
|
|
7
|
+
- 1.0.97 - 2022-05-29
|
|
8
|
+
|
|
9
|
+
- Allow single quotes in atoms
|
|
10
|
+
|
|
11
|
+
- 1.0.96 - 2022-05-18
|
|
12
|
+
|
|
13
|
+
- Allow non-standard characters in ATOM if it's the string after NO/OK/BAD response
|
|
14
|
+
|
|
3
15
|
- 1.0.95 - 2022-05-03
|
|
4
16
|
|
|
5
17
|
- Do not use FETCH BINARY, unless `binary` option is `true`
|
|
@@ -60,16 +60,16 @@ class ParserInstance {
|
|
|
60
60
|
let match, element, errPos;
|
|
61
61
|
|
|
62
62
|
if (this.remainder.match(/^\s/)) {
|
|
63
|
-
throw new Error(
|
|
63
|
+
throw new Error(`Unexpected whitespace at position ${this.pos} [E1]`);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
if ((match = this.remainder.match(/^\s*[^\s]+(?=\s|$)/))) {
|
|
67
67
|
element = match[0];
|
|
68
68
|
if ((errPos = imapFormalSyntax.verify(element, syntax)) >= 0) {
|
|
69
|
-
throw new Error(
|
|
69
|
+
throw new Error(`Unexpected char at position ${this.pos + errPos} [E2: ${JSON.stringify(element.charAt(errPos))}]`);
|
|
70
70
|
}
|
|
71
71
|
} else {
|
|
72
|
-
throw new Error(
|
|
72
|
+
throw new Error(`Unexpected end of input at position ${this.pos} [E3]`);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
this.pos += match[0].length;
|
|
@@ -80,11 +80,11 @@ class ParserInstance {
|
|
|
80
80
|
|
|
81
81
|
async getSpace() {
|
|
82
82
|
if (!this.remainder.length) {
|
|
83
|
-
throw new Error(
|
|
83
|
+
throw new Error(`Unexpected end of input at position ${this.pos} [E4]`);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
if (imapFormalSyntax.verify(this.remainder.charAt(0), imapFormalSyntax.SP()) >= 0) {
|
|
87
|
-
throw new Error(
|
|
87
|
+
throw new Error(`Unexpected char at position ${this.pos} [E5: ${JSON.stringify(this.remainder.charAt(0))}]`);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
this.pos++;
|
|
@@ -93,11 +93,11 @@ class ParserInstance {
|
|
|
93
93
|
|
|
94
94
|
async getAttributes() {
|
|
95
95
|
if (!this.remainder.length) {
|
|
96
|
-
throw new Error(
|
|
96
|
+
throw new Error(`Unexpected end of input at position ${this.pos} [E6]`);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
if (this.remainder.match(/^\s/)) {
|
|
100
|
-
throw new Error(
|
|
100
|
+
throw new Error(`Unexpected whitespace at position ${this.pos} [E7]`);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
const tokenParser = new TokenParser(this, this.pos, this.remainder, this.options);
|
|
@@ -36,7 +36,7 @@ class TokenParser {
|
|
|
36
36
|
|
|
37
37
|
// If the node was never closed, throw it
|
|
38
38
|
if (!node.isClosed) {
|
|
39
|
-
throw new Error(
|
|
39
|
+
throw new Error(`Unexpected end of input at position ${this.pos + this.str.length - 1} [E6]`);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
let type = (node.type || '').toString().toUpperCase();
|
|
@@ -149,7 +149,7 @@ class TokenParser {
|
|
|
149
149
|
// ) closes a list
|
|
150
150
|
case ')':
|
|
151
151
|
if (this.currentNode.type !== 'LIST') {
|
|
152
|
-
throw new Error(
|
|
152
|
+
throw new Error(`Unexpected list terminator ) at position ${this.pos + i} [E7]`);
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
this.currentNode.isClosed = true;
|
|
@@ -162,7 +162,7 @@ class TokenParser {
|
|
|
162
162
|
// ] closes section group
|
|
163
163
|
case ']':
|
|
164
164
|
if (this.currentNode.type !== 'SECTION') {
|
|
165
|
-
throw new Error(
|
|
165
|
+
throw new Error(`Unexpected section terminator ] at position ${this.pos + i} [E8]`);
|
|
166
166
|
}
|
|
167
167
|
this.currentNode.isClosed = true;
|
|
168
168
|
this.currentNode.endPos = this.pos + i;
|
|
@@ -189,7 +189,7 @@ class TokenParser {
|
|
|
189
189
|
case '~': {
|
|
190
190
|
let nextChr = this.str.charAt(i + 1);
|
|
191
191
|
if (nextChr !== '{') {
|
|
192
|
-
throw new Error(
|
|
192
|
+
throw new Error(`Unexpected literal8 marker at position ${this.pos + i} [E9]`);
|
|
193
193
|
}
|
|
194
194
|
this.expectedLiteralType = 'literal8';
|
|
195
195
|
break;
|
|
@@ -270,7 +270,7 @@ class TokenParser {
|
|
|
270
270
|
// Allow \ as the first char for atom to support system flags
|
|
271
271
|
// Allow % to support LIST '' %
|
|
272
272
|
if (imapFormalSyntax['ATOM-CHAR']().indexOf(chr) < 0 && chr !== '\\' && chr !== '%') {
|
|
273
|
-
throw new Error(
|
|
273
|
+
throw new Error(`Unexpected char at position ${this.pos + i} [E10: ${JSON.stringify(chr)}]`);
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
@@ -317,7 +317,7 @@ class TokenParser {
|
|
|
317
317
|
if (chr === '[') {
|
|
318
318
|
// allowed only for selected elements
|
|
319
319
|
if (['BODY', 'BODY.PEEK', 'BINARY', 'BINARY.PEEK'].indexOf(this.currentNode.value.toUpperCase()) < 0) {
|
|
320
|
-
throw new Error(
|
|
320
|
+
throw new Error(`Unexpected section start char [ at position ${this.pos} [E11]`);
|
|
321
321
|
}
|
|
322
322
|
this.currentNode.endPos = this.pos + i;
|
|
323
323
|
this.currentNode = this.createNode(this.currentNode.parentNode, this.pos + i);
|
|
@@ -328,14 +328,19 @@ class TokenParser {
|
|
|
328
328
|
}
|
|
329
329
|
|
|
330
330
|
if (chr === '<') {
|
|
331
|
-
throw new Error(
|
|
331
|
+
throw new Error(`Unexpected start of partial at position ${this.pos} [E12]`);
|
|
332
332
|
}
|
|
333
333
|
|
|
334
334
|
// if the char is not ATOM compatible, throw. Allow \* as an exception
|
|
335
|
-
if (
|
|
336
|
-
|
|
335
|
+
if (
|
|
336
|
+
imapFormalSyntax['ATOM-CHAR']().indexOf(chr) < 0 &&
|
|
337
|
+
chr !== ']' &&
|
|
338
|
+
!(chr === '*' && this.currentNode.value === '\\') &&
|
|
339
|
+
(!this.parent || !this.parent.command || !['NO', 'BAD', 'OK'].includes(this.parent.command))
|
|
340
|
+
) {
|
|
341
|
+
throw new Error(`Unexpected char at position ${this.pos + i} [E13: ${JSON.stringify(chr)}]`);
|
|
337
342
|
} else if (this.currentNode.value === '\\*') {
|
|
338
|
-
throw new Error(
|
|
343
|
+
throw new Error(`Unexpected char at position ${this.pos + i} [E14: ${JSON.stringify(chr)}]`);
|
|
339
344
|
}
|
|
340
345
|
|
|
341
346
|
this.currentNode.value += chr;
|
|
@@ -357,7 +362,7 @@ class TokenParser {
|
|
|
357
362
|
if (chr === '\\') {
|
|
358
363
|
i++;
|
|
359
364
|
if (i >= len) {
|
|
360
|
-
throw new Error(
|
|
365
|
+
throw new Error(`Unexpected end of input at position ${this.pos + i} [E15]`);
|
|
361
366
|
}
|
|
362
367
|
chr = this.str.charAt(i);
|
|
363
368
|
}
|
|
@@ -368,7 +373,7 @@ class TokenParser {
|
|
|
368
373
|
case 'PARTIAL':
|
|
369
374
|
if (chr === '>') {
|
|
370
375
|
if (this.currentNode.value.substr(-1) === '.') {
|
|
371
|
-
throw new Error(
|
|
376
|
+
throw new Error(`Unexpected end of partial at position ${this.pos} [E16]`);
|
|
372
377
|
}
|
|
373
378
|
this.currentNode.endPos = this.pos + i;
|
|
374
379
|
this.currentNode.isClosed = true;
|
|
@@ -379,15 +384,15 @@ class TokenParser {
|
|
|
379
384
|
}
|
|
380
385
|
|
|
381
386
|
if (chr === '.' && (!this.currentNode.value.length || this.currentNode.value.match(/\./))) {
|
|
382
|
-
throw new Error(
|
|
387
|
+
throw new Error(`Unexpected partial separator . at position ${this.pos} [E17]`);
|
|
383
388
|
}
|
|
384
389
|
|
|
385
390
|
if (imapFormalSyntax.DIGIT().indexOf(chr) < 0 && chr !== '.') {
|
|
386
|
-
throw new Error(
|
|
391
|
+
throw new Error(`Unexpected char at position ${this.pos + i} [E18: ${JSON.stringify(chr)}]`);
|
|
387
392
|
}
|
|
388
393
|
|
|
389
394
|
if (this.currentNode.value.match(/^0$|\.0$/) && chr !== '.') {
|
|
390
|
-
throw new Error(
|
|
395
|
+
throw new Error(`Invalid partial at position ${this.pos + i} [E19: ${JSON.stringify(chr)}]`);
|
|
391
396
|
}
|
|
392
397
|
|
|
393
398
|
this.currentNode.value += chr;
|
|
@@ -426,14 +431,14 @@ class TokenParser {
|
|
|
426
431
|
|
|
427
432
|
if (chr === '}') {
|
|
428
433
|
if (!('literalLength' in this.currentNode)) {
|
|
429
|
-
throw new Error(
|
|
434
|
+
throw new Error(`Unexpected literal prefix end char } at position ${this.pos + i} [E20]`);
|
|
430
435
|
}
|
|
431
436
|
if (this.str.charAt(i + 1) === '\n') {
|
|
432
437
|
i++;
|
|
433
438
|
} else if (this.str.charAt(i + 1) === '\r' && this.str.charAt(i + 2) === '\n') {
|
|
434
439
|
i += 2;
|
|
435
440
|
} else {
|
|
436
|
-
throw new Error(
|
|
441
|
+
throw new Error(`Unexpected char at position ${this.pos + i} [E21: ${JSON.stringify(chr)}]`);
|
|
437
442
|
}
|
|
438
443
|
|
|
439
444
|
this.currentNode.literalLength = Number(this.currentNode.literalLength);
|
|
@@ -474,10 +479,10 @@ class TokenParser {
|
|
|
474
479
|
break;
|
|
475
480
|
}
|
|
476
481
|
if (imapFormalSyntax.DIGIT().indexOf(chr) < 0) {
|
|
477
|
-
throw new Error(
|
|
482
|
+
throw new Error(`Unexpected char at position ${this.pos + i} [E22: ${JSON.stringify(chr)}]`);
|
|
478
483
|
}
|
|
479
484
|
if (this.currentNode.literalLength === '0') {
|
|
480
|
-
throw new Error(
|
|
485
|
+
throw new Error(`Invalid literal at position ${this.pos + i} [E23]`);
|
|
481
486
|
}
|
|
482
487
|
this.currentNode.literalLength = (this.currentNode.literalLength || '') + chr;
|
|
483
488
|
break;
|
|
@@ -486,11 +491,11 @@ class TokenParser {
|
|
|
486
491
|
// space finishes the sequence set
|
|
487
492
|
if (chr === ' ') {
|
|
488
493
|
if (!this.currentNode.value.substr(-1).match(/\d/) && this.currentNode.value.substr(-1) !== '*') {
|
|
489
|
-
throw new Error(
|
|
494
|
+
throw new Error(`Unexpected whitespace at position ${this.pos + i} [E24]`);
|
|
490
495
|
}
|
|
491
496
|
|
|
492
497
|
if (this.currentNode.value !== '*' && this.currentNode.value.substr(-1) === '*' && this.currentNode.value.substr(-2, 1) !== ':') {
|
|
493
|
-
throw new Error(
|
|
498
|
+
throw new Error(`Unexpected whitespace at position ${this.pos + i} [E25]`);
|
|
494
499
|
}
|
|
495
500
|
|
|
496
501
|
this.currentNode.isClosed = true;
|
|
@@ -513,25 +518,25 @@ class TokenParser {
|
|
|
513
518
|
|
|
514
519
|
if (chr === ':') {
|
|
515
520
|
if (!this.currentNode.value.substr(-1).match(/\d/) && this.currentNode.value.substr(-1) !== '*') {
|
|
516
|
-
throw new Error(
|
|
521
|
+
throw new Error(`Unexpected range separator : at position ${this.pos + i} [E26]`);
|
|
517
522
|
}
|
|
518
523
|
} else if (chr === '*') {
|
|
519
524
|
if ([',', ':'].indexOf(this.currentNode.value.substr(-1)) < 0) {
|
|
520
|
-
throw new Error(
|
|
525
|
+
throw new Error(`Unexpected range wildcard at position ${this.pos + i} [E27]`);
|
|
521
526
|
}
|
|
522
527
|
} else if (chr === ',') {
|
|
523
528
|
if (!this.currentNode.value.substr(-1).match(/\d/) && this.currentNode.value.substr(-1) !== '*') {
|
|
524
|
-
throw new Error(
|
|
529
|
+
throw new Error(`Unexpected sequence separator , at position ${this.pos + i} [E28]`);
|
|
525
530
|
}
|
|
526
531
|
if (this.currentNode.value.substr(-1) === '*' && this.currentNode.value.substr(-2, 1) !== ':') {
|
|
527
|
-
throw new Error(
|
|
532
|
+
throw new Error(`Unexpected sequence separator , at position ${this.pos + i} [E29]`);
|
|
528
533
|
}
|
|
529
534
|
} else if (!chr.match(/\d/)) {
|
|
530
|
-
throw new Error(
|
|
535
|
+
throw new Error(`Unexpected char at position ${this.pos + i} [E30: ${JSON.stringify(chr)}]`);
|
|
531
536
|
}
|
|
532
537
|
|
|
533
538
|
if (chr.match(/\d/) && this.currentNode.value.substr(-1) === '*') {
|
|
534
|
-
throw new Error(
|
|
539
|
+
throw new Error(`Unexpected number at position ${this.pos + i} [E31: ${JSON.stringify(chr)}]`);
|
|
535
540
|
}
|
|
536
541
|
|
|
537
542
|
this.currentNode.value += chr;
|
package/lib/imap-flow.js
CHANGED
|
@@ -623,7 +623,7 @@ class ImapFlow extends EventEmitter {
|
|
|
623
623
|
|
|
624
624
|
// Make sure we have namespace set. This should also throw if Exchange actually failed authentication
|
|
625
625
|
let nsResponse = await this.run('NAMESPACE');
|
|
626
|
-
if (nsResponse.error && nsResponse.status === 'BAD' && /User is authenticated but not connected/i.test(nsResponse.text)) {
|
|
626
|
+
if (nsResponse && nsResponse.error && nsResponse.status === 'BAD' && /User is authenticated but not connected/i.test(nsResponse.text)) {
|
|
627
627
|
// Not a NAMESPACE failure but authentication failure, so report as
|
|
628
628
|
this.authenticated = false;
|
|
629
629
|
let err = new Error('Authentication failed');
|
package/lib/tools.js
CHANGED
|
@@ -350,25 +350,38 @@ module.exports = {
|
|
|
350
350
|
},
|
|
351
351
|
|
|
352
352
|
parseEnvelope(entry) {
|
|
353
|
+
let getStrValue = obj => {
|
|
354
|
+
if (!obj) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
if (typeof obj.value === 'string') {
|
|
358
|
+
return obj.value;
|
|
359
|
+
}
|
|
360
|
+
if (Buffer.isBuffer(obj.value)) {
|
|
361
|
+
return obj.value.toString();
|
|
362
|
+
}
|
|
363
|
+
return obj.value;
|
|
364
|
+
};
|
|
365
|
+
|
|
353
366
|
let processAddresses = function (list) {
|
|
354
367
|
return [].concat(list || []).map(addr => ({
|
|
355
|
-
name: libmime.decodeWords((addr[0]
|
|
356
|
-
address: ((addr[2]
|
|
368
|
+
name: libmime.decodeWords(getStrValue(addr[0]) || ''),
|
|
369
|
+
address: (getStrValue(addr[2]) || '') + '@' + (getStrValue(addr[3]) || '')
|
|
357
370
|
}));
|
|
358
371
|
},
|
|
359
372
|
envelope = {};
|
|
360
373
|
|
|
361
374
|
if (entry[0] && entry[0].value) {
|
|
362
|
-
let date = new Date(entry[0]
|
|
375
|
+
let date = new Date(getStrValue(entry[0]));
|
|
363
376
|
if (date.toString() === 'Invalid Date') {
|
|
364
|
-
envelope.date = entry[0]
|
|
377
|
+
envelope.date = getStrValue(entry[0]);
|
|
365
378
|
} else {
|
|
366
379
|
envelope.date = date;
|
|
367
380
|
}
|
|
368
381
|
}
|
|
369
382
|
|
|
370
383
|
if (entry[1] && entry[1].value) {
|
|
371
|
-
envelope.subject = libmime.decodeWords(entry[1]
|
|
384
|
+
envelope.subject = libmime.decodeWords(getStrValue(entry[1]));
|
|
372
385
|
}
|
|
373
386
|
|
|
374
387
|
if (entry[2] && entry[2].length) {
|
|
@@ -396,11 +409,11 @@ module.exports = {
|
|
|
396
409
|
}
|
|
397
410
|
|
|
398
411
|
if (entry[8] && entry[8].value) {
|
|
399
|
-
envelope.inReplyTo = entry[8].
|
|
412
|
+
envelope.inReplyTo = (getStrValue(entry[8]) || '').toString().trim();
|
|
400
413
|
}
|
|
401
414
|
|
|
402
415
|
if (entry[9] && entry[9].value) {
|
|
403
|
-
envelope.messageId = entry[9].
|
|
416
|
+
envelope.messageId = (getStrValue(entry[9]) || '').toString().trim();
|
|
404
417
|
}
|
|
405
418
|
|
|
406
419
|
return envelope;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imapflow",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.98",
|
|
4
4
|
"description": "IMAP Client for Node",
|
|
5
5
|
"main": "./lib/imap-flow.js",
|
|
6
6
|
"scripts": {
|
|
@@ -27,16 +27,16 @@
|
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://imapflow.com/",
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@babel/eslint-parser": "7.
|
|
30
|
+
"@babel/eslint-parser": "7.18.2",
|
|
31
31
|
"@babel/eslint-plugin": "7.17.7",
|
|
32
32
|
"@babel/plugin-syntax-class-properties": "7.12.13",
|
|
33
|
-
"@babel/preset-env": "7.
|
|
34
|
-
"@types/node": "17.0.
|
|
33
|
+
"@babel/preset-env": "7.18.2",
|
|
34
|
+
"@types/node": "17.0.36",
|
|
35
35
|
"braintree-jsdoc-template": "3.3.0",
|
|
36
|
-
"eslint": "8.
|
|
36
|
+
"eslint": "8.16.0",
|
|
37
37
|
"eslint-config-nodemailer": "1.2.0",
|
|
38
38
|
"eslint-config-prettier": "8.5.0",
|
|
39
|
-
"grunt": "1.5.
|
|
39
|
+
"grunt": "1.5.3",
|
|
40
40
|
"grunt-cli": "1.4.3",
|
|
41
41
|
"grunt-contrib-nodeunit": "4.0.0",
|
|
42
42
|
"grunt-eslint": "24.0.0",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"libmime": "5.1.0",
|
|
52
52
|
"libqp": "1.1.0",
|
|
53
53
|
"mailsplit": "5.3.2",
|
|
54
|
-
"nodemailer": "6.7.
|
|
54
|
+
"nodemailer": "6.7.5",
|
|
55
55
|
"pino": "7.11.0",
|
|
56
56
|
"socks": "2.6.2"
|
|
57
57
|
}
|
package/test/imap-parser-test.js
CHANGED
|
@@ -905,3 +905,31 @@ module.exports['IMAP Parser: escaped quotes'] = test =>
|
|
|
905
905
|
);
|
|
906
906
|
|
|
907
907
|
module.exports['IMAP Parser: mimetorture'] = test => asyncWrapper(test, async test => test.deepEqual(await parser(mimetorture.input), mimetorture.output));
|
|
908
|
+
|
|
909
|
+
module.exports['IMAP Parser, unicode select'] = test =>
|
|
910
|
+
asyncWrapper(test, async test =>
|
|
911
|
+
test.deepEqual((await parser('F OK [READ-WRITE] [Gmail]/Visi laiškai selected. (Success) [THROTTLED]')).attributes, [
|
|
912
|
+
{
|
|
913
|
+
type: 'ATOM',
|
|
914
|
+
value: '',
|
|
915
|
+
section: [{ type: 'ATOM', value: 'READ-WRITE' }]
|
|
916
|
+
},
|
|
917
|
+
{ type: 'ATOM', value: '[Gmail]/Visi' },
|
|
918
|
+
{ type: 'ATOM', value: 'laiškai' },
|
|
919
|
+
{ type: 'ATOM', value: 'selected.' },
|
|
920
|
+
[{ type: 'ATOM', value: 'Success' }],
|
|
921
|
+
{ type: 'ATOM', value: '[THROTTLED]' }
|
|
922
|
+
])
|
|
923
|
+
);
|
|
924
|
+
|
|
925
|
+
module.exports['IMAP Parser, single quote in atom'] = test =>
|
|
926
|
+
asyncWrapper(test, async test =>
|
|
927
|
+
test.deepEqual((await parser('* LIST (HasNoChildren UnMarked) "/" \'a')).attributes, [
|
|
928
|
+
[
|
|
929
|
+
{ type: 'ATOM', value: 'HasNoChildren' },
|
|
930
|
+
{ type: 'ATOM', value: 'UnMarked' }
|
|
931
|
+
],
|
|
932
|
+
{ type: 'STRING', value: '/' },
|
|
933
|
+
{ type: 'ATOM', value: "'a" }
|
|
934
|
+
])
|
|
935
|
+
);
|