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.
@@ -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({ src: 's', msg: 'read from socket', data: chunk.toString('base64'), compress: !!this.compress, cid: this.cid });
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.closed && node.type === 'SEQUENCE' && node.value === '*') {
33
- node.closed = true;
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.closed) {
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
- closed: true
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.closed = false;
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.closed = false;
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.closed = true;
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.closed = true;
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.closed = false;
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.closed = false;
205
+ this.currentNode.isClosed = false;
194
206
  break;
195
207
 
196
- // ( starts a new sequence
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.closed = false;
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.closed = false;
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.closed = true;
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.closed = true;
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.closed = true;
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.closed = false;
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.closed = true;
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.closed = true;
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
- //if(imapFormalSyntax['CHAR8']().indexOf(chr) < 0){
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.closed = true;
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.closed = true;
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.closed = true;
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.closed = true;
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.closed = true;
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({ src: 'c', msg: 'write to socket', data: chunk.toString('base64'), compress: !!this._deflate, cid: this.id });
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.closed) {
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.closed) {
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.closed) {
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
- reject(new Error('Unexpected close'));
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.closed) {
1328
+ if (this.isClosed) {
1306
1329
  return;
1307
1330
  }
1308
- this.closed = true;
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
- if (key.indexOf('body[') >= 0) {
295
- let partKey = key.replace(/^body\[|]$/g, '');
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;