imapflow 1.2.13 → 1.2.15

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;
@@ -585,7 +585,7 @@ class ImapFlow extends EventEmitter {
585
585
  });
586
586
 
587
587
  // Prevent unhandled promise rejection if close() rejects this request
588
- // via setImmediate before the caller's handler is attached. The rejection
588
+ // synchronously before the caller's handler is attached. The rejection
589
589
  // still propagates normally to the caller's await/.catch().
590
590
  promise.catch(noop);
591
591
 
@@ -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
 
@@ -1728,7 +1728,7 @@ class ImapFlow extends EventEmitter {
1728
1728
  });
1729
1729
 
1730
1730
  // Prevent unhandled promise rejection if close() rejects the connect
1731
- // promise via setImmediate. The rejection still propagates to the caller.
1731
+ // promise synchronously. The rejection still propagates to the caller.
1732
1732
  connectPromise.catch(noop);
1733
1733
 
1734
1734
  await connectPromise;
@@ -1784,9 +1784,10 @@ class ImapFlow extends EventEmitter {
1784
1784
  this.initialReject = false;
1785
1785
  let err = new Error('Unexpected close');
1786
1786
  err.code = `ClosedAfterConnect${this.secureConnection ? 'TLS' : 'Text'}`;
1787
- // Reject via setImmediate so the rest of close() cleanup runs first.
1788
- // The caller's connect() promise will reject after sockets are torn down.
1789
- setImmediate(() => reject(err));
1787
+ // Synchronous rejection is safe: connectPromise.catch(noop) is already
1788
+ // attached, so the rejection is observed immediately. close() is synchronous,
1789
+ // so all cleanup completes before any microtask rejection handler runs.
1790
+ reject(err);
1790
1791
  }
1791
1792
 
