@bumail/smtp 0.8.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.
Files changed (41) hide show
  1. package/README.md +7 -5
  2. package/dist/chunks/{index-5228egp5.js → index-twt5myrr.js} +40 -3
  3. package/dist/chunks/{index-5228egp5.js.map → index-twt5myrr.js.map} +5 -4
  4. package/dist/client/dsn.d.ts +21 -0
  5. package/dist/client/dsn.d.ts.map +1 -0
  6. package/dist/client/index.js +89 -10
  7. package/dist/client/index.js.map +7 -6
  8. package/dist/client/options.d.ts +21 -0
  9. package/dist/client/options.d.ts.map +1 -1
  10. package/dist/client/refusal.d.ts +7 -3
  11. package/dist/client/refusal.d.ts.map +1 -1
  12. package/dist/client/settings.d.ts +2 -0
  13. package/dist/client/settings.d.ts.map +1 -1
  14. package/dist/client/transaction.d.ts +2 -0
  15. package/dist/client/transaction.d.ts.map +1 -1
  16. package/dist/errors.d.ts +16 -2
  17. package/dist/errors.d.ts.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +116 -15
  21. package/dist/index.js.map +10 -8
  22. package/dist/protocol/command.d.ts +2 -0
  23. package/dist/protocol/command.d.ts.map +1 -1
  24. package/dist/protocol/xtext.d.ts +14 -0
  25. package/dist/protocol/xtext.d.ts.map +1 -0
  26. package/dist/server/commands.d.ts.map +1 -1
  27. package/dist/server/dsn-params.d.ts +20 -0
  28. package/dist/server/dsn-params.d.ts.map +1 -0
  29. package/dist/server/envelope.d.ts +5 -0
  30. package/dist/server/envelope.d.ts.map +1 -0
  31. package/dist/server/intake.d.ts.map +1 -1
  32. package/dist/server/options.d.ts +28 -0
  33. package/dist/server/options.d.ts.map +1 -1
  34. package/dist/server/state.d.ts +4 -1
  35. package/dist/server/state.d.ts.map +1 -1
  36. package/dist/server/transaction.d.ts +2 -2
  37. package/dist/server/transaction.d.ts.map +1 -1
  38. package/docs/guide.md +108 -6
  39. package/docs/roadmap.md +14 -5
  40. package/docs/troubleshooting.md +54 -6
  41. 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-5228egp5.js";
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 = { from: path.address, to: [], smtputf8, body };
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
- if (Object.keys(parameters).length > 0) {
357
- return connection.fail(reply(555, "5.5.4", `${Object.keys(parameters)[0]} is not supported`));
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=9B356F51DEFD8FA364756E2164756E21
2056
+ //# debugId=5D8963483AFC536F64756E2164756E21
1956
2057
  //# sourceMappingURL=index.js.map