imapflow 1.2.13 → 1.2.14

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/lib/imap-flow.js CHANGED
@@ -298,7 +298,6 @@ class ImapFlow extends EventEmitter {
298
298
  this.state = this.states.NOT_AUTHENTICATED;
299
299
 
300
300
  this.lockCounter = 0;
301
- this.currentLock = false;
302
301
 
303
302
  this.tagCounter = 0;
304
303
  this.requestTagMap = new Map();
@@ -407,7 +406,8 @@ class ImapFlow extends EventEmitter {
407
406
  let rid = BigInt('0x' + crypto.randomBytes(13).toString('hex')).toString(36);
408
407
  if (rid.length < 20) {
409
408
  rid = '0'.repeat(20 - rid.length) + rid;
410
- } else if (rid.length > 20) {
409
+ }
410
+ if (rid.length > 20) {
411
411
  rid = rid.substr(0, 20);
412
412
  }
413
413
  return rid;
@@ -1087,11 +1087,11 @@ class ImapFlow extends EventEmitter {
1087
1087
  let err = new Error('Server does not support STARTTLS');
1088
1088
  err.tlsFailed = true;
1089
1089
  throw err;
1090
- } else {
1091
- // Opportunistic STARTTLS. But it's not possible right now.
1092
- // Attention: Could be a downgrade attack.
1093
- return false;
1094
1090
  }
1091
+
1092
+ // Opportunistic STARTTLS. But it's not possible right now.
1093
+ // Attention: Could be a downgrade attack.
1094
+ return false;
1095
1095
  }
1096
1096
 
1097
1097
  /**
@@ -1510,7 +1510,7 @@ class ImapFlow extends EventEmitter {
1510
1510
  return false;
1511
1511
  }
1512
1512
 
1513
- if ((!this.mailbox && path) || (this.mailbox && path && !comparePaths(this, this.mailbox.path, path))) {
1513
+ if (!this.mailbox || !comparePaths(this, this.mailbox.path, path)) {
1514
1514
  return await this.mailboxOpen(path);
1515
1515
  }
1516
1516
 
@@ -2515,13 +2515,7 @@ class ImapFlow extends EventEmitter {
2515
2515
  * await client.append('INBOX', rawMessageBuffer, ['\\Seen'], new Date(2000, 1, 1));
2516
2516
  */
2517
2517
  async append(path, content, flags, idate) {
2518
- let response = await this.run('APPEND', path, content, flags, idate);
2519
-
2520
- if (!response) {
2521
- return false;
2522
- }
2523
-
2524
- return response;
2518
+ return (await this.run('APPEND', path, content, flags, idate)) || false;
2525
2519
  }
2526
2520
 
2527
2521
  /**
@@ -2608,13 +2602,7 @@ class ImapFlow extends EventEmitter {
2608
2602
  return;
2609
2603
  }
2610
2604
 
2611
- let response = await this.run('SEARCH', query, options);
2612
-
2613
- if (!response) {
2614
- return false;
2615
- }
2616
-
2617
- return response;
2605
+ return (await this.run('SEARCH', query, options)) || false;
2618
2606
  }
2619
2607
 
2620
2608
  /**
@@ -2753,9 +2741,8 @@ class ImapFlow extends EventEmitter {
2753
2741
  let entry = rowQueue.shift();
2754
2742
  if (entry.err) {
2755
2743
  return reject(entry.err);
2756
- } else {
2757
- return resolve(entry.value);
2758
2744
  }
2745
+ return resolve(entry.value);
2759
2746
  }
2760
2747
 
2761
2748
  if (finished) {
@@ -3257,6 +3244,29 @@ class ImapFlow extends EventEmitter {
3257
3244
  // stream and then fetch remaining chunks via fetchAllParts().
3258
3245
  // setImmediate ensures the caller gets the {meta, content} return
3259
3246
  // value before streaming begins.
3247
+ let runFetchAllParts = () => {
3248
+ fetchAllParts()
3249
+ .catch(err => {
3250
+ if (!fetchAborted && stream && !stream.destroyed) {
3251
+ stream.emit('error', err);
3252
+ } else {
3253
+ // Log when error cannot be emitted to stream
3254
+ this.log.warn({
3255
+ msg: 'Download error after stream closed',
3256
+ err,
3257
+ fetchAborted,
3258
+ streamDestroyed: stream?.destroyed,
3259
+ cid: this.id
3260
+ });
3261
+ }
3262
+ })
3263
+ .finally(() => {
3264
+ if (!fetchAborted && stream && !stream.destroyed) {
3265
+ stream.end();
3266
+ }
3267
+ });
3268
+ };
3269
+
3260
3270
  setImmediate(() => {
3261
3271
  let writeResult;
3262
3272
  try {
@@ -3273,49 +3283,11 @@ class ImapFlow extends EventEmitter {
3273
3283
  // Initial chunk filled the buffer, wait for drain
3274
3284
  stream.once('drain', () => {
3275
3285
  if (!fetchAborted) {
3276
- fetchAllParts()
3277
- .catch(err => {
3278
- if (!fetchAborted && stream && !stream.destroyed) {
3279
- stream.emit('error', err);
3280
- } else {
3281
- // Log when error cannot be emitted to stream
3282
- this.log.warn({
3283
- msg: 'Download error after stream closed',
3284
- err,
3285
- fetchAborted,
3286
- streamDestroyed: stream?.destroyed,
3287
- cid: this.id
3288
- });
3289
- }
3290
- })
3291
- .finally(() => {
3292
- if (!fetchAborted && stream && !stream.destroyed) {
3293
- stream.end();
3294
- }
3295
- });
3286
+ runFetchAllParts();
3296
3287
  }
3297
3288
  });
3298
3289
  } else {
3299
- fetchAllParts()
3300
- .catch(err => {
3301
- if (!fetchAborted && stream && !stream.destroyed) {
3302
- stream.emit('error', err);
3303
- } else {
3304
- // Log when error cannot be emitted to stream
3305
- this.log.warn({
3306
- msg: 'Download error after stream closed',
3307
- err,
3308
- fetchAborted,
3309
- streamDestroyed: stream?.destroyed,
3310
- cid: this.id
3311
- });
3312
- }
3313
- })
3314
- .finally(() => {
3315
- if (!fetchAborted && stream && !stream.destroyed) {
3316
- stream.end();
3317
- }
3318
- });
3290
+ runFetchAllParts();
3319
3291
  }
3320
3292
  });
3321
3293
 
@@ -3514,14 +3486,6 @@ class ImapFlow extends EventEmitter {
3514
3486
  // Process all locks in queue until empty
3515
3487
  let processedCount = 0;
3516
3488
  while (this.locks.length > 0) {
3517
- if (!this.locks.length) {
3518
- this.log.trace({
3519
- msg: 'Mailbox locking queue processed',
3520
- idling: this.idling
3521
- });
3522
- return;
3523
- }
3524
-
3525
3489
  // Yield to event loop periodically to prevent CPU blocking
3526
3490
  processedCount++;
3527
3491
  if (processedCount % 5 === 0) {
@@ -3568,46 +3532,46 @@ class ImapFlow extends EventEmitter {
3568
3532
  this.currentLock = lock;
3569
3533
  resolve({ path, release });
3570
3534
  break; // Stop processing; next lock waits for release()
3571
- } else {
3572
- try {
3573
- // Need to SELECT/EXAMINE a different mailbox
3574
- await this.mailboxOpen(path, options);
3575
- this.log.trace({
3576
- msg: 'Mailbox lock acquired [selected]',
3577
- path,
3578
- lockId,
3579
- idling: this.idling,
3580
- ...(options.description && { description: options.description })
3581
- });
3582
- this.currentLock = lock;
3583
- resolve({ path, release });
3584
- break; // Wait for this lock to be released
3585
- } catch (err) {
3586
- if (err.responseStatus === 'NO') {
3587
- // SELECT failed with NO -- verify whether the mailbox exists
3588
- // at all by running LIST. This sets mailboxMissing on the error
3589
- // so the caller can distinguish "doesn't exist" from other failures.
3590
- try {
3591
- let folders = await this.run('LIST', '', path, { listOnly: true });
3592
- if (!folders || !folders.length) {
3593
- err.mailboxMissing = true;
3594
- }
3595
- } catch (E) {
3596
- this.log.trace({ msg: 'Failed to verify failed mailbox', path, err: E });
3535
+ }
3536
+
3537
+ try {
3538
+ // Need to SELECT/EXAMINE a different mailbox
3539
+ await this.mailboxOpen(path, options);
3540
+ this.log.trace({
3541
+ msg: 'Mailbox lock acquired [selected]',
3542
+ path,
3543
+ lockId,
3544
+ idling: this.idling,
3545
+ ...(options.description && { description: options.description })
3546
+ });
3547
+ this.currentLock = lock;
3548
+ resolve({ path, release });
3549
+ break; // Wait for this lock to be released
3550
+ } catch (err) {
3551
+ if (err.responseStatus === 'NO') {
3552
+ // SELECT failed with NO -- verify whether the mailbox exists
3553
+ // at all by running LIST. This sets mailboxMissing on the error
3554
+ // so the caller can distinguish "doesn't exist" from other failures.
3555
+ try {
3556
+ let folders = await this.run('LIST', '', path, { listOnly: true });
3557
+ if (!folders || !folders.length) {
3558
+ err.mailboxMissing = true;
3597
3559
  }
3560
+ } catch (E) {
3561
+ this.log.trace({ msg: 'Failed to verify failed mailbox', path, err: E });
3598
3562
  }
3599
-
3600
- this.log.trace({
3601
- msg: 'Failed to acquire mailbox lock',
3602
- path,
3603
- lockId,
3604
- idling: this.idling,
3605
- ...(options.description && { description: options.description }),
3606
- err
3607
- });
3608
- reject(err);
3609
- // Continue to next lock in queue
3610
3563
  }
3564
+
3565
+ this.log.trace({
3566
+ msg: 'Failed to acquire mailbox lock',
3567
+ path,
3568
+ lockId,
3569
+ idling: this.idling,
3570
+ ...(options.description && { description: options.description }),
3571
+ err
3572
+ });
3573
+ reject(err);
3574
+ // Continue to next lock in queue
3611
3575
  }
3612
3576
  }
3613
3577
  } finally {
@@ -3691,16 +3655,15 @@ class ImapFlow extends EventEmitter {
3691
3655
  synteticLogger[level] = (...args) => {
3692
3656
  // using {logger:false} disables logging
3693
3657
  if (this.options.logger !== false) {
3694
- if (logger)
3695
- if (typeof mainLogger[level] !== 'function') {
3696
- // we are checking to make sure the level is supported.
3697
- // if it isn't supported but the level is error or fatal, log to console anyway.
3698
- if (level === 'fatal' || level === 'error') {
3699
- console.log(JSON.stringify(...args));
3700
- }
3701
- } else {
3702
- mainLogger[level](...args);
3658
+ if (typeof mainLogger[level] !== 'function') {
3659
+ // we are checking to make sure the level is supported.
3660
+ // if it isn't supported but the level is error or fatal, log to console anyway.
3661
+ if (level === 'fatal' || level === 'error') {
3662
+ console.log(JSON.stringify(...args));
3703
3663
  }
3664
+ } else {
3665
+ mainLogger[level](...args);
3666
+ }
3704
3667
  }
3705
3668
 
3706
3669
  if (this.emitLogs && args && args[0] && typeof args[0] === 'object') {
@@ -316,16 +316,10 @@ module.exports.searchCompiler = (connection, query) => {
316
316
 
317
317
  // NOT operator
318
318
  case 'NOT':
319
- {
320
- if (!params[term]) {
321
- break;
322
- }
323
-
324
- if (typeof params[term] === 'object') {
325
- attributes.push({ type: 'ATOM', value: 'NOT' });
326
- // Recursively process NOT conditions
327
- walk(params[term]);
328
- }
319
+ if (params[term] && typeof params[term] === 'object') {
320
+ attributes.push({ type: 'ATOM', value: 'NOT' });
321
+ // Recursively process NOT conditions
322
+ walk(params[term]);
329
323
  }
330
324
  break;
331
325
 
package/lib/tools.js CHANGED
@@ -77,7 +77,7 @@ const tools = {
77
77
  }
78
78
 
79
79
  // ensure namespace prefix if needed
80
- if (!skipNamespace && connection.namespace && connection.namespace.prefix && path.indexOf(connection.namespace.prefix) !== 0) {
80
+ if (!skipNamespace && connection.namespace && connection.namespace.prefix && !path.startsWith(connection.namespace.prefix)) {
81
81
  path = connection.namespace.prefix + path;
82
82
  }
83
83
 
@@ -111,7 +111,7 @@ const tools = {
111
111
  if (list && Array.isArray(list)) {
112
112
  list.forEach(val => {
113
113
  if (typeof val.value !== 'string') {
114
- return false;
114
+ return;
115
115
  }
116
116
  let capability = val.value.toUpperCase().trim();
117
117
 
@@ -120,7 +120,7 @@ const tools = {
120
120
  return;
121
121
  }
122
122
 
123
- if (capability.indexOf('APPENDLIMIT=') === 0) {
123
+ if (capability.startsWith('APPENDLIMIT=')) {
124
124
  let splitPos = capability.indexOf('=');
125
125
  let appendLimit = Number(capability.substr(splitPos + 1)) || 0;
126
126
  map.set('APPENDLIMIT', appendLimit);
@@ -205,8 +205,6 @@ const tools = {
205
205
  let cur = node.folders && node.folders.find(folder => folder.name === parent);
206
206
  if (cur) {
207
207
  node = cur;
208
- } else {
209
- // not yet set, will be created by the caller
210
208
  }
211
209
  }
212
210
 
@@ -294,7 +292,7 @@ const tools = {
294
292
 
295
293
  const color = bit0 | bit1 | bit2; // eslint-disable-line no-bitwise
296
294
 
297
- return FLAG_COLORS[color] || 'red'; // default to red for the unused \b111
295
+ return FLAG_COLORS[color] ?? 'red'; // default to red for the unused \b111
298
296
  },
299
297
 
300
298
  /**
@@ -306,23 +304,20 @@ const tools = {
306
304
  getColorFlags(color) {
307
305
  // Reverse mapping from a color name to the Apple Mail $MailFlagBit0/1/2 flags.
308
306
  // Returns an object with 'add' and 'remove' arrays so the caller can STORE +FLAGS/-FLAGS.
309
- const colorCode = color ? FLAG_COLORS.indexOf((color || '').toString().toLowerCase().trim()) : null;
310
- if (colorCode < 0 && colorCode !== null) {
307
+ const colorCode = color ? FLAG_COLORS.indexOf(color.toString().toLowerCase().trim()) : null;
308
+ if (colorCode === null || colorCode < 0) {
309
+ if (colorCode === null) {
310
+ // Remove color: remove \Flagged and all MailFlagBit flags
311
+ return { add: [], remove: ['\\Flagged', '$MailFlagBit0', '$MailFlagBit1', '$MailFlagBit2'] };
312
+ }
311
313
  return null;
312
314
  }
313
315
 
314
316
  // Decompose color index back into its 3-bit representation
315
- const bits = [];
316
- bits[0] = colorCode & 1; // eslint-disable-line no-bitwise
317
- bits[1] = colorCode & 2; // eslint-disable-line no-bitwise
318
- bits[2] = colorCode & 4; // eslint-disable-line no-bitwise
319
-
320
- // If colorCode is truthy (non-zero), add \Flagged; if zero/null, remove \Flagged
321
- let result = { add: colorCode ? ['\\Flagged'] : [], remove: colorCode ? [] : ['\\Flagged'] };
322
-
323
- // For each bit, add the corresponding $MailFlagBitN if set, remove it if unset
324
- for (let i = 0; i < bits.length; i++) {
325
- if (bits[i]) {
317
+ let result = { add: ['\\Flagged'], remove: [] };
318
+ for (let i = 0; i < 3; i++) {
319
+ // eslint-disable-next-line no-bitwise
320
+ if (colorCode & (1 << i)) {
326
321
  result.add.push(`$MailFlagBit${i}`);
327
322
  } else {
328
323
  result.remove.push(`$MailFlagBit${i}`);
@@ -529,7 +524,7 @@ const tools = {
529
524
  processName(name) {
530
525
  name = (name || '').toString();
531
526
  if (name.length > 2 && name.at(0) === '"' && name.at(-1) === '"') {
532
- name = name.replace(/^"|"$/g, '');
527
+ name = name.slice(1, -1);
533
528
  }
534
529
  return name;
535
530
  },
@@ -679,7 +674,7 @@ const tools = {
679
674
  value = params[key];
680
675
 
681
676
  // The first segment (*0*) may contain charset and language: charset'language'value
682
- if (nr === 0 && match[0].charAt(match[0].length - 1) === '*' && (match = value.match(/^([^']*)'[^']*'(.*)$/))) {
677
+ if (nr === 0 && match[0].at(-1) === '*' && (match = value.match(/^([^']*)'[^']*'(.*)$/))) {
683
678
  params[actualKey].charset = match[1] || 'utf-8';
684
679
  value = match[2];
685
680
  }
@@ -713,12 +708,11 @@ const tools = {
713
708
  value
714
709
  // fix invalidly encoded chars
715
710
  .replace(/[=?_\s]/g, s => {
716
- let c = s.charCodeAt(0).toString(16);
717
711
  if (s === ' ') {
718
712
  return '_';
719
- } else {
720
- return '%' + (c.length < 2 ? '0' : '') + c;
721
713
  }
714
+ let c = s.charCodeAt(0).toString(16);
715
+ return '%' + (c.length < 2 ? '0' : '') + c;
722
716
  })
723
717
  // change from urlencoding to percent encoding
724
718
  .replace(/%/g, '=') +
@@ -788,25 +782,25 @@ const tools = {
788
782
 
789
783
  // id
790
784
  if (node[i]) {
791
- curNode.id = ((node[i] || {}).value || '').toString();
785
+ curNode.id = (node[i].value || '').toString();
792
786
  }
793
787
  i++;
794
788
 
795
789
  // description
796
790
  if (node[i]) {
797
- curNode.description = ((node[i] || {}).value || '').toString();
791
+ curNode.description = (node[i].value || '').toString();
798
792
  }
799
793
  i++;
800
794
 
801
795
  // encoding
802
796
  if (node[i]) {
803
- curNode.encoding = ((node[i] || {}).value || '').toString().toLowerCase();
797
+ curNode.encoding = (node[i].value || '').toString().toLowerCase();
804
798
  }
805
799
  i++;
806
800
 
807
801
  // size
808
802
  if (node[i]) {
809
- curNode.size = Number((node[i] || {}).value || 0) || 0;
803
+ curNode.size = Number(node[i].value || 0) || 0;
810
804
  }
811
805
  i++;
812
806
 
@@ -834,7 +828,7 @@ const tools = {
834
828
 
835
829
  // line count
836
830
  if (node[i]) {
837
- curNode.lineCount = Number((node[i] || {}).value || 0) || 0;
831
+ curNode.lineCount = Number(node[i].value || 0) || 0;
838
832
  }
839
833
  i++;
840
834
  }
@@ -850,8 +844,7 @@ const tools = {
850
844
  } else {
851
845
  // correct structure, line count number is provided
852
846
  if (node[i]) {
853
- // line count
854
- curNode.lineCount = Number((node[i] || {}).value || 0) || 0;
847
+ curNode.lineCount = Number(node[i].value || 0) || 0;
855
848
  }
856
849
  i++;
857
850
  }
@@ -862,7 +855,7 @@ const tools = {
862
855
  // md5
863
856
  if (i < node.length - 1) {
864
857
  if (node[i]) {
865
- curNode.md5 = ((node[i] || {}).value || '').toString().toLowerCase();
858
+ curNode.md5 = (node[i].value || '').toString().toLowerCase();
866
859
  }
867
860
  i++;
868
861
  }
@@ -874,7 +867,7 @@ const tools = {
874
867
  // body disposition
875
868
  if (i < node.length - 1) {
876
869
  if (Array.isArray(node[i]) && node[i].length) {
877
- curNode.disposition = ((node[i][0] || {}).value || '').toString().toLowerCase();
870
+ curNode.disposition = ((node[i][0] && node[i][0].value) || '').toString().toLowerCase();
878
871
  if (Array.isArray(node[i][1])) {
879
872
  curNode.dispositionParameters = tools.getStructuredParams(node[i][1]);
880
873
  }
@@ -895,7 +888,7 @@ const tools = {
895
888
  // Errata: http://www.rfc-editor.org/errata_search.php?rfc=3501
896
889
  if (i < node.length - 1) {
897
890
  if (node[i]) {
898
- curNode.location = ((node[i] || {}).value || '').toString();
891
+ curNode.location = (node[i].value || '').toString();
899
892
  }
900
893
  }
901
894
 
@@ -990,7 +983,7 @@ const tools = {
990
983
  case '\\flagged':
991
984
  case '\\deleted':
992
985
  case '\\draft':
993
- // can not set or remove
986
+ // normalize capitalization (e.g., "\\seen" -> "\\Seen")
994
987
  return flag.toLowerCase().replace(/^\\./, c => c.toUpperCase());
995
988
  }
996
989
  return flag;
@@ -1048,7 +1041,7 @@ const tools = {
1048
1041
  */
1049
1042
  getDecoder(charset) {
1050
1043
  charset = (charset || 'ascii').toString().trim().toLowerCase();
1051
- if (/^jis|^iso-?2022-?jp|^EUCJP/i.test(charset)) {
1044
+ if (/^jis|^iso-?2022-?jp|^euc-?jp/.test(charset)) {
1052
1045
  // special case not supported by iconv-lite
1053
1046
  return new JPDecoder(charset);
1054
1047
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imapflow",
3
- "version": "1.2.13",
3
+ "version": "1.2.14",
4
4
  "description": "IMAP Client for Node",
5
5
  "main": "lib/imap-flow.js",
6
6
  "types": "lib/imap-flow.d.ts",
@@ -28,7 +28,7 @@
28
28
  "homepage": "https://imapflow.com/",
29
29
  "devDependencies": {
30
30
  "@eslint/js": "10.0.1",
31
- "@types/node": "25.3.5",
31
+ "@types/node": "25.5.0",
32
32
  "c8": "11.0.0",
33
33
  "eslint": "10.0.3",
34
34
  "eslint-config-nodemailer": "1.2.0",