@bumail/smtp 0.5.0 → 0.7.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 +22 -2
- package/dist/chunks/{index-n4xwep5m.js → index-5228egp5.js} +52 -49
- package/dist/chunks/{index-n4xwep5m.js.map → index-5228egp5.js.map} +6 -6
- package/dist/client/index.js +23 -7
- package/dist/client/index.js.map +5 -5
- package/dist/client/refusal.d.ts +6 -0
- package/dist/client/refusal.d.ts.map +1 -1
- package/dist/client/socket.d.ts +3 -3
- package/dist/client/socket.d.ts.map +1 -1
- package/dist/client/transaction.d.ts.map +1 -1
- package/dist/errors.d.ts +15 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.js +100 -34
- package/dist/index.js.map +8 -8
- package/dist/server/admission.d.ts +10 -2
- package/dist/server/admission.d.ts.map +1 -1
- package/dist/server/server.d.ts +9 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/settings.d.ts +3 -0
- package/dist/server/settings.d.ts.map +1 -1
- package/dist/server/tls-context.d.ts +36 -6
- package/dist/server/tls-context.d.ts.map +1 -1
- package/docs/guide.md +85 -6
- package/docs/roadmap.md +6 -0
- package/docs/troubleshooting.md +46 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -110,6 +110,26 @@ handshake, and holds a slot of `maxConnections` and
|
|
|
110
110
|
`maxConnectionsPerClient` meanwhile: a socket that never sends its
|
|
111
111
|
ClientHello is closed, without a word.
|
|
112
112
|
|
|
113
|
+
## Renew the certificate without a restart
|
|
114
|
+
|
|
115
|
+
`setTls` takes a renewed key and certificate: the next STARTTLS upgrade or
|
|
116
|
+
implicit TLS connection uses them, and the sessions already open keep the
|
|
117
|
+
TLS they have. The pair is read and checked first; one that cannot be used
|
|
118
|
+
rejects with `INVALID_OPTION` and the old pair stays in use.
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
// After certbot, Traefik or an ACME client wrote new files:
|
|
122
|
+
await server.setTls({
|
|
123
|
+
key: Bun.file('/etc/ssl/smtp.example.com.key'),
|
|
124
|
+
cert: Bun.file('/etc/ssl/smtp.example.com.crt'),
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Call it on each server that holds the pair (the MX on 25, submission on
|
|
129
|
+
587 and on 465). The
|
|
130
|
+
[guide](https://github.com/softistx/bumail/blob/develop/packages/smtp/docs/guide.md#renewing-the-certificate-without-a-restart)
|
|
131
|
+
says how it works on each kind of port.
|
|
132
|
+
|
|
113
133
|
## Hooks for policy
|
|
114
134
|
|
|
115
135
|
A hook returns nothing to accept, or a `reply(code, status, text)` with a
|
|
@@ -458,10 +478,10 @@ host; a 5xx stops. A null MX (RFC 7505) fails at once with `NULL_MX`.
|
|
|
458
478
|
| export | |
|
|
459
479
|
| --- | --- |
|
|
460
480
|
| `createSmtpServer(options)` | the server; throws an `SmtpError` (`INVALID_OPTION`) on a bad option |
|
|
461
|
-
| `SmtpServer` | `listen({ port, hostname? })` (once; again, even before the first resolved, throws `ALREADY_LISTENING`; on implicit TLS, a key or certificate it cannot read or use throws `INVALID_OPTION`), `stop(closeConnections?)` (`true` hangs up on every client, after STARTTLS too), `connections` |
|
|
481
|
+
| `SmtpServer` | `listen({ port, hostname? })` (once; again, even before the first resolved, throws `ALREADY_LISTENING`; on implicit TLS, a key or certificate it cannot read or use throws `INVALID_OPTION`), `setTls({ key, cert })` (a renewed pair for new STARTTLS upgrades and new implicit TLS connections; open sessions keep theirs; a pair that cannot be used throws `INVALID_OPTION` and the old one stays), `stop(closeConnections?)` (`true` hangs up on every client, after STARTTLS too), `connections` |
|
|
462
482
|
| `SmtpServerOptions` | `hostname`, `mode`, `localDomains`, `tls`, `implicitTls`, `authenticate`, the limits (`maxConnectionsPerClient` among them), `hookTimeout`, `handshakeTimeout`, `greetingDelay`, `proxyProtocol`, `onError`, and the hooks |
|
|
463
483
|
| `ProxyProtocolOptions` | `trusted`: the proxies' IPv4 and IPv6 addresses and CIDRs, the only peers whose PROXY header (v1 or v2) is read |
|
|
464
|
-
| `SmtpError`, `SmtpErrorCode` | `code`: `INVALID_OPTION`, `ALREADY_LISTENING`, `STOPPED` (a `stop()` came before `listen` resolved), and what a content stream or `onError` can get: `MESSAGE_TOO_BIG`, `BARE_LINE_BREAK`, `CONNECTION_LOST`, `HOOK_TIMEOUT`, `INVALID_HOOK_REPLY`, `MESSAGE_NOT_READ`; `sendMail`'s are listed below. `temporary`, `reply` and `rejected` are set for `sendMail`'s errors |
|
|
484
|
+
| `SmtpError`, `SmtpErrorCode` | `code`: `INVALID_OPTION`, `ALREADY_LISTENING`, `STOPPED` (a `stop()` came before `listen` resolved), and what a content stream or `onError` can get: `MESSAGE_TOO_BIG`, `BARE_LINE_BREAK`, `CONNECTION_LOST`, `HOOK_TIMEOUT`, `INVALID_HOOK_REPLY`, `MESSAGE_NOT_READ`; `sendMail`'s are listed below. `temporary`, `reply` and `rejected` are set for `sendMail`'s errors, and `afterData` when the final dot was written and no reply came (the server may have taken the message) |
|
|
465
485
|
| `SmtpHooks` | `onConnect`, `onMailFrom`, `onRcptTo`, `onData` |
|
|
466
486
|
| `HookResult` | what a hook returns: `undefined` to accept, a `Reply` to refuse |
|
|
467
487
|
| `Session` | `id`, `remoteAddress`, `secure`, `helo`, `esmtp`, `user`, and `data` for your own state |
|
|
@@ -14,6 +14,7 @@ class SmtpError2 extends Error {
|
|
|
14
14
|
temporary;
|
|
15
15
|
reply;
|
|
16
16
|
rejected;
|
|
17
|
+
afterData;
|
|
17
18
|
constructor(code, message, details = {}) {
|
|
18
19
|
super(message, details.cause === undefined ? undefined : { cause: details.cause });
|
|
19
20
|
this.code = code;
|
|
@@ -22,6 +23,8 @@ class SmtpError2 extends Error {
|
|
|
22
23
|
this.reply = details.reply;
|
|
23
24
|
if (details.rejected)
|
|
24
25
|
this.rejected = details.rejected;
|
|
26
|
+
if (details.afterData)
|
|
27
|
+
this.afterData = true;
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
|
|
@@ -144,53 +147,6 @@ function encodeLoginStep(text) {
|
|
|
144
147
|
return base64(text);
|
|
145
148
|
}
|
|
146
149
|
|
|
147
|
-
// src/io/outgoing.ts
|
|
148
|
-
class Outgoing {
|
|
149
|
-
#write;
|
|
150
|
-
#queue = [];
|
|
151
|
-
#waiters = [];
|
|
152
|
-
constructor(write) {
|
|
153
|
-
this.#write = write;
|
|
154
|
-
}
|
|
155
|
-
get empty() {
|
|
156
|
-
return this.#queue.length === 0;
|
|
157
|
-
}
|
|
158
|
-
write(bytes) {
|
|
159
|
-
if (this.#queue.length > 0) {
|
|
160
|
-
this.#queue.push(bytes);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
const written = this.#write(bytes);
|
|
164
|
-
if (written < bytes.length) {
|
|
165
|
-
this.#queue.push(bytes.subarray(Math.max(written, 0)));
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
drain() {
|
|
169
|
-
while (this.#queue.length > 0) {
|
|
170
|
-
const bytes = this.#queue[0];
|
|
171
|
-
const written = this.#write(bytes);
|
|
172
|
-
if (written < bytes.length) {
|
|
173
|
-
this.#queue[0] = bytes.subarray(Math.max(written, 0));
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
this.#queue.shift();
|
|
177
|
-
}
|
|
178
|
-
for (const wake of this.#waiters.splice(0))
|
|
179
|
-
wake();
|
|
180
|
-
return true;
|
|
181
|
-
}
|
|
182
|
-
drained() {
|
|
183
|
-
if (this.#queue.length === 0)
|
|
184
|
-
return Promise.resolve();
|
|
185
|
-
return new Promise((wake) => this.#waiters.push(wake));
|
|
186
|
-
}
|
|
187
|
-
clear() {
|
|
188
|
-
this.#queue = [];
|
|
189
|
-
for (const wake of this.#waiters.splice(0))
|
|
190
|
-
wake();
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
150
|
// src/protocol/lines.ts
|
|
195
151
|
var MAX_LINE = 2048;
|
|
196
152
|
|
|
@@ -249,7 +205,54 @@ function concat(a, b) {
|
|
|
249
205
|
return out;
|
|
250
206
|
}
|
|
251
207
|
|
|
208
|
+
// src/io/outgoing.ts
|
|
209
|
+
class Outgoing {
|
|
210
|
+
#write;
|
|
211
|
+
#queue = [];
|
|
212
|
+
#waiters = [];
|
|
213
|
+
constructor(write) {
|
|
214
|
+
this.#write = write;
|
|
215
|
+
}
|
|
216
|
+
get empty() {
|
|
217
|
+
return this.#queue.length === 0;
|
|
218
|
+
}
|
|
219
|
+
write(bytes) {
|
|
220
|
+
if (this.#queue.length > 0) {
|
|
221
|
+
this.#queue.push(bytes);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const written = this.#write(bytes);
|
|
225
|
+
if (written < bytes.length) {
|
|
226
|
+
this.#queue.push(bytes.subarray(Math.max(written, 0)));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
drain() {
|
|
230
|
+
while (this.#queue.length > 0) {
|
|
231
|
+
const bytes = this.#queue[0];
|
|
232
|
+
const written = this.#write(bytes);
|
|
233
|
+
if (written < bytes.length) {
|
|
234
|
+
this.#queue[0] = bytes.subarray(Math.max(written, 0));
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
this.#queue.shift();
|
|
238
|
+
}
|
|
239
|
+
for (const wake of this.#waiters.splice(0))
|
|
240
|
+
wake();
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
drained() {
|
|
244
|
+
if (this.#queue.length === 0)
|
|
245
|
+
return Promise.resolve();
|
|
246
|
+
return new Promise((wake) => this.#waiters.push(wake));
|
|
247
|
+
}
|
|
248
|
+
clear() {
|
|
249
|
+
this.#queue = [];
|
|
250
|
+
for (const wake of this.#waiters.splice(0))
|
|
251
|
+
wake();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
252
255
|
export { SmtpError2, POSTMASTER, isHelloName, parsePath2, decodePlain2, decodeLoginStep2, encodePlain, encodeLoginStep, MAX_LINE, LineSplitter, Outgoing };
|
|
253
256
|
|
|
254
|
-
//# debugId=
|
|
255
|
-
//# sourceMappingURL=index-
|
|
257
|
+
//# debugId=6AFC48FB7973019764756E2164756E21
|
|
258
|
+
//# sourceMappingURL=index-5228egp5.js.map
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/errors.ts", "../src/protocol/path.ts", "../src/protocol/sasl.ts", "../src/
|
|
3
|
+
"sources": ["../src/errors.ts", "../src/protocol/path.ts", "../src/protocol/sasl.ts", "../src/protocol/lines.ts", "../src/io/outgoing.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { Reply } from './protocol/reply';\n\n/** Why the server refused an option, why a message stream ended in error, or why `sendMail` failed. */\nexport type SmtpErrorCode =\n\t| 'INVALID_OPTION'\n\t| 'ALREADY_LISTENING'\n\t| 'STOPPED'\n\t| 'MESSAGE_TOO_BIG'\n\t| 'BARE_LINE_BREAK'\n\t| 'CONNECTION_LOST'\n\t| 'HOOK_TIMEOUT'\n\t| 'INVALID_HOOK_REPLY'\n\t| 'MESSAGE_NOT_READ'\n\t| 'CONNECTION_FAILED'\n\t| 'TIMEOUT'\n\t| 'BAD_REPLY'\n\t| 'REFUSED'\n\t| 'RECIPIENTS_REFUSED'\n\t| 'TLS_UNAVAILABLE'\n\t| 'TLS_FAILED'\n\t| 'AUTH_UNAVAILABLE'\n\t| 'EXTENSION_MISSING'\n\t| 'NULL_MX'\n\t| 'DNS_FAILED';\n\n/** One recipient and the server's reply to its `RCPT TO`. */\nexport interface RecipientReply {\n\treadonly recipient: string;\n\treadonly reply: Reply;\n}\n\n/** What `sendMail` adds to an error: whether to try again, and the reply behind it. */\nexport interface SmtpErrorDetails {\n\t/** Trying again later may succeed: a 4xx, the network, a timeout. */\n\treadonly temporary?: boolean;\n\t/** The server's reply that failed the delivery, enhanced status code included. */\n\treadonly reply?: Reply;\n\t/** With `RECIPIENTS_REFUSED`: each recipient, and why it was refused. */\n\treadonly rejected?: readonly RecipientReply[];\n\t/** The error behind this one, as `Error`'s `cause`. */\n\treadonly cause?: unknown;\n}\n\n/** The codes that are temporary unless a reply or `details` says otherwise. */\nconst TEMPORARY = new Set<SmtpErrorCode>([\n\t'CONNECTION_LOST',\n\t'CONNECTION_FAILED',\n\t'TIMEOUT',\n\t'BAD_REPLY',\n\t'TLS_UNAVAILABLE',\n\t'TLS_FAILED',\n]);\n\n/**\n * Thrown by `createSmtpServer` and `listen` for a wrong option or call; the\n * error a message's `content` stream ends with when the message must not be\n * delivered; what `onError` is given for a hook that broke a rule; and what\n * `sendMail` rejects with. `temporary` tells a failure worth trying again\n * later (a 4xx, the network, a timeout) from a permanent one (a 5xx, a null\n * MX, a message the server can never take).\n */\nexport class SmtpError extends Error {\n\toverride readonly name = 'SmtpError';\n\treadonly code: SmtpErrorCode;\n\t/** Trying again later may succeed. Meaningful for `sendMail`'s errors. */\n\treadonly temporary: boolean;\n\t/** The server reply behind a `sendMail` failure, enhanced status code included. */\n\treadonly reply?: Reply;\n\t/** With `RECIPIENTS_REFUSED`: each recipient, and the reply that refused it. */\n\treadonly rejected?: readonly RecipientReply[];\n\n\tconstructor(\n\t\tcode: SmtpErrorCode,\n\t\tmessage: string,\n\t\tdetails: SmtpErrorDetails = {},\n\t) {\n\t\tsuper(\n\t\t\tmessage,\n\t\t\tdetails.cause === undefined ? undefined : { cause: details.cause },\n\t\t);\n\t\tthis.code = code;\n\t\tthis.temporary =\n\t\t\tdetails.temporary ??\n\t\t\t(details.reply ? details.reply.code < 500 : TEMPORARY.has(code));\n\t\tif (details.reply) this.reply = details.reply;\n\t\tif (details.rejected) this.rejected = details.rejected;\n\t}\n}\n",
|
|
5
|
+
"import type { Reply } from './protocol/reply';\n\n/** Why the server refused an option, why a message stream ended in error, or why `sendMail` failed. */\nexport type SmtpErrorCode =\n\t| 'INVALID_OPTION'\n\t| 'ALREADY_LISTENING'\n\t| 'STOPPED'\n\t| 'MESSAGE_TOO_BIG'\n\t| 'BARE_LINE_BREAK'\n\t| 'CONNECTION_LOST'\n\t| 'HOOK_TIMEOUT'\n\t| 'INVALID_HOOK_REPLY'\n\t| 'MESSAGE_NOT_READ'\n\t| 'CONNECTION_FAILED'\n\t| 'TIMEOUT'\n\t| 'BAD_REPLY'\n\t| 'REFUSED'\n\t| 'RECIPIENTS_REFUSED'\n\t| 'TLS_UNAVAILABLE'\n\t| 'TLS_FAILED'\n\t| 'AUTH_UNAVAILABLE'\n\t| 'EXTENSION_MISSING'\n\t| 'NULL_MX'\n\t| 'DNS_FAILED';\n\n/** One recipient and the server's reply to its `RCPT TO`. */\nexport interface RecipientReply {\n\treadonly recipient: string;\n\treadonly reply: Reply;\n}\n\n/** What `sendMail` adds to an error: whether to try again, and the reply behind it. */\nexport interface SmtpErrorDetails {\n\t/** Trying again later may succeed: a 4xx, the network, a timeout. */\n\treadonly temporary?: boolean;\n\t/** The server's reply that failed the delivery, enhanced status code included. */\n\treadonly reply?: Reply;\n\t/** With `RECIPIENTS_REFUSED`: each recipient, and why it was refused. */\n\treadonly rejected?: readonly RecipientReply[];\n\t/** The error behind this one, as `Error`'s `cause`. */\n\treadonly cause?: unknown;\n\t/**\n\t * The final dot of the message was written and no reply to it came (a\n\t * timeout, a lost connection, a reply that is not one): the server may\n\t * have taken the message. See `SmtpError.afterData`.\n\t */\n\treadonly afterData?: boolean;\n}\n\n/** The codes that are temporary unless a reply or `details` says otherwise. */\nconst TEMPORARY = new Set<SmtpErrorCode>([\n\t'CONNECTION_LOST',\n\t'CONNECTION_FAILED',\n\t'TIMEOUT',\n\t'BAD_REPLY',\n\t'TLS_UNAVAILABLE',\n\t'TLS_FAILED',\n]);\n\n/**\n * Thrown by `createSmtpServer` and `listen` for a wrong option or call; the\n * error a message's `content` stream ends with when the message must not be\n * delivered; what `onError` is given for a hook that broke a rule; and what\n * `sendMail` rejects with. `temporary` tells a failure worth trying again\n * later (a 4xx, the network, a timeout) from a permanent one (a 5xx, a null\n * MX, a message the server can never take).\n */\nexport class SmtpError extends Error {\n\toverride readonly name = 'SmtpError';\n\treadonly code: SmtpErrorCode;\n\t/** Trying again later may succeed. Meaningful for `sendMail`'s errors. */\n\treadonly temporary: boolean;\n\t/** The server reply behind a `sendMail` failure, enhanced status code included. */\n\treadonly reply?: Reply;\n\t/** With `RECIPIENTS_REFUSED`: each recipient, and the reply that refused it. */\n\treadonly rejected?: readonly RecipientReply[];\n\t/**\n\t * `true` when `sendMail` wrote the message's final dot and then got no\n\t * reply to it (a `TIMEOUT`, a `CONNECTION_LOST`, a `BAD_REPLY`): the\n\t * server may already have taken the message (RFC 5321 §4.1.5), so a\n\t * retry, or a delivery by another route, can duplicate it. Absent for a\n\t * failure before the dot (the server drops a message with no dot) and for\n\t * a reply that refused the message (a 4xx or 5xx says it was not taken).\n\t */\n\treadonly afterData?: boolean;\n\n\tconstructor(\n\t\tcode: SmtpErrorCode,\n\t\tmessage: string,\n\t\tdetails: SmtpErrorDetails = {},\n\t) {\n\t\tsuper(\n\t\t\tmessage,\n\t\t\tdetails.cause === undefined ? undefined : { cause: details.cause },\n\t\t);\n\t\tthis.code = code;\n\t\tthis.temporary =\n\t\t\tdetails.temporary ??\n\t\t\t(details.reply ? details.reply.code < 500 : TEMPORARY.has(code));\n\t\tif (details.reply) this.reply = details.reply;\n\t\tif (details.rejected) this.rejected = details.rejected;\n\t\tif (details.afterData) this.afterData = true;\n\t}\n}\n",
|
|
6
6
|
"/** An envelope address, as `MAIL FROM` and `RCPT TO` give it. */\nexport interface Path {\n\t/**\n\t * `local@domain`; `''` for the null reverse-path `<>`; `'postmaster'`\n\t * for `RCPT TO:<postmaster>`, which has no domain.\n\t */\n\treadonly address: string;\n\treadonly local: string;\n\t/** Lower case; `''` for `<>` and for `<postmaster>`. */\n\treadonly domain: string;\n\t/**\n\t * `true` for `RCPT TO:<postmaster>`, in any case, with no domain (RFC\n\t * 5321 §4.5.1): the postmaster of this server, whatever domains it\n\t * hosts. Absent for every other path, `<postmaster@domain>` included.\n\t */\n\treadonly postmaster?: true;\n}\n\n/** The bare `<postmaster>` path `RCPT TO` takes (RFC 5321 §4.1.1.3, §4.5.1). */\nexport const POSTMASTER: Path = Object.freeze({\n\taddress: 'postmaster',\n\tlocal: 'postmaster',\n\tdomain: '',\n\tpostmaster: true,\n});\n\nconst ATOM = /^[A-Za-z0-9!#$%&'*+\\-/=?^_`{|}~\\u0080-\\u{10ffff}]+$/u;\nconst LABEL =\n\t/^(?:[A-Za-z0-9\\u0080-\\u{10ffff}](?:[A-Za-z0-9\\-\\u0080-\\u{10ffff}]*[A-Za-z0-9\\u0080-\\u{10ffff}])?)$/u;\n\n/**\n * C1 controls, Unicode format characters (zero-width, bidi, BOM), the\n * line and paragraph separators, and lone surrogates: none has a place in\n * an address, and each would land as it is in the Received field and the\n * app's records — a lone surrogate as U+FFFD, once encoded for the wire.\n */\nconst INVISIBLE = /[\\u0080-\\u009f\\u2028\\u2029\\p{Cf}\\p{Cs}]/u;\n\n/**\n * C0 controls (CR, LF and NUL among them), DEL and `>`, anywhere in the\n * path, quoted or not: a CR or an LF would end the command line and start\n * another, and a `>` would end the path where a reader of the line stops.\n */\nfunction hasControl(text: string): boolean {\n\tfor (let i = 0; i < text.length; i++) {\n\t\tconst code = text.charCodeAt(i);\n\t\tif (code < 0x20 || code === 0x7f || code === 0x3e) return true;\n\t}\n\treturn false;\n}\n\n/** One hop of a source route, `@domain`, and what follows it: `,` or `:`. */\nconst HOP = /^@(\\[[^\\]]*\\]|[^,:@[\\]]*)([,:])/;\n\nfunction isDotString(text: string): boolean {\n\treturn text.split('.').every((atom) => ATOM.test(atom));\n}\n\nfunction isQuotedString(text: string): boolean {\n\treturn /^\"(?:[\\x20\\x21\\x23-\\x5b\\x5d-\\x7e\\u0080-\\u{10ffff}]|\\\\[\\x20-\\x7e])*\"$/u.test(\n\t\ttext,\n\t);\n}\n\n/**\n * An address literal (§4.1.3): `[IPv6:…]`, its tag in any case (a quoted\n * string in RFC 5234 is), or `[a.b.c.d]` with each octet at most 255.\n */\nexport function isAddressLiteral(text: string): boolean {\n\tif (/^\\[IPv6:[0-9a-f:.]+\\]$/i.test(text)) return true;\n\tconst ipv4 = /^\\[(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\]$/.exec(text);\n\treturn ipv4?.slice(1).every((octet) => Number(octet) <= 255) === true;\n}\n\n/**\n * The argument of EHLO or HELO (RFC 5321 §4.1.1.1): a host name of\n * letters, digits and hyphens, or an address literal.\n */\nexport function isHelloName(text: string): boolean {\n\treturn (\n\t\t/^[A-Za-z0-9-]+(?:\\.[A-Za-z0-9-]+)*$/.test(text) || isAddressLiteral(text)\n\t);\n}\n\nfunction isDomain(domain: string): boolean {\n\tif (isAddressLiteral(domain)) return true;\n\treturn (\n\t\tdomain.length <= 255 &&\n\t\tdomain.split('.').every((label) => label.length <= 63 && LABEL.test(label))\n\t);\n}\n\n/**\n * What `parsePath` does with a source route (`<@a,@b:local@domain>`, RFC 5321\n * Appendix C): a server accepts and discards it (`'discard'`), a client\n * refuses it, as §4.1.1.3 says clients should not send one (`'refuse'`).\n */\nexport type SourceRoute = 'discard' | 'refuse';\n\n/**\n * The mailbox after a source route, each of whose hops must be\n * `@domain` with a valid domain; `undefined` for any other route.\n */\nfunction afterRoute(inner: string): string | undefined {\n\tlet rest = inner;\n\tfor (;;) {\n\t\tconst hop = HOP.exec(rest);\n\t\tif (!hop || !isDomain(hop[1] as string)) return undefined;\n\t\trest = rest.slice(hop[0].length);\n\t\tif (hop[2] === ':') return rest;\n\t}\n}\n\n/**\n * Parses the `<path>` of RFC 5321 §4.1.2: `<local@domain>`, or `<>` when\n * `allowNull`. A source route (`<@a,@b:local@domain>`, §C) is dropped when\n * `sourceRoute` is `'discard'`, the default, provided each hop is\n * `@domain`; with `'refuse'` the path is not one. A C0 control (CR, LF,\n * NUL…), DEL or `>` anywhere refuses the path, as do C1 controls,\n * U+2028 and U+2029, Unicode format characters and lone surrogates. An IPv4 address literal\n * takes octets up to 255.\n * Non-ASCII is allowed: whether the session may use it is SMTPUTF8's\n * question (RFC 6531), not the grammar's. `undefined` when it is not a path.\n *\n * The local part is kept as written, quotes included: `\"v@x.example\"` and\n * `v%x.example` are local parts of the domain after the last `@`, not\n * addresses elsewhere. Code that delivers must not split on the first `@`.\n */\nexport function parsePath(\n\ttext: string,\n\tallowNull: boolean,\n\tsourceRoute: SourceRoute = 'discard',\n): Path | undefined {\n\tif (!text.startsWith('<') || !text.endsWith('>')) return undefined;\n\tlet inner: string | undefined = text.slice(1, -1);\n\tif (hasControl(inner) || INVISIBLE.test(inner)) return undefined;\n\tif (inner === '')\n\t\treturn allowNull ? { address: '', local: '', domain: '' } : undefined;\n\tif (inner.startsWith('@')) {\n\t\tif (sourceRoute === 'refuse') return undefined;\n\t\tinner = afterRoute(inner);\n\t\tif (inner === undefined) return undefined;\n\t}\n\tconst at = inner.lastIndexOf('@');\n\tif (at <= 0) return undefined;\n\tconst local = inner.slice(0, at);\n\tconst domain = inner.slice(at + 1);\n\tif (local.length > 64 || !(isDotString(local) || isQuotedString(local)))\n\t\treturn undefined;\n\tif (!isDomain(domain)) return undefined;\n\treturn {\n\t\taddress: `${local}@${domain.toLowerCase()}`,\n\t\tlocal,\n\t\tdomain: domain.toLowerCase(),\n\t};\n}\n",
|
|
7
7
|
"/** Credentials a client gave with `AUTH`. */\nexport interface Credentials {\n\treadonly mechanism: 'PLAIN' | 'LOGIN';\n\treadonly username: string;\n\treadonly password: string;\n\t/**\n\t * PLAIN's authorization identity, when it differs from the username.\n\t * The server refuses such credentials before `authenticate` is asked.\n\t */\n\treadonly authorizationId?: string;\n}\n\nfunction base64ToText(text: string): string | undefined {\n\tif (!/^[A-Za-z0-9+/]*={0,2}$/.test(text) || text.length % 4 !== 0)\n\t\treturn undefined;\n\ttry {\n\t\treturn new TextDecoder('utf-8', { fatal: true }).decode(\n\t\t\tUint8Array.fromBase64(text),\n\t\t);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** PLAIN (RFC 4616): `authzid NUL authcid NUL passwd`, in base64. */\nexport function decodePlain(response: string): Credentials | undefined {\n\tconst text = base64ToText(response);\n\tif (text === undefined) return undefined;\n\tconst parts = text.split('\\0');\n\tif (parts.length !== 3) return undefined;\n\tconst [authorizationId = '', username = '', password = ''] = parts;\n\tif (username === '' || password === '') return undefined;\n\treturn {\n\t\tmechanism: 'PLAIN',\n\t\tusername,\n\t\tpassword,\n\t\t...(authorizationId === '' || authorizationId === username\n\t\t\t? {}\n\t\t\t: { authorizationId }),\n\t};\n}\n\n/** One step of LOGIN: a base64 line to its text. */\nexport function decodeLoginStep(response: string): string | undefined {\n\treturn base64ToText(response);\n}\n\nconst base64 = (text: string) => new TextEncoder().encode(text).toBase64();\n\n/** A client's PLAIN response (RFC 4616): `NUL username NUL password`, in base64, no authorization identity. */\nexport function encodePlain(username: string, password: string): string {\n\treturn base64(`\\0${username}\\0${password}`);\n}\n\n/** One step of LOGIN, the username or the password, in base64. */\nexport function encodeLoginStep(text: string): string {\n\treturn base64(text);\n}\n",
|
|
8
|
-
"/**\n *
|
|
9
|
-
"/**\n *
|
|
8
|
+
"/**\n * The longest line taken, CRLF included: a command (RFC 5321 §4.5.3.1.4\n * asks for 512 at least) or a reply line (§4.5.3.1.5, 512 too).\n */\nexport const MAX_LINE = 2048;\n\n/** One line from the peer, or a line that was too long and was skipped. */\nexport type LineEvent = { line: string } | { tooLong: true };\n\n/**\n * Cuts a peer's bytes into lines — a client's commands, a server's\n * replies — ending at LF with or without CR. It holds one partial line at most: past `MAX_LINE`, the line is\n * skipped to its end and reported once.\n */\nexport class LineSplitter {\n\t#held: Uint8Array = new Uint8Array(0);\n\t#skipping = false;\n\t#rest: Uint8Array = new Uint8Array(0);\n\n\t/** Starts on a chunk; read its lines with `next`. */\n\tpush(chunk: Uint8Array): void {\n\t\tthis.#rest = this.#held.length > 0 ? concat(this.#held, chunk) : chunk;\n\t\tthis.#held = new Uint8Array(0);\n\t}\n\n\t/** The next line of the current chunk, or `undefined` when it has no more. */\n\tnext(): LineEvent | undefined {\n\t\tfor (;;) {\n\t\t\tconst data = this.#rest;\n\t\t\tconst lf = data.indexOf(0x0a);\n\t\t\tif (lf < 0) {\n\t\t\t\tthis.#rest = new Uint8Array(0);\n\t\t\t\tif (this.#skipping) return undefined;\n\t\t\t\tif (data.length > MAX_LINE) {\n\t\t\t\t\tthis.#skipping = true;\n\t\t\t\t\treturn { tooLong: true };\n\t\t\t\t}\n\t\t\t\tthis.#held = data.slice();\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tconst end = lf > 0 && data[lf - 1] === 0x0d ? lf - 1 : lf;\n\t\t\tthis.#rest = data.subarray(lf + 1);\n\t\t\tif (this.#skipping) {\n\t\t\t\tthis.#skipping = false;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (end > MAX_LINE) return { tooLong: true };\n\t\t\treturn { line: new TextDecoder().decode(data.subarray(0, end)) };\n\t\t}\n\t}\n\n\t/** Bytes are held or left: a line begun, or lines not yet read. */\n\tget holding(): boolean {\n\t\treturn this.#held.length > 0 || this.#rest.length > 0;\n\t}\n\n\t/** What is left of the current chunk: message content pipelined after DATA. */\n\ttakeRest(): Uint8Array {\n\t\tconst rest = this.#rest;\n\t\tthis.#rest = new Uint8Array(0);\n\t\treturn rest;\n\t}\n\n\t/** Forgets everything held: after STARTTLS, or after a refused DATA. */\n\tclear(): void {\n\t\tthis.#held = new Uint8Array(0);\n\t\tthis.#rest = new Uint8Array(0);\n\t\tthis.#skipping = false;\n\t}\n}\n\nfunction concat(a: Uint8Array, b: Uint8Array): Uint8Array {\n\tconst out = new Uint8Array(a.length + b.length);\n\tout.set(a, 0);\n\tout.set(b, a.length);\n\treturn out;\n}\n",
|
|
9
|
+
"/**\n * What waits to leave a Bun socket. Bun's sockets do not buffer: `write`\n * takes what the kernel takes and says how much. The rest waits here for\n * the socket's `drain` event, so nothing written is lost to a peer that\n * reads slowly. The server's transport and the client's socket share it.\n */\nexport class Outgoing {\n\treadonly #write: (bytes: Uint8Array) => number;\n\t#queue: Uint8Array[] = [];\n\t#waiters: (() => void)[] = [];\n\n\tconstructor(write: (bytes: Uint8Array) => number) {\n\t\tthis.#write = write;\n\t}\n\n\t/** Nothing waits to leave. */\n\tget empty(): boolean {\n\t\treturn this.#queue.length === 0;\n\t}\n\n\t/** Writes what the socket takes now, and keeps the rest for `drain`. */\n\twrite(bytes: Uint8Array): void {\n\t\tif (this.#queue.length > 0) {\n\t\t\tthis.#queue.push(bytes);\n\t\t\treturn;\n\t\t}\n\t\tconst written = this.#write(bytes);\n\t\tif (written < bytes.length) {\n\t\t\tthis.#queue.push(bytes.subarray(Math.max(written, 0)));\n\t\t}\n\t}\n\n\t/** The socket's `drain`: it can take more. True once everything has left. */\n\tdrain(): boolean {\n\t\twhile (this.#queue.length > 0) {\n\t\t\tconst bytes = this.#queue[0] as Uint8Array;\n\t\t\tconst written = this.#write(bytes);\n\t\t\tif (written < bytes.length) {\n\t\t\t\tthis.#queue[0] = bytes.subarray(Math.max(written, 0));\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthis.#queue.shift();\n\t\t}\n\t\tfor (const wake of this.#waiters.splice(0)) wake();\n\t\treturn true;\n\t}\n\n\t/** Resolves once everything written has left, or the socket closed. */\n\tdrained(): Promise<void> {\n\t\tif (this.#queue.length === 0) return Promise.resolve();\n\t\treturn new Promise((wake) => this.#waiters.push(wake));\n\t}\n\n\t/** The socket closed: nothing more will leave. */\n\tclear(): void {\n\t\tthis.#queue = [];\n\t\tfor (const wake of this.#waiters.splice(0)) wake();\n\t}\n}\n"
|
|
10
10
|
],
|
|
11
|
-
"mappings": ";
|
|
12
|
-
"debugId": "
|
|
11
|
+
"mappings": "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",
|
|
12
|
+
"debugId": "6AFC48FB7973019764756E2164756E21",
|
|
13
13
|
"names": []
|
|
14
14
|
}
|
package/dist/client/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
MAX_LINE,
|
|
8
8
|
LineSplitter,
|
|
9
9
|
Outgoing
|
|
10
|
-
} from "../chunks/index-
|
|
10
|
+
} from "../chunks/index-5228egp5.js";
|
|
11
11
|
// src/client/address.ts
|
|
12
12
|
function isMailbox(address) {
|
|
13
13
|
return typeof address === "string" && parsePath2(`<${address}>`, false, "refuse") !== undefined;
|
|
@@ -302,6 +302,16 @@ var badReply = (host, what) => new SmtpError2("BAD_REPLY", `${host} sent ${what}
|
|
|
302
302
|
var lost = (host, what) => new SmtpError2("CONNECTION_LOST", what === undefined ? `The connection to ${host} closed while the message was sent` : `The connection to ${host} closed before ${what}`);
|
|
303
303
|
var timedOut = (host, what, seconds, deadline) => new SmtpError2("TIMEOUT", deadline === undefined ? `Timed out after ${seconds} s waiting for ${what} (${host})` : `The deadline of ${deadline} s passed waiting for ${what} (${host})`);
|
|
304
304
|
var tlsFailed = (host, why) => new SmtpError2("TLS_FAILED", `TLS with ${host} failed: ${why}`);
|
|
305
|
+
function afterDot(error) {
|
|
306
|
+
if (!(error instanceof SmtpError2) || error.afterData || error.reply)
|
|
307
|
+
return error;
|
|
308
|
+
return new SmtpError2(error.code, error.message, {
|
|
309
|
+
temporary: error.temporary,
|
|
310
|
+
afterData: true,
|
|
311
|
+
...error.rejected ? { rejected: error.rejected } : {},
|
|
312
|
+
...error.cause === undefined ? {} : { cause: error.cause }
|
|
313
|
+
});
|
|
314
|
+
}
|
|
305
315
|
|
|
306
316
|
// src/protocol/ehlo.ts
|
|
307
317
|
function parseEhlo(lines) {
|
|
@@ -705,15 +715,19 @@ class ClientSocket {
|
|
|
705
715
|
}
|
|
706
716
|
}
|
|
707
717
|
within(promise, seconds, what) {
|
|
718
|
+
const over = new AbortController;
|
|
708
719
|
const gone = this.#gone.promise.then(() => {
|
|
709
|
-
|
|
720
|
+
if (!over.signal.aborted)
|
|
721
|
+
throw this.#hungUp(lost(this.host, what));
|
|
710
722
|
});
|
|
711
|
-
|
|
723
|
+
const race = Promise.race([promise, gone]);
|
|
724
|
+
return this.#race(race, seconds, what).finally(() => over.abort());
|
|
712
725
|
}
|
|
713
726
|
write(data) {
|
|
714
727
|
if (this.#failure || this.#closed)
|
|
715
|
-
return;
|
|
728
|
+
return false;
|
|
716
729
|
this.#outgoing.write(typeof data === "string" ? new TextEncoder().encode(data) : data);
|
|
730
|
+
return true;
|
|
717
731
|
}
|
|
718
732
|
async drained(seconds) {
|
|
719
733
|
await this.#race(this.#outgoing.drained(), seconds, "the server to take the message");
|
|
@@ -840,8 +854,10 @@ async function data(socket, settings, content, ready) {
|
|
|
840
854
|
socket.close(true);
|
|
841
855
|
throw error;
|
|
842
856
|
}
|
|
843
|
-
socket.write(tail);
|
|
844
|
-
const end = await socket.reply("the reply to the final dot", timeouts.dataEnd)
|
|
857
|
+
const dotSent = socket.write(tail);
|
|
858
|
+
const end = await socket.reply("the reply to the final dot", timeouts.dataEnd).catch((error) => {
|
|
859
|
+
throw dotSent ? afterDot(error) : error;
|
|
860
|
+
});
|
|
845
861
|
if (end.code !== 250)
|
|
846
862
|
throw refused(host, "the message", end);
|
|
847
863
|
return end;
|
|
@@ -951,5 +967,5 @@ export {
|
|
|
951
967
|
sendMail
|
|
952
968
|
};
|
|
953
969
|
|
|
954
|
-
//# debugId=
|
|
970
|
+
//# debugId=30DCD813BBFF1F0564756E2164756E21
|
|
955
971
|
//# sourceMappingURL=index.js.map
|