1792
1793
  if (typeof this.preCheck === 'function') {
@@ -1828,32 +1829,27 @@ class ImapFlow extends EventEmitter {
1828
1829
  return error;
1829
1830
  };
1830
1831
 
1831
- // Reject pending requests via setImmediate so this synchronous close()
1832
- // method finishes first. This prevents unhandled promise rejections that
1833
- // would occur if we rejected inline before callers set up .catch() handlers.
1834
- if (pendingRequests.length) {
1835
- let byeReason = this.byeReason;
1836
- setImmediate(() => {
1837
- for (let request of pendingRequests) {
1838
- request.reject(createNoConnectionError(byeReason));
1839
- }
1840
- });
1832
+ // Reject pending requests and locks synchronously. Each exec() and
1833
+ // getMailboxLock() promise already has .catch(noop) attached, so the
1834
+ // rejection is observed immediately and will not trigger
1835
+ // unhandledRejection. close() is synchronous, so all remaining cleanup
1836
+ // runs before any microtask rejection handler fires.
1837
+ let byeReason = this.byeReason;
1838
+
1839
+ for (let request of pendingRequests) {
1840
+ request.reject(createNoConnectionError(byeReason));
1841
1841
  }
1842
1842
 
1843
1843
  // Clear current lock - holder will see errors when they try operations
1844
1844
  this.currentLock = false;
1845
1845
 
1846
- // Reject pending mailbox locks via setImmediate for consistency
1847
1846
  if (this.locks && this.locks.length) {
1848
- let byeReason = this.byeReason;
1849
1847
  let pendingLocks = this.locks.splice(0); // Take all locks and clear the array
1850
- setImmediate(() => {
1851
- for (let lock of pendingLocks) {
1852
- if (typeof lock.reject === 'function') {
1853
- lock.reject(createNoConnectionError(byeReason));
1854
- }
1848
+ for (let lock of pendingLocks) {
1849
+ if (typeof lock.reject === 'function') {
1850
+ lock.reject(createNoConnectionError(byeReason));
1855
1851
  }
1856
- });
1852
+ }
1857
1853
  }
1858
1854
 
1859
1855
  // cleanup compression streams if they exist
@@ -2515,13 +2511,7 @@ class ImapFlow extends EventEmitter {
2515
2511
  * await client.append('INBOX', rawMessageBuffer, ['\\Seen'], new Date(2000, 1, 1));
2516
2512
  */
2517
2513
  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;
2514
+ return (await this.run('APPEND', path, content, flags, idate)) || false;
2525
2515
  }
2526
2516
 
2527
2517
  /**
@@ -2608,13 +2598,7 @@ class ImapFlow extends EventEmitter {
2608
2598
  return;
2609
2599
  }
2610
2600
 
2611
- let response = await this.run('SEARCH', query, options);
2612
-
2613
- if (!response) {
2614
- return false;
2615
- }
2616
-
2617
- return response;
2601
+ return (await this.run('SEARCH', query, options)) || false;
2618
2602
  }
2619
2603
 
2620
2604
  /**
@@ -2753,9 +2737,8 @@ class ImapFlow extends EventEmitter {
2753
2737
  let entry = rowQueue.shift();
2754
2738
  if (entry.err) {
2755
2739
  return reject(entry.err);
2756
- } else {
2757
- return resolve(entry.value);
2758
2740
  }
2741
+ return resolve(entry.value);
2759
2742
  }
2760
2743
 
2761
2744
  if (finished) {
@@ -3257,6 +3240,29 @@ class ImapFlow extends EventEmitter {
3257
3240
  // stream and then fetch remaining chunks via fetchAllParts().
3258
3241
  // setImmediate ensures the caller gets the {meta, content} return
3259
3242
  // value before streaming begins.
3243
+ let runFetchAllParts = () => {
3244
+ fetchAllParts()
3245
+ .catch(err => {
3246
+ if (!fetchAborted && stream && !stream.destroyed) {
3247
+ stream.emit('error', err);
3248
+ } else {
3249
+ // Log when error cannot be emitted to stream
3250
+ this.log.warn({
3251
+ msg: 'Download error after stream closed',
3252
+ err,
3253
+ fetchAborted,
3254
+ streamDestroyed: stream?.destroyed,
3255
+ cid: this.id
3256
+ });
3257
+ }
3258
+ })
3259
+ .finally(() => {
3260
+ if (!fetchAborted && stream && !stream.destroyed) {
3261
+ stream.end();
3262
+ }
3263
+ });
3264
+ };
3265
+
3260
3266
  setImmediate(() => {
3261
3267
  let writeResult;
3262
3268
  try {
@@ -3273,49 +3279,11 @@ class ImapFlow extends EventEmitter {
3273
3279
  // Initial chunk filled the buffer, wait for drain
3274
3280
  stream.once('drain', () => {
3275
3281
  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
- });
3282
+ runFetchAllParts();
3296
3283
  }
3297
3284
  });
3298
3285
  } 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
- });
3286
+ runFetchAllParts();
3319
3287
  }
3320
3288
  });
3321
3289
 
@@ -3514,14 +3482,6 @@ class ImapFlow extends EventEmitter {
3514
3482
  // Process all locks in queue until empty
3515
3483
  let processedCount = 0;
3516
3484
  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
3485
  // Yield to event loop periodically to prevent CPU blocking
3526
3486
  processedCount++;
3527
3487
  if (processedCount % 5 === 0) {
@@ -3568,46 +3528,46 @@ class ImapFlow extends EventEmitter {
3568
3528
  this.currentLock = lock;
3569
3529
  resolve({ path, release });
3570
3530
  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 });
3531
+ }
3532
+
3533
+ try {
3534
+ // Need to SELECT/EXAMINE a different mailbox
3535
+ await this.mailboxOpen(path, options);
3536
+ this.log.trace({
3537
+ msg: 'Mailbox lock acquired [selected]',
3538
+ path,
3539
+ lockId,
3540
+ idling: this.idling,
3541
+ ...(options.description && { description: options.description })
3542
+ });
3543
+ this.currentLock = lock;
3544
+ resolve({ path, release });
3545
+ break; // Wait for this lock to be released
3546
+ } catch (err) {
3547
+ if (err.responseStatus === 'NO') {
3548
+ // SELECT failed with NO -- verify whether the mailbox exists
3549
+ // at all by running LIST. This sets mailboxMissing on the error
3550
+ // so the caller can distinguish "doesn't exist" from other failures.
3551
+ try {
3552
+ let folders = await this.run('LIST', '', path, { listOnly: true });
3553
+ if (!folders || !folders.length) {
3554
+ err.mailboxMissing = true;
3597
3555
  }
3556
+ } catch (E) {
3557
+ this.log.trace({ msg: 'Failed to verify failed mailbox', path, err: E });
3598
3558
  }
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
3559
  }
3560
+
3561
+ this.log.trace({
3562
+ msg: 'Failed to acquire mailbox lock',
3563
+ path,
3564
+ lockId,
3565
+ idling: this.idling,
3566
+ ...(options.description && { description: options.description }),
3567
+ err
3568
+ });
3569
+ reject(err);
3570
+ // Continue to next lock in queue
3611
3571
  }
3612
3572
  }
3613
3573
  } finally {
@@ -3670,7 +3630,7 @@ class ImapFlow extends EventEmitter {
3670
3630
  });
3671
3631
 
3672
3632
  // Prevent unhandled promise rejection if close() rejects this lock
3673
- // via setImmediate. The rejection still propagates to the caller.
3633
+ // synchronously. The rejection still propagates to the caller.
3674
3634
  lockPromise.catch(noop);
3675
3635
 
3676
3636
  return lockPromise;
@@ -3691,16 +3651,15 @@ class ImapFlow extends EventEmitter {
3691
3651
  synteticLogger[level] = (...args) => {
3692
3652
  // using {logger:false} disables logging
3693
3653
  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);
3654
+ if (typeof mainLogger[level] !== 'function') {
3655
+ // we are checking to make sure the level is supported.
3656
+ // if it isn't supported but the level is error or fatal, log to console anyway.
3657
+ if (level === 'fatal' || level === 'error') {
3658
+ console.log(JSON.stringify(...args));
3703
3659
  }
3660
+ } else {
3661
+ mainLogger[level](...args);
3662
+ }
3704
3663
  }
3705
3664
 
3706
3665
  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.15",
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",