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