imapflow 1.2.8 → 1.2.10

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.
Files changed (58) hide show
  1. package/.ncurc.js +1 -1
  2. package/.release-please-manifest.json +1 -1
  3. package/CHANGELOG.md +14 -0
  4. package/README.md +36 -58
  5. package/eslint.config.js +18 -16
  6. package/lib/charsets.js +15 -0
  7. package/lib/commands/append.js +62 -54
  8. package/lib/commands/authenticate.js +99 -52
  9. package/lib/commands/capability.js +12 -2
  10. package/lib/commands/close.js +11 -1
  11. package/lib/commands/compress.js +10 -1
  12. package/lib/commands/copy.js +18 -1
  13. package/lib/commands/create.js +15 -2
  14. package/lib/commands/delete.js +10 -1
  15. package/lib/commands/enable.js +12 -1
  16. package/lib/commands/expunge.js +18 -2
  17. package/lib/commands/fetch.js +39 -4
  18. package/lib/commands/id.js +22 -3
  19. package/lib/commands/idle.js +39 -4
  20. package/lib/commands/list.js +86 -48
  21. package/lib/commands/login.js +12 -1
  22. package/lib/commands/logout.js +11 -2
  23. package/lib/commands/move.js +17 -1
  24. package/lib/commands/namespace.js +32 -2
  25. package/lib/commands/noop.js +6 -1
  26. package/lib/commands/quota.js +33 -14
  27. package/lib/commands/rename.js +13 -1
  28. package/lib/commands/search.js +16 -1
  29. package/lib/commands/select.js +76 -33
  30. package/lib/commands/starttls.js +6 -1
  31. package/lib/commands/status.js +64 -52
  32. package/lib/commands/store.js +27 -4
  33. package/lib/commands/subscribe.js +7 -1
  34. package/lib/commands/unsubscribe.js +7 -1
  35. package/lib/handler/imap-compiler.js +44 -2
  36. package/lib/handler/imap-formal-syntax.js +51 -3
  37. package/lib/handler/imap-handler.js +8 -0
  38. package/lib/handler/imap-parser.js +23 -2
  39. package/lib/handler/imap-stream.js +84 -31
  40. package/lib/handler/parser-instance.js +61 -1
  41. package/lib/handler/token-parser.js +66 -9
  42. package/lib/imap-commands.js +11 -0
  43. package/lib/imap-flow.d.ts +6 -0
  44. package/lib/imap-flow.js +175 -46
  45. package/lib/jp-decoder.js +10 -0
  46. package/lib/limited-passthrough.js +12 -5
  47. package/lib/proxy-connection.js +18 -12
  48. package/lib/search-compiler.js +3 -11
  49. package/lib/special-use.js +23 -16
  50. package/lib/tools.js +218 -13
  51. package/package.json +5 -17
  52. package/test/commands-integration-test.js +33 -0
  53. package/test/connection-edge-cases-test.js +105 -0
  54. package/test/special-use-test.js +32 -0
  55. package/.babelrc +0 -6
  56. package/.eslintrc +0 -16
  57. package/assets/favicon.ico +0 -0
  58. package/jsdoc.json +0 -28
@@ -1464,3 +1464,108 @@ module.exports['Connection Edge: connect throws if called twice'] = async test =
1464
1464
 
1465
1465
  test.done();
1466
1466
  };
