imapkit 4.3.0 → 4.3.1

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.
@@ -482,6 +482,8 @@ declare class IMAPConnection {
482
482
  /** notifications are sent right away instead of before the next tagged response (IDLE) */
483
483
  directNotifications: boolean;
484
484
  notificationQueue: Notification[];
485
+ /** the plain socket that STARTTLS wrapped in TLS */
486
+ tcpSocket: net.Socket | null | undefined;
485
487
  /** the number of the connection, 1 for the first one the server accepted (script rules match it) */
486
488
  sessionNumber: number;
487
489
  /** the tag and name of the command whose input handler reads the lines that follow (IDLE, AUTHENTICATE) */
@@ -1491,16 +1491,10 @@ class IMAPConnection {
1491
1491
  socket.pause();
1492
1492
  // a RST sent in the same tick as the last output can get lost: on macOS the client received the output but
1493
1493
  // never the RST (#81). Node has no signal for output that reached the peer, so the RST waits a little
1494
+ const tcpSocket = this.tcpSocket;
1494
1495
  setTimeout(() => {
1495
- if (socket.destroyed) {
1496
- return;
1497
- }
1498
- // resetAndDestroy sends a TCP RST (Node 16.17), not every runtime has it
1499
- if (typeof socket.resetAndDestroy === 'function') {
1500
- socket.resetAndDestroy();
1501
- }
1502
- else {
1503
- socket.destroy();
1496
+ if (!socket.destroyed) {
1497
+ resetSocket(socket, tcpSocket);
1504
1498
  }
1505
1499
  }, RESET_DELAY);
1506
1500
  }
@@ -2121,6 +2115,9 @@ class IMAPConnection {
2121
2115
  };
2122
2116
  // STARTTLS runs on a live connection
2123
2117
  const socket = this.socket;
2118
+ // the RST of a script rule goes to the TCP socket under the TLS layer, see resetSocket. Node also has it as the
2119
+ // _parent of the TLS socket, Bun does not
2120
+ this.tcpSocket = socket;
2124
2121
  // remove all listeners from the original socket besides the error handler
2125
2122
  socket.removeAllListeners();
2126
2123
  socket.on('error', this.onError.bind(this));
@@ -2830,6 +2827,32 @@ class IMAPConnection {
2830
2827
  }
2831
2828
  }
2832
2829
  exports.IMAPConnection = IMAPConnection;
2830
+ /**
2831
+ * Destroys a socket with a TCP RST (`close: 'reset'` of a script rule). resetAndDestroy (Node 16.17) needs a socket with
2832
+ * a TCP handle, a TLS socket throws ERR_INVALID_HANDLE_TYPE (#84), so the RST goes to the TCP socket under the TLS
2833
+ * layer: the plain socket of STARTTLS, or the parent socket of implicit TLS (Node). A runtime without resetAndDestroy,
2834
+ * or a socket that can not be reset, is destroyed without a RST
2835
+ *
2836
+ * @param {Object} socket The socket of the connection
2837
+ * @param {Object} [tcpSocket] The plain socket that STARTTLS wrapped
2838
+ */
2839
+ function resetSocket(socket, tcpSocket) {
2840
+ const parent = socket._parent;
2841
+ const candidates = [tcpSocket, parent instanceof node_net_1.default.Socket ? parent : null, socket];
2842
+ for (const candidate of candidates) {
2843
+ if (candidate && !candidate.destroyed && typeof candidate.resetAndDestroy === 'function') {
2844
+ try {
2845
+ candidate.resetAndDestroy();
2846
+ break;
2847
+ }
2848
+ catch {
2849
+ // not a TCP handle, try the next one
2850
+ }
2851
+ }
2852
+ }
2853
+ // the TLS socket closes with the TCP socket under it, and emits close for the connection
2854
+ socket.destroy();
2855
+ }
2833
2856
  /**
2834
2857
  * Copies a response tree for the `mutate` action of a script rule: arrays and plain objects are copied,
2835
2858
  * other values (Buffers) are shared
@@ -482,6 +482,8 @@ declare class IMAPConnection {
482
482
  /** notifications are sent right away instead of before the next tagged response (IDLE) */
483
483
  directNotifications: boolean;
484
484
  notificationQueue: Notification[];
485
+ /** the plain socket that STARTTLS wrapped in TLS */
486
+ tcpSocket: net.Socket | null | undefined;
485
487
  /** the number of the connection, 1 for the first one the server accepted (script rules match it) */
486
488
  sessionNumber: number;
487
489
  /** the tag and name of the command whose input handler reads the lines that follow (IDLE, AUTHENTICATE) */
@@ -1449,16 +1449,10 @@ class IMAPConnection {
1449
1449
  socket.pause();
1450
1450
  // a RST sent in the same tick as the last output can get lost: on macOS the client received the output but
1451
1451
  // never the RST (#81). Node has no signal for output that reached the peer, so the RST waits a little
1452
+ const tcpSocket = this.tcpSocket;
1452
1453
  setTimeout(() => {
1453
- if (socket.destroyed) {
1454
- return;
1455
- }
1456
- // resetAndDestroy sends a TCP RST (Node 16.17), not every runtime has it
1457
- if (typeof socket.resetAndDestroy === 'function') {
1458
- socket.resetAndDestroy();
1459
- }
1460
- else {
1461
- socket.destroy();
1454
+ if (!socket.destroyed) {
1455
+ resetSocket(socket, tcpSocket);
1462
1456
  }
1463
1457
  }, RESET_DELAY);
1464
1458
  }
@@ -2079,6 +2073,9 @@ class IMAPConnection {
2079
2073
  };
2080
2074
  // STARTTLS runs on a live connection
2081
2075
  const socket = this.socket;
2076
+ // the RST of a script rule goes to the TCP socket under the TLS layer, see resetSocket. Node also has it as the
2077
+ // _parent of the TLS socket, Bun does not
2078
+ this.tcpSocket = socket;
2082
2079
  // remove all listeners from the original socket besides the error handler
2083
2080
  socket.removeAllListeners();
2084
2081
  socket.on('error', this.onError.bind(this));
@@ -2787,6 +2784,32 @@ class IMAPConnection {
2787
2784
  }, mailbox, ignoreSelf || ignoreExists ? this : false);
2788
2785
  }
