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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +15 -0
- package/lib/charsets.js +1 -4
- package/lib/commands/append.js +1 -1
- package/lib/commands/copy.js +3 -24
- package/lib/commands/copyuid-parser.js +33 -0
- package/lib/commands/idle.js +5 -7
- package/lib/commands/list.js +1 -1
- package/lib/commands/move.js +4 -27
- package/lib/commands/store.js +1 -3
- package/lib/handler/imap-compiler.js +1 -3
- package/lib/handler/imap-formal-syntax.js +1 -7
- package/lib/handler/imap-parser.js +1 -1
- package/lib/handler/imap-stream.js +7 -13
- package/lib/handler/parser-instance.js +2 -2
- package/lib/handler/token-parser.js +9 -9
- package/lib/imap-flow.js +100 -141
- package/lib/search-compiler.js +4 -10
- package/lib/tools.js +29 -36
- package/package.json +2 -2
- package/test/charsets-test.js +161 -0
- package/test/commands-integration-test.js +61 -0
- package/test/connection-edge-cases-test.js +102 -16
- package/test/copyuid-parser-test.js +153 -0
- package/test/imap-compiler-test.js +66 -0
- package/test/imap-formal-syntax-test.js +275 -0
- package/test/imap-stream-edge-cases-test.js +268 -0
- package/test/imapflow-test.js +212 -0
- package/test/search-compiler-test.js +74 -0
- package/test/token-parser-test.js +374 -0
- package/test/tools-test.js +149 -2
- package/test/unhandled-rejection-test.js +150 -11
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
|
-
}
|
|
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
|
-
//
|
|
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 (
|
|
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
|
|
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
|
-
//
|
|
1788
|
-
//
|
|
1789
|
-
|
|
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
|
|
1832
|
-
//
|
|
1833
|
-
//
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
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
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
})
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
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
|
-
//
|
|
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 (
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
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') {
|
package/lib/search-compiler.js
CHANGED
|
@@ -316,16 +316,10 @@ module.exports.searchCompiler = (connection, query) => {
|
|
|
316
316
|
|
|
317
317
|
// NOT operator
|
|
318
318
|
case 'NOT':
|
|
319
|
-
{
|
|
320
|
-
|
|
321
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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]
|
|
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(
|
|
310
|
-
if (colorCode
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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.
|
|
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].
|
|
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 = (
|
|
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 = (
|
|
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 = (
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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 = (
|
|
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]
|
|
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 = (
|
|
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
|
-
//
|
|
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|^
|
|
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.
|
|
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.
|
|
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",
|