1467
+
1468
+ // Helper to create a mock client with compression enabled
1469
+ async function setupCompressedClient() {
1470
+ let client = new ImapFlow({
1471
+ host: 'imap.example.com',
1472
+ port: 993,
1473
+ auth: { user: 'test', pass: 'test' }
1474
+ });
1475
+
1476
+ let mockSocket = new EventEmitter();
1477
+ mockSocket.pipe = dest => dest;
1478
+ mockSocket.unpipe = () => {};
1479
+ mockSocket.destroy = () => {};
1480
+ mockSocket.destroyed = false;
1481
+ client.socket = mockSocket;
1482
+ client.streamer = new EventEmitter();
1483
+
1484
+ client.run = async command => {
1485
+ if (command === 'COMPRESS') {
1486
+ return true;
1487
+ }
1488
+ };
1489
+
1490
+ await client.compress();
1491
+ return { client, mockSocket };
1492
+ }
1493
+
1494
+ module.exports['Connection Edge: compress writeSocket error forwarded to socket when live'] = async test => {
1495
+ let { client, mockSocket } = await setupCompressedClient();
1496
+
1497
+ let errorReceived = false;
1498
+ mockSocket.on('error', err => {
1499
+ errorReceived = true;
1500
+ test.equal(err.message, 'writeSocket error');
1501
+ });
1502
+
1503
+ client.writeSocket.emit('error', new Error('writeSocket error'));
1504
+
1505
+ test.ok(errorReceived, 'Error should be forwarded to socket');
1506
+ test.done();
1507
+ };
1508
+
1509
+ module.exports['Connection Edge: compress _deflate error forwarded to socket when live'] = async test => {
1510
+ let { client, mockSocket } = await setupCompressedClient();
1511
+
1512
+ let errorReceived = false;
1513
+ mockSocket.on('error', err => {
1514
+ errorReceived = true;
1515
+ test.equal(err.message, 'deflate error');
1516
+ });
1517
+
1518
+ client._deflate.emit('error', new Error('deflate error'));
1519
+
1520
+ test.ok(errorReceived, 'Error should be forwarded to socket');
1521
+ test.done();
1522
+ };
1523
+
1524
+ module.exports['Connection Edge: compress readable after close does not crash'] = async test => {
1525
+ let { client } = await setupCompressedClient();
1526
+
1527
+ // Save reference before close() nulls it
1528
+ let writeSocket = client.writeSocket;
1529
+
1530
+ client.close();
1531
+
1532
+ // Emit readable on the saved reference after close has nulled this.writeSocket
1533
+ // This should not throw (guards at lines 1048 and 1021)
1534
+ test.doesNotThrow(() => {
1535
+ writeSocket.emit('readable');
1536
+ });
1537
+
1538
+ test.done();
1539
+ };
1540
+
1541
+ module.exports['Connection Edge: compress writeSocket error after close does not crash'] = async test => {
1542
+ let { client } = await setupCompressedClient();
1543
+
1544
+ let writeSocket = client.writeSocket;
1545
+
1546
+ client.close();
1547
+
1548
+ // Emit error on saved writeSocket after close has nulled this.socket
1549
+ // This should not throw (guard at lines 1053-1055)
1550
+ test.doesNotThrow(() => {
1551
+ writeSocket.emit('error', new Error('late writeSocket error'));
1552
+ });
1553
+
1554
+ test.done();
1555
+ };
1556
+
1557
+ module.exports['Connection Edge: compress _deflate error after close does not crash'] = async test => {
1558
+ let { client } = await setupCompressedClient();
1559
+
1560
+ let deflate = client._deflate;
1561
+
1562
+ client.close();
1563
+
1564
+ // Emit error on saved _deflate after close has nulled this.socket
1565
+ // This should not throw (guard at lines 1060-1062)
1566
+ test.doesNotThrow(() => {
1567
+ deflate.emit('error', new Error('late deflate error'));
1568
+ });
1569
+
1570
+ test.done();
1571
+ };
@@ -47,3 +47,35 @@ module.exports['Special Use: Junk folder names'] = test => {
47
47
  test.ok(junkNames.includes('junk'));
48
48
  test.done();
49
49
  };
50
+
51
+ // ============================================
52
+ // specialUse() function branch tests
53
+ // ============================================
54
+
55
+ module.exports['Special Use: specialUse returns extension flag when extension enabled and flag found'] = test => {
56
+ const result = specialUse.specialUse(true, { flags: new Set(['\\Sent']), name: 'Foo' });
57
+ test.equal(result.flag, '\\Sent');
58
+ test.equal(result.source, 'extension');
59
+ test.done();
60
+ };
61
+
62
+ module.exports['Special Use: specialUse falls back to name when extension enabled but no flag'] = test => {
63
+ const result = specialUse.specialUse(true, { flags: new Set(), name: 'Sent' });
64
+ test.equal(result.flag, '\\Sent');
65
+ test.equal(result.source, 'name');
66
+ test.done();
67
+ };
68
+
69
+ module.exports['Special Use: specialUse matches by name when extension disabled'] = test => {
70
+ const result = specialUse.specialUse(false, { flags: new Set(), name: 'Drafts' });
71
+ test.equal(result.flag, '\\Drafts');
72
+ test.equal(result.source, 'name');
73
+ test.done();
74
+ };
75
+
76
+ module.exports['Special Use: specialUse returns null flag when no match'] = test => {
77
+ const result = specialUse.specialUse(false, { flags: new Set(), name: 'CustomFolder' });
78
+ test.equal(result.flag, null);
79
+ test.equal(result.source, undefined);
80
+ test.done();
81
+ };
package/.babelrc DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "presets": ["@babel/env"],
3
- "plugins": [
4
- "@babel/plugin-syntax-class-properties"
5
- ]
6
- }
package/.eslintrc DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "rules": {
3
- "no-await-in-loop": 0,
4
- "require-atomic-updates": 0
5
- },
6
- "globals": {
7
- "BigInt": true
8
- },
9
- "extends": ["nodemailer", "prettier"],
10
- "parser": "@babel/eslint-parser",
11
- "parserOptions": {
12
- "ecmaVersion": 2020,
13
- "sourceType": "script"
14
- },
15
- "plugins": ["@babel"]
16
- }
Binary file
package/jsdoc.json DELETED
@@ -1,28 +0,0 @@
1
- {
2
- "templates": {
3
- "referenceTitle": "ImapFlow",
4
- "disableSort": false,
5
- "collapse": true,
6
- "resources": {
7
- "Source Code": "https://github.com/postalsys/imapflow"
8
- },
9
- "cleverLinks": true,
10
- "monospaceLinks": false,
11
- "default": {
12
- "outputSourceFiles": false
13
- },
14
- "search": {
15
- "apiKey": "082c6635b32d44ed095369a5f1c790fd",
16
- "indexName": "imapflow",
17
- "hitsPerPage": 7
18
- }
19
- },
20
- "plugins": ["plugins/markdown"],
21
- "opts": {
22
- "destination": "./docs/",
23
- "encoding": "utf8",
24
- "private": true,
25
- "recurse": true,
26
- "template": "./node_modules/imapflow-jsdoc-template"
27
- }
28
- }