imapflow 1.0.91 → 1.0.92
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 +6 -1
- package/lib/commands/append.js +11 -1
- package/lib/commands/authenticate.js +1 -1
- package/lib/commands/fetch.js +1 -1
- package/lib/handler/imap-compiler.js +1 -1
- package/lib/handler/imap-stream.js +9 -1
- package/lib/handler/token-parser.js +38 -27
- package/lib/imap-flow.js +22 -8
- package/lib/tools.js +2 -2
- package/lib/types.d.ts +2 -0
- package/package.json +1 -1
- package/test/imap-compiler-test.js +41 -2
- package/test/imap-parser-test.js +30 -0
package/.gitattributes
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*.js text eol=lf
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: Run tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
strategy:
|
|
10
|
+
matrix:
|
|
11
|
+
node: [16.x, 18.x]
|
|
12
|
+
os: [ubuntu-latest]
|
|
13
|
+
runs-on: ${{ matrix.os }}
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v2
|
|
16
|
+
- name: Use Node.js ${{ matrix.node }}
|
|
17
|
+
uses: actions/setup-node@v1
|
|
18
|
+
with:
|
|
19
|
+
node-version: ${{ matrix.node }}
|
|
20
|
+
- run: npm install
|
|
21
|
+
- run: npm test
|
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
- 1.0.92 - 2022-05-03
|
|
4
|
+
|
|
5
|
+
- Added support for IMAP BINARY extension (rfc3516)
|
|
6
|
+
- Logging improvements
|
|
7
|
+
|
|
3
8
|
- 1.0.91 - 2022-05-02
|
|
4
9
|
|
|
5
10
|
- Do not throw if literal includes a null byte
|
|
6
11
|
- Use UTF8 path names by default
|
|
7
|
-
- Overrides '\*' range query with
|
|
12
|
+
- Overrides '\*' range query with the EXISTS value
|
|
8
13
|
|
|
9
14
|
- 1.0.90 - 2022-04-04
|
|
10
15
|
|
package/lib/commands/append.js
CHANGED
|
@@ -38,7 +38,17 @@ module.exports = async (connection, destination, content, flags, idate) => {
|
|
|
38
38
|
attributes.push({ type: 'STRING', value: idate }); // force quotes as required by date-time
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
let isLiteral8 = false;
|
|
42
|
+
if (connection.capabilities.has('BINARY') && !connection.disableBinary) {
|
|
43
|
+
if (typeof content === 'string') {
|
|
44
|
+
content = Buffer.from(content);
|
|
45
|
+
}
|
|
46
|
+
// Value is literal8 if it contains NULL bytes. The server must support the BINARY extension
|
|
47
|
+
// and if it does not then send the value as a regular literal and hope for the best
|
|
48
|
+
isLiteral8 = content.indexOf(Buffer.from([0])) >= 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
attributes.push({ type: 'LITERAL', value: content, isLiteral8 });
|
|
42
52
|
|
|
43
53
|
let map = { destination };
|
|
44
54
|
if (connection.mailbox && connection.mailbox.path) {
|
|
@@ -39,7 +39,7 @@ module.exports = async (connection, username, accessToken) => {
|
|
|
39
39
|
try {
|
|
40
40
|
errorResponse = JSON.parse(Buffer.from(resp.attributes[0].value, 'base64').toString());
|
|
41
41
|
} catch (err) {
|
|
42
|
-
connection.log.debug({
|
|
42
|
+
connection.log.debug({ errorResponse: resp.attributes[0].value, err });
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
package/lib/commands/fetch.js
CHANGED
|
@@ -85,7 +85,7 @@ module.exports = async (response, options) => {
|
|
|
85
85
|
let canAppend = !asArray || literalPlus || (literalMinus && literalLength <= 4096);
|
|
86
86
|
let usePlus = canAppend && (literalMinus || literalPlus);
|
|
87
87
|
|
|
88
|
-
resp +=
|
|
88
|
+
resp += `${node.isLiteral8 ? '~' : ''}{${literalLength}${usePlus ? '+' : ''}}\r\n`;
|
|
89
89
|
|
|
90
90
|
if (canAppend) {
|
|
91
91
|
resp += (node.value || '').toString('binary');
|
|
@@ -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,25 +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';
|
|
192
|
-
this.currentNode.literalType = 'literal';
|
|
202
|
+
this.currentNode.literalType = this.expectedLiteralType || 'literal';
|
|
203
|
+
this.expectedLiteralType = false;
|
|
193
204
|
this.state = 'LITERAL';
|
|
194
|
-
this.currentNode.
|
|
205
|
+
this.currentNode.isClosed = false;
|
|
195
206
|
break;
|
|
196
207
|
|
|
197
|
-
//
|
|
208
|
+
// * starts a new sequence
|
|
198
209
|
case '*':
|
|
199
210
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
200
211
|
this.currentNode.type = 'SEQUENCE';
|
|
201
212
|
this.currentNode.value = chr;
|
|
202
|
-
this.currentNode.
|
|
213
|
+
this.currentNode.isClosed = false;
|
|
203
214
|
this.state = 'SEQUENCE';
|
|
204
215
|
break;
|
|
205
216
|
|
|
@@ -219,7 +230,7 @@ class TokenParser {
|
|
|
219
230
|
|
|
220
231
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
221
232
|
this.currentNode.type = 'SECTION';
|
|
222
|
-
this.currentNode.
|
|
233
|
+
this.currentNode.isClosed = false;
|
|
223
234
|
this.state = 'NORMAL';
|
|
224
235
|
|
|
225
236
|
// RFC2221 defines a response code REFERRAL whose payload is an
|
|
@@ -245,7 +256,7 @@ class TokenParser {
|
|
|
245
256
|
this.currentNode = this.currentNode.parentNode;
|
|
246
257
|
|
|
247
258
|
// close out the SECTION
|
|
248
|
-
this.currentNode.
|
|
259
|
+
this.currentNode.isClosed = true;
|
|
249
260
|
this.currentNode = this.currentNode.parentNode;
|
|
250
261
|
checkSP();
|
|
251
262
|
}
|
|
@@ -287,7 +298,7 @@ class TokenParser {
|
|
|
287
298
|
this.currentNode.endPos = this.pos + i - 1;
|
|
288
299
|
this.currentNode = this.currentNode.parentNode;
|
|
289
300
|
|
|
290
|
-
this.currentNode.
|
|
301
|
+
this.currentNode.isClosed = true;
|
|
291
302
|
this.currentNode.endPos = this.pos + i;
|
|
292
303
|
this.currentNode = this.currentNode.parentNode;
|
|
293
304
|
this.state = 'NORMAL';
|
|
@@ -298,20 +309,20 @@ class TokenParser {
|
|
|
298
309
|
|
|
299
310
|
if ((chr === ',' || chr === ':') && this.currentNode.value.match(/^\d+$/)) {
|
|
300
311
|
this.currentNode.type = 'SEQUENCE';
|
|
301
|
-
this.currentNode.
|
|
312
|
+
this.currentNode.isClosed = true;
|
|
302
313
|
this.state = 'SEQUENCE';
|
|
303
314
|
}
|
|
304
315
|
|
|
305
316
|
// [ starts a section group for this element
|
|
306
317
|
if (chr === '[') {
|
|
307
318
|
// allowed only for selected elements
|
|
308
|
-
if (['BODY', 'BODY.PEEK'].indexOf(this.currentNode.value.toUpperCase()) < 0) {
|
|
319
|
+
if (['BODY', 'BODY.PEEK', 'BINARY', 'BINARY.PEEK'].indexOf(this.currentNode.value.toUpperCase()) < 0) {
|
|
309
320
|
throw new Error('Unexpected section start char [ at position ' + this.pos);
|
|
310
321
|
}
|
|
311
322
|
this.currentNode.endPos = this.pos + i;
|
|
312
323
|
this.currentNode = this.createNode(this.currentNode.parentNode, this.pos + i);
|
|
313
324
|
this.currentNode.type = 'SECTION';
|
|
314
|
-
this.currentNode.
|
|
325
|
+
this.currentNode.isClosed = false;
|
|
315
326
|
this.state = 'NORMAL';
|
|
316
327
|
break;
|
|
317
328
|
}
|
|
@@ -334,7 +345,7 @@ class TokenParser {
|
|
|
334
345
|
// DQUOTE ends the string sequence
|
|
335
346
|
if (chr === '"') {
|
|
336
347
|
this.currentNode.endPos = this.pos + i;
|
|
337
|
-
this.currentNode.
|
|
348
|
+
this.currentNode.isClosed = true;
|
|
338
349
|
this.currentNode = this.currentNode.parentNode;
|
|
339
350
|
this.state = 'NORMAL';
|
|
340
351
|
|
|
@@ -360,7 +371,7 @@ class TokenParser {
|
|
|
360
371
|
throw new Error('Unexpected end of partial at position ' + this.pos);
|
|
361
372
|
}
|
|
362
373
|
this.currentNode.endPos = this.pos + i;
|
|
363
|
-
this.currentNode.
|
|
374
|
+
this.currentNode.isClosed = true;
|
|
364
375
|
this.currentNode = this.currentNode.parentNode;
|
|
365
376
|
this.state = 'NORMAL';
|
|
366
377
|
checkSP();
|
|
@@ -386,7 +397,7 @@ class TokenParser {
|
|
|
386
397
|
if (this.currentNode.started) {
|
|
387
398
|
// only relevant if literals are not already parsed out from input
|
|
388
399
|
|
|
389
|
-
// Disabled
|
|
400
|
+
// Disabled NULL byte check
|
|
390
401
|
// See https://github.com/emailjs/emailjs-imap-handler/commit/f11b2822bedabe492236e8263afc630134a3c41c
|
|
391
402
|
/*
|
|
392
403
|
if (chr === '\u0000') {
|
|
@@ -398,7 +409,7 @@ class TokenParser {
|
|
|
398
409
|
|
|
399
410
|
if (this.currentNode.chPos >= this.currentNode.literalLength) {
|
|
400
411
|
this.currentNode.endPos = this.pos + i;
|
|
401
|
-
this.currentNode.
|
|
412
|
+
this.currentNode.isClosed = true;
|
|
402
413
|
this.currentNode.value = this.currentNode.chBuffer.toString('binary');
|
|
403
414
|
this.currentNode.chBuffer = Buffer.alloc(0);
|
|
404
415
|
this.currentNode = this.currentNode.parentNode;
|
|
@@ -431,7 +442,7 @@ class TokenParser {
|
|
|
431
442
|
// special case where literal content length is 0
|
|
432
443
|
// close the node right away, do not wait for additional input
|
|
433
444
|
this.currentNode.endPos = this.pos + i;
|
|
434
|
-
this.currentNode.
|
|
445
|
+
this.currentNode.isClosed = true;
|
|
435
446
|
this.currentNode = this.currentNode.parentNode;
|
|
436
447
|
this.state = 'NORMAL';
|
|
437
448
|
checkSP();
|
|
@@ -449,7 +460,7 @@ class TokenParser {
|
|
|
449
460
|
this.currentNode.endPos = this.pos + i + this.currentNode.value.length;
|
|
450
461
|
|
|
451
462
|
this.currentNode.started = false;
|
|
452
|
-
this.currentNode.
|
|
463
|
+
this.currentNode.isClosed = true;
|
|
453
464
|
this.currentNode = this.currentNode.parentNode;
|
|
454
465
|
this.state = 'NORMAL';
|
|
455
466
|
checkSP();
|
|
@@ -482,7 +493,7 @@ class TokenParser {
|
|
|
482
493
|
throw new Error('Unexpected whitespace at position ' + (this.pos + i));
|
|
483
494
|
}
|
|
484
495
|
|
|
485
|
-
this.currentNode.
|
|
496
|
+
this.currentNode.isClosed = true;
|
|
486
497
|
this.currentNode.endPos = this.pos + i - 1;
|
|
487
498
|
this.currentNode = this.currentNode.parentNode;
|
|
488
499
|
this.state = 'NORMAL';
|
|
@@ -491,7 +502,7 @@ class TokenParser {
|
|
|
491
502
|
this.currentNode.endPos = this.pos + i - 1;
|
|
492
503
|
this.currentNode = this.currentNode.parentNode;
|
|
493
504
|
|
|
494
|
-
this.currentNode.
|
|
505
|
+
this.currentNode.isClosed = true;
|
|
495
506
|
this.currentNode.endPos = this.pos + i;
|
|
496
507
|
this.currentNode = this.currentNode.parentNode;
|
|
497
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;
|
|
@@ -263,6 +265,8 @@ class ImapFlow extends EventEmitter {
|
|
|
263
265
|
|
|
264
266
|
this.maxIdleTime = this.options.maxIdleTime || false;
|
|
265
267
|
|
|
268
|
+
this.disableBinary = !!this.options.disableBinary;
|
|
269
|
+
|
|
266
270
|
this.streamer.on('error', err => {
|
|
267
271
|
if (['Z_BUF_ERROR', 'ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'EHOSTUNREACH'].includes(err.code)) {
|
|
268
272
|
// just close the connection, usually nothing but noise
|
|
@@ -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
|
|
|
@@ -1281,7 +1293,9 @@ class ImapFlow extends EventEmitter {
|
|
|
1281
1293
|
let reject = this.initialReject;
|
|
1282
1294
|
this.initialResolve = false;
|
|
1283
1295
|
this.initialReject = false;
|
|
1284
|
-
|
|
1296
|
+
let err = new Error('Unexpected close');
|
|
1297
|
+
err.code = `ClosedAfterConnect${this.secureConnection ? 'TLS' : 'Text'}`;
|
|
1298
|
+
reject(err);
|
|
1285
1299
|
}
|
|
1286
1300
|
|
|
1287
1301
|
if (typeof this.preCheck === 'function') {
|
|
@@ -1311,10 +1325,10 @@ class ImapFlow extends EventEmitter {
|
|
|
1311
1325
|
}
|
|
1312
1326
|
|
|
1313
1327
|
this.state = this.states.LOGOUT;
|
|
1314
|
-
if (this.
|
|
1328
|
+
if (this.isClosed) {
|
|
1315
1329
|
return;
|
|
1316
1330
|
}
|
|
1317
|
-
this.
|
|
1331
|
+
this.isClosed = true;
|
|
1318
1332
|
|
|
1319
1333
|
if (this.writeSocket && !this.writeSocket.destroyed) {
|
|
1320
1334
|
try {
|
package/lib/tools.js
CHANGED
|
@@ -307,9 +307,9 @@ module.exports = {
|
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
default: {
|
|
310
|
-
let match = key.match(/(body)\[/i);
|
|
310
|
+
let match = key.match(/(body|binary)\[/i);
|
|
311
311
|
if (match) {
|
|
312
|
-
let partKey = key.replace(/^(body)\[|]$/gi, '');
|
|
312
|
+
let partKey = key.replace(/^(body|binary)\[|]$/gi, '');
|
|
313
313
|
partKey = partKey.replace(/\.fields.*$/g, '');
|
|
314
314
|
|
|
315
315
|
let value = getBuffer(attribute);
|
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
|
package/package.json
CHANGED
|
@@ -200,8 +200,7 @@ module.exports['IMAP Compiler: hide long strings'] = test =>
|
|
|
200
200
|
attributes: [
|
|
201
201
|
{
|
|
202
202
|
type: 'String',
|
|
203
|
-
value:
|
|
204
|
-
'Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere!'
|
|
203
|
+
value: 'Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere!'
|
|
205
204
|
},
|
|
206
205
|
'Vana kere'
|
|
207
206
|
]
|
|
@@ -248,6 +247,46 @@ module.exports['IMAP Compiler: LITERAL text'] = test =>
|
|
|
248
247
|
)
|
|
249
248
|
);
|
|
250
249
|
|
|
250
|
+
module.exports['IMAP Compiler: LITERAL literal'] = test =>
|
|
251
|
+
asyncWrapper(test, async test =>
|
|
252
|
+
test.equal(
|
|
253
|
+
await compiler({
|
|
254
|
+
tag: '*',
|
|
255
|
+
command: 'CMD',
|
|
256
|
+
attributes: [
|
|
257
|
+
// keep indentation
|
|
258
|
+
{
|
|
259
|
+
type: 'LITERAL',
|
|
260
|
+
value: 'Tere\x00 tere!',
|
|
261
|
+
isLiteral8: false
|
|
262
|
+
},
|
|
263
|
+
'Vana kere'
|
|
264
|
+
]
|
|
265
|
+
}),
|
|
266
|
+
'* CMD {11}\r\nTere\x00 tere! "Vana kere"'
|
|
267
|
+
)
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
module.exports['IMAP Compiler: LITERAL literal8'] = test =>
|
|
271
|
+
asyncWrapper(test, async test =>
|
|
272
|
+
test.equal(
|
|
273
|
+
await compiler({
|
|
274
|
+
tag: '*',
|
|
275
|
+
command: 'CMD',
|
|
276
|
+
attributes: [
|
|
277
|
+
// keep indentation
|
|
278
|
+
{
|
|
279
|
+
type: 'LITERAL',
|
|
280
|
+
value: 'Tere\x00 tere!',
|
|
281
|
+
isLiteral8: true
|
|
282
|
+
},
|
|
283
|
+
'Vana kere'
|
|
284
|
+
]
|
|
285
|
+
}),
|
|
286
|
+
'* CMD ~{11}\r\nTere\x00 tere! "Vana kere"'
|
|
287
|
+
)
|
|
288
|
+
);
|
|
289
|
+
|
|
251
290
|
module.exports['IMAP Compiler: LITERAL array 1'] = test =>
|
|
252
291
|
asyncWrapper(test, async test =>
|
|
253
292
|
test.deepEqual(
|
package/test/imap-parser-test.js
CHANGED
|
@@ -309,6 +309,36 @@ module.exports['IMAP Parser: Literals: single literal'] = test =>
|
|
|
309
309
|
])
|
|
310
310
|
);
|
|
311
311
|
|
|
312
|
+
module.exports['IMAP Parser: Literals: literal with NULL'] = test =>
|
|
313
|
+
asyncWrapper(test, async test =>
|
|
314
|
+
test.deepEqual((await parser('TAG1 CMD {4}\r\n', { literals: [Buffer.from('ab\x00d')] })).attributes, [
|
|
315
|
+
{
|
|
316
|
+
type: 'LITERAL',
|
|
317
|
+
value: Buffer.from('ab\x00d')
|
|
318
|
+
}
|
|
319
|
+
])
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
module.exports['IMAP Parser: Literals: literal8'] = test =>
|
|
323
|
+
asyncWrapper(test, async test =>
|
|
324
|
+
test.deepEqual((await parser('TAG1 CMD ~{4}\r\n', { literals: [Buffer.from('ab\x00d')] })).attributes, [
|
|
325
|
+
{
|
|
326
|
+
type: 'LITERAL',
|
|
327
|
+
value: Buffer.from('ab\x00d')
|
|
328
|
+
}
|
|
329
|
+
])
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
module.exports['IMAP Parser: Literals: unexpected literal8 prefix'] = test =>
|
|
333
|
+
asyncWrapper(test, async test => {
|
|
334
|
+
try {
|
|
335
|
+
await parser('TAG1 CMD ~\r\n');
|
|
336
|
+
test.ok(false, 'Must throw');
|
|
337
|
+
} catch (err) {
|
|
338
|
+
test.ok(err, 'Error must exist');
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
312
342
|
module.exports['IMAP Parser: Literals: multiple literals'] = test =>
|
|
313
343
|
asyncWrapper(test, async test =>
|
|
314
344
|
test.deepEqual((await parser('TAG1 CMD {4}\r\n {4}\r\n', { literals: [Buffer.from('abcd'), Buffer.from('kere')] })).attributes, [
|