@bumail/smtp 0.9.0 → 0.10.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 +6 -4
- package/dist/chunks/{index-wc25ywta.js → index-twt5myrr.js} +37 -3
- package/dist/chunks/{index-wc25ywta.js.map → index-twt5myrr.js.map} +4 -3
- package/dist/client/dsn.d.ts +21 -0
- package/dist/client/dsn.d.ts.map +1 -0
- package/dist/client/index.js +81 -5
- package/dist/client/index.js.map +6 -5
- package/dist/client/options.d.ts +21 -0
- package/dist/client/options.d.ts.map +1 -1
- package/dist/client/settings.d.ts +2 -0
- package/dist/client/settings.d.ts.map +1 -1
- package/dist/client/transaction.d.ts +2 -0
- package/dist/client/transaction.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +116 -15
- package/dist/index.js.map +10 -8
- package/dist/protocol/command.d.ts +2 -0
- package/dist/protocol/command.d.ts.map +1 -1
- package/dist/protocol/xtext.d.ts +14 -0
- package/dist/protocol/xtext.d.ts.map +1 -0
- package/dist/server/commands.d.ts.map +1 -1
- package/dist/server/dsn-params.d.ts +20 -0
- package/dist/server/dsn-params.d.ts.map +1 -0
- package/dist/server/envelope.d.ts +5 -0
- package/dist/server/envelope.d.ts.map +1 -0
- package/dist/server/intake.d.ts.map +1 -1
- package/dist/server/options.d.ts +28 -0
- package/dist/server/options.d.ts.map +1 -1
- package/dist/server/state.d.ts +4 -1
- package/dist/server/state.d.ts.map +1 -1
- package/dist/server/transaction.d.ts +2 -2
- package/dist/server/transaction.d.ts.map +1 -1
- package/docs/guide.md +100 -5
- package/docs/roadmap.md +11 -5
- package/docs/troubleshooting.md +54 -6
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -6,8 +6,9 @@ import {
|
|
|
6
6
|
decodePlain2,
|
|
7
7
|
decodeLoginStep2,
|
|
8
8
|
LineSplitter,
|
|
9
|
+
decodeXtext,
|
|
9
10
|
Outgoing
|
|
10
|
-
} from "./chunks/index-
|
|
11
|
+
} from "./chunks/index-twt5myrr.js";
|
|
11
12
|
// src/protocol/command.ts
|
|
12
13
|
function parseCommand(line) {
|
|
13
14
|
const space = line.indexOf(" ");
|
|
@@ -27,6 +28,7 @@ function parsePathCommand(argument, keyword) {
|
|
|
27
28
|
if (!path)
|
|
28
29
|
return;
|
|
29
30
|
const parameters = {};
|
|
31
|
+
const repeated = [];
|
|
30
32
|
for (const item of match[2].trim().split(/\s+/)) {
|
|
31
33
|
if (item === "")
|
|
32
34
|
continue;
|
|
@@ -34,9 +36,11 @@ function parsePathCommand(argument, keyword) {
|
|
|
34
36
|
const key = (equals < 0 ? item : item.slice(0, equals)).toUpperCase();
|
|
35
37
|
if (!/^[A-Z0-9][A-Z0-9-]*$/.test(key))
|
|
36
38
|
return;
|
|
39
|
+
if (key in parameters)
|
|
40
|
+
repeated.push(key);
|
|
37
41
|
parameters[key] = equals < 0 ? "" : item.slice(equals + 1);
|
|
38
42
|
}
|
|
39
|
-
return { path, parameters };
|
|
43
|
+
return repeated.length > 0 ? { path, parameters, repeated } : { path, parameters };
|
|
40
44
|
}
|
|
41
45
|
// src/protocol/data.ts
|
|
42
46
|
var CR = 13;
|
|
@@ -287,10 +291,75 @@ async function continueAuth(connection, line) {
|
|
|
287
291
|
// src/server/state.ts
|
|
288
292
|
var emptyTransaction = () => ({
|
|
289
293
|
to: [],
|
|
294
|
+
recipients: [],
|
|
290
295
|
smtputf8: false,
|
|
291
296
|
body: "7BIT"
|
|
292
297
|
});
|
|
293
298
|
|
|
299
|
+
// src/server/dsn-params.ts
|
|
300
|
+
var MAIL_DSN = new Set(["RET", "ENVID"]);
|
|
301
|
+
var RCPT_DSN = new Set(["NOTIFY", "ORCPT"]);
|
|
302
|
+
var EVENTS = ["SUCCESS", "FAILURE", "DELAY"];
|
|
303
|
+
function envid(value) {
|
|
304
|
+
const text = value.length <= 100 ? decodeXtext(value) : undefined;
|
|
305
|
+
return text !== undefined && /^[ -~]+$/.test(text) ? text : undefined;
|
|
306
|
+
}
|
|
307
|
+
function notify(value) {
|
|
308
|
+
const items = value.toUpperCase().split(",");
|
|
309
|
+
if (items.length === 1 && items[0] === "NEVER")
|
|
310
|
+
return "NEVER";
|
|
311
|
+
const events = [];
|
|
312
|
+
for (const item of items) {
|
|
313
|
+
const event = EVENTS.find((candidate) => candidate === item);
|
|
314
|
+
if (!event || events.includes(event))
|
|
315
|
+
return;
|
|
316
|
+
events.push(event);
|
|
317
|
+
}
|
|
318
|
+
return events;
|
|
319
|
+
}
|
|
320
|
+
function orcpt(value) {
|
|
321
|
+
const semicolon = value.indexOf(";");
|
|
322
|
+
if (semicolon < 1 || value.length > 500)
|
|
323
|
+
return;
|
|
324
|
+
const type = value.slice(0, semicolon);
|
|
325
|
+
const address = decodeXtext(value.slice(semicolon + 1));
|
|
326
|
+
if (!/^[A-Za-z0-9-]+$/.test(type) || !address)
|
|
327
|
+
return;
|
|
328
|
+
return { type, address };
|
|
329
|
+
}
|
|
330
|
+
function readMailDsn(into, key, value, repeated) {
|
|
331
|
+
if (repeated.includes(key))
|
|
332
|
+
return `${key} given twice`;
|
|
333
|
+
if (key === "RET") {
|
|
334
|
+
const ret = value.toUpperCase();
|
|
335
|
+
if (ret !== "FULL" && ret !== "HDRS")
|
|
336
|
+
return "Syntax: RET=FULL or RET=HDRS";
|
|
337
|
+
into.ret = ret;
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const id = envid(value);
|
|
341
|
+
if (id === undefined)
|
|
342
|
+
return "Syntax: ENVID=<xtext, 1 to 100 characters>";
|
|
343
|
+
into.envid = id;
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
function readRcptDsn(into, key, value, repeated) {
|
|
347
|
+
if (repeated.includes(key))
|
|
348
|
+
return `${key} given twice`;
|
|
349
|
+
if (key === "NOTIFY") {
|
|
350
|
+
const events = notify(value);
|
|
351
|
+
if (!events)
|
|
352
|
+
return "Syntax: NOTIFY=NEVER or NOTIFY=SUCCESS,FAILURE,DELAY";
|
|
353
|
+
into.notify = events;
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const original = orcpt(value);
|
|
357
|
+
if (!original)
|
|
358
|
+
return "Syntax: ORCPT=<addr-type>;<xtext>";
|
|
359
|
+
into.orcpt = original;
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
294
363
|
// src/server/transaction.ts
|
|
295
364
|
function nonAscii(text) {
|
|
296
365
|
for (let i = 0;i < text.length; i++)
|
|
@@ -311,7 +380,8 @@ async function mail(connection, argument) {
|
|
|
311
380
|
const command = parsePathCommand(argument, "FROM");
|
|
312
381
|
if (!command)
|
|
313
382
|
return connection.fail(reply(501, "5.5.4", "Syntax: MAIL FROM:<address>"));
|
|
314
|
-
const { path, parameters } = command;
|
|
383
|
+
const { path, parameters, repeated = [] } = command;
|
|
384
|
+
const dsn = {};
|
|
315
385
|
let body = "7BIT";
|
|
316
386
|
let smtputf8 = false;
|
|
317
387
|
for (const [key, value] of Object.entries(parameters)) {
|
|
@@ -331,7 +401,11 @@ async function mail(connection, argument) {
|
|
|
331
401
|
body = kind;
|
|
332
402
|
} else if (key === "SMTPUTF8" && value === "") {
|
|
333
403
|
smtputf8 = true;
|
|
334
|
-
} else if (key === "AUTH") {} else {
|
|
404
|
+
} else if (key === "AUTH") {} else if (MAIL_DSN.has(key)) {
|
|
405
|
+
const wrong = readMailDsn(dsn, key, value, repeated);
|
|
406
|
+
if (wrong)
|
|
407
|
+
return connection.fail(reply(501, "5.5.4", wrong));
|
|
408
|
+
} else {
|
|
335
409
|
return connection.fail(reply(555, "5.5.4", `${key} is not supported`));
|
|
336
410
|
}
|
|
337
411
|
}
|
|
@@ -342,7 +416,14 @@ async function mail(connection, argument) {
|
|
|
342
416
|
const refused = options.onMailFrom ? await connection.hook("onMailFrom", () => options.onMailFrom?.(path, connection.session)) : undefined;
|
|
343
417
|
if (refused)
|
|
344
418
|
return connection.send(refused);
|
|
345
|
-
state.transaction = {
|
|
419
|
+
state.transaction = {
|
|
420
|
+
from: path.address,
|
|
421
|
+
to: [],
|
|
422
|
+
recipients: [],
|
|
423
|
+
smtputf8,
|
|
424
|
+
body,
|
|
425
|
+
...dsn
|
|
426
|
+
};
|
|
346
427
|
connection.send(reply(250, "2.1.0", "OK"));
|
|
347
428
|
}
|
|
348
429
|
async function rcpt(connection, argument) {
|
|
@@ -352,9 +433,19 @@ async function rcpt(connection, argument) {
|
|
|
352
433
|
const command = parsePathCommand(argument, "TO");
|
|
353
434
|
if (!command)
|
|
354
435
|
return connection.fail(reply(501, "5.5.4", "Syntax: RCPT TO:<address>"));
|
|
355
|
-
const { path, parameters } = command;
|
|
356
|
-
|
|
357
|
-
|
|
436
|
+
const { path, parameters, repeated = [] } = command;
|
|
437
|
+
const recipient = {
|
|
438
|
+
address: path.address
|
|
439
|
+
};
|
|
440
|
+
for (const [key, value] of Object.entries(parameters)) {
|
|
441
|
+
if (!state.esmtp)
|
|
442
|
+
return connection.fail(reply(555, "5.5.4", `${key} needs EHLO`));
|
|
443
|
+
if (!RCPT_DSN.has(key)) {
|
|
444
|
+
return connection.fail(reply(555, "5.5.4", `${key} is not supported`));
|
|
445
|
+
}
|
|
446
|
+
const wrong = readRcptDsn(recipient, key, value, repeated);
|
|
447
|
+
if (wrong)
|
|
448
|
+
return connection.fail(reply(501, "5.5.4", wrong));
|
|
358
449
|
}
|
|
359
450
|
if (!state.transaction.smtputf8 && nonAscii(path.address)) {
|
|
360
451
|
return connection.fail(reply(553, "5.6.7", "A non-ASCII address needs SMTPUTF8"));
|
|
@@ -375,6 +466,7 @@ async function rcpt(connection, argument) {
|
|
|
375
466
|
if (refused)
|
|
376
467
|
return connection.send(refused);
|
|
377
468
|
state.transaction.to.push(path.address);
|
|
469
|
+
state.transaction.recipients.push(recipient);
|
|
378
470
|
connection.send(reply(250, "2.1.5", "OK"));
|
|
379
471
|
}
|
|
380
472
|
|
|
@@ -387,6 +479,7 @@ function ehloLines(connection) {
|
|
|
387
479
|
`SIZE ${settings.maxMessageSize}`,
|
|
388
480
|
"8BITMIME",
|
|
389
481
|
"SMTPUTF8",
|
|
482
|
+
"DSN",
|
|
390
483
|
"ENHANCEDSTATUSCODES"
|
|
391
484
|
];
|
|
392
485
|
if (settings.options.tls && !state.secure)
|
|
@@ -473,6 +566,19 @@ async function runCommand(connection, line) {
|
|
|
473
566
|
}
|
|
474
567
|
}
|
|
475
568
|
|
|
569
|
+
// src/server/envelope.ts
|
|
570
|
+
function envelopeOf(transaction) {
|
|
571
|
+
return {
|
|
572
|
+
from: transaction.from ?? "",
|
|
573
|
+
to: [...transaction.to],
|
|
574
|
+
smtputf8: transaction.smtputf8,
|
|
575
|
+
body: transaction.body,
|
|
576
|
+
...transaction.ret !== undefined && { ret: transaction.ret },
|
|
577
|
+
...transaction.envid !== undefined && { envid: transaction.envid },
|
|
578
|
+
recipients: [...transaction.recipients]
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
|
|
476
582
|
// src/server/received.ts
|
|
477
583
|
import { isIPv6 } from "node:net";
|
|
478
584
|
var DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
@@ -592,12 +698,7 @@ class Intake {
|
|
|
592
698
|
this.#push(new TextEncoder().encode(receivedField(connection, transaction.to, this.id)));
|
|
593
699
|
const message = {
|
|
594
700
|
id: this.id,
|
|
595
|
-
envelope:
|
|
596
|
-
from: transaction.from ?? "",
|
|
597
|
-
to: [...transaction.to],
|
|
598
|
-
smtputf8: transaction.smtputf8,
|
|
599
|
-
body: transaction.body
|
|
600
|
-
},
|
|
701
|
+
envelope: envelopeOf(transaction),
|
|
601
702
|
content,
|
|
602
703
|
signal: this.#refusal.signal
|
|
603
704
|
};
|
|
@@ -1952,5 +2053,5 @@ export {
|
|
|
1952
2053
|
reply
|
|
1953
2054
|
};
|
|
1954
2055
|
|
|
1955
|
-
//# debugId=
|
|
2056
|
+
//# debugId=5D8963483AFC536F64756E2164756E21
|
|
1956
2057
|
//# sourceMappingURL=index.js.map
|