imapflow 1.2.12 → 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
@@ -40,6 +40,8 @@ const {
40
40
 
41
41
  const imapCommands = require('./imap-commands.js');
42
42
 
43
+ const noop = () => {};
44
+
43
45
  const CONNECT_TIMEOUT = 90 * 1000;
44
46
  const GREETING_TIMEOUT = 16 * 1000;
45
47
  const UPGRADE_TIMEOUT = 10 * 1000;
@@ -296,7 +298,6 @@ class ImapFlow extends EventEmitter {
296
298
  this.state = this.states.NOT_AUTHENTICATED;
297
299
 
298
300
  this.lockCounter = 0;
299
- this.currentLock = false;
300
301
 
301
302
  this.tagCounter = 0;
302
303
  this.requestTagMap = new Map();
@@ -405,7 +406,8 @@ class ImapFlow extends EventEmitter {
405
406
  let rid = BigInt('0x' + crypto.randomBytes(13).toString('hex')).toString(36);
406
407
  if (rid.length < 20) {
407
408
  rid = '0'.repeat(20 - rid.length) + rid;
408
- } else if (rid.length > 20) {
409
+ }
410
+ if (rid.length > 20) {
409
411
  rid = rid.substr(0, 20);
410
412
  }
411
413
  return rid;
@@ -552,24 +554,28 @@ class ImapFlow extends EventEmitter {
552
554
  });
553
555
  }
554
556
 
555
- async exec(command, attributes, options) {
557
+ exec(command, attributes, options) {
556
558
  if (this.state === this.states.LOGOUT || this.isClosed) {
557
559
  const error = new Error('Connection not available');
558
560
  error.code = 'NoConnection';
559
- throw error;
561
+ let p = Promise.reject(error);
562
+ p.catch(noop);
563
+ return p;
560
564
  }
561
565
 
562
566
  if (!this.socket || this.socket.destroyed) {
563
567
  let error = new Error('Connection closed');
564
568
  error.code = 'EConnectionClosed';
565
- throw error;
569
+ let p = Promise.reject(error);
570
+ p.catch(noop);
571
+ return p;
566
572
  }
567
573
 
568
574
  let tag = (++this.tagCounter).toString(16).toUpperCase();
569
575
 
570
576
  options = options || {};
571
577
 
572
- return new Promise((resolve, reject) => {
578
+ let promise = new Promise((resolve, reject) => {
573
579
  this.requestTagMap.set(tag, { command, attributes, options, resolve, reject });
574
580
  this.requestQueue.push({ tag, command, attributes, options });
575
581
  this.trySend().catch(err => {
@@ -577,6 +583,13 @@ class ImapFlow extends EventEmitter {
577
583
  reject(err);
578
584
  });
579
585
  });
586
+
587
+ // Prevent unhandled promise rejection if close() rejects this request
588
+ // via setImmediate before the caller's handler is attached. The rejection
589
+ // still propagates normally to the caller's await/.catch().
590
+ promise.catch(noop);
591
+
592
+ return promise;
580
593
  }
581
594
 
582
595
  // Resolves the handler for an untagged server response. IMAP untagged responses
@@ -1074,11 +1087,11 @@ class ImapFlow extends EventEmitter {
1074
1087
  let err = new Error('Server does not support STARTTLS');
1075
1088
  err.tlsFailed = true;
1076
1089
  throw err;
1077
- } else {
1078
- // Opportunistic STARTTLS. But it's not possible right now.
1079
- // Attention: Could be a downgrade attack.
1080
- return false;
1081
1090
  }
1091
+
1092
+ // Opportunistic STARTTLS. But it's not possible right now.
1093
+ // Attention: Could be a downgrade attack.
1094
+ return false;
1082
1095
  }
1083
1096
 
1084
1097
  /**
@@ -1497,7 +1510,7 @@ class ImapFlow extends EventEmitter {
1497
1510
  return false;
1498
1511
  }
1499
1512
 
1500
- if ((!this.mailbox && path) || (this.mailbox && path && !comparePaths(this, this.mailbox.path, path))) {
1513
+ if (!this.mailbox || !comparePaths(this, this.mailbox.path, path)) {
1501
1514
  return await this.mailboxOpen(path);
1502
1515
  }
1503
1516
 
@@ -1619,7 +1632,7 @@ class ImapFlow extends EventEmitter {
1619
1632
  }
1620
1633
  }
1621
1634
 
1622
- await new Promise((resolve, reject) => {
1635
+ let connectPromise = new Promise((resolve, reject) => {
1623
1636
  this.connectTimeout = setTimeout(() => {
1624
1637
  let err = new Error('Failed to establish connection in required time');
1625
1638
  err.code = 'CONNECT_TIMEOUT';
@@ -1713,6 +1726,12 @@ class ImapFlow extends EventEmitter {
1713
1726
  };
1714
1727
  this.socket.on('error', this._connectErrorHandler);
1715
1728
  });
1729
+
1730
+ // Prevent unhandled promise rejection if close() rejects the connect
1731
+ // promise via setImmediate. The rejection still propagates to the caller.
1732
+ connectPromise.catch(noop);
1733
+
1734
+ await connectPromise;
1716
1735
  }
1717
1736
 
1718
1737
  /**
@@ -2496,13 +2515,7 @@ class ImapFlow extends EventEmitter {
2496
2515
  * await client.append('INBOX', rawMessageBuffer, ['\\Seen'], new Date(2000, 1, 1));
2497
2516
  */
2498
2517
  async append(path, content, flags, idate) {
2499
- let response = await this.run('APPEND', path, content, flags, idate);
2500
-
2501
- if (!response) {
2502
- return false;
2503
- }
2504
-
2505
- return response;
2518
+ return (await this.run('APPEND', path, content, flags, idate)) || false;
2506
2519
  }
2507
2520
 
2508
2521
  /**
@@ -2589,13 +2602,7 @@ class ImapFlow extends EventEmitter {
2589
2602
  return;
2590
2603
  }
2591
2604
 
2592
- let response = await this.run('SEARCH', query, options);
2593
-
2594
- if (!response) {
2595
- return false;
2596
- }
2597
-
2598
- return response;
2605
+ return (await this.run('SEARCH', query, options)) || false;
2599
2606
  }
2600
2607
 
2601
2608
  /**
@@ -2734,9 +2741,8 @@ class ImapFlow extends EventEmitter {
2734
2741
  let entry = rowQueue.shift();
2735
2742
  if (entry.err) {
2736
2743
  return reject(entry.err);
2737
- } else {
2738
- return resolve(entry.value);
2739
2744
  }
2745
+ return resolve(entry.value);
2740
2746
  }
2741
2747
 
2742
2748
  if (finished) {
@@ -3238,6 +3244,29 @@ class ImapFlow extends EventEmitter {
3238
3244
  // stream and then fetch remaining chunks via fetchAllParts().
3239
3245
  // setImmediate ensures the caller gets the {meta, content} return
3240
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
+
3241
3270
  setImmediate(() => {
3242
3271
  let writeResult;
3243
3272
  try {
@@ -3254,49 +3283,11 @@ class ImapFlow extends EventEmitter {
3254
3283
  // Initial chunk filled the buffer, wait for drain
3255
3284
  stream.once('drain', () => {
3256
3285
  if (!fetchAborted) {
3257
- fetchAllParts()
3258
- .catch(err => {
3259
- if (!fetchAborted && stream && !stream.destroyed) {
3260
- stream.emit('error', err);
3261
- } else {
3262
- // Log when error cannot be emitted to stream
3263
- this.log.warn({
3264
- msg: 'Download error after stream closed',
3265
- err,
3266
- fetchAborted,
3267
- streamDestroyed: stream?.destroyed,
3268
- cid: this.id
3269
- });
3270
- }
3271
- })
3272
- .finally(() => {
3273
- if (!fetchAborted && stream && !stream.destroyed) {
3274
- stream.end();
3275
- }
3276
- });
3286
+ runFetchAllParts();
3277
3287
  }
3278
3288
  });
3279
3289
  } else {
3280
- fetchAllParts()
3281
- .catch(err => {
3282
- if (!fetchAborted && stream && !stream.destroyed) {
3283
- stream.emit('error', err);
3284
- } else {
3285
- // Log when error cannot be emitted to stream
3286
- this.log.warn({
3287
- msg: 'Download error after stream closed',
3288
- err,
3289
- fetchAborted,
3290
- streamDestroyed: stream?.destroyed,
3291
- cid: this.id
3292
- });
3293
- }
3294
- })
3295
- .finally(() => {
3296
- if (!fetchAborted && stream && !stream.destroyed) {
3297
- stream.end();
3298
- }
3299
- });
3290
+ runFetchAllParts();
3300
3291
  }
3301
3292
  });
3302
3293
 
@@ -3495,14 +3486,6 @@ class ImapFlow extends EventEmitter {
3495
3486
  // Process all locks in queue until empty
3496
3487
  let processedCount = 0;
3497
3488
  while (this.locks.length > 0) {
3498
- if (!this.locks.length) {
3499
- this.log.trace({
3500
- msg: 'Mailbox locking queue processed',
3501
- idling: this.idling
3502
- });
3503
- return;
3504
- }
3505
-
3506
3489
  // Yield to event loop periodically to prevent CPU blocking
3507
3490
  processedCount++;
3508
3491
  if (processedCount % 5 === 0) {
@@ -3549,46 +3532,46 @@ class ImapFlow extends EventEmitter {
3549
3532
  this.currentLock = lock;
3550
3533
  resolve({ path, release });
3551
3534
  break; // Stop processing; next lock waits for release()
3552
- } else {
3553
- try {
3554
- // Need to SELECT/EXAMINE a different mailbox
3555
- await this.mailboxOpen(path, options);
3556
- this.log.trace({
3557
- msg: 'Mailbox lock acquired [selected]',
3558
- path,
3559
- lockId,
3560
- idling: this.idling,
3561
- ...(options.description && { description: options.description })
3562
- });
3563
- this.currentLock = lock;
3564
- resolve({ path, release });
3565
- break; // Wait for this lock to be released
3566
- } catch (err) {
3567
- if (err.responseStatus === 'NO') {
3568
- // SELECT failed with NO -- verify whether the mailbox exists
3569
- // at all by running LIST. This sets mailboxMissing on the error
3570
- // so the caller can distinguish "doesn't exist" from other failures.
3571
- try {
3572
- let folders = await this.run('LIST', '', path, { listOnly: true });
3573
- if (!folders || !folders.length) {
3574
- err.mailboxMissing = true;
3575
- }
3576
- } catch (E) {
3577
- 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;
3578
3559
  }
3560
+ } catch (E) {
3561
+ this.log.trace({ msg: 'Failed to verify failed mailbox', path, err: E });
3579
3562
  }
3580
-
3581
- this.log.trace({
3582
- msg: 'Failed to acquire mailbox lock',
3583
- path,
3584
- lockId,
3585
- idling: this.idling,
3586
- ...(options.description && { description: options.description }),
3587
- err
3588
- });
3589
- reject(err);
3590
- // Continue to next lock in queue
3591
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
3592
3575
  }
3593
3576
  }
3594
3577
  } finally {
@@ -3626,7 +3609,7 @@ class ImapFlow extends EventEmitter {
3626
3609
  * lock.release();
3627
3610
  * }
3628
3611
  */
3629
- async getMailboxLock(path, options) {
3612
+ getMailboxLock(path, options) {
3630
3613
  options = options || {};
3631
3614
 
3632
3615
  path = normalizePath(this, path);
@@ -3645,10 +3628,16 @@ class ImapFlow extends EventEmitter {
3645
3628
  : null
3646
3629
  });
3647
3630
 
3648
- return await new Promise((resolve, reject) => {
3631
+ let lockPromise = new Promise((resolve, reject) => {
3649
3632
  this.locks.push({ resolve, reject, path, options, lockId });
3650
3633
  this.processLocks().catch(err => reject(err));
3651
3634
  });
3635
+
3636
+ // Prevent unhandled promise rejection if close() rejects this lock
3637
+ // via setImmediate. The rejection still propagates to the caller.
3638
+ lockPromise.catch(noop);
3639
+
3640
+ return lockPromise;
3652
3641
  }
3653
3642
 
3654
3643
  getLogger() {
@@ -3666,16 +3655,15 @@ class ImapFlow extends EventEmitter {
3666
3655
  synteticLogger[level] = (...args) => {
3667
3656
  // using {logger:false} disables logging
3668
3657
  if (this.options.logger !== false) {
3669
- if (logger)
3670
- if (typeof mainLogger[level] !== 'function') {
3671
- // we are checking to make sure the level is supported.
3672
- // if it isn't supported but the level is error or fatal, log to console anyway.
3673
- if (level === 'fatal' || level === 'error') {
3674
- console.log(JSON.stringify(...args));
3675
- }
3676
- } else {
3677
- 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));
3678
3663
  }
3664
+ } else {
3665
+ mainLogger[level](...args);
3666
+ }
3679
3667
  }
3680
3668
 
3681
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.12",
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,9 +28,9 @@
28
28
  "homepage": "https://imapflow.com/",
29
29
  "devDependencies": {
30
30
  "@eslint/js": "10.0.1",
31
- "@types/node": "25.3.3",
31
+ "@types/node": "25.5.0",
32
32
  "c8": "11.0.0",
33
- "eslint": "10.0.2",
33
+ "eslint": "10.0.3",
34
34
  "eslint-config-nodemailer": "1.2.0",
35
35
  "eslint-config-prettier": "10.1.8",
36
36
  "grunt": "1.6.1",
@@ -48,7 +48,7 @@
48
48
  "libbase64": "1.3.0",
49
49
  "libmime": "5.3.7",
50
50
  "libqp": "2.1.1",
51
- "nodemailer": "8.0.1",
51
+ "nodemailer": "8.0.2",
52
52
  "pino": "10.3.1",
53
53
  "socks": "2.8.7"
54
54
  }