imapflow 1.0.90 → 1.0.93
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/.gitattributes +1 -0
- package/.github/workflows/test.yml +21 -0
- package/CHANGELOG.md +15 -0
- package/lib/commands/append.js +13 -3
- package/lib/commands/authenticate.js +1 -1
- package/lib/commands/copy.js +2 -2
- package/lib/commands/create.js +2 -2
- package/lib/commands/delete.js +2 -2
- package/lib/commands/fetch.js +4 -2
- package/lib/commands/list.js +44 -60
- package/lib/commands/move.js +2 -2
- package/lib/commands/quota.js +2 -2
- package/lib/commands/rename.js +3 -3
- package/lib/commands/select.js +93 -89
- package/lib/commands/status.js +2 -2
- package/lib/commands/subscribe.js +2 -2
- package/lib/commands/unsubscribe.js +2 -2
- package/lib/handler/imap-compiler.js +1 -1
- package/lib/handler/imap-formal-syntax.js +13 -13
- package/lib/handler/imap-stream.js +9 -1
- package/lib/handler/token-parser.js +41 -26
- package/lib/imap-flow.js +46 -12
- package/lib/tools.js +29 -11
- package/lib/types.d.ts +11 -3
- package/package.json +12 -10
- package/test/imap-compiler-test.js +41 -2
- package/test/imap-parser-test.js +30 -0
|
@@ -26,7 +26,7 @@ function excludeChars(source, exclude) {
|
|
|
26
26
|
module.exports = {
|
|
27
27
|
CHAR() {
|
|
28
28
|
let value = expandRange(0x01, 0x7f);
|
|
29
|
-
this.CHAR = function() {
|
|
29
|
+
this.CHAR = function () {
|
|
30
30
|
return value;
|
|
31
31
|
};
|
|
32
32
|
return value;
|
|
@@ -34,7 +34,7 @@ module.exports = {
|
|
|
34
34
|
|
|
35
35
|
CHAR8() {
|
|
36
36
|
let value = expandRange(0x01, 0xff);
|
|
37
|
-
this.CHAR8 = function() {
|
|
37
|
+
this.CHAR8 = function () {
|
|
38
38
|
return value;
|
|
39
39
|
};
|
|
40
40
|
return value;
|
|
@@ -46,7 +46,7 @@ module.exports = {
|
|
|
46
46
|
|
|
47
47
|
CTL() {
|
|
48
48
|
let value = expandRange(0x00, 0x1f) + '\x7F';
|
|
49
|
-
this.CTL = function() {
|
|
49
|
+
this.CTL = function () {
|
|
50
50
|
return value;
|
|
51
51
|
};
|
|
52
52
|
return value;
|
|
@@ -58,7 +58,7 @@ module.exports = {
|
|
|
58
58
|
|
|
59
59
|
ALPHA() {
|
|
60
60
|
let value = expandRange(0x41, 0x5a) + expandRange(0x61, 0x7a);
|
|
61
|
-
this.ALPHA = function() {
|
|
61
|
+
this.ALPHA = function () {
|
|
62
62
|
return value;
|
|
63
63
|
};
|
|
64
64
|
return value;
|
|
@@ -66,7 +66,7 @@ module.exports = {
|
|
|
66
66
|
|
|
67
67
|
DIGIT() {
|
|
68
68
|
let value = expandRange(0x30, 0x39) + expandRange(0x61, 0x7a);
|
|
69
|
-
this.DIGIT = function() {
|
|
69
|
+
this.DIGIT = function () {
|
|
70
70
|
return value;
|
|
71
71
|
};
|
|
72
72
|
return value;
|
|
@@ -74,7 +74,7 @@ module.exports = {
|
|
|
74
74
|
|
|
75
75
|
'ATOM-CHAR'() {
|
|
76
76
|
let value = excludeChars(this.CHAR(), this['atom-specials']());
|
|
77
|
-
this['ATOM-CHAR'] = function() {
|
|
77
|
+
this['ATOM-CHAR'] = function () {
|
|
78
78
|
return value;
|
|
79
79
|
};
|
|
80
80
|
return value;
|
|
@@ -82,7 +82,7 @@ module.exports = {
|
|
|
82
82
|
|
|
83
83
|
'ASTRING-CHAR'() {
|
|
84
84
|
let value = this['ATOM-CHAR']() + this['resp-specials']();
|
|
85
|
-
this['ASTRING-CHAR'] = function() {
|
|
85
|
+
this['ASTRING-CHAR'] = function () {
|
|
86
86
|
return value;
|
|
87
87
|
};
|
|
88
88
|
return value;
|
|
@@ -90,7 +90,7 @@ module.exports = {
|
|
|
90
90
|
|
|
91
91
|
'TEXT-CHAR'() {
|
|
92
92
|
let value = excludeChars(this.CHAR(), '\r\n');
|
|
93
|
-
this['TEXT-CHAR'] = function() {
|
|
93
|
+
this['TEXT-CHAR'] = function () {
|
|
94
94
|
return value;
|
|
95
95
|
};
|
|
96
96
|
return value;
|
|
@@ -98,7 +98,7 @@ module.exports = {
|
|
|
98
98
|
|
|
99
99
|
'atom-specials'() {
|
|
100
100
|
let value = '(' + ')' + '{' + this.SP() + this.CTL() + this['list-wildcards']() + this['quoted-specials']() + this['resp-specials']();
|
|
101
|
-
this['atom-specials'] = function() {
|
|
101
|
+
this['atom-specials'] = function () {
|
|
102
102
|
return value;
|
|
103
103
|
};
|
|
104
104
|
return value;
|
|
@@ -109,8 +109,8 @@ module.exports = {
|
|
|
109
109
|
},
|
|
110
110
|
|
|
111
111
|
'quoted-specials'() {
|
|
112
|
-
let value = this.DQUOTE() + '\\
|
|
113
|
-
this['quoted-specials'] = function() {
|
|
112
|
+
let value = this.DQUOTE() + "'\\";
|
|
113
|
+
this['quoted-specials'] = function () {
|
|
114
114
|
return value;
|
|
115
115
|
};
|
|
116
116
|
return value;
|
|
@@ -122,7 +122,7 @@ module.exports = {
|
|
|
122
122
|
|
|
123
123
|
tag() {
|
|
124
124
|
let value = excludeChars(this['ASTRING-CHAR'](), '+');
|
|
125
|
-
this.tag = function() {
|
|
125
|
+
this.tag = function () {
|
|
126
126
|
return value;
|
|
127
127
|
};
|
|
128
128
|
return value;
|
|
@@ -130,7 +130,7 @@ module.exports = {
|
|
|
130
130
|
|
|
131
131
|
command() {
|
|
132
132
|
let value = this.ALPHA() + this.DIGIT() + '-';
|
|
133
|
-
this.command = function() {
|
|
133
|
+
this.command = function () {
|
|
134
134
|
return value;
|
|
135
135
|
};
|
|
136
136
|
return value;
|
|
@@ -40,6 +40,7 @@ class ImapStream extends Transform {
|
|
|
40
40
|
this.literals = [];
|
|
41
41
|
|
|
42
42
|
this.compress = false;
|
|
43
|
+
this.secureConnection = this.options.secureConnection;
|
|
43
44
|
|
|
44
45
|
this.processingInput = false;
|
|
45
46
|
this.inputQueue = []; // unprocessed input chunks
|
|
@@ -197,7 +198,14 @@ class ImapStream extends Transform {
|
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
if (this.options.logRaw) {
|
|
200
|
-
this.log.trace({
|
|
201
|
+
this.log.trace({
|
|
202
|
+
src: 's',
|
|
203
|
+
msg: 'read from socket',
|
|
204
|
+
data: chunk.toString('base64'),
|
|
205
|
+
compress: !!this.compress,
|
|
206
|
+
secure: !!this.secureConnection,
|
|
207
|
+
cid: this.cid
|
|
208
|
+
});
|
|
201
209
|
}
|
|
202
210
|
|
|
203
211
|
if (chunk && chunk.length) {
|
|
@@ -29,13 +29,13 @@ class TokenParser {
|
|
|
29
29
|
let elm;
|
|
30
30
|
let partial;
|
|
31
31
|
|
|
32
|
-
if (!node.
|
|
33
|
-
node.
|
|
32
|
+
if (!node.isClosed && node.type === 'SEQUENCE' && node.value === '*') {
|
|
33
|
+
node.isClosed = true;
|
|
34
34
|
node.type = 'ATOM';
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// If the node was never closed, throw it
|
|
38
|
-
if (!node.
|
|
38
|
+
if (!node.isClosed) {
|
|
39
39
|
throw new Error('Unexpected end of input at position ' + (this.pos + this.str.length - 1));
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -97,7 +97,7 @@ class TokenParser {
|
|
|
97
97
|
childNodes: [],
|
|
98
98
|
type: false,
|
|
99
99
|
value: '',
|
|
100
|
-
|
|
100
|
+
isClosed: true
|
|
101
101
|
};
|
|
102
102
|
|
|
103
103
|
if (parentNode) {
|
|
@@ -136,14 +136,14 @@ class TokenParser {
|
|
|
136
136
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
137
137
|
this.currentNode.type = 'string';
|
|
138
138
|
this.state = 'STRING';
|
|
139
|
-
this.currentNode.
|
|
139
|
+
this.currentNode.isClosed = false;
|
|
140
140
|
break;
|
|
141
141
|
|
|
142
142
|
// ( starts a new list
|
|
143
143
|
case '(':
|
|
144
144
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
145
145
|
this.currentNode.type = 'LIST';
|
|
146
|
-
this.currentNode.
|
|
146
|
+
this.currentNode.isClosed = false;
|
|
147
147
|
break;
|
|
148
148
|
|
|
149
149
|
// ) closes a list
|
|
@@ -152,7 +152,7 @@ class TokenParser {
|
|
|
152
152
|
throw new Error('Unexpected list terminator ) at position ' + (this.pos + i));
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
this.currentNode.
|
|
155
|
+
this.currentNode.isClosed = true;
|
|
156
156
|
this.currentNode.endPos = this.pos + i;
|
|
157
157
|
this.currentNode = this.currentNode.parentNode;
|
|
158
158
|
|
|
@@ -164,7 +164,7 @@ class TokenParser {
|
|
|
164
164
|
if (this.currentNode.type !== 'SECTION') {
|
|
165
165
|
throw new Error('Unexpected section terminator ] at position ' + (this.pos + i));
|
|
166
166
|
}
|
|
167
|
-
this.currentNode.
|
|
167
|
+
this.currentNode.isClosed = true;
|
|
168
168
|
this.currentNode.endPos = this.pos + i;
|
|
169
169
|
this.currentNode = this.currentNode.parentNode;
|
|
170
170
|
checkSP();
|
|
@@ -181,24 +181,36 @@ class TokenParser {
|
|
|
181
181
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
182
182
|
this.currentNode.type = 'PARTIAL';
|
|
183
183
|
this.state = 'PARTIAL';
|
|
184
|
-
this.currentNode.
|
|
184
|
+
this.currentNode.isClosed = false;
|
|
185
185
|
}
|
|
186
186
|
break;
|
|
187
187
|
|
|
188
|
+
// binary literal8
|
|
189
|
+
case '~': {
|
|
190
|
+
let nextChr = this.str.charAt(i + 1);
|
|
191
|
+
if (nextChr !== '{') {
|
|
192
|
+
throw new Error('Unexpected literal8 marker at position ' + (this.pos + i));
|
|
193
|
+
}
|
|
194
|
+
this.expectedLiteralType = 'literal8';
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
|
|
188
198
|
// { starts a new literal
|
|
189
199
|
case '{':
|
|
190
200
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
191
201
|
this.currentNode.type = 'LITERAL';
|
|
202
|
+
this.currentNode.literalType = this.expectedLiteralType || 'literal';
|
|
203
|
+
this.expectedLiteralType = false;
|
|
192
204
|
this.state = 'LITERAL';
|
|
193
|
-
this.currentNode.
|
|
205
|
+
this.currentNode.isClosed = false;
|
|
194
206
|
break;
|
|
195
207
|
|
|
196
|
-
//
|
|
208
|
+
// * starts a new sequence
|
|
197
209
|
case '*':
|
|
198
210
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
199
211
|
this.currentNode.type = 'SEQUENCE';
|
|
200
212
|
this.currentNode.value = chr;
|
|
201
|
-
this.currentNode.
|
|
213
|
+
this.currentNode.isClosed = false;
|
|
202
214
|
this.state = 'SEQUENCE';
|
|
203
215
|
break;
|
|
204
216
|
|
|
@@ -218,7 +230,7 @@ class TokenParser {
|
|
|
218
230
|
|
|
219
231
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
220
232
|
this.currentNode.type = 'SECTION';
|
|
221
|
-
this.currentNode.
|
|
233
|
+
this.currentNode.isClosed = false;
|
|
222
234
|
this.state = 'NORMAL';
|
|
223
235
|
|
|
224
236
|
// RFC2221 defines a response code REFERRAL whose payload is an
|
|
@@ -244,7 +256,7 @@ class TokenParser {
|
|
|
244
256
|
this.currentNode = this.currentNode.parentNode;
|
|
245
257
|
|
|
246
258
|
// close out the SECTION
|
|
247
|
-
this.currentNode.
|
|
259
|
+
this.currentNode.isClosed = true;
|
|
248
260
|
this.currentNode = this.currentNode.parentNode;
|
|
249
261
|
checkSP();
|
|
250
262
|
}
|
|
@@ -286,7 +298,7 @@ class TokenParser {
|
|
|
286
298
|
this.currentNode.endPos = this.pos + i - 1;
|
|
287
299
|
this.currentNode = this.currentNode.parentNode;
|
|
288
300
|
|
|
289
|
-
this.currentNode.
|
|
301
|
+
this.currentNode.isClosed = true;
|
|
290
302
|
this.currentNode.endPos = this.pos + i;
|
|
291
303
|
this.currentNode = this.currentNode.parentNode;
|
|
292
304
|
this.state = 'NORMAL';
|
|
@@ -297,20 +309,20 @@ class TokenParser {
|
|
|
297
309
|
|
|
298
310
|
if ((chr === ',' || chr === ':') && this.currentNode.value.match(/^\d+$/)) {
|
|
299
311
|
this.currentNode.type = 'SEQUENCE';
|
|
300
|
-
this.currentNode.
|
|
312
|
+
this.currentNode.isClosed = true;
|
|
301
313
|
this.state = 'SEQUENCE';
|
|
302
314
|
}
|
|
303
315
|
|
|
304
316
|
// [ starts a section group for this element
|
|
305
317
|
if (chr === '[') {
|
|
306
318
|
// allowed only for selected elements
|
|
307
|
-
if (['BODY', 'BODY.PEEK'].indexOf(this.currentNode.value.toUpperCase()) < 0) {
|
|
319
|
+
if (['BODY', 'BODY.PEEK', 'BINARY', 'BINARY.PEEK'].indexOf(this.currentNode.value.toUpperCase()) < 0) {
|
|
308
320
|
throw new Error('Unexpected section start char [ at position ' + this.pos);
|
|
309
321
|
}
|
|
310
322
|
this.currentNode.endPos = this.pos + i;
|
|
311
323
|
this.currentNode = this.createNode(this.currentNode.parentNode, this.pos + i);
|
|
312
324
|
this.currentNode.type = 'SECTION';
|
|
313
|
-
this.currentNode.
|
|
325
|
+
this.currentNode.isClosed = false;
|
|
314
326
|
this.state = 'NORMAL';
|
|
315
327
|
break;
|
|
316
328
|
}
|
|
@@ -333,7 +345,7 @@ class TokenParser {
|
|
|
333
345
|
// DQUOTE ends the string sequence
|
|
334
346
|
if (chr === '"') {
|
|
335
347
|
this.currentNode.endPos = this.pos + i;
|
|
336
|
-
this.currentNode.
|
|
348
|
+
this.currentNode.isClosed = true;
|
|
337
349
|
this.currentNode = this.currentNode.parentNode;
|
|
338
350
|
this.state = 'NORMAL';
|
|
339
351
|
|
|
@@ -359,7 +371,7 @@ class TokenParser {
|
|
|
359
371
|
throw new Error('Unexpected end of partial at position ' + this.pos);
|
|
360
372
|
}
|
|
361
373
|
this.currentNode.endPos = this.pos + i;
|
|
362
|
-
this.currentNode.
|
|
374
|
+
this.currentNode.isClosed = true;
|
|
363
375
|
this.currentNode = this.currentNode.parentNode;
|
|
364
376
|
this.state = 'NORMAL';
|
|
365
377
|
checkSP();
|
|
@@ -385,16 +397,19 @@ class TokenParser {
|
|
|
385
397
|
if (this.currentNode.started) {
|
|
386
398
|
// only relevant if literals are not already parsed out from input
|
|
387
399
|
|
|
388
|
-
//
|
|
400
|
+
// Disabled NULL byte check
|
|
401
|
+
// See https://github.com/emailjs/emailjs-imap-handler/commit/f11b2822bedabe492236e8263afc630134a3c41c
|
|
402
|
+
/*
|
|
389
403
|
if (chr === '\u0000') {
|
|
390
404
|
throw new Error('Unexpected \\x00 at position ' + (this.pos + i));
|
|
391
405
|
}
|
|
406
|
+
*/
|
|
392
407
|
|
|
393
408
|
this.currentNode.chBuffer[this.currentNode.chPos++] = chr.charCodeAt(0);
|
|
394
409
|
|
|
395
410
|
if (this.currentNode.chPos >= this.currentNode.literalLength) {
|
|
396
411
|
this.currentNode.endPos = this.pos + i;
|
|
397
|
-
this.currentNode.
|
|
412
|
+
this.currentNode.isClosed = true;
|
|
398
413
|
this.currentNode.value = this.currentNode.chBuffer.toString('binary');
|
|
399
414
|
this.currentNode.chBuffer = Buffer.alloc(0);
|
|
400
415
|
this.currentNode = this.currentNode.parentNode;
|
|
@@ -427,7 +442,7 @@ class TokenParser {
|
|
|
427
442
|
// special case where literal content length is 0
|
|
428
443
|
// close the node right away, do not wait for additional input
|
|
429
444
|
this.currentNode.endPos = this.pos + i;
|
|
430
|
-
this.currentNode.
|
|
445
|
+
this.currentNode.isClosed = true;
|
|
431
446
|
this.currentNode = this.currentNode.parentNode;
|
|
432
447
|
this.state = 'NORMAL';
|
|
433
448
|
checkSP();
|
|
@@ -445,7 +460,7 @@ class TokenParser {
|
|
|
445
460
|
this.currentNode.endPos = this.pos + i + this.currentNode.value.length;
|
|
446
461
|
|
|
447
462
|
this.currentNode.started = false;
|
|
448
|
-
this.currentNode.
|
|
463
|
+
this.currentNode.isClosed = true;
|
|
449
464
|
this.currentNode = this.currentNode.parentNode;
|
|
450
465
|
this.state = 'NORMAL';
|
|
451
466
|
checkSP();
|
|
@@ -478,7 +493,7 @@ class TokenParser {
|
|
|
478
493
|
throw new Error('Unexpected whitespace at position ' + (this.pos + i));
|
|
479
494
|
}
|
|
480
495
|
|
|
481
|
-
this.currentNode.
|
|
496
|
+
this.currentNode.isClosed = true;
|
|
482
497
|
this.currentNode.endPos = this.pos + i - 1;
|
|
483
498
|
this.currentNode = this.currentNode.parentNode;
|
|
484
499
|
this.state = 'NORMAL';
|
|
@@ -487,7 +502,7 @@ class TokenParser {
|
|
|
487
502
|
this.currentNode.endPos = this.pos + i - 1;
|
|
488
503
|
this.currentNode = this.currentNode.parentNode;
|
|
489
504
|
|
|
490
|
-
this.currentNode.
|
|
505
|
+
this.currentNode.isClosed = true;
|
|
491
506
|
this.currentNode.endPos = this.pos + i;
|
|
492
507
|
this.currentNode = this.currentNode.parentNode;
|
|
493
508
|
this.state = 'NORMAL';
|
package/lib/imap-flow.js
CHANGED
|
@@ -130,6 +130,7 @@ class ImapFlow extends EventEmitter {
|
|
|
130
130
|
* @param {Boolean} [options.proxy] Optional proxy URL. Supports HTTP CONNECT (`http://`, `https://`) and SOCKS (`socks://`, `socks4://`, `socks5://`) proxies
|
|
131
131
|
* @param {Boolean} [options.qresync=false] If true, then enables QRESYNC support. EXPUNGE notifications will include `uid` property instead of `seq`
|
|
132
132
|
* @param {Number} [options.maxIdleTime] If set, then breaks and restarts IDLE every maxIdleTime ms
|
|
133
|
+
* @param {Boolean} [options.disableBinary=false] If true, then ignores the BINARY extension when making FETCH and APPEND calls
|
|
133
134
|
*/
|
|
134
135
|
constructor(options) {
|
|
135
136
|
super({ captureRejections: true });
|
|
@@ -183,7 +184,8 @@ class ImapFlow extends EventEmitter {
|
|
|
183
184
|
this.streamer = new ImapStream({
|
|
184
185
|
logger: this.log,
|
|
185
186
|
cid: this.id,
|
|
186
|
-
logRaw: this.logRaw
|
|
187
|
+
logRaw: this.logRaw,
|
|
188
|
+
secureConnection: this.secureConnection
|
|
187
189
|
});
|
|
188
190
|
|
|
189
191
|
this.reading = false;
|
|
@@ -261,7 +263,9 @@ class ImapFlow extends EventEmitter {
|
|
|
261
263
|
this.currentLock = false;
|
|
262
264
|
this.locks = [];
|
|
263
265
|
|
|
264
|
-
this.maxIdleTime = options.maxIdleTime || false;
|
|
266
|
+
this.maxIdleTime = this.options.maxIdleTime || false;
|
|
267
|
+
|
|
268
|
+
this.disableBinary = !!this.options.disableBinary;
|
|
265
269
|
|
|
266
270
|
this.streamer.on('error', err => {
|
|
267
271
|
if (['Z_BUF_ERROR', 'ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'EHOSTUNREACH'].includes(err.code)) {
|
|
@@ -310,7 +314,14 @@ class ImapFlow extends EventEmitter {
|
|
|
310
314
|
}
|
|
311
315
|
|
|
312
316
|
if (this.logRaw) {
|
|
313
|
-
this.log.trace({
|
|
317
|
+
this.log.trace({
|
|
318
|
+
src: 'c',
|
|
319
|
+
msg: 'write to socket',
|
|
320
|
+
data: chunk.toString('base64'),
|
|
321
|
+
compress: !!this._deflate,
|
|
322
|
+
secure: !!this.secureConnection,
|
|
323
|
+
cid: this.id
|
|
324
|
+
});
|
|
314
325
|
}
|
|
315
326
|
|
|
316
327
|
this.writeSocket.write(chunk);
|
|
@@ -750,13 +761,14 @@ class ImapFlow extends EventEmitter {
|
|
|
750
761
|
this.upgrading = true;
|
|
751
762
|
this.socket = tls.connect(opts, () => {
|
|
752
763
|
clearTimeout(this.upgradeTimeout);
|
|
753
|
-
if (this.
|
|
764
|
+
if (this.isClosed) {
|
|
754
765
|
// not sure if this is possible?
|
|
755
766
|
return this.close();
|
|
756
767
|
}
|
|
757
768
|
|
|
758
769
|
this.secureConnection = true;
|
|
759
770
|
this.upgrading = false;
|
|
771
|
+
this.streamer.secureConnection = true;
|
|
760
772
|
this.socket.pipe(this.streamer);
|
|
761
773
|
this.tls = typeof this.socket.getCipher === 'function' ? this.socket.getCipher() : false;
|
|
762
774
|
if (this.tls) {
|
|
@@ -838,7 +850,7 @@ class ImapFlow extends EventEmitter {
|
|
|
838
850
|
this.untaggedHandlers.OK = null;
|
|
839
851
|
this.untaggedHandlers.PREAUTH = null;
|
|
840
852
|
|
|
841
|
-
if (this.
|
|
853
|
+
if (this.isClosed) {
|
|
842
854
|
return;
|
|
843
855
|
}
|
|
844
856
|
|
|
@@ -871,7 +883,7 @@ class ImapFlow extends EventEmitter {
|
|
|
871
883
|
this.untaggedHandlers.OK = null;
|
|
872
884
|
this.untaggedHandlers.PREAUTH = null;
|
|
873
885
|
|
|
874
|
-
if (this.
|
|
886
|
+
if (this.isClosed) {
|
|
875
887
|
return;
|
|
876
888
|
}
|
|
877
889
|
|
|
@@ -1063,6 +1075,15 @@ class ImapFlow extends EventEmitter {
|
|
|
1063
1075
|
range = range.toString();
|
|
1064
1076
|
}
|
|
1065
1077
|
|
|
1078
|
+
// special case, some servers allow this, some do not, so replace it with the last known EXISTS value
|
|
1079
|
+
if (range === '*') {
|
|
1080
|
+
if (!this.mailbox.exists) {
|
|
1081
|
+
return false;
|
|
1082
|
+
}
|
|
1083
|
+
range = this.mailbox.exists.toString();
|
|
1084
|
+
options.uid = false; // sequence query
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1066
1087
|
if (range && typeof range === 'object' && !Array.isArray(range)) {
|
|
1067
1088
|
if (range.all && Object.keys(range) === 1) {
|
|
1068
1089
|
range = '1:*';
|
|
@@ -1272,7 +1293,9 @@ class ImapFlow extends EventEmitter {
|
|
|
1272
1293
|
let reject = this.initialReject;
|
|
1273
1294
|
this.initialResolve = false;
|
|
1274
1295
|
this.initialReject = false;
|
|
1275
|
-
|
|
1296
|
+
let err = new Error('Unexpected close');
|
|
1297
|
+
err.code = `ClosedAfterConnect${this.secureConnection ? 'TLS' : 'Text'}`;
|
|
1298
|
+
reject(err);
|
|
1276
1299
|
}
|
|
1277
1300
|
|
|
1278
1301
|
if (typeof this.preCheck === 'function') {
|
|
@@ -1302,10 +1325,10 @@ class ImapFlow extends EventEmitter {
|
|
|
1302
1325
|
}
|
|
1303
1326
|
|
|
1304
1327
|
this.state = this.states.LOGOUT;
|
|
1305
|
-
if (this.
|
|
1328
|
+
if (this.isClosed) {
|
|
1306
1329
|
return;
|
|
1307
1330
|
}
|
|
1308
|
-
this.
|
|
1331
|
+
this.isClosed = true;
|
|
1309
1332
|
|
|
1310
1333
|
if (this.writeSocket && !this.writeSocket.destroyed) {
|
|
1311
1334
|
try {
|
|
@@ -1357,9 +1380,12 @@ class ImapFlow extends EventEmitter {
|
|
|
1357
1380
|
/**
|
|
1358
1381
|
* @typedef {Object} ListResponse
|
|
1359
1382
|
* @global
|
|
1360
|
-
* @property {String} path mailbox path
|
|
1383
|
+
* @property {String} path mailbox path (unicode string)
|
|
1384
|
+
* @property {String} pathAsListed mailbox path as listed in the LIST/LSUB response
|
|
1361
1385
|
* @property {String} name mailbox name (last part of path after delimiter)
|
|
1362
1386
|
* @property {String} delimiter mailbox path delimiter, usually "." or "/"
|
|
1387
|
+
* @property {Array<string>} parent An array of parent folder names. All names are in unicode
|
|
1388
|
+
* @property {String} parentPath Same as `parent`, but as a complete string path (unicode string)
|
|
1363
1389
|
* @property {Set<string>} flags a set of flags for this mailbox
|
|
1364
1390
|
* @property {String} specialUse one of special-use flags (if applicable): "\All", "\Archive", "\Drafts", "\Flagged", "\Junk", "\Sent", "\Trash". Additionally INBOX has non-standard "\Inbox" flag set
|
|
1365
1391
|
* @property {Boolean} listed `true` if mailbox was found from the output of LIST command
|
|
@@ -1563,7 +1589,7 @@ class ImapFlow extends EventEmitter {
|
|
|
1563
1589
|
/**
|
|
1564
1590
|
* Requests the status of the indicated mailbox. Only requested status values will be returned.
|
|
1565
1591
|
*
|
|
1566
|
-
* @param {String} path mailbox path to check for
|
|
1592
|
+
* @param {String} path mailbox path to check for (unicode string)
|
|
1567
1593
|
* @param {Object} query defines requested status items
|
|
1568
1594
|
* @param {Boolean} query.messages if `true` request count of messages
|
|
1569
1595
|
* @param {Boolean} query.recent if `true` request count of messages with \\Recent tag
|
|
@@ -1786,7 +1812,7 @@ class ImapFlow extends EventEmitter {
|
|
|
1786
1812
|
/**
|
|
1787
1813
|
* Appends a new message to a mailbox
|
|
1788
1814
|
*
|
|
1789
|
-
* @param {String} path Mailbox path to upload the message to
|
|
1815
|
+
* @param {String} path Mailbox path to upload the message to (unicode string)
|
|
1790
1816
|
* @param {string|Buffer} content RFC822 formatted email message
|
|
1791
1817
|
* @param {string[]} [flags] an array of flags to be set for the uploaded message
|
|
1792
1818
|
* @param {Date|string} [idate=now] internal date to be set for the message
|
|
@@ -2096,6 +2122,14 @@ class ImapFlow extends EventEmitter {
|
|
|
2096
2122
|
return;
|
|
2097
2123
|
}
|
|
2098
2124
|
|
|
2125
|
+
if (seq === '*') {
|
|
2126
|
+
if (!this.mailbox.exists) {
|
|
2127
|
+
return false;
|
|
2128
|
+
}
|
|
2129
|
+
seq = this.mailbox.exists.toString();
|
|
2130
|
+
options = Object.assign({}, options || {}, { uid: false }); // force into a sequence query
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2099
2133
|
let response = await this.run('FETCH', (seq || '').toString(), query, options);
|
|
2100
2134
|
|
|
2101
2135
|
if (!response || !response.list || !response.list.length) {
|
package/lib/tools.js
CHANGED
|
@@ -9,6 +9,30 @@ const { JPDecoder } = require('./jp-decoder');
|
|
|
9
9
|
const iconv = require('iconv-lite');
|
|
10
10
|
|
|
11
11
|
module.exports = {
|
|
12
|
+
encodePath(connection, path) {
|
|
13
|
+
path = (path || '').toString();
|
|
14
|
+
if (!connection.enabled.has('UTF8=ACCEPT') && /[&\x00-\x08\x0b-\x0c\x0e-\x1f\u0080-\uffff]/.test(path)) {
|
|
15
|
+
try {
|
|
16
|
+
path = iconv.encode(path, 'utf-7-imap').toString();
|
|
17
|
+
} catch (err) {
|
|
18
|
+
// ignore, keep name as is
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return path;
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
decodePath(connection, path) {
|
|
25
|
+
path = (path || '').toString();
|
|
26
|
+
if (!connection.enabled.has('UTF8=ACCEPT') && /[&]/.test(path)) {
|
|
27
|
+
try {
|
|
28
|
+
path = iconv.decode(Buffer.from(path), 'utf-7-imap').toString();
|
|
29
|
+
} catch (err) {
|
|
30
|
+
// ignore, keep name as is
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return path;
|
|
34
|
+
},
|
|
35
|
+
|
|
12
36
|
normalizePath(connection, path, skipNamespace) {
|
|
13
37
|
if (Array.isArray(path)) {
|
|
14
38
|
path = path.join((connection.namespace && connection.namespace.delimiter) || '');
|
|
@@ -19,14 +43,6 @@ module.exports = {
|
|
|
19
43
|
return 'INBOX';
|
|
20
44
|
}
|
|
21
45
|
|
|
22
|
-
if (!connection.enabled.has('UTF8=ACCEPT') && /[\x00-\x08\x0b-\x0c\x0e-\x1f\u0080-\uffff]/.test(path)) {
|
|
23
|
-
try {
|
|
24
|
-
path = iconv.encode(path, 'utf-7-imap').toString();
|
|
25
|
-
} catch (err) {
|
|
26
|
-
// ignore, keep name as is
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
46
|
// ensure namespace prefix if needed
|
|
31
47
|
if (!skipNamespace && connection.namespace && connection.namespace.prefix && path.indexOf(connection.namespace.prefix) !== 0) {
|
|
32
48
|
path = connection.namespace.prefix + path;
|
|
@@ -290,9 +306,10 @@ module.exports = {
|
|
|
290
306
|
break;
|
|
291
307
|
}
|
|
292
308
|
|
|
293
|
-
default:
|
|
294
|
-
|
|
295
|
-
|
|
309
|
+
default: {
|
|
310
|
+
let match = key.match(/(body|binary)\[/i);
|
|
311
|
+
if (match) {
|
|
312
|
+
let partKey = key.replace(/^(body|binary)\[|]$/gi, '');
|
|
296
313
|
partKey = partKey.replace(/\.fields.*$/g, '');
|
|
297
314
|
|
|
298
315
|
let value = getBuffer(attribute);
|
|
@@ -308,6 +325,7 @@ module.exports = {
|
|
|
308
325
|
break;
|
|
309
326
|
}
|
|
310
327
|
break;
|
|
328
|
+
}
|
|
311
329
|
}
|
|
312
330
|
}
|
|
313
331
|
|
package/lib/types.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ declare module "imapflow" {
|
|
|
29
29
|
* @param [options.proxy] - Optional proxy URL. Supports HTTP CONNECT (`http://`, `https://`) and SOCKS (`socks://`, `socks4://`, `socks5://`) proxies
|
|
30
30
|
* @param [options.qresync = false] - If true, then enables QRESYNC support. EXPUNGE notifications will include `uid` property instead of `seq`
|
|
31
31
|
* @param [options.maxIdleTime] - If set, then breaks and restarts IDLE every maxIdleTime ms
|
|
32
|
+
* @param [options.disableBinary = false] - If true, then ignores the BINARY extension when making FETCH and APPEND calls
|
|
32
33
|
*/
|
|
33
34
|
class ImapFlow extends EventEmitter {
|
|
34
35
|
constructor(options: {
|
|
@@ -56,6 +57,7 @@ declare module "imapflow" {
|
|
|
56
57
|
proxy?: boolean;
|
|
57
58
|
qresync?: boolean;
|
|
58
59
|
maxIdleTime?: number;
|
|
60
|
+
disableBinary?: boolean;
|
|
59
61
|
});
|
|
60
62
|
/**
|
|
61
63
|
* Current module version as a static class property
|
|
@@ -241,7 +243,7 @@ declare module "imapflow" {
|
|
|
241
243
|
* let status = await client.status('INBOX', {unseen: true});
|
|
242
244
|
* console.log(status.unseen);
|
|
243
245
|
* // 123
|
|
244
|
-
* @param path - mailbox path to check for
|
|
246
|
+
* @param path - mailbox path to check for (unicode string)
|
|
245
247
|
* @param query - defines requested status items
|
|
246
248
|
* @param query.messages - if `true` request count of messages
|
|
247
249
|
* @param query.recent - if `true` request count of messages with \\Recent tag
|
|
@@ -342,7 +344,7 @@ declare module "imapflow" {
|
|
|
342
344
|
* Appends a new message to a mailbox
|
|
343
345
|
* @example
|
|
344
346
|
* await client.append('INBOX', rawMessageBuffer, ['\\Seen'], new Date(2000, 1, 1));
|
|
345
|
-
* @param path - Mailbox path to upload the message to
|
|
347
|
+
* @param path - Mailbox path to upload the message to (unicode string)
|
|
346
348
|
* @param content - RFC822 formatted email message
|
|
347
349
|
* @param [flags] - an array of flags to be set for the uploaded message
|
|
348
350
|
* @param [idate = now] - internal date to be set for the message
|
|
@@ -565,9 +567,12 @@ declare type QuotaResponse = {
|
|
|
565
567
|
};
|
|
566
568
|
|
|
567
569
|
/**
|
|
568
|
-
* @property path - mailbox path
|
|
570
|
+
* @property path - mailbox path (unicode string)
|
|
571
|
+
* @property pathAsListed - mailbox path as listed in the LIST/LSUB response
|
|
569
572
|
* @property name - mailbox name (last part of path after delimiter)
|
|
570
573
|
* @property delimiter - mailbox path delimiter, usually "." or "/"
|
|
574
|
+
* @property parent - An array of parent folder names. All names are in unicode
|
|
575
|
+
* @property parentPath - Same as `parent`, but as a complete string path (unicode string)
|
|
571
576
|
* @property flags - a set of flags for this mailbox
|
|
572
577
|
* @property specialUse - one of special-use flags (if applicable): "\All", "\Archive", "\Drafts", "\Flagged", "\Junk", "\Sent", "\Trash". Additionally INBOX has non-standard "\Inbox" flag set
|
|
573
578
|
* @property listed - `true` if mailbox was found from the output of LIST command
|
|
@@ -575,8 +580,11 @@ declare type QuotaResponse = {
|
|
|
575
580
|
*/
|
|
576
581
|
declare type ListResponse = {
|
|
577
582
|
path: string;
|
|
583
|
+
pathAsListed: string;
|
|
578
584
|
name: string;
|
|
579
585
|
delimiter: string;
|
|
586
|
+
parent: string[];
|
|
587
|
+
parentPath: string;
|
|
580
588
|
flags: Set<string>;
|
|
581
589
|
specialUse: string;
|
|
582
590
|
listed: boolean;
|