imapflow 1.6.5 → 1.6.6
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 +7 -0
- package/lib/commands/append.js +26 -4
- package/lib/commands/copyuid-parser.js +4 -2
- package/lib/commands/expunge.js +5 -2
- package/lib/commands/fetch.js +9 -3
- package/lib/commands/list.js +18 -38
- package/lib/commands/namespace.js +2 -2
- package/lib/commands/quota.js +10 -2
- package/lib/commands/search.js +54 -14
- package/lib/commands/select.js +81 -70
- package/lib/commands/status-fields.js +68 -0
- package/lib/commands/status.js +23 -61
- package/lib/handler/imap-stream.js +56 -2
- package/lib/handler/limits.js +16 -4
- package/lib/imap-flow.d.ts +22 -2
- package/lib/imap-flow.js +131 -61
- package/lib/jp-decoder.js +30 -5
- package/lib/limited-passthrough.js +19 -1
- package/lib/tools.js +190 -39
- package/package.json +4 -4
- package/test/commands-branches-test.js +4 -0
- package/test/commands-integration-test.js +683 -0
- package/test/copyuid-parser-test.js +20 -0
- package/test/imap-flow-coverage-test.js +4 -2
- package/test/imap-flow-fetch-download-test.js +26 -0
- package/test/imap-flow-methods-test.js +92 -0
- package/test/imap-stream-edge-cases-test.js +136 -0
- package/test/jp-decoder-test.js +57 -0
- package/test/limited-passthrough-test.js +24 -0
- package/test/parser-limits-test.js +18 -0
- package/test/reliability-improvements-test.js +3 -3
- package/test/timer-policy-test.js +27 -1
- package/test/tools-test.js +151 -2
package/lib/tools.js
CHANGED
|
@@ -11,11 +11,26 @@ const iconv = require('iconv-lite');
|
|
|
11
11
|
|
|
12
12
|
const FLAG_COLORS = ['red', 'orange', 'yellow', 'green', 'blue', 'purple', 'grey'];
|
|
13
13
|
|
|
14
|
-
// Upper bound for expanding
|
|
15
|
-
//
|
|
16
|
-
//
|
|
14
|
+
// Upper bound for expanding server-supplied sequence ranges (see expandRange). 2^24
|
|
15
|
+
// entries in total is far beyond any legitimate mailbox while keeping the worst-case
|
|
16
|
+
// expansion of a hostile range set bounded.
|
|
17
17
|
const EXPANDED_RANGE_LIMIT = 0x1000000;
|
|
18
18
|
|
|
19
|
+
// Digit bounds for untrusted numeric values in server responses. UIDs, UIDVALIDITY and
|
|
20
|
+
// message counts are 32-bit unsigned (nz-number in the RFC 9051 grammar, so at most 10
|
|
21
|
+
// digits); MODSEQ and number64 values are 63-bit unsigned (RFC 7162, RFC 9051), at most
|
|
22
|
+
// 19 digits. The bound is checked before BigInt()/Number(): a response line may carry up
|
|
23
|
+
// to maxLineLength digits, and BigInt() on a multi-megabyte digit run costs hundreds of
|
|
24
|
+
// milliseconds of non-yielding CPU.
|
|
25
|
+
const MAX_UINT32_DIGITS = 10;
|
|
26
|
+
const MAX_NUMBER64_DIGITS = 19;
|
|
27
|
+
|
|
28
|
+
// Object keys that reach through the prototype chain when assigned to, or resolve to an
|
|
29
|
+
// inherited member when read. Server-controlled strings become keys in several places
|
|
30
|
+
// (STATUS items, QUOTA resources, BODYSTRUCTURE parameters, FETCH body part names), so they
|
|
31
|
+
// all consult this one set rather than each carrying its own list.
|
|
32
|
+
const UNSAFE_OBJECT_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
33
|
+
|
|
19
34
|
// Extensions that RFC 9051 (IMAP4rev2) folds into the base protocol (Appendix E).
|
|
20
35
|
// When IMAP4rev2 is active, these are available even without their own capability
|
|
21
36
|
// token. BINARY is deliberately excluded - RFC 9051 only folds in the FETCH side,
|
|
@@ -252,7 +267,8 @@ const tools = {
|
|
|
252
267
|
|
|
253
268
|
if (list && Array.isArray(list)) {
|
|
254
269
|
list.forEach(val => {
|
|
255
|
-
|
|
270
|
+
// any entry can be a parsed NIL
|
|
271
|
+
if (!val || typeof val.value !== 'string') {
|
|
256
272
|
return;
|
|
257
273
|
}
|
|
258
274
|
let capability = val.value.toUpperCase().trim();
|
|
@@ -269,8 +285,7 @@ const tools = {
|
|
|
269
285
|
|
|
270
286
|
if (capability.startsWith('APPENDLIMIT=')) {
|
|
271
287
|
let splitPos = capability.indexOf('=');
|
|
272
|
-
|
|
273
|
-
map.set('APPENDLIMIT', appendLimit);
|
|
288
|
+
map.set('APPENDLIMIT', tools.parseUintValue(capability.substr(splitPos + 1)) || 0);
|
|
274
289
|
return;
|
|
275
290
|
}
|
|
276
291
|
|
|
@@ -493,7 +508,9 @@ const tools = {
|
|
|
493
508
|
async formatMessageResponse(untagged, mailbox) {
|
|
494
509
|
let map = {};
|
|
495
510
|
|
|
496
|
-
|
|
511
|
+
// The sequence number indexes into mailbox state, so an unusable one is dropped rather
|
|
512
|
+
// than coerced to NaN or Infinity
|
|
513
|
+
map.seq = tools.parseUintValue(untagged.command, MAX_UINT32_DIGITS) || undefined;
|
|
497
514
|
|
|
498
515
|
let key;
|
|
499
516
|
let attributes = (untagged.attributes && untagged.attributes[1]) || [];
|
|
@@ -538,15 +555,14 @@ const tools = {
|
|
|
538
555
|
}
|
|
539
556
|
};
|
|
540
557
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
};
|
|
558
|
+
// NIL (parsed as null) and other non-array values yield an empty array, so callers
|
|
559
|
+
// can safely index into the result. RFC 8474 allows e.g. `THREADID NIL` when the
|
|
560
|
+
// server has no thread relation to report.
|
|
561
|
+
let getArray = attribute => tools.getStringList(attribute);
|
|
562
|
+
|
|
563
|
+
// Counts, sizes and UIDs are written into mailbox state and into range
|
|
564
|
+
// computations, so only a bounded decimal run is usable - see parseUintValue().
|
|
565
|
+
let getUint = (attribute, maxDigits) => tools.parseUintValue(getString(attribute), maxDigits);
|
|
550
566
|
|
|
551
567
|
switch (key) {
|
|
552
568
|
case 'body[]':
|
|
@@ -555,7 +571,9 @@ const tools = {
|
|
|
555
571
|
break;
|
|
556
572
|
|
|
557
573
|
case 'uid':
|
|
558
|
-
|
|
574
|
+
// A UID feeds mailbox.uidNext one line below, and from there every range
|
|
575
|
+
// computation, so an unusable one is dropped rather than coerced
|
|
576
|
+
map.uid = getUint(attribute, MAX_UINT32_DIGITS) || undefined;
|
|
559
577
|
// If the UID we just saw is >= the mailbox's uidNext, bump uidNext.
|
|
560
578
|
// This keeps the local uidNext estimate current without requiring a
|
|
561
579
|
// separate STATUS command, handling cases where new messages arrived
|
|
@@ -565,14 +583,22 @@ const tools = {
|
|
|
565
583
|
}
|
|
566
584
|
break;
|
|
567
585
|
|
|
568
|
-
case 'modseq':
|
|
569
|
-
|
|
586
|
+
case 'modseq': {
|
|
587
|
+
// BigInt() throws on a non-numeric or missing value, and the throw
|
|
588
|
+
// drops the whole message from the result set - so a malformed
|
|
589
|
+
// MODSEQ from the server must be skipped, not surfaced.
|
|
590
|
+
let modseq = tools.parseBigIntValue(getArray(attribute)[0]);
|
|
591
|
+
if (modseq === false) {
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
map.modseq = modseq;
|
|
570
595
|
// Similarly, keep the local highestModseq estimate up to date.
|
|
571
596
|
// This is critical for CONDSTORE/QRESYNC delta syncing.
|
|
572
597
|
if (map.modseq && (!mailbox.highestModseq || mailbox.highestModseq < map.modseq)) {
|
|
573
598
|
mailbox.highestModseq = map.modseq;
|
|
574
599
|
}
|
|
575
600
|
break;
|
|
601
|
+
}
|
|
576
602
|
|
|
577
603
|
case 'emailid':
|
|
578
604
|
// OBJECTID extension (RFC 8474): server-assigned stable email identifier
|
|
@@ -599,7 +625,7 @@ const tools = {
|
|
|
599
625
|
break;
|
|
600
626
|
|
|
601
627
|
case 'rfc822.size':
|
|
602
|
-
map.size =
|
|
628
|
+
map.size = getUint(attribute) || 0;
|
|
603
629
|
break;
|
|
604
630
|
|
|
605
631
|
case 'flags':
|
|
@@ -735,6 +761,11 @@ const tools = {
|
|
|
735
761
|
return []
|
|
736
762
|
.concat(list || [])
|
|
737
763
|
.map(addr => {
|
|
764
|
+
if (!addr) {
|
|
765
|
+
// A NIL entry inside an address list: skip it instead of
|
|
766
|
+
// throwing on the dereference and dropping the message
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
738
769
|
let address = (getStrValue(addr[2]) || '') + '@' + (getStrValue(addr[3]) || '');
|
|
739
770
|
if (address === '@') {
|
|
740
771
|
address = '';
|
|
@@ -744,7 +775,7 @@ const tools = {
|
|
|
744
775
|
address
|
|
745
776
|
};
|
|
746
777
|
})
|
|
747
|
-
.filter(addr => addr.name || addr.address);
|
|
778
|
+
.filter(addr => addr && (addr.name || addr.address));
|
|
748
779
|
},
|
|
749
780
|
envelope = {};
|
|
750
781
|
|
|
@@ -813,7 +844,12 @@ const tools = {
|
|
|
813
844
|
// BODYSTRUCTURE parameters come as flat key/value pairs: [key1, val1, key2, val2, ...]
|
|
814
845
|
[].concat(arr || []).forEach((val, j) => {
|
|
815
846
|
if (j % 2) {
|
|
816
|
-
|
|
847
|
+
// Parameter names are server-controlled. The load-bearing check is the one in
|
|
848
|
+
// the continuation pass below, where the value is an object; here the value is
|
|
849
|
+
// always a string, which the __proto__ setter ignores anyway.
|
|
850
|
+
if (!tools.isUnsafeKey(key)) {
|
|
851
|
+
params[key] = libmime.decodeWords(((val && val.value) || '').toString());
|
|
852
|
+
}
|
|
817
853
|
} else {
|
|
818
854
|
key = ((val && val.value) || '').toString().toLowerCase();
|
|
819
855
|
}
|
|
@@ -848,6 +884,14 @@ const tools = {
|
|
|
848
884
|
actualKey = key.substr(0, match.index).toLowerCase();
|
|
849
885
|
nr = Number(match[2]) || 0;
|
|
850
886
|
|
|
887
|
+
if (tools.isUnsafeKey(actualKey)) {
|
|
888
|
+
// A continuation key like "__proto__*0*" would group under "__proto__":
|
|
889
|
+
// params['__proto__'] resolves to Object.prototype, so the grouping
|
|
890
|
+
// writes below would mutate it (process-wide pollution). Drop the part.
|
|
891
|
+
delete params[key];
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
|
|
851
895
|
if (!params[actualKey] || typeof params[actualKey] !== 'object') {
|
|
852
896
|
params[actualKey] = {
|
|
853
897
|
charset: false,
|
|
@@ -1198,49 +1242,143 @@ const tools = {
|
|
|
1198
1242
|
return Number.isSafeInteger(value) && value > 0 && value <= 0xffffffff;
|
|
1199
1243
|
},
|
|
1200
1244
|
|
|
1245
|
+
/**
|
|
1246
|
+
* Checks that an untrusted response value is a pure decimal digit run no longer than
|
|
1247
|
+
* the given bound.
|
|
1248
|
+
*
|
|
1249
|
+
* `!isNaN(value)` is not usable for this: it also passes '1e5', ' 12 ', '0x10' and
|
|
1250
|
+
* 'Infinity'. BigInt() throws on all of them and Number() silently returns a value the
|
|
1251
|
+
* grammar never allowed, so both are wrong in a response handler that is only trying to
|
|
1252
|
+
* read one field. The length bound is checked before the pattern so an arbitrarily long
|
|
1253
|
+
* digit run is rejected without any conversion work.
|
|
1254
|
+
*
|
|
1255
|
+
* @param {*} value - Raw value from the response.
|
|
1256
|
+
* @param {Number} maxDigits - Maximum number of digits accepted.
|
|
1257
|
+
* @returns {Boolean} True if the value is a decimal string within the bound.
|
|
1258
|
+
*/
|
|
1259
|
+
isDecimalString(value, maxDigits) {
|
|
1260
|
+
return typeof value === 'string' && value.length > 0 && value.length <= maxDigits && /^[0-9]+$/.test(value);
|
|
1261
|
+
},
|
|
1262
|
+
|
|
1263
|
+
/**
|
|
1264
|
+
* Checks whether a server-supplied string is unsafe to use as a key on a plain object.
|
|
1265
|
+
* Assigning "__proto__" writes through the prototype setter instead of creating an own
|
|
1266
|
+
* property, and reading "constructor" or "prototype" resolves to an inherited member.
|
|
1267
|
+
*
|
|
1268
|
+
* @param {*} key - Candidate key from a server response.
|
|
1269
|
+
* @returns {Boolean} True if the key must not be used.
|
|
1270
|
+
*/
|
|
1271
|
+
isUnsafeKey(key) {
|
|
1272
|
+
return UNSAFE_OBJECT_KEYS.has(key);
|
|
1273
|
+
},
|
|
1274
|
+
|
|
1275
|
+
/**
|
|
1276
|
+
* Reads a parsed attribute list of atoms or strings (a flag list, a capability list) into
|
|
1277
|
+
* an array of strings. Any element can be a parsed NIL, and the list itself can be NIL,
|
|
1278
|
+
* so both levels are guarded here rather than at each call site.
|
|
1279
|
+
*
|
|
1280
|
+
* @param {*} list - Parsed attribute list from a response.
|
|
1281
|
+
* @returns {String[]} The string values, in order, with unusable entries dropped.
|
|
1282
|
+
*/
|
|
1283
|
+
getStringList(list) {
|
|
1284
|
+
if (!Array.isArray(list)) {
|
|
1285
|
+
return [];
|
|
1286
|
+
}
|
|
1287
|
+
return list.map(entry => (entry && typeof entry.value === 'string' ? entry.value : false)).filter(entry => entry);
|
|
1288
|
+
},
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* Parses an untrusted decimal value from a server response into a BigInt.
|
|
1292
|
+
*
|
|
1293
|
+
* @param {*} value - Raw value from the response.
|
|
1294
|
+
* @param {Number} [maxDigits=MAX_NUMBER64_DIGITS] - Maximum number of digits accepted.
|
|
1295
|
+
* @returns {BigInt|false} The parsed value, or false when it is not usable.
|
|
1296
|
+
*/
|
|
1297
|
+
parseBigIntValue(value, maxDigits) {
|
|
1298
|
+
if (!tools.isDecimalString(value, maxDigits || MAX_NUMBER64_DIGITS)) {
|
|
1299
|
+
return false;
|
|
1300
|
+
}
|
|
1301
|
+
return BigInt(value);
|
|
1302
|
+
},
|
|
1303
|
+
|
|
1304
|
+
/**
|
|
1305
|
+
* Parses an untrusted decimal value from a server response into a Number. Values beyond
|
|
1306
|
+
* the safe integer range are rejected rather than rounded: a silently rounded count or
|
|
1307
|
+
* UID corrupts every range computation derived from it.
|
|
1308
|
+
*
|
|
1309
|
+
* @param {*} value - Raw value from the response.
|
|
1310
|
+
* @param {Number} [maxDigits=MAX_NUMBER64_DIGITS] - Maximum number of digits accepted.
|
|
1311
|
+
* @returns {Number|false} The parsed value, or false when it is not usable.
|
|
1312
|
+
*/
|
|
1313
|
+
parseUintValue(value, maxDigits) {
|
|
1314
|
+
if (!tools.isDecimalString(value, maxDigits || MAX_NUMBER64_DIGITS)) {
|
|
1315
|
+
return false;
|
|
1316
|
+
}
|
|
1317
|
+
let num = Number(value);
|
|
1318
|
+
return Number.isSafeInteger(num) ? num : false;
|
|
1319
|
+
},
|
|
1320
|
+
|
|
1201
1321
|
/**
|
|
1202
1322
|
* Expands an IMAP sequence range string (e.g. "1:3,5,7:9") into an array of numbers.
|
|
1203
1323
|
*
|
|
1204
1324
|
* Entries with endpoints that are not valid nz-numbers are skipped - the input
|
|
1205
1325
|
* may come from an untrusted server, and 'Infinity' or similar garbage would
|
|
1206
|
-
* otherwise loop without bound.
|
|
1207
|
-
* EXPANDED_RANGE_LIMIT entries: legitimate responses never reach the limit
|
|
1208
|
-
* (the mailbox would need that many messages), while
|
|
1209
|
-
*
|
|
1326
|
+
* otherwise loop without bound. The whole set is expanded to at most
|
|
1327
|
+
* EXPANDED_RANGE_LIMIT entries in total: legitimate responses never reach the limit
|
|
1328
|
+
* (the mailbox would need that many messages), while hostile input is cut off
|
|
1329
|
+
* instead of exhausting memory. The total is capped, not just each range -
|
|
1330
|
+
* otherwise "1:16777216,1:16777216,..." would multiply the per-range bound by an
|
|
1331
|
+
* unbounded number of ranges.
|
|
1210
1332
|
*
|
|
1211
1333
|
* @param {String} range - IMAP sequence range string
|
|
1212
1334
|
* @returns {Number[]} Array of expanded sequence numbers
|
|
1213
1335
|
*/
|
|
1214
1336
|
expandRange(range) {
|
|
1215
|
-
|
|
1337
|
+
let result = [];
|
|
1338
|
+
// Callers pass whatever the response parser produced for the sequence set, and a
|
|
1339
|
+
// malformed response can leave that as `false` (e.g. a VANISHED response carrying
|
|
1340
|
+
// only the (EARLIER) tag). Nothing to expand then, and throwing here would abort
|
|
1341
|
+
// the handler for the rest of the response.
|
|
1342
|
+
if (typeof range !== 'string') {
|
|
1343
|
+
return result;
|
|
1344
|
+
}
|
|
1345
|
+
for (let entry of range.split(',')) {
|
|
1346
|
+
if (result.length >= EXPANDED_RANGE_LIMIT) {
|
|
1347
|
+
break;
|
|
1348
|
+
}
|
|
1216
1349
|
entry = entry.trim();
|
|
1217
1350
|
let colon = entry.indexOf(':');
|
|
1218
1351
|
if (colon < 0) {
|
|
1219
1352
|
let value = Number(entry);
|
|
1220
|
-
|
|
1353
|
+
if (tools.isValidSequenceValue(value)) {
|
|
1354
|
+
result.push(value);
|
|
1355
|
+
}
|
|
1356
|
+
continue;
|
|
1221
1357
|
}
|
|
1222
1358
|
let first = Number(entry.substr(0, colon));
|
|
1223
1359
|
let second = Number(entry.substr(colon + 1));
|
|
1224
1360
|
if (!tools.isValidSequenceValue(first) || !tools.isValidSequenceValue(second)) {
|
|
1225
|
-
|
|
1361
|
+
continue;
|
|
1226
1362
|
}
|
|
1227
1363
|
if (first === second) {
|
|
1228
|
-
|
|
1364
|
+
result.push(first);
|
|
1365
|
+
continue;
|
|
1229
1366
|
}
|
|
1230
|
-
|
|
1367
|
+
// Remaining total budget doubles as the per-range bound
|
|
1368
|
+
let remaining = EXPANDED_RANGE_LIMIT - result.length;
|
|
1231
1369
|
if (first < second) {
|
|
1232
|
-
let last = Math.min(second, first +
|
|
1370
|
+
let last = Math.min(second, first + remaining - 1);
|
|
1233
1371
|
for (let i = first; i <= last; i++) {
|
|
1234
|
-
|
|
1372
|
+
result.push(i);
|
|
1235
1373
|
}
|
|
1236
1374
|
} else {
|
|
1237
|
-
let last = Math.max(second, first -
|
|
1375
|
+
let last = Math.max(second, first - remaining + 1);
|
|
1238
1376
|
for (let i = first; i >= last; i--) {
|
|
1239
|
-
|
|
1377
|
+
result.push(i);
|
|
1240
1378
|
}
|
|
1241
1379
|
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1380
|
+
}
|
|
1381
|
+
return result;
|
|
1244
1382
|
},
|
|
1245
1383
|
|
|
1246
1384
|
/**
|
|
@@ -1248,13 +1386,17 @@ const tools = {
|
|
|
1248
1386
|
* charset decoder for JIS/ISO-2022-JP, otherwise delegates to iconv-lite.
|
|
1249
1387
|
*
|
|
1250
1388
|
* @param {String} [charset='ascii'] - Character set name
|
|
1389
|
+
* @param {Number} [maxBytes] - Bound for the bytes the decoder may buffer. Only
|
|
1390
|
+
* relevant for the Japanese decoder, which must buffer its whole input before
|
|
1391
|
+
* it can decode: without the bound a server could defeat a caller's maxBytes
|
|
1392
|
+
* download limit simply by labelling the part with a Japanese charset.
|
|
1251
1393
|
* @returns {Object} A stream decoder (Transform stream) for the charset
|
|
1252
1394
|
*/
|
|
1253
|
-
getDecoder(charset) {
|
|
1395
|
+
getDecoder(charset, maxBytes) {
|
|
1254
1396
|
charset = (charset || 'ascii').toString().trim().toLowerCase();
|
|
1255
1397
|
if (/^jis|^iso-?2022-?jp|^euc-?jp/.test(charset)) {
|
|
1256
1398
|
// special case not supported by iconv-lite
|
|
1257
|
-
return new JPDecoder(charset);
|
|
1399
|
+
return new JPDecoder(charset, maxBytes);
|
|
1258
1400
|
}
|
|
1259
1401
|
|
|
1260
1402
|
return iconv.decodeStream(charset);
|
|
@@ -1302,3 +1444,12 @@ const tools = {
|
|
|
1302
1444
|
};
|
|
1303
1445
|
|
|
1304
1446
|
module.exports = tools;
|
|
1447
|
+
|
|
1448
|
+
// Shared bound for expanding server-supplied sequence sets (see expandRange). Exported so
|
|
1449
|
+
// other places that expand server sequences (e.g. ESEARCH ALL in commands/search.js) can
|
|
1450
|
+
// apply the same absolute ceiling instead of inventing their own.
|
|
1451
|
+
module.exports.EXPANDED_RANGE_LIMIT = EXPANDED_RANGE_LIMIT;
|
|
1452
|
+
|
|
1453
|
+
// Exported so call sites can ask for the tighter 32-bit bound where the grammar requires it
|
|
1454
|
+
// (UID, UIDVALIDITY, message counts) instead of the parsers' 63-bit default.
|
|
1455
|
+
module.exports.MAX_UINT32_DIGITS = MAX_UINT32_DIGITS;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imapflow",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.6",
|
|
4
4
|
"description": "IMAP Client for Node",
|
|
5
5
|
"main": "lib/imap-flow.js",
|
|
6
6
|
"types": "lib/imap-flow.d.ts",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"eslint": "10.8.0",
|
|
36
36
|
"eslint-config-nodemailer": "1.2.0",
|
|
37
37
|
"eslint-config-prettier": "10.1.8",
|
|
38
|
-
"grunt": "1.6.
|
|
38
|
+
"grunt": "1.6.3",
|
|
39
39
|
"grunt-cli": "1.5.0",
|
|
40
40
|
"grunt-contrib-nodeunit": "5.0.0",
|
|
41
41
|
"grunt-eslint": "26.0.0",
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"typescript": "7.0.2"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@zone-eu/mailsplit": "5.4.
|
|
47
|
+
"@zone-eu/mailsplit": "5.4.15",
|
|
48
48
|
"encoding-japanese": "2.2.0",
|
|
49
49
|
"iconv-lite": "0.7.3",
|
|
50
50
|
"libbase64": "1.3.0",
|
|
51
|
-
"libmime": "5.4.
|
|
51
|
+
"libmime": "5.4.2",
|
|
52
52
|
"libqp": "2.1.1",
|
|
53
53
|
"pino": "10.3.1",
|
|
54
54
|
"socks": "2.8.9"
|
|
@@ -56,6 +56,10 @@ const createMockConnection = (overrides = {}) => {
|
|
|
56
56
|
// A live transport: command implementations that guard against polling or writing on a
|
|
57
57
|
// dead connection (idle.js) need this to look established.
|
|
58
58
|
socket: overrides.socket || { destroyed: false },
|
|
59
|
+
// Mirrors ImapFlow.throttleWait(): resolves false on normal expiry, true when close()
|
|
60
|
+
// aborted the wait. The mock resolves immediately so throttle retries stay fast.
|
|
61
|
+
throttleWait: overrides.throttleWait || (async () => false),
|
|
62
|
+
createNoConnectionError: overrides.createNoConnectionError || (() => Object.assign(new Error('Connection not available'), { code: 'NoConnection' })),
|
|
59
63
|
messageFlagsAdd: overrides.messageFlagsAdd || (async () => {}),
|
|
60
64
|
messageCopy: overrides.messageCopy || (async () => {}),
|
|
61
65
|
messageDelete: overrides.messageDelete || (async () => {}),
|