2789
2786
  }
2787
+ /**
2788
+ * Destroys a socket with a TCP RST (`close: 'reset'` of a script rule). resetAndDestroy (Node 16.17) needs a socket with
2789
+ * a TCP handle, a TLS socket throws ERR_INVALID_HANDLE_TYPE (#84), so the RST goes to the TCP socket under the TLS
2790
+ * layer: the plain socket of STARTTLS, or the parent socket of implicit TLS (Node). A runtime without resetAndDestroy,
2791
+ * or a socket that can not be reset, is destroyed without a RST
2792
+ *
2793
+ * @param {Object} socket The socket of the connection
2794
+ * @param {Object} [tcpSocket] The plain socket that STARTTLS wrapped
2795
+ */
2796
+ function resetSocket(socket, tcpSocket) {
2797
+ const parent = socket._parent;
2798
+ const candidates = [tcpSocket, parent instanceof net.Socket ? parent : null, socket];
2799
+ for (const candidate of candidates) {
2800
+ if (candidate && !candidate.destroyed && typeof candidate.resetAndDestroy === 'function') {
2801
+ try {
2802
+ candidate.resetAndDestroy();
2803
+ break;
2804
+ }
2805
+ catch {
2806
+ // not a TCP handle, try the next one
2807
+ }
2808
+ }
2809
+ }
2810
+ // the TLS socket closes with the TCP socket under it, and emits close for the connection
2811
+ socket.destroy();
2812
+ }
2790
2813
  /**
2791
2814
  * Copies a response tree for the `mutate` action of a script rule: arrays and plain objects are copied,
2792
2815
  * other values (Buffers) are shared
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imapkit",
3
- "version": "4.3.0",
3
+ "version": "4.3.1",
4
4
  "description": "Scriptable, strictly RFC compliant in-memory IMAP server for testing IMAP clients",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",