@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.
- package/README.md +148 -6
- package/dist/chunks/index-2ybj2z1w.js +216 -0
- package/dist/chunks/index-2ybj2z1w.js.map +14 -0
- package/dist/client/content.d.ts +25 -0
- package/dist/client/content.d.ts.map +1 -0
- package/dist/client/inbox.d.ts +18 -0
- package/dist/client/inbox.d.ts.map +1 -0
- package/dist/client/index.d.ts +6 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +945 -0
- package/dist/client/index.js.map +22 -0
- package/dist/client/mx.d.ts +29 -0
- package/dist/client/mx.d.ts.map +1 -0
- package/dist/client/options.d.ts +136 -0
- package/dist/client/options.d.ts.map +1 -0
- package/dist/client/refusal.d.ts +15 -0
- package/dist/client/refusal.d.ts.map +1 -0
- package/dist/client/send.d.ts +13 -0
- package/dist/client/send.d.ts.map +1 -0
- package/dist/client/session.d.ts +11 -0
- package/dist/client/session.d.ts.map +1 -0
- package/dist/client/settings.d.ts +30 -0
- package/dist/client/settings.d.ts.map +1 -0
- package/dist/client/socket.d.ts +31 -0
- package/dist/client/socket.d.ts.map +1 -0
- package/dist/client/target.d.ts +29 -0
- package/dist/client/target.d.ts.map +1 -0
- package/dist/client/transaction.d.ts +15 -0
- package/dist/client/transaction.d.ts.map +1 -0
- package/dist/errors.d.ts +28 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.js +115 -196
- package/dist/index.js.map +9 -13
- package/dist/io/outgoing.d.ts +21 -0
- package/dist/io/outgoing.d.ts.map +1 -0
- package/dist/protocol/data-writer.d.ts +22 -0
- package/dist/protocol/data-writer.d.ts.map +1 -0
- package/dist/protocol/ehlo.d.ts +7 -0
- package/dist/protocol/ehlo.d.ts.map +1 -0
- package/dist/{server → protocol}/lines.d.ts +9 -4
- package/dist/protocol/lines.d.ts.map +1 -0
- package/dist/protocol/reply-reader.d.ts +19 -0
- package/dist/protocol/reply-reader.d.ts.map +1 -0
- package/dist/protocol/sasl.d.ts +4 -0
- package/dist/protocol/sasl.d.ts.map +1 -1
- package/dist/server/connection.d.ts +8 -0
- package/dist/server/connection.d.ts.map +1 -1
- package/dist/server/input.d.ts.map +1 -1
- package/dist/server/options.d.ts +2 -1
- package/dist/server/options.d.ts.map +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/settings.d.ts.map +1 -1
- package/dist/server/transport.d.ts +21 -4
- package/dist/server/transport.d.ts.map +1 -1
- package/docs/README.md +3 -3
- package/docs/guide.md +381 -9
- package/docs/roadmap.md +43 -8
- package/docs/troubleshooting.md +668 -7
- package/package.json +12 -4
- package/dist/server/lines.d.ts.map +0 -1
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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,
|
|
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 =
|
|
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,
|
|
239
|
+
return check(connection, decodePlain2(line));
|
|
313
240
|
case "auth-login-user":
|
|
314
241
|
return loginUser(connection, line);
|
|
315
242
|
default: {
|
|
316
|
-
const password =
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
1164
|
-
|
|
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
|
-
|
|
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.#
|
|
1184
|
-
|
|
1185
|
-
|
|
1057
|
+
this.#closed = true;
|
|
1058
|
+
this.#disarm();
|
|
1059
|
+
this.#outgoing.clear();
|
|
1186
1060
|
}
|
|
1187
1061
|
end() {
|
|
1188
|
-
if (this.#
|
|
1189
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1291
|
-
|
|
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=
|
|
1237
|
+
//# debugId=7E57D02B62ACF1F164756E2164756E21
|
|
1319
1238
|
//# sourceMappingURL=index.js.map
|