@bumail/smtp 0.1.0 → 0.2.0

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 (60) hide show
  1. package/README.md +148 -6
  2. package/dist/chunks/index-2ybj2z1w.js +216 -0
  3. package/dist/chunks/index-2ybj2z1w.js.map +14 -0
  4. package/dist/client/content.d.ts +25 -0
  5. package/dist/client/content.d.ts.map +1 -0
  6. package/dist/client/inbox.d.ts +18 -0
  7. package/dist/client/inbox.d.ts.map +1 -0
  8. package/dist/client/index.d.ts +6 -0
  9. package/dist/client/index.d.ts.map +1 -0
  10. package/dist/client/index.js +945 -0
  11. package/dist/client/index.js.map +22 -0
  12. package/dist/client/mx.d.ts +29 -0
  13. package/dist/client/mx.d.ts.map +1 -0
  14. package/dist/client/options.d.ts +136 -0
  15. package/dist/client/options.d.ts.map +1 -0
  16. package/dist/client/refusal.d.ts +15 -0
  17. package/dist/client/refusal.d.ts.map +1 -0
  18. package/dist/client/send.d.ts +13 -0
  19. package/dist/client/send.d.ts.map +1 -0
  20. package/dist/client/session.d.ts +11 -0
  21. package/dist/client/session.d.ts.map +1 -0
  22. package/dist/client/settings.d.ts +30 -0
  23. package/dist/client/settings.d.ts.map +1 -0
  24. package/dist/client/socket.d.ts +31 -0
  25. package/dist/client/socket.d.ts.map +1 -0
  26. package/dist/client/target.d.ts +29 -0
  27. package/dist/client/target.d.ts.map +1 -0
  28. package/dist/client/transaction.d.ts +15 -0
  29. package/dist/client/transaction.d.ts.map +1 -0
  30. package/dist/errors.d.ts +28 -4
  31. package/dist/errors.d.ts.map +1 -1
  32. package/dist/index.js +115 -196
  33. package/dist/index.js.map +9 -13
  34. package/dist/io/outgoing.d.ts +21 -0
  35. package/dist/io/outgoing.d.ts.map +1 -0
  36. package/dist/protocol/data-writer.d.ts +22 -0
  37. package/dist/protocol/data-writer.d.ts.map +1 -0
  38. package/dist/protocol/ehlo.d.ts +7 -0
  39. package/dist/protocol/ehlo.d.ts.map +1 -0
  40. package/dist/{server → protocol}/lines.d.ts +9 -4
  41. package/dist/protocol/lines.d.ts.map +1 -0
  42. package/dist/protocol/reply-reader.d.ts +19 -0
  43. package/dist/protocol/reply-reader.d.ts.map +1 -0
  44. package/dist/protocol/sasl.d.ts +4 -0
  45. package/dist/protocol/sasl.d.ts.map +1 -1
  46. package/dist/server/connection.d.ts +8 -0
  47. package/dist/server/connection.d.ts.map +1 -1
  48. package/dist/server/input.d.ts.map +1 -1
  49. package/dist/server/options.d.ts +2 -1
  50. package/dist/server/options.d.ts.map +1 -1
  51. package/dist/server/server.d.ts.map +1 -1
  52. package/dist/server/settings.d.ts.map +1 -1
  53. package/dist/server/transport.d.ts +21 -4
  54. package/dist/server/transport.d.ts.map +1 -1
  55. package/docs/README.md +3 -3
  56. package/docs/guide.md +381 -9
  57. package/docs/roadmap.md +43 -8
  58. package/docs/troubleshooting.md +668 -7
  59. package/package.json +12 -4
  60. package/dist/server/lines.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,MAAM,MAAM,aAAa,GACtB,gBAAgB,GAChB,mBAAmB,GACnB,iBAAiB,GACjB,iBAAiB,GACjB,iBAAiB,GACjB,cAAc,GACd,oBAAoB,GACpB,kBAAkB,CAAC;AAEtB;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,KAAK;IACnC,SAAkB,IAAI,eAAe;IACrC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;gBAEjB,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM;CAIhD"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAE9C,uGAAuG;AACvG,MAAM,MAAM,aAAa,GACtB,gBAAgB,GAChB,mBAAmB,GACnB,iBAAiB,GACjB,iBAAiB,GACjB,iBAAiB,GACjB,cAAc,GACd,oBAAoB,GACpB,kBAAkB,GAClB,mBAAmB,GACnB,SAAS,GACT,WAAW,GACX,SAAS,GACT,oBAAoB,GACpB,iBAAiB,GACjB,YAAY,GACZ,kBAAkB,GAClB,mBAAmB,GACnB,SAAS,GACT,YAAY,CAAC;AAEhB,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACtB;AAED,uFAAuF;AACvF,MAAM,WAAW,gBAAgB;IAChC,qEAAqE;IACrE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;CAC9C;AAYD;;;;;;;GAOG;AACH,qBAAa,SAAU,SAAQ,KAAK;IACnC,SAAkB,IAAI,eAAe;IACrC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,0EAA0E;IAC1E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,mFAAmF;IACnF,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;gBAG7C,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,gBAAqB;CAU/B"}
