imapflow 1.0.153 → 1.0.155
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 +14 -0
- package/lib/commands/fetch.js +1 -1
- package/lib/handler/imap-parser.js +27 -19
- package/lib/handler/parser-instance.js +45 -7
- package/lib/handler/token-parser.js +104 -26
- package/lib/imap-flow.js +74 -73
- package/package.json +2 -2
- package/test/imap-parser-test.js +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.155](https://github.com/postalsys/imapflow/compare/v1.0.154...v1.0.155) (2024-03-06)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **exchange:** handle special error response from exchange ([3ed4198](https://github.com/postalsys/imapflow/commit/3ed4198c1b5504ab0b46926fec179c682988b2e3))
|
|
9
|
+
|
|
10
|
+
## [1.0.154](https://github.com/postalsys/imapflow/compare/v1.0.153...v1.0.154) (2024-03-05)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **FETCH:** throw on failing FETCH ([12f9a45](https://github.com/postalsys/imapflow/commit/12f9a45530c6f3a2f35ba2968819011eea9c32b2))
|
|
16
|
+
|
|
3
17
|
## [1.0.153](https://github.com/postalsys/imapflow/compare/v1.0.152...v1.0.153) (2024-02-29)
|
|
4
18
|
|
|
5
19
|
|
package/lib/commands/fetch.js
CHANGED
|
@@ -24,29 +24,37 @@ module.exports = async (command, options) => {
|
|
|
24
24
|
const parser = new ParserInstance(command, options);
|
|
25
25
|
const response = {};
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
response.command = await parser.getCommand();
|
|
27
|
+
try {
|
|
28
|
+
response.tag = await parser.getTag();
|
|
30
29
|
|
|
31
|
-
if (nullBytesRemoved) {
|
|
32
|
-
response.nullBytesRemoved = nullBytesRemoved;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (['UID', 'AUTHENTICATE'].indexOf((response.command || '').toUpperCase()) >= 0) {
|
|
36
30
|
await parser.getSpace();
|
|
37
|
-
response.command
|
|
38
|
-
}
|
|
31
|
+
response.command = await parser.getCommand();
|
|
39
32
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
33
|
+
if (nullBytesRemoved) {
|
|
34
|
+
response.nullBytesRemoved = nullBytesRemoved;
|
|
35
|
+
}
|
|
44
36
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
37
|
+
if (['UID', 'AUTHENTICATE'].indexOf((response.command || '').toUpperCase()) >= 0) {
|
|
38
|
+
await parser.getSpace();
|
|
39
|
+
response.command += ' ' + (await parser.getElement(imapFormalSyntax.command()));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (parser.remainder.trim().length) {
|
|
43
|
+
await parser.getSpace();
|
|
44
|
+
response.attributes = await parser.getAttributes();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (parser.humanReadable) {
|
|
48
|
+
response.attributes = (response.attributes || []).concat({
|
|
49
|
+
type: 'TEXT',
|
|
50
|
+
value: parser.humanReadable
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
} catch (err) {
|
|
54
|
+
if (err.code === 'ParserErrorExchange' && err.parserContext && err.parserContext.value) {
|
|
55
|
+
return err.parserContext.value;
|
|
56
|
+
}
|
|
57
|
+
throw err;
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
return response;
|
|
@@ -73,16 +73,42 @@ class ParserInstance {
|
|
|
73
73
|
let match, element, errPos;
|
|
74
74
|
|
|
75
75
|
if (this.remainder.match(/^\s/)) {
|
|
76
|
-
|
|
76
|
+
let error = new Error(`Unexpected whitespace at position ${this.pos} [E1]`);
|
|
77
|
+
error.code = 'ParserError1';
|
|
78
|
+
error.parserContext = { input: this.input, pos: this.pos };
|
|
79
|
+
throw error;
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
if ((match = this.remainder.match(/^\s*[^\s]+(?=\s|$)/))) {
|
|
80
83
|
element = match[0];
|
|
81
84
|
if ((errPos = imapFormalSyntax.verify(element, syntax)) >= 0) {
|
|
82
|
-
|
|
85
|
+
if (this.tag === 'Server' && element === 'Unavailable.') {
|
|
86
|
+
// Exchange error
|
|
87
|
+
let error = new Error(`Server returned an error: ${this.input}`);
|
|
88
|
+
error.code = 'ParserErrorExchange';
|
|
89
|
+
error.parserContext = {
|
|
90
|
+
input: this.input,
|
|
91
|
+
element,
|
|
92
|
+
pos: this.pos,
|
|
93
|
+
value: {
|
|
94
|
+
tag: '*',
|
|
95
|
+
command: 'BAD',
|
|
96
|
+
attributes: [{ type: 'TEXT', value: this.input }]
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let error = new Error(`Unexpected char at position ${this.pos + errPos} [E2: ${JSON.stringify(element.charAt(errPos))}]`);
|
|
103
|
+
error.code = 'ParserError2';
|
|
104
|
+
error.parserContext = { input: this.input, element, pos: this.pos };
|
|
105
|
+
throw error;
|
|
83
106
|
}
|
|
84
107
|
} else {
|
|
85
|
-
|
|
108
|
+
let error = new Error(`Unexpected end of input at position ${this.pos} [E3]`);
|
|
109
|
+
error.code = 'ParserError3';
|
|
110
|
+
error.parserContext = { input: this.input, pos: this.pos };
|
|
111
|
+
throw error;
|
|
86
112
|
}
|
|
87
113
|
|
|
88
114
|
this.pos += match[0].length;
|
|
@@ -93,11 +119,17 @@ class ParserInstance {
|
|
|
93
119
|
|
|
94
120
|
async getSpace() {
|
|
95
121
|
if (!this.remainder.length) {
|
|
96
|
-
|
|
122
|
+
let error = new Error(`Unexpected end of input at position ${this.pos} [E4]`);
|
|
123
|
+
error.code = 'ParserError4';
|
|
124
|
+
error.parserContext = { input: this.input, pos: this.pos };
|
|
125
|
+
throw error;
|
|
97
126
|
}
|
|
98
127
|
|
|
99
128
|
if (imapFormalSyntax.verify(this.remainder.charAt(0), imapFormalSyntax.SP()) >= 0) {
|
|
100
|
-
|
|
129
|
+
let error = new Error(`Unexpected char at position ${this.pos} [E5: ${JSON.stringify(this.remainder.charAt(0))}]`);
|
|
130
|
+
error.code = 'ParserError5';
|
|
131
|
+
error.parserContext = { input: this.input, element: this.remainder, pos: this.pos };
|
|
132
|
+
throw error;
|
|
101
133
|
}
|
|
102
134
|
|
|
103
135
|
this.pos++;
|
|
@@ -106,11 +138,17 @@ class ParserInstance {
|
|
|
106
138
|
|
|
107
139
|
async getAttributes() {
|
|
108
140
|
if (!this.remainder.length) {
|
|
109
|
-
|
|
141
|
+
let error = new Error(`Unexpected end of input at position ${this.pos} [E6]`);
|
|
142
|
+
error.code = 'ParserError6';
|
|
143
|
+
error.parserContext = { input: this.input, pos: this.pos };
|
|
144
|
+
throw error;
|
|
110
145
|
}
|
|
111
146
|
|
|
112
147
|
if (this.remainder.match(/^\s/)) {
|
|
113
|
-
|
|
148
|
+
let error = new Error(`Unexpected whitespace at position ${this.pos} [E7]`);
|
|
149
|
+
error.code = 'ParserError7';
|
|
150
|
+
error.parserContext = { input: this.input, element: this.remainder, pos: this.pos };
|
|
151
|
+
throw error;
|
|
114
152
|
}
|
|
115
153
|
|
|
116
154
|
const tokenParser = new TokenParser(this, this.pos, this.remainder, this.options);
|
|
@@ -36,7 +36,10 @@ class TokenParser {
|
|
|
36
36
|
|
|
37
37
|
// If the node was never closed, throw it
|
|
38
38
|
if (!node.isClosed) {
|
|
39
|
-
|
|
39
|
+
let error = new Error(`Unexpected end of input at position ${this.pos + this.str.length - 1} [E9]`);
|
|
40
|
+
error.code = 'ParserError9';
|
|
41
|
+
error.parserContext = { input: this.str, pos: this.pos + this.str.length - 1 };
|
|
42
|
+
throw error;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
let type = (node.type || '').toString().toUpperCase();
|
|
@@ -149,7 +152,10 @@ class TokenParser {
|
|
|
149
152
|
// ) closes a list
|
|
150
153
|
case ')':
|
|
151
154
|
if (this.currentNode.type !== 'LIST') {
|
|
152
|
-
|
|
155
|
+
let error = new Error(`Unexpected list terminator ) at position ${this.pos + i} [E10]`);
|
|
156
|
+
error.code = 'ParserError10';
|
|
157
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
158
|
+
throw error;
|
|
153
159
|
}
|
|
154
160
|
|
|
155
161
|
this.currentNode.isClosed = true;
|
|
@@ -162,7 +168,10 @@ class TokenParser {
|
|
|
162
168
|
// ] closes section group
|
|
163
169
|
case ']':
|
|
164
170
|
if (this.currentNode.type !== 'SECTION') {
|
|
165
|
-
|
|
171
|
+
let error = new Error(`Unexpected section terminator ] at position ${this.pos + i} [E11]`);
|
|
172
|
+
error.code = 'ParserError11';
|
|
173
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
174
|
+
throw error;
|
|
166
175
|
}
|
|
167
176
|
this.currentNode.isClosed = true;
|
|
168
177
|
this.currentNode.endPos = this.pos + i;
|
|
@@ -199,7 +208,10 @@ class TokenParser {
|
|
|
199
208
|
break;
|
|
200
209
|
}
|
|
201
210
|
|
|
202
|
-
|
|
211
|
+
let error = new Error(`Unexpected literal8 marker at position ${this.pos + i} [E12]`);
|
|
212
|
+
error.code = 'ParserError12';
|
|
213
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
214
|
+
throw error;
|
|
203
215
|
}
|
|
204
216
|
this.expectedLiteralType = 'literal8';
|
|
205
217
|
break;
|
|
@@ -282,7 +294,10 @@ class TokenParser {
|
|
|
282
294
|
// Allow % to support LIST '' %
|
|
283
295
|
// Allow 8bit characters (presumably unicode)
|
|
284
296
|
if (imapFormalSyntax['ATOM-CHAR']().indexOf(chr) < 0 && chr !== '\\' && chr !== '%' && chr.charCodeAt(0) < 0x80) {
|
|
285
|
-
|
|
297
|
+
let error = new Error(`Unexpected char at position ${this.pos + i} [E13: ${JSON.stringify(chr)}]`);
|
|
298
|
+
error.code = 'ParserError13';
|
|
299
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
300
|
+
throw error;
|
|
286
301
|
}
|
|
287
302
|
|
|
288
303
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
@@ -329,7 +344,10 @@ class TokenParser {
|
|
|
329
344
|
if (chr === '[') {
|
|
330
345
|
// allowed only for selected elements
|
|
331
346
|
if (['BODY', 'BODY.PEEK', 'BINARY', 'BINARY.PEEK'].indexOf(this.currentNode.value.toUpperCase()) < 0) {
|
|
332
|
-
|
|
347
|
+
let error = new Error(`Unexpected section start char [ at position ${this.pos + i} [E14]`);
|
|
348
|
+
error.code = 'ParserError14';
|
|
349
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
350
|
+
throw error;
|
|
333
351
|
}
|
|
334
352
|
this.currentNode.endPos = this.pos + i;
|
|
335
353
|
this.currentNode = this.createNode(this.currentNode.parentNode, this.pos + i);
|
|
@@ -340,7 +358,10 @@ class TokenParser {
|
|
|
340
358
|
}
|
|
341
359
|
|
|
342
360
|
if (chr === '<') {
|
|
343
|
-
|
|
361
|
+
let error = new Error(`Unexpected start of partial at position ${this.pos + i} [E15]`);
|
|
362
|
+
error.code = 'ParserError15';
|
|
363
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
364
|
+
throw error;
|
|
344
365
|
}
|
|
345
366
|
|
|
346
367
|
// if the char is not ATOM compatible, throw. Allow \* as an exception
|
|
@@ -351,9 +372,15 @@ class TokenParser {
|
|
|
351
372
|
!(chr === '*' && this.currentNode.value === '\\') &&
|
|
352
373
|
(!this.parent || !this.parent.command || !['NO', 'BAD', 'OK'].includes(this.parent.command))
|
|
353
374
|
) {
|
|
354
|
-
|
|
375
|
+
let error = new Error(`Unexpected char at position ${this.pos + i} [E16: ${JSON.stringify(chr)}]`);
|
|
376
|
+
error.code = 'ParserError16';
|
|
377
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
378
|
+
throw error;
|
|
355
379
|
} else if (this.currentNode.value === '\\*') {
|
|
356
|
-
|
|
380
|
+
let error = new Error(`Unexpected char at position ${this.pos + i} [E17: ${JSON.stringify(chr)}]`);
|
|
381
|
+
error.code = 'ParserError17';
|
|
382
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
383
|
+
throw error;
|
|
357
384
|
}
|
|
358
385
|
|
|
359
386
|
this.currentNode.value += chr;
|
|
@@ -375,7 +402,10 @@ class TokenParser {
|
|
|
375
402
|
if (chr === '\\') {
|
|
376
403
|
i++;
|
|
377
404
|
if (i >= len) {
|
|
378
|
-
|
|
405
|
+
let error = new Error(`Unexpected end of input at position ${this.pos + i} [E18]`);
|
|
406
|
+
error.code = 'ParserError18';
|
|
407
|
+
error.parserContext = { input: this.str, pos: this.pos + i };
|
|
408
|
+
throw error;
|
|
379
409
|
}
|
|
380
410
|
chr = this.str.charAt(i);
|
|
381
411
|
}
|
|
@@ -386,7 +416,10 @@ class TokenParser {
|
|
|
386
416
|
case 'PARTIAL':
|
|
387
417
|
if (chr === '>') {
|
|
388
418
|
if (this.currentNode.value.substr(-1) === '.') {
|
|
389
|
-
|
|
419
|
+
let error = new Error(`Unexpected end of partial at position ${this.pos + 1} [E19]`);
|
|
420
|
+
error.code = 'ParserError19';
|
|
421
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
422
|
+
throw error;
|
|
390
423
|
}
|
|
391
424
|
this.currentNode.endPos = this.pos + i;
|
|
392
425
|
this.currentNode.isClosed = true;
|
|
@@ -397,15 +430,24 @@ class TokenParser {
|
|
|
397
430
|
}
|
|
398
431
|
|
|
399
432
|
if (chr === '.' && (!this.currentNode.value.length || this.currentNode.value.match(/\./))) {
|
|
400
|
-
|
|
433
|
+
let error = new Error(`Unexpected partial separator . at position ${this.pos + 1} [E20]`);
|
|
434
|
+
error.code = 'ParserError20';
|
|
435
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
436
|
+
throw error;
|
|
401
437
|
}
|
|
402
438
|
|
|
403
439
|
if (imapFormalSyntax.DIGIT().indexOf(chr) < 0 && chr !== '.') {
|
|
404
|
-
|
|
440
|
+
let error = new Error(`Unexpected char at position ${this.pos + i} [E21: ${JSON.stringify(chr)}]`);
|
|
441
|
+
error.code = 'ParserError21';
|
|
442
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
443
|
+
throw error;
|
|
405
444
|
}
|
|
406
445
|
|
|
407
446
|
if (this.currentNode.value.match(/^0$|\.0$/) && chr !== '.') {
|
|
408
|
-
|
|
447
|
+
let error = new Error(`Invalid partial at position ${this.pos + i} [E22: ${JSON.stringify(chr)}]`);
|
|
448
|
+
error.code = 'ParserError22';
|
|
449
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
450
|
+
throw error;
|
|
409
451
|
}
|
|
410
452
|
|
|
411
453
|
this.currentNode.value += chr;
|
|
@@ -444,14 +486,20 @@ class TokenParser {
|
|
|
444
486
|
|
|
445
487
|
if (chr === '}') {
|
|
446
488
|
if (!('literalLength' in this.currentNode)) {
|
|
447
|
-
|
|
489
|
+
let error = new Error(`Unexpected literal prefix end char } at position ${this.pos + i} [E23]`);
|
|
490
|
+
error.code = 'ParserError23';
|
|
491
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
492
|
+
throw error;
|
|
448
493
|
}
|
|
449
494
|
if (this.str.charAt(i + 1) === '\n') {
|
|
450
495
|
i++;
|
|
451
496
|
} else if (this.str.charAt(i + 1) === '\r' && this.str.charAt(i + 2) === '\n') {
|
|
452
497
|
i += 2;
|
|
453
498
|
} else {
|
|
454
|
-
|
|
499
|
+
let error = new Error(`Unexpected char at position ${this.pos + i} [E24: ${JSON.stringify(chr)}]`);
|
|
500
|
+
error.code = 'ParserError24';
|
|
501
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
502
|
+
throw error;
|
|
455
503
|
}
|
|
456
504
|
|
|
457
505
|
this.currentNode.literalLength = Number(this.currentNode.literalLength);
|
|
@@ -492,10 +540,16 @@ class TokenParser {
|
|
|
492
540
|
break;
|
|
493
541
|
}
|
|
494
542
|
if (imapFormalSyntax.DIGIT().indexOf(chr) < 0) {
|
|
495
|
-
|
|
543
|
+
let error = new Error(`Unexpected char at position ${this.pos + i} [E25: ${JSON.stringify(chr)}]`);
|
|
544
|
+
error.code = 'ParserError25';
|
|
545
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
546
|
+
throw error;
|
|
496
547
|
}
|
|
497
548
|
if (this.currentNode.literalLength === '0') {
|
|
498
|
-
|
|
549
|
+
let error = new Error(`Invalid literal at position ${this.pos + i} [E26]`);
|
|
550
|
+
error.code = 'ParserError26';
|
|
551
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
552
|
+
throw error;
|
|
499
553
|
}
|
|
500
554
|
this.currentNode.literalLength = (this.currentNode.literalLength || '') + chr;
|
|
501
555
|
break;
|
|
@@ -504,11 +558,17 @@ class TokenParser {
|
|
|
504
558
|
// space finishes the sequence set
|
|
505
559
|
if (chr === ' ') {
|
|
506
560
|
if (!this.currentNode.value.substr(-1).match(/\d/) && this.currentNode.value.substr(-1) !== '*') {
|
|
507
|
-
|
|
561
|
+
let error = new Error(`Unexpected whitespace at position ${this.pos + i} [E27]`);
|
|
562
|
+
error.code = 'ParserError27';
|
|
563
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
564
|
+
throw error;
|
|
508
565
|
}
|
|
509
566
|
|
|
510
567
|
if (this.currentNode.value !== '*' && this.currentNode.value.substr(-1) === '*' && this.currentNode.value.substr(-2, 1) !== ':') {
|
|
511
|
-
|
|
568
|
+
let error = new Error(`Unexpected whitespace at position ${this.pos + i} [E28]`);
|
|
569
|
+
error.code = 'ParserError28';
|
|
570
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
571
|
+
throw error;
|
|
512
572
|
}
|
|
513
573
|
|
|
514
574
|
this.currentNode.isClosed = true;
|
|
@@ -531,25 +591,43 @@ class TokenParser {
|
|
|
531
591
|
|
|
532
592
|
if (chr === ':') {
|
|
533
593
|
if (!this.currentNode.value.substr(-1).match(/\d/) && this.currentNode.value.substr(-1) !== '*') {
|
|
534
|
-
|
|
594
|
+
let error = new Error(`Unexpected range separator : at position ${this.pos + i} [E29]`);
|
|
595
|
+
error.code = 'ParserError29';
|
|
596
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
597
|
+
throw error;
|
|
535
598
|
}
|
|
536
599
|
} else if (chr === '*') {
|
|
537
600
|
if ([',', ':'].indexOf(this.currentNode.value.substr(-1)) < 0) {
|
|
538
|
-
|
|
601
|
+
let error = new Error(`Unexpected range wildcard at position ${this.pos + i} [E30]`);
|
|
602
|
+
error.code = 'ParserError30';
|
|
603
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
604
|
+
throw error;
|
|
539
605
|
}
|
|
540
606
|
} else if (chr === ',') {
|
|
541
607
|
if (!this.currentNode.value.substr(-1).match(/\d/) && this.currentNode.value.substr(-1) !== '*') {
|
|
542
|
-
|
|
608
|
+
let error = new Error(`Unexpected sequence separator , at position ${this.pos + i} [E31]`);
|
|
609
|
+
error.code = 'ParserError31';
|
|
610
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
611
|
+
throw error;
|
|
543
612
|
}
|
|
544
613
|
if (this.currentNode.value.substr(-1) === '*' && this.currentNode.value.substr(-2, 1) !== ':') {
|
|
545
|
-
|
|
614
|
+
let error = new Error(`Unexpected sequence separator , at position ${this.pos + i} [E32]`);
|
|
615
|
+
error.code = 'ParserError32';
|
|
616
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
617
|
+
throw error;
|
|
546
618
|
}
|
|
547
619
|
} else if (!chr.match(/\d/)) {
|
|
548
|
-
|
|
620
|
+
let error = new Error(`Unexpected char at position ${this.pos + i} [E33: ${JSON.stringify(chr)}]`);
|
|
621
|
+
error.code = 'ParserError33';
|
|
622
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
623
|
+
throw error;
|
|
549
624
|
}
|
|
550
625
|
|
|
551
626
|
if (chr.match(/\d/) && this.currentNode.value.substr(-1) === '*') {
|
|
552
|
-
|
|
627
|
+
let error = new Error(`Unexpected number at position ${this.pos + i} [E34: ${JSON.stringify(chr)}]`);
|
|
628
|
+
error.code = 'ParserError34';
|
|
629
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
630
|
+
throw error;
|
|
553
631
|
}
|
|
554
632
|
|
|
555
633
|
this.currentNode.value += chr;
|
package/lib/imap-flow.js
CHANGED
|
@@ -2254,93 +2254,94 @@ class ImapFlow extends EventEmitter {
|
|
|
2254
2254
|
*/
|
|
2255
2255
|
async *fetch(range, query, options) {
|
|
2256
2256
|
options = options || {};
|
|
2257
|
-
try {
|
|
2258
|
-
if (!this.mailbox) {
|
|
2259
|
-
// no mailbox selected, nothing to do
|
|
2260
|
-
return;
|
|
2261
|
-
}
|
|
2262
2257
|
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2258
|
+
if (!this.mailbox) {
|
|
2259
|
+
// no mailbox selected, nothing to do
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
2267
2262
|
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
new Promise((resolve, reject) => {
|
|
2273
|
-
let check = () => {
|
|
2274
|
-
if (rowQueue.length) {
|
|
2275
|
-
let entry = rowQueue.shift();
|
|
2276
|
-
if (entry.err) {
|
|
2277
|
-
return reject(entry.err);
|
|
2278
|
-
} else {
|
|
2279
|
-
return resolve(entry.value);
|
|
2280
|
-
}
|
|
2281
|
-
}
|
|
2282
|
-
if (finished) {
|
|
2283
|
-
return resolve(null);
|
|
2284
|
-
}
|
|
2263
|
+
range = await this.resolveRange(range, options);
|
|
2264
|
+
if (!range) {
|
|
2265
|
+
return false;
|
|
2266
|
+
}
|
|
2285
2267
|
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
check();
|
|
2290
|
-
};
|
|
2291
|
-
};
|
|
2292
|
-
check();
|
|
2293
|
-
});
|
|
2268
|
+
let finished = false;
|
|
2269
|
+
let push = false;
|
|
2270
|
+
let rowQueue = [];
|
|
2294
2271
|
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2272
|
+
let getNext = () =>
|
|
2273
|
+
new Promise((resolve, reject) => {
|
|
2274
|
+
let check = () => {
|
|
2275
|
+
if (rowQueue.length) {
|
|
2276
|
+
let entry = rowQueue.shift();
|
|
2277
|
+
if (entry.err) {
|
|
2278
|
+
return reject(entry.err);
|
|
2279
|
+
} else {
|
|
2280
|
+
return resolve(entry.value);
|
|
2304
2281
|
}
|
|
2305
|
-
});
|
|
2306
|
-
if (typeof push === 'function') {
|
|
2307
|
-
push();
|
|
2308
2282
|
}
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
finished = true;
|
|
2313
|
-
if (typeof push === 'function') {
|
|
2314
|
-
push();
|
|
2283
|
+
|
|
2284
|
+
if (finished) {
|
|
2285
|
+
return resolve(null);
|
|
2315
2286
|
}
|
|
2316
|
-
})
|
|
2317
|
-
.catch(err => {
|
|
2318
|
-
rowQueue.push({ err });
|
|
2319
|
-
});
|
|
2320
2287
|
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2288
|
+
// wait until data is pushed to queue and try again
|
|
2289
|
+
push = () => {
|
|
2290
|
+
push = false;
|
|
2291
|
+
check();
|
|
2292
|
+
};
|
|
2293
|
+
};
|
|
2294
|
+
check();
|
|
2295
|
+
});
|
|
2328
2296
|
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2297
|
+
this.run('FETCH', range, query, {
|
|
2298
|
+
uid: !!options.uid,
|
|
2299
|
+
binary: options.binary,
|
|
2300
|
+
changedSince: options.changedSince,
|
|
2301
|
+
onUntaggedFetch: (untagged, next) => {
|
|
2302
|
+
rowQueue.push({
|
|
2303
|
+
value: {
|
|
2304
|
+
response: untagged,
|
|
2305
|
+
next
|
|
2306
|
+
}
|
|
2307
|
+
});
|
|
2308
|
+
if (typeof push === 'function') {
|
|
2309
|
+
push();
|
|
2332
2310
|
}
|
|
2333
2311
|
}
|
|
2312
|
+
})
|
|
2313
|
+
.then(() => {
|
|
2314
|
+
finished = true;
|
|
2315
|
+
if (typeof push === 'function') {
|
|
2316
|
+
push();
|
|
2317
|
+
}
|
|
2318
|
+
})
|
|
2319
|
+
.catch(err => {
|
|
2320
|
+
rowQueue.push({ err });
|
|
2321
|
+
if (typeof push === 'function') {
|
|
2322
|
+
push();
|
|
2323
|
+
}
|
|
2324
|
+
});
|
|
2334
2325
|
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
error
|
|
2326
|
+
let res;
|
|
2327
|
+
while ((res = await getNext())) {
|
|
2328
|
+
if (this.isClosed || this.socket.destroyed) {
|
|
2329
|
+
let error = new Error('Connection closed');
|
|
2330
|
+
error.code = 'EConnectionClosed';
|
|
2339
2331
|
throw error;
|
|
2340
2332
|
}
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2333
|
+
|
|
2334
|
+
if (res !== null) {
|
|
2335
|
+
yield res.response;
|
|
2336
|
+
res.next();
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
if (!finished) {
|
|
2341
|
+
// FETCH never finished!
|
|
2342
|
+
let error = new Error('FETCH did not finish');
|
|
2343
|
+
error.code = 'ENotFinished';
|
|
2344
|
+
throw error;
|
|
2344
2345
|
}
|
|
2345
2346
|
}
|
|
2346
2347
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imapflow",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.155",
|
|
4
4
|
"description": "IMAP Client for Node",
|
|
5
5
|
"main": "./lib/imap-flow.js",
|
|
6
6
|
"scripts": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@babel/eslint-plugin": "7.23.5",
|
|
33
33
|
"@babel/plugin-syntax-class-properties": "7.12.13",
|
|
34
34
|
"@babel/preset-env": "7.24.0",
|
|
35
|
-
"@types/node": "20.11.
|
|
35
|
+
"@types/node": "20.11.24",
|
|
36
36
|
"eslint": "8.57.0",
|
|
37
37
|
"eslint-config-nodemailer": "1.2.0",
|
|
38
38
|
"eslint-config-prettier": "9.1.0",
|
package/test/imap-parser-test.js
CHANGED
|
@@ -978,6 +978,14 @@ module.exports['IMAP Parser, NO with a dot'] = test =>
|
|
|
978
978
|
test.deepEqual(parsed.attributes, [{ type: 'TEXT', value: 'Server Unavailable. 15' }]);
|
|
979
979
|
});
|
|
980
980
|
|
|
981
|
+
module.exports['IMAP Parser, no tag or response (Exchange)'] = test =>
|
|
982
|
+
asyncWrapper(test, async test => {
|
|
983
|
+
let parsed = await parser('Server Unavailable. 15');
|
|
984
|
+
console.log(parsed);
|
|
985
|
+
test.equal(parsed.command, 'BAD');
|
|
986
|
+
test.deepEqual(parsed.attributes, [{ type: 'TEXT', value: 'Server Unavailable. 15' }]);
|
|
987
|
+
});
|
|
988
|
+
|
|
981
989
|
module.exports['IMAP Parser, BAD with throttling'] = test =>
|
|
982
990
|
asyncWrapper(test, async test => {
|
|
983
991
|
let parsed = await parser('X BAD Request is throttled. Suggested Backoff Time: 92415 milliseconds');
|