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