package/dist/index.js CHANGED
@@ -1,54 +1,11 @@
1
- // src/errors.ts
2
- class SmtpError extends Error {
3
- name = "SmtpError";
4
- code;
5
- constructor(code, message) {
6
- super(message);
7
- this.code = code;
8
- }
9
- }
10
- // src/protocol/path.ts
11
- var ATOM = /^[A-Za-z0-9!#$%&'*+\-/=?^_`{|}~\u0080-\u{10ffff}]+$/u;
12
- var LABEL = /^(?:[A-Za-z0-9\u0080-\u{10ffff}](?:[A-Za-z0-9\-\u0080-\u{10ffff}]*[A-Za-z0-9\u0080-\u{10ffff}])?)$/u;
13
- var INVISIBLE = /[\u0080-\u009f\u2028\u2029\p{Cf}]/u;
14
- function isDotString(text) {
15
- return text.split(".").every((atom) => ATOM.test(atom));
16
- }
17
- function isQuotedString(text) {
18
- return /^"(?:[\x20\x21\x23-\x5b\x5d-\x7e\u0080-\u{10ffff}]|\\[\x20-\x7e])*"$/u.test(text);
19
- }
20
- function isDomain(domain) {
21
- if (/^\[(?:IPv6:[0-9A-Fa-f:.]+|\d{1,3}(?:\.\d{1,3}){3})\]$/.test(domain))
22
- return true;
23
- return domain.length <= 255 && domain.split(".").every((label) => label.length <= 63 && LABEL.test(label));
24
- }
25
- function parsePath(text, allowNull) {
26
- if (!text.startsWith("<") || !text.endsWith(">"))
27
- return;
28
- let inner = text.slice(1, -1);
29
- if (INVISIBLE.test(inner))
30
- return;
31
- if (inner === "")
32
- return allowNull ? { address: "", local: "", domain: "" } : undefined;
33
- const route = /^@[^:]+:/.exec(inner);
34
- if (route)
35
- inner = inner.slice(route[0].length);
36
- const at = inner.lastIndexOf("@");
37
- if (at <= 0)
38
- return;
39
- const local = inner.slice(0, at);
40
- const domain = inner.slice(at + 1);
41
- if (local.length > 64 || !(isDotString(local) || isQuotedString(local)))
42
- return;
43
- if (!isDomain(domain))
44
- return;
45
- return {
46
- address: `${local}@${domain.toLowerCase()}`,
47
- local,
48
- domain: domain.toLowerCase()
49
- };
50
- }
51
-
1
+ import {
2
+ SmtpError2,
3
+ parsePath2,
4
+ decodePlain2,
5
+ decodeLoginStep2,
6
+ LineSplitter,
7
+ Outgoing
8
+ } from "./chunks/index-2ybj2z1w.js";
52
9
  // src/protocol/command.ts
53
10
  function parseCommand(line) {
54
11
  const space = line.indexOf(" ");
@@ -63,7 +20,7 @@ function parsePathCommand(argument, keyword) {
63
20
  const match = new RegExp(`^${keyword}:\\s*(<[^>]*>)(.*)$`, "i").exec(argument);
64
21
  if (!match)
65
22
  return;
66
- const path = parsePath(match[1], keyword === "FROM");
23
+ const path = parsePath2(match[1], keyword === "FROM");
67
24
  if (!path)
68
25
  return;
69
26
  const parameters = {};
@@ -165,36 +122,6 @@ function formatReply(reply, enhanced = true) {
165
122
  }).join("");
166
123
  }
167
124
  var reply = (code, status, text) => status === undefined ? { code, text } : { code, status, text };
168
- // src/protocol/sasl.ts
169
- function base64ToText(text) {
170
- if (!/^[A-Za-z0-9+/]*={0,2}$/.test(text) || text.length % 4 !== 0)
171
- return;
172
- try {
173
- return new TextDecoder("utf-8", { fatal: true }).decode(Uint8Array.fromBase64(text));
174
- } catch {
175
- return;
176
- }
177
- }
178
- function decodePlain(response) {
179
- const text = base64ToText(response);
180
- if (text === undefined)
181
- return;
182
- const parts = text.split("\x00");
183
- if (parts.length !== 3)
184
- return;
185
- const [authorizationId = "", username = "", password = ""] = parts;
186
- if (username === "" || password === "")
187
- return;
188
- return {
189
- mechanism: "PLAIN",
190
- username,
191
- password,
192
- ...authorizationId === "" || authorizationId === username ? {} : { authorizationId }
193
- };
194
- }
195
- function decodeLoginStep(response) {
196
- return base64ToText(response);
197
- }
198
125
  // src/server/guard.ts
199
126
  var LOCAL_ERROR = reply(451, "4.3.0", "Local error in processing");
200
127
  var TIMED_OUT = Symbol("timed out");
@@ -213,7 +140,7 @@ function timedOut(value) {
213
140
  return value === TIMED_OUT;
214
141
  }
215
142
  function hookTimeout(name, seconds) {
216
- return new SmtpError("HOOK_TIMEOUT", `${name} did not settle within hookTimeout (${seconds} s)`);
143
+ return new SmtpError2("HOOK_TIMEOUT", `${name} did not settle within hookTimeout (${seconds} s)`);
217
144
  }
218
145
  function refusalOf(name, answer, report) {
219
146
  if (answer === undefined)
@@ -222,7 +149,7 @@ function refusalOf(name, answer, report) {
222
149
  if (typeof code === "number" && code >= 400 && code < 600) {
223
150
  return answer;
224
151
  }
225
- report(new SmtpError("INVALID_HOOK_REPLY", `${name} answered ${JSON.stringify(answer)}, which is not a refusal: a hook refuses with a 4xx or 5xx reply, and accepts with undefined`));
152
+ report(new SmtpError2("INVALID_HOOK_REPLY", `${name} answered ${JSON.stringify(answer)}, which is not a refusal: a hook refuses with a 4xx or 5xx reply, and accepts with undefined`));
226
153
  return LOCAL_ERROR;
227
154
  }
228
155
 
@@ -281,7 +208,7 @@ async function authenticate(connection, argument) {
281
208
  state.waiting = "auth-plain";
282
209
  return connection.send(reply(334, undefined, ""));
283
210
  }
284
- return check(connection, decodePlain(initial === "=" ? "" : initial));
211
+ return check(connection, decodePlain2(initial === "=" ? "" : initial));
285
212
  case "LOGIN":
286
213
  if (initial === undefined) {
287
214
  state.waiting = "auth-login-user";
@@ -293,7 +220,7 @@ async function authenticate(connection, argument) {
293
220
  }
294
221
  }
295
222
  function loginUser(connection, response) {
296
- const user = decodeLoginStep(response);
223
+ const user = decodeLoginStep2(response);
297
224
  if (!user)
298
225
  return check(connection, undefined);
299
226
  connection.state.loginUser = user;
@@ -309,11 +236,11 @@ async function continueAuth(connection, line) {
309
236
  }
310
237
  switch (state.waiting) {
311
238
  case "auth-plain":
312
- return check(connection, decodePlain(line));
239
+ return check(connection, decodePlain2(line));
313
240
  case "auth-login-user":
314
241
  return loginUser(connection, line);
315
242
  default: {
316
- const password = decodeLoginStep(line);
243
+ const password = decodeLoginStep2(line);
317
244
  const username = state.loginUser ?? "";
318
245
  return check(connection, password ? { mechanism: "LOGIN", username, password } : undefined);
319
246
  }
@@ -499,7 +426,7 @@ async function runCommand(connection, line) {
499
426
  case "HELP":
500
427
  return connection.send(reply(214, "2.0.0", "See RFC 5321"));
501
428
  case "QUIT":
502
- return connection.close(reply(221, "2.0.0", `${connection.settings.options.hostname} closing connection`));
429
+ return connection.quit(reply(221, "2.0.0", `${connection.settings.options.hostname} closing connection`));
503
430
  case "STARTTLS":
504
431
  return startTls(connection, argument);
505
432
  case "AUTH":
@@ -565,22 +492,22 @@ function receivedField(connection, recipients, id, now = new Date) {
565
492
  var TOO_BIG = reply(552, "5.3.4", "Message too big for system");
566
493
  var BARE_LINE_BREAK = reply(550, "5.6.11", "Bare CR or LF is not allowed in a message");
567
494
  function tooBig(maxMessageSize) {
568
- return new SmtpError("MESSAGE_TOO_BIG", `The message is larger than maxMessageSize (${maxMessageSize} bytes); do not deliver it`);
495
+ return new SmtpError2("MESSAGE_TOO_BIG", `The message is larger than maxMessageSize (${maxMessageSize} bytes); do not deliver it`);
569
496
  }
570
497
  function bareLineBreak() {
571
- return new SmtpError("BARE_LINE_BREAK", "The message holds a bare CR or LF (SMTP smuggling); do not deliver it");
498
+ return new SmtpError2("BARE_LINE_BREAK", "The message holds a bare CR or LF (SMTP smuggling); do not deliver it");
572
499
  }
573
500
  function connectionLost(ended) {
574
- return new SmtpError("CONNECTION_LOST", ended ? "The connection closed before the reply to the message; do not deliver it" : "The connection closed before the end of the message; do not deliver it");
501
+ return new SmtpError2("CONNECTION_LOST", ended ? "The connection closed before the reply to the message; do not deliver it" : "The connection closed before the end of the message; do not deliver it");
575
502
  }
576
503
  function notAnswered(seconds) {
577
- return new SmtpError("HOOK_TIMEOUT", `onData did not answer within hookTimeout (${seconds} s); do not deliver it`);
504
+ return new SmtpError2("HOOK_TIMEOUT", `onData did not answer within hookTimeout (${seconds} s); do not deliver it`);
578
505
  }
579
506
  function notReadInTime(seconds) {
580
- return new SmtpError("HOOK_TIMEOUT", `onData did not read the message within hookTimeout (${seconds} s); do not deliver it`);
507
+ return new SmtpError2("HOOK_TIMEOUT", `onData did not read the message within hookTimeout (${seconds} s); do not deliver it`);
581
508
  }
582
509
  function notRead() {
583
- return new SmtpError("MESSAGE_NOT_READ", "onData answered without reading the message to its end; it was not taken");
510
+ return new SmtpError2("MESSAGE_NOT_READ", "onData answered without reading the message to its end; it was not taken");
584
511
  }
585
512
 
586
513
  // src/server/intake.ts
@@ -755,61 +682,6 @@ class Intake {
755
682
  }
756
683
  }
757
684
 
758
- // src/server/lines.ts
759
- var MAX_LINE = 2048;
760
-
761
- class LineSplitter {
762
- #held = new Uint8Array(0);
763
- #skipping = false;
764
- #rest = new Uint8Array(0);
765
- push(chunk) {
766
- this.#rest = this.#held.length > 0 ? concat(this.#held, chunk) : chunk;
767
- this.#held = new Uint8Array(0);
768
- }
769
- next() {
770
- for (;; ) {
771
- const data = this.#rest;
772
- const lf = data.indexOf(10);
773
- if (lf < 0) {
774
- this.#rest = new Uint8Array(0);
775
- if (this.#skipping)
776
- return;
777
- if (data.length > MAX_LINE) {
778
- this.#skipping = true;
779
- return { tooLong: true };
780
- }
781
- this.#held = data.slice();
782
- return;
783
- }
784
- const end = lf > 0 && data[lf - 1] === 13 ? lf - 1 : lf;
785
- this.#rest = data.subarray(lf + 1);
786
- if (this.#skipping) {
787
- this.#skipping = false;
788
- continue;
789
- }
790
- if (end > MAX_LINE)
791
- return { tooLong: true };
792
- return { line: new TextDecoder().decode(data.subarray(0, end)) };
793
- }
794
- }
795
- takeRest() {
796
- const rest = this.#rest;
797
- this.#rest = new Uint8Array(0);
798
- return rest;
799
- }
800
- clear() {
801
- this.#held = new Uint8Array(0);
802
- this.#rest = new Uint8Array(0);
803
- this.#skipping = false;
804
- }
805
- }
806
- function concat(a, b) {
807
- const out = new Uint8Array(a.length + b.length);
808
- out.set(a, 0);
809
- out.set(b, a.length);
810
- return out;
811
- }
812
-
813
685
  // src/server/input.ts
814
686
  var INPUT_LIMIT = 64 * 1024;
815
687
 
@@ -1008,6 +880,14 @@ class Connection {
1008
880
  this.send(answer);
1009
881
  this.#closed = true;
1010
882
  this.input.abort();
883
+ this.transport.abort();
884
+ }
885
+ quit(answer) {
886
+ if (this.#closed)
887
+ return;
888
+ this.send(answer);
889
+ this.#closed = true;
890
+ this.input.abort();
1011
891
  this.transport.end();
1012
892
  }
1013
893
  fail(answer) {
@@ -1072,7 +952,8 @@ class Connection {
1072
952
  }
1073
953
 
1074
954
  // src/server/settings.ts
1075
- var invalid = (message) => new SmtpError("INVALID_OPTION", `createSmtpServer(): ${message}`);
955
+ var MAX_TIMER_SECONDS = Math.floor(2147483647 / 1000);
956
+ var invalid = (message) => new SmtpError2("INVALID_OPTION", `createSmtpServer(): ${message}`);
1076
957
  function positive(name, value, fallback) {
1077
958
  if (value === undefined)
1078
959
  return fallback;
@@ -1081,6 +962,13 @@ function positive(name, value, fallback) {
1081
962
  }
1082
963
  return value;
1083
964
  }
965
+ function timer(name, value, fallback) {
966
+ const seconds = positive(name, value, fallback);
967
+ if (seconds > MAX_TIMER_SECONDS) {
968
+ throw invalid(`${name} must be at most ${MAX_TIMER_SECONDS} seconds, not ${seconds}`);
969
+ }
970
+ return seconds;
971
+ }
1084
972
  function nonNegative(name, value, fallback) {
1085
973
  if (value === undefined)
1086
974
  return fallback;
@@ -1128,68 +1016,87 @@ function settingsOf(options) {
1128
1016
  maxConnections: positive("maxConnections", options.maxConnections, 1000),
1129
1017
  maxErrors: positive("maxErrors", options.maxErrors, 10),
1130
1018
  timeout,
1131
- hookTimeout: positive("hookTimeout", options.hookTimeout, 60),
1019
+ hookTimeout: timer("hookTimeout", options.hookTimeout, 60),
1132
1020
  greetingDelay,
1133
1021
  isLocal: (domain) => isLocal(domain.toLowerCase())
1134
1022
  };
1135
1023
  }
1136
1024
 
1137
1025
  // src/server/transport.ts
1026
+ var CLOSE_GRACE_MS = 5000;
1027
+
1138
1028
  class SocketTransport {
1139
1029
  #socket;
1140
1030
  remoteAddress;
1141
1031
  secure;
1142
1032
  #startTls;
1143
- #queue = [];
1144
- #waiters = [];
1033
+ #outgoing;
1145
1034
  #ending = false;
1035
+ #closed = false;
1036
+ #grace;
1146
1037
  constructor(socket, secure, startTls, remoteAddress = socket.remoteAddress) {
1147
1038
  this.#socket = socket;
1148
1039
  this.secure = secure;
1149
1040
  this.#startTls = startTls;
1150
1041
  this.remoteAddress = remoteAddress;
1042
+ this.#outgoing = new Outgoing((bytes) => socket.write(bytes));
1151
1043
  }
1152
1044
  write(text) {
1153
- const bytes = new TextEncoder().encode(text);
1154
- if (this.#queue.length > 0) {
1155
- this.#queue.push(bytes);
1045
+ if (this.#closed)
1156
1046
  return;
1157
- }
1158
- const written = this.#socket.write(bytes);
1159
- if (written < bytes.length)
1160
- this.#queue.push(bytes.subarray(written));
1047
+ this.#outgoing.write(new TextEncoder().encode(text));
1161
1048
  }
1162
1049
  drain() {
1163
- while (this.#queue.length > 0) {
1164
- const bytes = this.#queue[0];
1165
- const written = this.#socket.write(bytes);
1166
- if (written < bytes.length) {
1167
- this.#queue[0] = bytes.subarray(written);
1168
- return;
1169
- }
1170
- this.#queue.shift();
1171
- }
1172
- for (const wake of this.#waiters.splice(0))
1173
- wake();
1174
- if (this.#ending)
1175
- this.#socket.end();
1050
+ if (this.#outgoing.drain() && this.#ending)
1051
+ this.#hangUp(true);
1176
1052
  }
1177
1053
  drained() {
1178
- if (this.#queue.length === 0)
1179
- return Promise.resolve();
1180
- return new Promise((wake) => this.#waiters.push(wake));
1054
+ return this.#outgoing.drained();
1181
1055
  }
1182
1056
  closed() {
1183
- this.#queue = [];
1184
- for (const wake of this.#waiters.splice(0))
1185
- wake();
1057
+ this.#closed = true;
1058
+ this.#disarm();
1059
+ this.#outgoing.clear();
1186
1060
  }
1187
1061
  end() {
1188
- if (this.#queue.length === 0)
1189
- this.#socket.end();
1062
+ if (this.#closed)
1063
+ return;
1064
+ this.#arm();
1065
+ if (this.#outgoing.empty)
1066
+ this.#hangUp(false);
1190
1067
  else
1191
1068
  this.#ending = true;
1192
1069
  }
1070
+ abort() {
1071
+ if (this.#closed)
1072
+ return;
1073
+ if (this.#outgoing.empty)
1074
+ this.end();
1075
+ else
1076
+ this.#terminate();
1077
+ }
1078
+ #hangUp(drained) {
1079
+ this.#ending = false;
1080
+ if (drained && this.secure)
1081
+ this.#socket.shutdown();
1082
+ else
1083
+ this.#socket.shutdown(true);
1084
+ }
1085
+ #terminate() {
1086
+ this.#disarm();
1087
+ this.#outgoing.clear();
1088
+ this.#socket.terminate();
1089
+ }
1090
+ #arm() {
1091
+ if (this.#grace)
1092
+ return;
1093
+ this.#grace = setTimeout(() => this.#terminate(), CLOSE_GRACE_MS);
1094
+ this.#grace.unref?.();
1095
+ }
1096
+ #disarm() {
1097
+ clearTimeout(this.#grace);
1098
+ this.#grace = undefined;
1099
+ }
1193
1100
  pause() {
1194
1101
  this.#socket.pause();
1195
1102
  }
@@ -1224,7 +1131,11 @@ function handlers(settings) {
1224
1131
  timeout(socket) {
1225
1132
  if (socket.data.upgraded)
1226
1133
  return;
1227
- socket.data.connection?.close(idle);
1134
+ const { connection, transport } = socket.data;
1135
+ if (connection?.closed)
1136
+ transport?.abort();
1137
+ else
1138
+ connection?.close(idle);
1228
1139
  }
1229
1140
  };
1230
1141
  }
@@ -1257,15 +1168,15 @@ function transportOf(socket, settings, secure) {
1257
1168
  function createSmtpServer(options) {
1258
1169
  const settings = settingsOf(options);
1259
1170
  let listener;
1260
- let open = 0;
1171
+ const open = new Set;
1261
1172
  const secure = options.implicitTls === true;
1262
1173
  return {
1263
1174
  get connections() {
1264
- return open;
1175
+ return open.size;
1265
1176
  },
1266
1177
  async listen({ port, hostname = "0.0.0.0" }) {
1267
1178
  if (listener) {
1268
- throw new SmtpError("ALREADY_LISTENING", `listen(): the server is already listening on ${listener.hostname}:${listener.port}`);
1179
+ throw new SmtpError2("ALREADY_LISTENING", `listen(): the server is already listening on ${listener.hostname}:${listener.port}`);
1269
1180
  }
1270
1181
  listener = Bun.listen({
1271
1182
  hostname,
@@ -1276,19 +1187,23 @@ function createSmtpServer(options) {
1276
1187
  open(socket) {
1277
1188
  socket.data = { upgraded: false };
1278
1189
  socket.timeout(settings.timeout);
1279
- if (open >= settings.maxConnections) {
1280
- socket.end(`421 4.3.2 ${options.hostname} Too many connections, try later\r
1190
+ if (open.size >= settings.maxConnections) {
1191
+ const refused = new SocketTransport(socket, secure, () => {});
1192
+ socket.data.transport = refused;
1193
+ refused.write(`421 4.3.2 ${options.hostname} Too many connections, try later\r
1281
1194
  `);
1195
+ refused.end();
1282
1196
  return;
1283
1197
  }
1284
- open++;
1285
1198
  const connection = new Connection(settings, transportOf(socket, settings, secure));
1199
+ open.add(connection);
1286
1200
  socket.data.connection = connection;
1287
1201
  connection.open();
1288
1202
  },
1289
1203
  close(socket) {
1290
- if (socket.data.connection)
1291
- open--;
1204
+ const connection = socket.data.connection;
1205
+ if (connection)
1206
+ open.delete(connection);
1292
1207
  socket.data.transport?.closed();
1293
1208
  socket.data.connection?.close();
1294
1209
  }
@@ -1299,21 +1214,25 @@ function createSmtpServer(options) {
1299
1214
  stop(closeConnections = false) {
1300
1215
  listener?.stop(closeConnections);
1301
1216
  listener = undefined;
1217
+ if (!closeConnections)
1218
+ return;
1219
+ for (const connection of [...open])
1220
+ connection.close();
1302
1221
  }
1303
1222
  };
1304
1223
  }
1305
1224
  export {
1306
1225
  DataReader,
1307
- SmtpError,
1226
+ SmtpError2 as SmtpError,
1308
1227
  createSmtpServer,
1309
- decodeLoginStep,
1310
- decodePlain,
1228
+ decodeLoginStep2 as decodeLoginStep,
1229
+ decodePlain2 as decodePlain,
1311
1230
  formatReply,
1312
1231
  parseCommand,
1313
- parsePath,
1232
+ parsePath2 as parsePath,
1314
1233
  parsePathCommand,
1315
1234
  reply
1316
1235
  };
1317
1236
 
1318
- //# debugId=DE4D9903D05BDADC64756E2164756E21
1237
+ //# debugId=7E57D02B62ACF1F164756E2164756E21
1319
1238
  //# sourceMappingURL=index.js.map