@nocobase/plugin-workflow-mailer 2.3.0-beta.13 → 2.3.0-beta.15

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 (173) hide show
  1. package/dist/externalVersion.js +6 -6
  2. package/dist/node_modules/joi/package.json +1 -1
  3. package/dist/node_modules/nodemailer/dist/cjs/addressparser/index.d.ts +59 -0
  4. package/dist/node_modules/nodemailer/dist/cjs/addressparser/index.js +790 -0
  5. package/dist/node_modules/nodemailer/dist/cjs/base64/index.d.ts +36 -0
  6. package/dist/node_modules/nodemailer/dist/cjs/base64/index.js +119 -0
  7. package/dist/node_modules/nodemailer/dist/cjs/dkim/index.d.ts +52 -0
  8. package/dist/node_modules/nodemailer/dist/cjs/dkim/index.js +228 -0
  9. package/dist/node_modules/nodemailer/dist/cjs/dkim/message-parser.d.ts +48 -0
  10. package/dist/node_modules/nodemailer/dist/cjs/dkim/message-parser.js +180 -0
  11. package/dist/node_modules/nodemailer/dist/cjs/dkim/relaxed-body.d.ts +36 -0
  12. package/dist/node_modules/nodemailer/dist/cjs/dkim/relaxed-body.js +148 -0
  13. package/dist/node_modules/nodemailer/dist/cjs/dkim/sign.d.ts +52 -0
  14. package/dist/node_modules/nodemailer/dist/cjs/dkim/sign.js +165 -0
  15. package/dist/node_modules/nodemailer/dist/cjs/errors.d.ts +96 -0
  16. package/dist/node_modules/nodemailer/dist/cjs/errors.js +70 -0
  17. package/dist/node_modules/nodemailer/dist/cjs/fetch/cookies.d.ts +107 -0
  18. package/dist/node_modules/nodemailer/dist/cjs/fetch/cookies.js +282 -0
  19. package/dist/node_modules/nodemailer/dist/cjs/fetch/index.d.ts +65 -0
  20. package/dist/node_modules/nodemailer/dist/cjs/fetch/index.js +384 -0
  21. package/dist/node_modules/nodemailer/dist/cjs/json-transport/index.d.ts +64 -0
  22. package/dist/node_modules/nodemailer/dist/cjs/json-transport/index.js +99 -0
  23. package/dist/node_modules/nodemailer/dist/cjs/mail-composer/index.d.ts +174 -0
  24. package/dist/node_modules/nodemailer/dist/cjs/mail-composer/index.js +613 -0
  25. package/dist/node_modules/nodemailer/dist/cjs/mailer/index.d.ts +237 -0
  26. package/dist/node_modules/nodemailer/dist/cjs/mailer/index.js +415 -0
  27. package/dist/node_modules/nodemailer/dist/cjs/mailer/mail-message.d.ts +97 -0
  28. package/dist/node_modules/nodemailer/dist/cjs/mailer/mail-message.js +342 -0
  29. package/dist/node_modules/nodemailer/dist/cjs/mime-funcs/index.d.ts +161 -0
  30. package/dist/node_modules/nodemailer/dist/cjs/mime-funcs/index.js +787 -0
  31. package/dist/node_modules/nodemailer/dist/cjs/mime-funcs/mime-types.d.ts +2 -0
  32. package/dist/node_modules/nodemailer/dist/cjs/mime-funcs/mime-types.js +2103 -0
  33. package/dist/node_modules/nodemailer/dist/cjs/mime-node/index.d.ts +333 -0
  34. package/dist/node_modules/nodemailer/dist/cjs/mime-node/index.js +1493 -0
  35. package/dist/node_modules/nodemailer/dist/cjs/mime-node/last-newline.d.ts +5 -0
  36. package/dist/node_modules/nodemailer/dist/cjs/mime-node/last-newline.js +32 -0
  37. package/dist/node_modules/nodemailer/dist/cjs/mime-node/le-unix.d.ts +9 -0
  38. package/dist/node_modules/nodemailer/dist/cjs/mime-node/le-unix.js +40 -0
  39. package/dist/node_modules/nodemailer/dist/cjs/mime-node/le-windows.d.ts +10 -0
  40. package/dist/node_modules/nodemailer/dist/cjs/mime-node/le-windows.js +47 -0
  41. package/dist/node_modules/nodemailer/dist/cjs/nodemailer.d.ts +124 -0
  42. package/dist/node_modules/nodemailer/dist/cjs/nodemailer.js +1 -0
  43. package/dist/node_modules/nodemailer/dist/cjs/package-info.d.ts +3 -0
  44. package/dist/node_modules/nodemailer/dist/cjs/package-info.js +7 -0
  45. package/dist/node_modules/nodemailer/dist/cjs/package.json +3 -0
  46. package/dist/node_modules/nodemailer/dist/cjs/punycode/index.d.ts +73 -0
  47. package/dist/node_modules/nodemailer/{lib → dist/cjs}/punycode/index.js +62 -111
  48. package/dist/node_modules/nodemailer/dist/cjs/qp/index.d.ts +32 -0
  49. package/dist/node_modules/nodemailer/dist/cjs/qp/index.js +207 -0
  50. package/dist/node_modules/nodemailer/dist/cjs/sendmail-transport/index.d.ts +69 -0
  51. package/dist/node_modules/nodemailer/dist/cjs/sendmail-transport/index.js +210 -0
  52. package/dist/node_modules/nodemailer/dist/cjs/ses-transport/index.d.ts +112 -0
  53. package/dist/node_modules/nodemailer/dist/cjs/ses-transport/index.js +252 -0
  54. package/dist/node_modules/nodemailer/dist/cjs/shared/index.d.ts +260 -0
  55. package/dist/node_modules/nodemailer/dist/cjs/shared/index.js +680 -0
  56. package/dist/node_modules/nodemailer/dist/cjs/shared/objects.d.ts +23 -0
  57. package/dist/node_modules/nodemailer/dist/cjs/shared/objects.js +43 -0
  58. package/dist/node_modules/nodemailer/dist/cjs/shared/url.d.ts +21 -0
  59. package/dist/node_modules/nodemailer/dist/cjs/shared/url.js +255 -0
  60. package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/data-stream.d.ts +15 -0
  61. package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/data-stream.js +108 -0
  62. package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/http-proxy-client.d.ts +47 -0
  63. package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/http-proxy-client.js +189 -0
  64. package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/index.d.ts +416 -0
  65. package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/index.js +1837 -0
  66. package/dist/node_modules/nodemailer/dist/cjs/smtp-pool/index.d.ts +133 -0
  67. package/dist/node_modules/nodemailer/dist/cjs/smtp-pool/index.js +569 -0
  68. package/dist/node_modules/nodemailer/dist/cjs/smtp-pool/pool-resource.d.ts +62 -0
  69. package/dist/node_modules/nodemailer/dist/cjs/smtp-pool/pool-resource.js +257 -0
  70. package/dist/node_modules/nodemailer/dist/cjs/smtp-transport/index.d.ts +156 -0
  71. package/dist/node_modules/nodemailer/dist/cjs/smtp-transport/index.js +371 -0
  72. package/dist/node_modules/nodemailer/dist/cjs/stream-transport/index.d.ts +65 -0
  73. package/dist/node_modules/nodemailer/dist/cjs/stream-transport/index.js +146 -0
  74. package/dist/node_modules/nodemailer/dist/cjs/well-known/index.d.ts +35 -0
  75. package/dist/node_modules/nodemailer/dist/cjs/well-known/index.js +41 -0
  76. package/dist/node_modules/nodemailer/dist/cjs/well-known/services.d.ts +2 -0
  77. package/dist/node_modules/nodemailer/dist/cjs/well-known/services.js +674 -0
  78. package/dist/node_modules/nodemailer/dist/cjs/xoauth2/index.d.ts +201 -0
  79. package/dist/node_modules/nodemailer/dist/cjs/xoauth2/index.js +426 -0
  80. package/dist/node_modules/nodemailer/dist/esm/addressparser/index.d.ts +59 -0
  81. package/dist/node_modules/nodemailer/{lib → dist/esm}/addressparser/index.js +361 -131
  82. package/dist/node_modules/nodemailer/dist/esm/base64/index.d.ts +36 -0
  83. package/dist/node_modules/nodemailer/{lib → dist/esm}/base64/index.js +31 -56
  84. package/dist/node_modules/nodemailer/dist/esm/dkim/index.d.ts +52 -0
  85. package/dist/node_modules/nodemailer/{lib → dist/esm}/dkim/index.js +49 -78
  86. package/dist/node_modules/nodemailer/dist/esm/dkim/message-parser.d.ts +48 -0
  87. package/dist/node_modules/nodemailer/{lib → dist/esm}/dkim/message-parser.js +64 -42
  88. package/dist/node_modules/nodemailer/dist/esm/dkim/relaxed-body.d.ts +36 -0
  89. package/dist/node_modules/nodemailer/dist/esm/dkim/relaxed-body.js +141 -0
  90. package/dist/node_modules/nodemailer/dist/esm/dkim/sign.d.ts +52 -0
  91. package/dist/node_modules/nodemailer/{lib → dist/esm}/dkim/sign.js +50 -46
  92. package/dist/node_modules/nodemailer/dist/esm/errors.d.ts +96 -0
  93. package/dist/node_modules/nodemailer/{lib → dist/esm}/errors.js +26 -18
  94. package/dist/node_modules/nodemailer/dist/esm/fetch/cookies.d.ts +107 -0
  95. package/dist/node_modules/nodemailer/dist/esm/fetch/cookies.js +242 -0
  96. package/dist/node_modules/nodemailer/dist/esm/fetch/index.d.ts +65 -0
  97. package/dist/node_modules/nodemailer/{lib → dist/esm}/fetch/index.js +80 -147
  98. package/dist/node_modules/nodemailer/dist/esm/json-transport/index.d.ts +64 -0
  99. package/dist/node_modules/nodemailer/{lib → dist/esm}/json-transport/index.js +19 -39
  100. package/dist/node_modules/nodemailer/dist/esm/mail-composer/index.d.ts +174 -0
  101. package/dist/node_modules/nodemailer/{lib → dist/esm}/mail-composer/index.js +199 -265
  102. package/dist/node_modules/nodemailer/dist/esm/mailer/index.d.ts +237 -0
  103. package/dist/node_modules/nodemailer/{lib → dist/esm}/mailer/index.js +128 -234
  104. package/dist/node_modules/nodemailer/dist/esm/mailer/mail-message.d.ts +97 -0
  105. package/dist/node_modules/nodemailer/{lib → dist/esm}/mailer/mail-message.js +62 -114
  106. package/dist/node_modules/nodemailer/dist/esm/mime-funcs/index.d.ts +161 -0
  107. package/dist/node_modules/nodemailer/dist/esm/mime-funcs/index.js +738 -0
  108. package/dist/node_modules/nodemailer/dist/esm/mime-funcs/mime-types.d.ts +2 -0
  109. package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-funcs/mime-types.js +29 -42
  110. package/dist/node_modules/nodemailer/dist/esm/mime-node/index.d.ts +333 -0
  111. package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-node/index.js +259 -372
  112. package/dist/node_modules/nodemailer/dist/esm/mime-node/last-newline.d.ts +5 -0
  113. package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-node/last-newline.js +4 -10
  114. package/dist/node_modules/nodemailer/dist/esm/mime-node/le-unix.d.ts +9 -0
  115. package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-node/le-unix.js +6 -11
  116. package/dist/node_modules/nodemailer/dist/esm/mime-node/le-windows.d.ts +10 -0
  117. package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-node/le-windows.js +6 -13
  118. package/dist/node_modules/nodemailer/dist/esm/nodemailer.d.ts +124 -0
  119. package/dist/node_modules/nodemailer/dist/esm/nodemailer.js +166 -0
  120. package/dist/node_modules/nodemailer/dist/esm/package-info.d.ts +3 -0
  121. package/dist/node_modules/nodemailer/dist/esm/package-info.js +4 -0
  122. package/dist/node_modules/nodemailer/dist/esm/package.json +3 -0
  123. package/dist/node_modules/nodemailer/dist/esm/punycode/index.d.ts +73 -0
  124. package/dist/node_modules/nodemailer/dist/esm/punycode/index.js +405 -0
  125. package/dist/node_modules/nodemailer/dist/esm/qp/index.d.ts +32 -0
  126. package/dist/node_modules/nodemailer/{lib → dist/esm}/qp/index.js +38 -67
  127. package/dist/node_modules/nodemailer/dist/esm/sendmail-transport/index.d.ts +69 -0
  128. package/dist/node_modules/nodemailer/{lib → dist/esm}/sendmail-transport/index.js +51 -101
  129. package/dist/node_modules/nodemailer/dist/esm/ses-transport/index.d.ts +112 -0
  130. package/dist/node_modules/nodemailer/dist/esm/ses-transport/index.js +212 -0
  131. package/dist/node_modules/nodemailer/dist/esm/shared/index.d.ts +260 -0
  132. package/dist/node_modules/nodemailer/{lib → dist/esm}/shared/index.js +156 -272
  133. package/dist/node_modules/nodemailer/dist/esm/shared/objects.d.ts +23 -0
  134. package/dist/node_modules/nodemailer/{lib → dist/esm}/shared/objects.js +13 -17
  135. package/dist/node_modules/nodemailer/dist/esm/shared/url.d.ts +21 -0
  136. package/dist/node_modules/nodemailer/dist/esm/shared/url.js +214 -0
  137. package/dist/node_modules/nodemailer/dist/esm/smtp-connection/data-stream.d.ts +15 -0
  138. package/dist/node_modules/nodemailer/dist/esm/smtp-connection/data-stream.js +103 -0
  139. package/dist/node_modules/nodemailer/dist/esm/smtp-connection/http-proxy-client.d.ts +47 -0
  140. package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-connection/http-proxy-client.js +49 -74
  141. package/dist/node_modules/nodemailer/dist/esm/smtp-connection/index.d.ts +416 -0
  142. package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-connection/index.js +574 -794
  143. package/dist/node_modules/nodemailer/dist/esm/smtp-pool/index.d.ts +133 -0
  144. package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-pool/index.js +172 -284
  145. package/dist/node_modules/nodemailer/dist/esm/smtp-pool/pool-resource.d.ts +62 -0
  146. package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-pool/pool-resource.js +57 -97
  147. package/dist/node_modules/nodemailer/dist/esm/smtp-transport/index.d.ts +156 -0
  148. package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-transport/index.js +81 -179
  149. package/dist/node_modules/nodemailer/dist/esm/stream-transport/index.d.ts +65 -0
  150. package/dist/node_modules/nodemailer/{lib → dist/esm}/stream-transport/index.js +36 -74
  151. package/dist/node_modules/nodemailer/dist/esm/well-known/index.d.ts +35 -0
  152. package/dist/node_modules/nodemailer/{lib → dist/esm}/well-known/index.js +5 -16
  153. package/dist/node_modules/nodemailer/dist/esm/well-known/services.d.ts +2 -0
  154. package/dist/node_modules/nodemailer/dist/esm/well-known/services.js +671 -0
  155. package/dist/node_modules/nodemailer/dist/esm/xoauth2/index.d.ts +201 -0
  156. package/dist/node_modules/nodemailer/{lib → dist/esm}/xoauth2/index.js +125 -187
  157. package/dist/node_modules/nodemailer/{lib → dist}/well-known/services.json +9 -2
  158. package/dist/node_modules/nodemailer/package.json +1 -1
  159. package/package.json +3 -3
  160. package/dist/node_modules/nodemailer/.gitattributes +0 -6
  161. package/dist/node_modules/nodemailer/.ncurc.js +0 -9
  162. package/dist/node_modules/nodemailer/.prettierignore +0 -8
  163. package/dist/node_modules/nodemailer/.prettierrc +0 -12
  164. package/dist/node_modules/nodemailer/.prettierrc.js +0 -10
  165. package/dist/node_modules/nodemailer/.release-please-config.json +0 -9
  166. package/dist/node_modules/nodemailer/eslint.config.js +0 -88
  167. package/dist/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
  168. package/dist/node_modules/nodemailer/lib/fetch/cookies.js +0 -276
  169. package/dist/node_modules/nodemailer/lib/mime-funcs/index.js +0 -671
  170. package/dist/node_modules/nodemailer/lib/nodemailer.js +0 -1
  171. package/dist/node_modules/nodemailer/lib/ses-transport/index.js +0 -266
  172. package/dist/node_modules/nodemailer/lib/shared/url.js +0 -151
  173. package/dist/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -105
@@ -0,0 +1,1493 @@
1
+ "use strict";
2
+ /* eslint no-undefined: 0, prefer-spread: 0, no-control-regex: 0 */
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ const node_crypto_1 = __importDefault(require("node:crypto"));
41
+ const node_fs_1 = __importDefault(require("node:fs"));
42
+ const punycode = __importStar(require("../punycode/index.js"));
43
+ const node_stream_1 = require("node:stream");
44
+ const shared = __importStar(require("../shared/index.js"));
45
+ const node_url_1 = __importDefault(require("node:url"));
46
+ const mimeFuncs = __importStar(require("../mime-funcs/index.js"));
47
+ const qp = __importStar(require("../qp/index.js"));
48
+ const base64 = __importStar(require("../base64/index.js"));
49
+ const index_js_1 = __importDefault(require("../addressparser/index.js"));
50
+ const index_js_2 = __importDefault(require("../fetch/index.js"));
51
+ const errors = __importStar(require("../errors.js"));
52
+ const last_newline_js_1 = __importDefault(require("./last-newline.js"));
53
+ const le_windows_js_1 = __importDefault(require("./le-windows.js"));
54
+ const le_unix_js_1 = __importDefault(require("./le-unix.js"));
55
+ const FORMATTED_HEADERS = ['From', 'Sender', 'To', 'Cc', 'Bcc', 'Reply-To', 'Date', 'References'];
56
+ // RFC 5321 atext, plus the non-ascii bytes that SMTPUTF8 (RFC 6531) adds to it. A local part
57
+ // built from these, with '.' as a separator, is a dot-atom and can be emitted bare
58
+ const ATEXT = "[A-Za-z0-9!#$%&'*+\\-/=?^_`{|}~\\x80-\\uFFFF]";
59
+ const DOT_ATOM = new RegExp('^' + ATEXT + '+(?:\\.' + ATEXT + '+)*$');
60
+ // A complete quoted-string: everything between the outer quotes is either a plain char or
61
+ // a quoted-pair. Anchored, so a value that only starts and ends with a quote does not pass
62
+ const QUOTED_STRING = /^"(?:[^"\\]|\\[\s\S])*"$/;
63
+ // An address that carries no special anywhere can be emitted bare in a header, everything
64
+ // else goes into angle brackets so that the header can not be read as more addresses than
65
+ // the envelope carries
66
+ const PLAIN_ADDRESS = /^[^\s"(),:;<>@[\\\]]+@[^\s"(),:;<>@[\\\]]+$/;
67
+ // domainToASCII and domainToUnicode are WHATWG host parsers rather than plain IDNA
68
+ // mappers, so they do more than map: they cut the host at '/', '\\', '?' and '#', drop C0
69
+ // controls, and percent-decode. Handing them 'evil.example/mail.corp.example' returns the
70
+ // deliverable 'evil.example', which would turn a value the bundled codec leaves as
71
+ // unroutable garbage into mail for a domain the sender never named. None of these
72
+ // characters are legal in a domain, so keep them away from the mapper.
73
+ const URL_PARSER_UNSAFE = /[/\\?#%\x00-\x20\x7F]/;
74
+ /**
75
+ * Encodes a domain the way browsers, the WHATWG URL Standard and DNS facing resolvers do,
76
+ * which is with UTS-46 mapping applied before the Punycode step.
77
+ *
78
+ * The bundled codec is plain RFC 3492 and maps nothing, so it disagrees with every
79
+ * conformant parser on any domain holding a mapped or ignored code point. An invisible
80
+ * U+00AD in 'compa\u00ADny.com' encoded to 'xn--company-pka.com' where a validator reads
81
+ * 'company.com', which let an allow-listed domain be checked and a different one mailed.
82
+ *
83
+ * Anything the URL parser does not accept as a hostname, an address literal such as
84
+ * '[127.0.0.1]' included, comes back empty and falls through to the bundled codec, which
85
+ * leaves those as they were supplied.
86
+ *
87
+ * @param domain Domain to encode, already lowercased by the caller
88
+ * @param toUnicode Return the U-label form instead of the A-label form
89
+ * @return Encoded domain
90
+ */
91
+ function normalizeDomain(domain, toUnicode) {
92
+ // domainToASCII and domainToUnicode landed in Node 7, the bundled codec covers Node 6
93
+ const mapper = toUnicode ? node_url_1.default.domainToUnicode : node_url_1.default.domainToASCII;
94
+ if (typeof mapper === 'function' && !URL_PARSER_UNSAFE.test(domain)) {
95
+ const mapped = mapper(domain);
96
+ if (mapped) {
97
+ return mapped;
98
+ }
99
+ }
100
+ return toUnicode ? punycode.toUnicode(domain) : punycode.toASCII(domain);
101
+ }
102
+ /**
103
+ * Removes the characters that must never reach a multipart delimiter line.
104
+ *
105
+ * A line break splits the delimiter, so the boundary declared in the header can never
106
+ * match it again and the parts go out as body lines instead. The other C0 controls and
107
+ * DEL do not split anything but must not be written either: RFC 5321 does not allow a
108
+ * NUL in DATA at all, and an MTA or a scanner that stops at one reads a different
109
+ * message than a client that does not, which is the same parser disagreement a split
110
+ * delimiter creates. Both sides are cleaned together, since the declared value and the
111
+ * delimiters come from this one result.
112
+ *
113
+ * @param value Value to clean
114
+ * @return Value with every control character removed
115
+ */
116
+ function _stripBoundaryControls(value) {
117
+ return value.replace(/[\x00-\x1f\x7f]+/g, '');
118
+ }
119
+ /**
120
+ * Creates a new mime tree node. Assumes 'multipart/*' as the content type
121
+ * if it is a branch, anything else counts as leaf. If rootNode is missing from
122
+ * the options, assumes this is the root.
123
+ *
124
+ * @param contentType Define the content type for the node. Can be left blank for attachments (derived from filename)
125
+ * @param [options] optional options
126
+ * @param [options.rootNode] root node for this tree
127
+ * @param [options.parentNode] immediate parent for this node
128
+ * @param [options.filename] filename for an attachment node
129
+ * @param [options.baseBoundary] shared part of the unique multipart boundary
130
+ * @param [options.keepBcc] If true, do not exclude Bcc from the generated headers
131
+ * @param [options.normalizeHeaderKey] method to normalize header keys for custom caseing
132
+ * @param [options.textEncoding] either 'Q' (the default) or 'B'
133
+ */
134
+ class MimeNode {
135
+ constructor(contentType, options) {
136
+ this.nodeCounter = 0;
137
+ options = options || {};
138
+ /**
139
+ * shared part of the unique multipart boundary. Control characters are dropped
140
+ * here rather than at the delimiter, see _stripBoundaryControls
141
+ */
142
+ this.baseBoundary = _stripBoundaryControls(options.baseBoundary || node_crypto_1.default.randomBytes(8).toString('hex'));
143
+ this.boundaryPrefix = _stripBoundaryControls(options.boundaryPrefix || '--_NmP');
144
+ this.disableFileAccess = !!options.disableFileAccess;
145
+ this.disableUrlAccess = !!options.disableUrlAccess;
146
+ this.normalizeHeaderKey = options.normalizeHeaderKey;
147
+ /**
148
+ * If date headers is missing and current node is the root, this value is used instead
149
+ */
150
+ this.date = options.parentNode ? null : new Date();
151
+ /**
152
+ * Root node for current mime tree
153
+ */
154
+ this.rootNode = options.rootNode || this;
155
+ /**
156
+ * If true include Bcc in generated headers (if available)
157
+ */
158
+ this.keepBcc = !!options.keepBcc;
159
+ /**
160
+ * If filename is specified but contentType is not (probably an attachment)
161
+ * detect the content type from filename extension
162
+ */
163
+ if (options.filename) {
164
+ /**
165
+ * Filename for this node. Useful with attachments
166
+ */
167
+ this.filename = options.filename;
168
+ if (!contentType) {
169
+ contentType = mimeFuncs.detectMimeType(this.filename.split('.').pop());
170
+ }
171
+ }
172
+ /**
173
+ * Indicates which encoding should be used for header strings: "Q" or "B"
174
+ */
175
+ this.textEncoding = (options.textEncoding || '').toString().trim().charAt(0).toUpperCase();
176
+ /**
177
+ * Immediate parent for this node (or undefined if not set)
178
+ */
179
+ this.parentNode = options.parentNode;
180
+ /**
181
+ * Hostname for default message-id values
182
+ */
183
+ this.hostname = options.hostname;
184
+ /**
185
+ * If set to 'win' then uses \r\n, if 'linux' then \n. If not set (or `raw` is used) then newlines are kept as is.
186
+ */
187
+ this.newline = options.newline;
188
+ /**
189
+ * An array for possible child nodes
190
+ */
191
+ this.childNodes = [];
192
+ /**
193
+ * Used for generating unique boundaries (prepended to the shared base)
194
+ */
195
+ this._nodeId = ++this.rootNode.nodeCounter;
196
+ /**
197
+ * A list of header values for this node in the form of [{key:'', value:''}]
198
+ */
199
+ this._headers = [];
200
+ /**
201
+ * True if the content only uses ASCII printable characters
202
+ * @type {Boolean}
203
+ */
204
+ this._isPlainText = false;
205
+ /**
206
+ * True if the content is plain text but has longer lines than allowed
207
+ * @type {Boolean}
208
+ */
209
+ this._hasLongLines = false;
210
+ /**
211
+ * If set, use instead this value for envelopes instead of generating one
212
+ * @type {Boolean}
213
+ */
214
+ this._envelope = false;
215
+ /**
216
+ * If set then use this value as the stream content instead of building it
217
+ * @type {String|Buffer|Stream}
218
+ */
219
+ this._raw = false;
220
+ /**
221
+ * Additional transform streams that the message will be piped before
222
+ * exposing by createReadStream
223
+ * @type {Array}
224
+ */
225
+ this._transforms = [];
226
+ /**
227
+ * Additional process functions that the message will be piped through before
228
+ * exposing by createReadStream. These functions are run after transforms
229
+ * @type {Array}
230
+ */
231
+ this._processFuncs = [];
232
+ /**
233
+ * If content type is set (or derived from the filename) add it to headers
234
+ */
235
+ if (contentType) {
236
+ this.setHeader('Content-Type', contentType);
237
+ }
238
+ }
239
+ /////// PUBLIC METHODS
240
+ /**
241
+ * Creates and appends a child node.Arguments provided are passed to MimeNode constructor
242
+ *
243
+ * @param [contentType] Optional content type
244
+ * @param [options] Optional options object
245
+ * @return Created node object
246
+ */
247
+ createChild(contentType, options) {
248
+ if (!options && typeof contentType === 'object') {
249
+ options = contentType;
250
+ contentType = undefined;
251
+ }
252
+ const node = new MimeNode(contentType, options);
253
+ this.appendChild(node);
254
+ return node;
255
+ }
256
+ /**
257
+ * Appends an existing node to the mime tree. Removes the node from an existing
258
+ * tree if needed
259
+ *
260
+ * @param childNode node to be appended
261
+ * @return Appended node object
262
+ */
263
+ appendChild(childNode) {
264
+ // Take the node out of the tree it is in first. Leaving it there keeps it in that
265
+ // parent's childNodes, so it still streams as part of the old tree while parentNode
266
+ // already points at the new one, and anything read off the parent chain answers for
267
+ // the wrong tree.
268
+ if (childNode.parentNode && childNode.parentNode !== this) {
269
+ childNode.remove();
270
+ }
271
+ if (childNode.rootNode !== this.rootNode) {
272
+ childNode.rootNode = this.rootNode;
273
+ childNode._nodeId = ++this.rootNode.nodeCounter;
274
+ }
275
+ childNode.parentNode = this;
276
+ this.childNodes.push(childNode);
277
+ return childNode;
278
+ }
279
+ /**
280
+ * Replaces current node with another node
281
+ *
282
+ * @param node Replacement node
283
+ * @return Replacement node
284
+ */
285
+ replace(node) {
286
+ if (node === this) {
287
+ return this;
288
+ }
289
+ this.parentNode.childNodes.forEach((childNode, i) => {
290
+ if (childNode === this) {
291
+ node.rootNode = this.rootNode;
292
+ node.parentNode = this.parentNode;
293
+ node._nodeId = this._nodeId;
294
+ this.rootNode = this;
295
+ this.parentNode = undefined;
296
+ node.parentNode.childNodes[i] = node;
297
+ }
298
+ });
299
+ return node;
300
+ }
301
+ /**
302
+ * Removes current node from the mime tree
303
+ *
304
+ * @return removed node
305
+ */
306
+ remove() {
307
+ if (!this.parentNode) {
308
+ return this;
309
+ }
310
+ for (let i = this.parentNode.childNodes.length - 1; i >= 0; i--) {
311
+ if (this.parentNode.childNodes[i] === this) {
312
+ this.parentNode.childNodes.splice(i, 1);
313
+ this.parentNode = undefined;
314
+ this.rootNode = this;
315
+ return this;
316
+ }
317
+ }
318
+ }
319
+ /**
320
+ * Sets a header value. If the value for selected key exists, it is overwritten.
321
+ * You can set multiple values as well by using [{key:'', value:''}] or
322
+ * {key: 'value'} as the first argument.
323
+ *
324
+ * @param key Header key or a list of key value pairs
325
+ * @param value Header value
326
+ * @return current node
327
+ */
328
+ setHeader(key, value) {
329
+ let added = false;
330
+ // Allow setting multiple headers at once
331
+ if (!value && key && typeof key === 'object') {
332
+ // allow {key:'content-type', value: 'text/plain'}
333
+ if (key.key && 'value' in key) {
334
+ this.setHeader(key.key, key.value);
335
+ }
336
+ else if (Array.isArray(key)) {
337
+ // allow [{key:'content-type', value: 'text/plain'}]
338
+ key.forEach(i => {
339
+ this.setHeader(i.key, i.value);
340
+ });
341
+ }
342
+ else {
343
+ // allow {'content-type': 'text/plain'}
344
+ Object.keys(key).forEach(i => {
345
+ this.setHeader(i, key[i]);
346
+ });
347
+ }
348
+ return this;
349
+ }
350
+ key = this._normalizeHeaderKey(key);
351
+ const headerValue = {
352
+ key,
353
+ value
354
+ };
355
+ // Check if the value exists and overwrite
356
+ for (let i = 0, len = this._headers.length; i < len; i++) {
357
+ if (this._headers[i].key === key) {
358
+ if (!added) {
359
+ // replace the first match
360
+ this._headers[i] = headerValue;
361
+ added = true;
362
+ }
363
+ else {
364
+ // remove following matches
365
+ this._headers.splice(i, 1);
366
+ i--;
367
+ len--;
368
+ }
369
+ }
370
+ }
371
+ // match not found, append the value
372
+ if (!added) {
373
+ this._headers.push(headerValue);
374
+ }
375
+ return this;
376
+ }
377
+ /**
378
+ * Adds a header value. If the value for selected key exists, the value is appended
379
+ * as a new field and old one is not touched.
380
+ * You can set multiple values as well by using [{key:'', value:''}] or
381
+ * {key: 'value'} as the first argument.
382
+ *
383
+ * @param key Header key or a list of key value pairs
384
+ * @param value Header value
385
+ * @return current node
386
+ */
387
+ addHeader(key, value) {
388
+ // Allow setting multiple headers at once
389
+ if (!value && key && typeof key === 'object') {
390
+ // allow {key:'content-type', value: 'text/plain'}
391
+ if (key.key && key.value) {
392
+ this.addHeader(key.key, key.value);
393
+ }
394
+ else if (Array.isArray(key)) {
395
+ // allow [{key:'content-type', value: 'text/plain'}]
396
+ key.forEach(i => {
397
+ this.addHeader(i.key, i.value);
398
+ });
399
+ }
400
+ else {
401
+ // allow {'content-type': 'text/plain'}
402
+ Object.keys(key).forEach(i => {
403
+ this.addHeader(i, key[i]);
404
+ });
405
+ }
406
+ return this;
407
+ }
408
+ else if (Array.isArray(value)) {
409
+ value.forEach(val => {
410
+ this.addHeader(key, val);
411
+ });
412
+ return this;
413
+ }
414
+ this._headers.push({
415
+ key: this._normalizeHeaderKey(key),
416
+ value
417
+ });
418
+ return this;
419
+ }
420
+ /**
421
+ * Retrieves the first mathcing value of a selected key
422
+ *
423
+ * @param key Key to search for
424
+ * @retun Value for the key
425
+ */
426
+ getHeader(key) {
427
+ key = this._normalizeHeaderKey(key);
428
+ for (let i = 0, len = this._headers.length; i < len; i++) {
429
+ if (this._headers[i].key === key) {
430
+ return this._headers[i].value;
431
+ }
432
+ }
433
+ }
434
+ /**
435
+ * Sets body content for current node. If the value is a string, charset is added automatically
436
+ * to Content-Type (if it is text/*). If the value is a Buffer, you need to specify
437
+ * the charset yourself
438
+ *
439
+ * @param content Body content
440
+ * @return current node
441
+ */
442
+ setContent(content) {
443
+ this.content = content;
444
+ if (typeof this.content.pipe === 'function') {
445
+ // pre-stream handler. might be triggered if a stream is set as content
446
+ // and 'error' fires before anything is done with this stream
447
+ this._contentErrorHandler = err => {
448
+ this.content.removeListener('error', this._contentErrorHandler);
449
+ this.content = err;
450
+ };
451
+ this.content.once('error', this._contentErrorHandler);
452
+ }
453
+ else if (typeof this.content === 'string') {
454
+ this._isPlainText = mimeFuncs.isPlainText(this.content);
455
+ if (this._isPlainText && mimeFuncs.hasLongerLines(this.content, 76)) {
456
+ // If there are lines longer than 76 symbols/bytes do not use 7bit
457
+ this._hasLongLines = true;
458
+ }
459
+ }
460
+ return this;
461
+ }
462
+ build(callback) {
463
+ let promise;
464
+ if (!callback) {
465
+ promise = new Promise((resolve, reject) => {
466
+ callback = shared.callbackPromise(resolve, reject);
467
+ });
468
+ }
469
+ const done = callback;
470
+ const stream = this.createReadStream();
471
+ const buf = [];
472
+ let buflen = 0;
473
+ let returned = false;
474
+ stream.on('readable', () => {
475
+ let chunk;
476
+ while ((chunk = stream.read()) !== null) {
477
+ buf.push(chunk);
478
+ buflen += chunk.length;
479
+ }
480
+ });
481
+ stream.once('error', err => {
482
+ if (returned) {
483
+ return;
484
+ }
485
+ returned = true;
486
+ return done(err);
487
+ });
488
+ stream.once('end', (chunk) => {
489
+ if (returned) {
490
+ return;
491
+ }
492
+ returned = true;
493
+ if (chunk && chunk.length) {
494
+ buf.push(chunk);
495
+ buflen += chunk.length;
496
+ }
497
+ return done(null, Buffer.concat(buf, buflen));
498
+ });
499
+ return promise;
500
+ }
501
+ getTransferEncoding() {
502
+ let transferEncoding = false;
503
+ const contentType = (this.getHeader('Content-Type') || '').toString().toLowerCase().trim();
504
+ if (this.content) {
505
+ transferEncoding = (this.getHeader('Content-Transfer-Encoding') || '').toString().toLowerCase().trim();
506
+ if (!transferEncoding || !['base64', 'quoted-printable'].includes(transferEncoding)) {
507
+ if (/^text\//i.test(contentType)) {
508
+ // If there are no special symbols, no need to modify the text
509
+ if (this._isPlainText && !this._hasLongLines) {
510
+ transferEncoding = '7bit';
511
+ }
512
+ else if (typeof this.content === 'string' || this.content instanceof Buffer) {
513
+ // detect preferred encoding for string value
514
+ transferEncoding = this._getTextEncoding(this.content) === 'Q' ? 'quoted-printable' : 'base64';
515
+ }
516
+ else {
517
+ // we can not check content for a stream, so either use preferred encoding or fallback to QP
518
+ transferEncoding = this.textEncoding === 'B' ? 'base64' : 'quoted-printable';
519
+ }
520
+ }
521
+ else if (!/^(multipart|message)\//i.test(contentType)) {
522
+ transferEncoding = transferEncoding || 'base64';
523
+ }
524
+ }
525
+ }
526
+ return transferEncoding;
527
+ }
528
+ /**
529
+ * Builds the header block for the mime node. Append \r\n\r\n before writing the content
530
+ *
531
+ * @returns Headers
532
+ */
533
+ buildHeaders() {
534
+ const transferEncoding = this.getTransferEncoding();
535
+ const headers = [];
536
+ if (transferEncoding) {
537
+ this.setHeader('Content-Transfer-Encoding', transferEncoding);
538
+ }
539
+ if (this.filename && !this.getHeader('Content-Disposition')) {
540
+ this.setHeader('Content-Disposition', 'attachment');
541
+ }
542
+ // Ensure mandatory header fields
543
+ if (this.rootNode === this) {
544
+ if (!this.getHeader('Date')) {
545
+ this.setHeader('Date', this.date.toUTCString().replace(/GMT/, '+0000'));
546
+ }
547
+ // ensure that Message-Id is present
548
+ this.messageId();
549
+ if (!this.getHeader('MIME-Version')) {
550
+ this.setHeader('MIME-Version', '1.0');
551
+ }
552
+ // Ensure that Content-Type is the last header for the root node
553
+ for (let i = this._headers.length - 2; i >= 0; i--) {
554
+ const header = this._headers[i];
555
+ if (header.key === 'Content-Type') {
556
+ this._headers.splice(i, 1);
557
+ this._headers.push(header);
558
+ }
559
+ }
560
+ }
561
+ this._headers.forEach(header => {
562
+ let key = header.key;
563
+ let value = header.value;
564
+ let structured;
565
+ let param;
566
+ const options = {};
567
+ const formattedHeaders = FORMATTED_HEADERS;
568
+ if (value && typeof value === 'object' && !formattedHeaders.includes(key)) {
569
+ // the keys come from a caller supplied header object and `options.prepared`
570
+ // below decides whether the value is emitted raw, so an own "__proto__" key
571
+ // here would turn an unfolded value into header injection
572
+ shared.copyOwnKeys(options, value, optionKey => optionKey === 'value');
573
+ value = (value.value || '').toString();
574
+ if (!value.trim()) {
575
+ return;
576
+ }
577
+ }
578
+ if (options.prepared) {
579
+ // header value is
580
+ if (options.foldLines) {
581
+ headers.push(mimeFuncs.foldLines(key + ': ' + value));
582
+ }
583
+ else {
584
+ headers.push(key + ': ' + value);
585
+ }
586
+ return;
587
+ }
588
+ switch (header.key) {
589
+ case 'Content-Disposition':
590
+ structured = mimeFuncs.parseHeaderValue(value);
591
+ if (this.filename) {
592
+ structured.params.filename = this.filename;
593
+ }
594
+ value = mimeFuncs.buildHeaderValue(structured);
595
+ break;
596
+ case 'Content-Type':
597
+ structured = mimeFuncs.parseHeaderValue(value);
598
+ // the type token decides multipart and charset below, so clean it before
599
+ // those run and not just on the way out, otherwise a control char makes
600
+ // the checks miss and the header ends up claiming a type it is not set up for
601
+ structured.value = (structured.value || '').toString().replace(/[\x00-\x1f\x7f]/g, '');
602
+ this._handleContentType(structured);
603
+ if (structured.value.match(/^text\/plain\b/) &&
604
+ typeof this.content === 'string' &&
605
+ /[\u0080-\uFFFF]/.test(this.content)) {
606
+ structured.params.charset = 'utf-8';
607
+ }
608
+ value = mimeFuncs.buildHeaderValue(structured);
609
+ if (this.filename) {
610
+ // add support for non-compliant clients like QQ webmail
611
+ // we can't build the value with buildHeaderValue as the value is non standard and
612
+ // would be converted to parameter continuation encoding that we do not want
613
+ // control chars can not be quoted here: HT is a fold point that unfolding would
614
+ // turn into a space, CR/LF can not appear in a header at all and DEL is not
615
+ // qtext, so force the mime encoded word that a non-ascii filename would get anyway
616
+ param = /[\x00-\x1f\x7f]/.test(this.filename)
617
+ ? mimeFuncs.encodeWord(this.filename, this._getTextEncoding(this.filename), 52)
618
+ : this._encodeWords(this.filename);
619
+ if (param !== this.filename || /[\s'"\\;:/=(),<>@[\]?]|^-/.test(param)) {
620
+ // include value in quotes if needed, escaping backslashes and quotes as
621
+ // quoted-pairs exactly like buildHeaderValue does for filename=, otherwise
622
+ // a trailing backslash would escape the closing quote
623
+ param = JSON.stringify(param);
624
+ }
625
+ value += '; name=' + param;
626
+ }
627
+ break;
628
+ case 'Bcc':
629
+ if (!this.keepBcc) {
630
+ // skip BCC values
631
+ return;
632
+ }
633
+ break;
634
+ }
635
+ value = this._encodeHeaderValue(key, value);
636
+ // skip empty lines
637
+ if (!(value || '').toString().trim()) {
638
+ return;
639
+ }
640
+ if (typeof this.normalizeHeaderKey === 'function') {
641
+ const normalized = this.normalizeHeaderKey(key, value);
642
+ // the result replaces the key on the way into the header, so it gets the same
643
+ // treatment the key it replaces already had. a line break here would end the
644
+ // header and start one of the caller's own
645
+ const cleaned = typeof normalized === 'string' ? normalized.replace(/[\x00-\x1f\x7f]/g, '') : '';
646
+ if (cleaned) {
647
+ key = cleaned;
648
+ }
649
+ }
650
+ headers.push(mimeFuncs.foldLines(key + ': ' + value, 76));
651
+ });
652
+ return headers.join('\r\n');
653
+ }
654
+ /**
655
+ * Streams the rfc2822 message from the current node. If this is a root node,
656
+ * mandatory header fields are set if missing (Date, Message-Id, MIME-Version)
657
+ *
658
+ * @return Compiled message
659
+ */
660
+ createReadStream(options) {
661
+ options = options || {};
662
+ const stream = new node_stream_1.PassThrough(options);
663
+ let outputStream = stream;
664
+ let transform;
665
+ this.stream(stream, options, err => {
666
+ if (err) {
667
+ outputStream.emit('error', err);
668
+ return;
669
+ }
670
+ stream.end();
671
+ });
672
+ for (let i = 0, len = this._transforms.length; i < len; i++) {
673
+ transform =
674
+ typeof this._transforms[i] === 'function' ? this._transforms[i]() : this._transforms[i];
675
+ outputStream.once('error', err => {
676
+ transform.emit('error', err);
677
+ });
678
+ outputStream = outputStream.pipe(transform);
679
+ }
680
+ // ensure terminating newline after possible user transforms
681
+ transform = new last_newline_js_1.default();
682
+ outputStream.once('error', err => {
683
+ transform.emit('error', err);
684
+ });
685
+ outputStream = outputStream.pipe(transform);
686
+ // dkim and stuff
687
+ for (let i = 0, len = this._processFuncs.length; i < len; i++) {
688
+ transform = this._processFuncs[i];
689
+ outputStream = transform(outputStream);
690
+ }
691
+ if (this.newline) {
692
+ const winbreak = ['win', 'windows', 'dos', '\r\n'].includes(this.newline.toString().toLowerCase());
693
+ const newlineTransform = winbreak ? new le_windows_js_1.default() : new le_unix_js_1.default();
694
+ const stream = outputStream.pipe(newlineTransform);
695
+ outputStream.on('error', err => stream.emit('error', err));
696
+ return stream;
697
+ }
698
+ return outputStream;
699
+ }
700
+ /**
701
+ * Appends a transform stream object to the transforms list. Final output
702
+ * is passed through this stream before exposing
703
+ *
704
+ * @param transform Read-Write stream
705
+ */
706
+ transform(transform) {
707
+ this._transforms.push(transform);
708
+ }
709
+ /**
710
+ * Appends a post process function. The functon is run after transforms and
711
+ * uses the following syntax
712
+ *
713
+ * processFunc(input) -> outputStream
714
+ *
715
+ * @param processFunc Read-Write stream
716
+ */
717
+ processFunc(processFunc) {
718
+ this._processFuncs.push(processFunc);
719
+ }
720
+ stream(outputStream, options, done) {
721
+ const transferEncoding = this.getTransferEncoding();
722
+ let contentStream;
723
+ let localStream;
724
+ // protect actual callback against multiple triggering
725
+ let returned = false;
726
+ const callback = (err) => {
727
+ if (returned) {
728
+ return;
729
+ }
730
+ returned = true;
731
+ done(err);
732
+ };
733
+ // for multipart nodes, push child nodes
734
+ // for content nodes end the stream
735
+ const finalize = () => {
736
+ let childId = 0;
737
+ const processChildNode = () => {
738
+ if (childId >= this.childNodes.length) {
739
+ outputStream.write('\r\n--' + this.boundary + '--\r\n');
740
+ return callback();
741
+ }
742
+ const child = this.childNodes[childId++];
743
+ outputStream.write((childId > 1 ? '\r\n' : '') + '--' + this.boundary + '\r\n');
744
+ child.stream(outputStream, options, err => {
745
+ if (err) {
746
+ return callback(err);
747
+ }
748
+ setImmediate(processChildNode);
749
+ });
750
+ };
751
+ if (this.multipart) {
752
+ setImmediate(processChildNode);
753
+ }
754
+ else {
755
+ return callback();
756
+ }
757
+ };
758
+ // pushes node content
759
+ const sendContent = () => {
760
+ if (this.content) {
761
+ if (Object.prototype.toString.call(this.content) === '[object Error]') {
762
+ // content is already errored
763
+ return callback(this.content);
764
+ }
765
+ if (typeof this.content.pipe === 'function') {
766
+ this.content.removeListener('error', this._contentErrorHandler);
767
+ this._contentErrorHandler = err => callback(err);
768
+ this.content.once('error', this._contentErrorHandler);
769
+ }
770
+ const createStream = () => {
771
+ if (['quoted-printable', 'base64'].includes(transferEncoding)) {
772
+ contentStream = new (transferEncoding === 'base64' ? base64 : qp).Encoder(options);
773
+ contentStream.pipe(outputStream, {
774
+ end: false
775
+ });
776
+ contentStream.once('end', finalize);
777
+ contentStream.once('error', err => callback(err));
778
+ localStream = this._getStream(this.content);
779
+ localStream.pipe(contentStream);
780
+ }
781
+ else {
782
+ // anything that is not QP or Base54 passes as-is
783
+ localStream = this._getStream(this.content);
784
+ localStream.pipe(outputStream, {
785
+ end: false
786
+ });
787
+ localStream.once('end', finalize);
788
+ }
789
+ localStream.once('error', err => callback(err));
790
+ };
791
+ if (this.content._resolve) {
792
+ const chunks = [];
793
+ let chunklen = 0;
794
+ let returned = false;
795
+ const sourceStream = this._getStream(this.content);
796
+ sourceStream.on('error', err => {
797
+ if (returned) {
798
+ return;
799
+ }
800
+ returned = true;
801
+ callback(err);
802
+ });
803
+ sourceStream.on('readable', () => {
804
+ let chunk;
805
+ while ((chunk = sourceStream.read()) !== null) {
806
+ chunks.push(chunk);
807
+ chunklen += chunk.length;
808
+ }
809
+ });
810
+ sourceStream.on('end', () => {
811
+ if (returned) {
812
+ return;
813
+ }
814
+ returned = true;
815
+ this.content._resolve = false;
816
+ this.content._resolvedValue = Buffer.concat(chunks, chunklen);
817
+ setImmediate(createStream);
818
+ });
819
+ }
820
+ else {
821
+ setImmediate(createStream);
822
+ }
823
+ return;
824
+ }
825
+ return setImmediate(finalize);
826
+ };
827
+ if (this._raw) {
828
+ setImmediate(() => {
829
+ if (Object.prototype.toString.call(this._raw) === '[object Error]') {
830
+ // content is already errored
831
+ return callback(this._raw);
832
+ }
833
+ // remove default error handler (if set)
834
+ if (typeof this._raw.pipe === 'function') {
835
+ this._raw.removeListener('error', this._contentErrorHandler);
836
+ }
837
+ const raw = this._getStream(this._raw);
838
+ raw.pipe(outputStream, {
839
+ end: false
840
+ });
841
+ raw.on('error', err => outputStream.emit('error', err));
842
+ raw.on('end', finalize);
843
+ });
844
+ }
845
+ else {
846
+ outputStream.write(this.buildHeaders() + '\r\n\r\n');
847
+ setImmediate(sendContent);
848
+ }
849
+ }
850
+ /**
851
+ * Sets envelope to be used instead of the generated one
852
+ *
853
+ * @return SMTP envelope in the form of {from: 'from@example.com', to: ['to@example.com']}
854
+ */
855
+ setEnvelope(envelope) {
856
+ let list;
857
+ this._envelope = {
858
+ from: false,
859
+ to: []
860
+ };
861
+ if (envelope.from) {
862
+ list = [];
863
+ this._convertAddresses(this._parseEnvelopeAddresses(envelope.from), list);
864
+ list = list.filter(address => address && address.address);
865
+ if (list.length && list[0]) {
866
+ this._envelope.from = list[0].address;
867
+ }
868
+ }
869
+ const seenRecipients = new Set();
870
+ const recipients = [];
871
+ ['to', 'cc', 'bcc'].forEach(key => {
872
+ if (envelope[key]) {
873
+ this._convertAddresses(this._parseEnvelopeAddresses(envelope[key]), recipients, seenRecipients);
874
+ }
875
+ });
876
+ this._envelope.to = recipients.map(to => to.address).filter(address => address);
877
+ const standardFields = ['to', 'cc', 'bcc', 'from'];
878
+ shared.copyOwnKeys(this._envelope, envelope, key => standardFields.includes(key));
879
+ return this;
880
+ }
881
+ /**
882
+ * Generates and returns an object with parsed address fields
883
+ *
884
+ * @return Address object
885
+ */
886
+ getAddresses() {
887
+ const addresses = {};
888
+ const seenByKey = new Map();
889
+ this._headers.forEach(header => {
890
+ const key = header.key.toLowerCase();
891
+ if (['from', 'sender', 'reply-to', 'to', 'cc', 'bcc'].includes(key)) {
892
+ if (!Array.isArray(addresses[key])) {
893
+ addresses[key] = [];
894
+ seenByKey.set(key, new Set());
895
+ }
896
+ this._convertAddresses(this._parseAddresses(header.value), addresses[key], seenByKey.get(key));
897
+ }
898
+ });
899
+ return addresses;
900
+ }
901
+ /**
902
+ * Generates and returns SMTP envelope with the sender address and a list of recipients addresses
903
+ *
904
+ * @return SMTP envelope in the form of {from: 'from@example.com', to: ['to@example.com']}
905
+ */
906
+ getEnvelope() {
907
+ if (this._envelope) {
908
+ return this._envelope;
909
+ }
910
+ const envelope = {
911
+ from: false,
912
+ to: []
913
+ };
914
+ // Built once and carried across the headers. Letting _convertAddresses seed it per
915
+ // call would cost O(headers x recipients), and a message can carry many address
916
+ // headers: `headers: { to: [...] }` emits one To per entry.
917
+ const seenRecipients = new Set();
918
+ const recipients = [];
919
+ this._headers.forEach(header => {
920
+ const list = [];
921
+ if (header.key === 'From' || (!envelope.from && ['Reply-To', 'Sender'].includes(header.key))) {
922
+ this._convertAddresses(this._parseAddresses(header.value), list);
923
+ if (list.length && list[0]) {
924
+ envelope.from = list[0].address;
925
+ }
926
+ }
927
+ else if (['To', 'Cc', 'Bcc'].includes(header.key)) {
928
+ this._convertAddresses(this._parseAddresses(header.value), recipients, seenRecipients);
929
+ }
930
+ });
931
+ envelope.to = recipients.map(to => to.address);
932
+ return envelope;
933
+ }
934
+ /**
935
+ * Returns Message-Id value. If it does not exist, then creates one
936
+ *
937
+ * @return Message-Id value
938
+ */
939
+ messageId() {
940
+ let messageId = this.getHeader('Message-ID');
941
+ // You really should define your own Message-Id field!
942
+ if (!messageId) {
943
+ messageId = this._generateMessageId();
944
+ this.setHeader('Message-ID', messageId);
945
+ }
946
+ return messageId;
947
+ }
948
+ /**
949
+ * Sets pregenerated content that will be used as the output of this node
950
+ *
951
+ * @param raw Raw MIME contents
952
+ */
953
+ setRaw(raw) {
954
+ this._raw = raw;
955
+ if (this._raw && typeof this._raw.pipe === 'function') {
956
+ // pre-stream handler. might be triggered if a stream is set as content
957
+ // and 'error' fires before anything is done with this stream
958
+ this._contentErrorHandler = err => {
959
+ this._raw.removeListener('error', this._contentErrorHandler);
960
+ this._raw = err;
961
+ };
962
+ this._raw.once('error', this._contentErrorHandler);
963
+ }
964
+ return this;
965
+ }
966
+ /////// PRIVATE METHODS
967
+ /**
968
+ * Checks an access policy flag for this node and every node above it. The flags are set
969
+ * from the options the node was built with, and createChild only ever sees the options
970
+ * the caller passed, so a child of a closed tree starts out open. Reading the answer off
971
+ * the parent chain keeps it right whatever order the tree was assembled in.
972
+ *
973
+ * @param flag Either 'disableFileAccess' or 'disableUrlAccess'
974
+ * @return true if this node or an ancestor closed that access
975
+ * @internal
976
+ */
977
+ _accessDisabled(flag) {
978
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
979
+ let node = this;
980
+ while (node) {
981
+ if (node[flag]) {
982
+ return true;
983
+ }
984
+ node = node.parentNode;
985
+ }
986
+ return false;
987
+ }
988
+ /**
989
+ * Detects and returns handle to a stream related with the content.
990
+ *
991
+ * @param content Node content
992
+ * @returns Stream object
993
+ * @internal
994
+ */
995
+ _getStream(content) {
996
+ let contentStream;
997
+ if (content._resolvedValue) {
998
+ // pass string or buffer content as a stream
999
+ contentStream = new node_stream_1.PassThrough();
1000
+ setImmediate(() => {
1001
+ try {
1002
+ contentStream.end(content._resolvedValue);
1003
+ }
1004
+ catch (_err) {
1005
+ contentStream.emit('error', _err);
1006
+ }
1007
+ });
1008
+ return contentStream;
1009
+ }
1010
+ if (typeof content.pipe === 'function') {
1011
+ // assume as stream
1012
+ return content;
1013
+ }
1014
+ if (content && typeof content.path === 'string' && !content.href) {
1015
+ if (this._accessDisabled('disableFileAccess')) {
1016
+ contentStream = new node_stream_1.PassThrough();
1017
+ setImmediate(() => {
1018
+ const err = new Error('File access rejected for ' + content.path);
1019
+ err.code = errors.EFILEACCESS;
1020
+ contentStream.emit('error', err);
1021
+ });
1022
+ return contentStream;
1023
+ }
1024
+ // read file
1025
+ return node_fs_1.default.createReadStream(content.path);
1026
+ }
1027
+ if (content && typeof content.href === 'string') {
1028
+ if (this._accessDisabled('disableUrlAccess')) {
1029
+ contentStream = new node_stream_1.PassThrough();
1030
+ setImmediate(() => {
1031
+ const err = new Error('Url access rejected for ' + content.href);
1032
+ err.code = errors.EURLACCESS;
1033
+ contentStream.emit('error', err);
1034
+ });
1035
+ return contentStream;
1036
+ }
1037
+ // fetch URL. nmfetch refuses any scheme that is not http(s), and it decides
1038
+ // that on the parsed URL. Testing the raw string here instead would reject
1039
+ // forms the parser accepts, such as a leading space or a slash-less authority
1040
+ return (0, index_js_2.default)(content.href, { headers: content.httpHeaders, tls: content.tls });
1041
+ }
1042
+ // pass string or buffer content as a stream
1043
+ contentStream = new node_stream_1.PassThrough();
1044
+ setImmediate(() => {
1045
+ try {
1046
+ contentStream.end(content || '');
1047
+ }
1048
+ catch (_err) {
1049
+ contentStream.emit('error', _err);
1050
+ }
1051
+ });
1052
+ return contentStream;
1053
+ }
1054
+ /**
1055
+ * Parses addresses. Takes in a single address or an array or an
1056
+ * array of address arrays (eg. To: [[first group], [second group],...])
1057
+ *
1058
+ * @param addresses Addresses to be parsed
1059
+ * @return An array of address objects
1060
+ * @internal
1061
+ */
1062
+ _parseAddresses(addresses) {
1063
+ const flattened = [];
1064
+ // Nested arrays are flattened iteratively. concat.apply throws once a recipient list
1065
+ // is long enough to pass the argument limit, and an array handed to addressparser is
1066
+ // stringified, which recurses once per nesting level and exhausts the call stack on
1067
+ // a deep enough value. Every array is walked once, which also ends a self-referential
1068
+ // input
1069
+ const seen = new WeakSet();
1070
+ const stack = [];
1071
+ const enter = (list) => {
1072
+ if (!seen.has(list)) {
1073
+ seen.add(list);
1074
+ stack.push({ list, pos: 0 });
1075
+ }
1076
+ };
1077
+ enter(Array.isArray(addresses) ? addresses : [addresses]);
1078
+ while (stack.length) {
1079
+ const frame = stack[stack.length - 1];
1080
+ if (frame.pos >= frame.list.length) {
1081
+ stack.pop();
1082
+ continue;
1083
+ }
1084
+ const address = frame.list[frame.pos++];
1085
+ if (Array.isArray(address)) {
1086
+ enter(address);
1087
+ continue;
1088
+ }
1089
+ if (address && address.address) {
1090
+ const normalized = this._normalizeAddress(address.address);
1091
+ if (normalized === address.address && typeof address.name === 'string') {
1092
+ // there is nothing to rewrite, so there is nothing to keep off the original
1093
+ flattened.push(address);
1094
+ continue;
1095
+ }
1096
+ // rewriting would land on the object the caller passed in and might
1097
+ // still hold a reference to, so rewrite a copy of it instead. An own
1098
+ // "__proto__" key would make the copy inherit from caller data, and
1099
+ // _convertAddresses reads `group` off it straight into the envelope
1100
+ const copy = shared.copyOwnKeys({}, address);
1101
+ copy.address = normalized;
1102
+ copy.name = address.name || '';
1103
+ flattened.push(copy);
1104
+ continue;
1105
+ }
1106
+ const parsed = this._normalizeParsedAddresses((0, index_js_1.default)(address));
1107
+ for (let i = 0; i < parsed.length; i++) {
1108
+ flattened.push(parsed[i]);
1109
+ }
1110
+ }
1111
+ return flattened;
1112
+ }
1113
+ /**
1114
+ * Normalizes the addresses of a freshly parsed address list, groups included.
1115
+ *
1116
+ * Everything this method returns carries a normalized address, whether it arrived as an
1117
+ * object or was parsed out of a header value. Without this the two shapes disagree, and
1118
+ * a consumer reading the parsed form back is handed the ambiguous
1119
+ * 'user@evil.com@good.com' that the header and the envelope no longer carry.
1120
+ *
1121
+ * @param parsed An array of address objects, as returned by addressparser
1122
+ * @return The same array, with every address normalized
1123
+ * @internal
1124
+ */
1125
+ _normalizeParsedAddresses(parsed) {
1126
+ // addressparser builds these objects, so no caller holds a reference to rewrite around
1127
+ parsed.forEach(entry => {
1128
+ if (entry.address) {
1129
+ entry.address = this._normalizeAddress(entry.address);
1130
+ }
1131
+ else if (entry.group) {
1132
+ this._normalizeParsedAddresses(entry.group);
1133
+ }
1134
+ });
1135
+ return parsed;
1136
+ }
1137
+ /**
1138
+ * Parses the addresses of an explicitly set envelope.
1139
+ *
1140
+ * An envelope value is an addr-spec and never a display name, so a bare local username
1141
+ * such as 'root' is the address here. Header parsing has to read the same value as a
1142
+ * display name, as a value with no '@' in it can not be an addr-spec in a header.
1143
+ *
1144
+ * @param addresses Addresses to be parsed
1145
+ * @return An array of address objects
1146
+ * @internal
1147
+ */
1148
+ _parseEnvelopeAddresses(addresses) {
1149
+ return this._parseAddresses(addresses).map(entry => {
1150
+ if (entry.address || entry.group || !entry.name || /[\s@]/.test(entry.name)) {
1151
+ return entry;
1152
+ }
1153
+ return { address: this._normalizeAddress(entry.name), name: '' };
1154
+ });
1155
+ }
1156
+ /**
1157
+ * Normalizes a header key, uses Camel-Case form, except for uppercase MIME-
1158
+ *
1159
+ * @param key Key to be normalized
1160
+ * @return key in Camel-Case form
1161
+ * @internal
1162
+ */
1163
+ _normalizeHeaderKey(key) {
1164
+ key = (key || '')
1165
+ .toString()
1166
+ // no newlines in keys
1167
+ .replace(/\r?\n|\r/g, ' ')
1168
+ // a field name is printable ascii without the colon, so a control char or DEL
1169
+ // can only be dropped, there is no quoting construct around a field name
1170
+ .replace(/[\x00-\x1f\x7f]/g, '')
1171
+ .trim()
1172
+ .toLowerCase()
1173
+ // use uppercase words, except MIME
1174
+ .replace(/^X-SMTPAPI$|^(MIME|DKIM|ARC|BIMI)\b|^[a-z]|-(SPF|FBL|ID|MD5)$|-[a-z]/gi, c => c.toUpperCase())
1175
+ // special case
1176
+ .replace(/^Content-Features$/i, 'Content-features');
1177
+ return key;
1178
+ }
1179
+ /**
1180
+ * Checks if the content type is multipart and defines boundary if needed.
1181
+ * Doesn't return anything, modifies object argument instead.
1182
+ *
1183
+ * @param structured Parsed header value for 'Content-Type' key
1184
+ * @internal
1185
+ */
1186
+ _handleContentType(structured) {
1187
+ this.contentType = structured.value.trim().toLowerCase();
1188
+ this.multipart = /^multipart\//i.test(this.contentType) ? this.contentType.substr(this.contentType.indexOf('/') + 1) : false;
1189
+ if (this.multipart) {
1190
+ // The declared value and the delimiters are assigned from the same expression
1191
+ // here, so whatever the header emitter then does with it, the two sides cannot
1192
+ // disagree about which characters the boundary is made of.
1193
+ //
1194
+ // Stripping runs before the fallback rather than over the whole chain: a
1195
+ // boundary made only of control characters would otherwise strip to '' and
1196
+ // leave the node declaring no boundary and streaming bare '--' delimiters.
1197
+ // _generateBoundary cleans what it builds, but this is the one place the boundary
1198
+ // is written, so it cleans the value it is about to write rather than trusting
1199
+ // where it came from. MimeNode is exported and subclassable, so the generator is
1200
+ // not necessarily the one below. Stripping twice costs nothing, it is idempotent.
1201
+ const declared = _stripBoundaryControls(structured.params.boundary || this.boundary || '');
1202
+ this.boundary = structured.params.boundary =
1203
+ declared || _stripBoundaryControls(this._generateBoundary());
1204
+ }
1205
+ else {
1206
+ this.boundary = false;
1207
+ }
1208
+ }
1209
+ /**
1210
+ * Generates a multipart boundary value
1211
+ *
1212
+ * @return boundary value
1213
+ * @internal
1214
+ */
1215
+ _generateBoundary() {
1216
+ // baseBoundary and boundaryPrefix are public, so they may hold something the
1217
+ // constructor never cleaned, and the -Part_ suffix keeps the result non empty
1218
+ return _stripBoundaryControls(this.rootNode.boundaryPrefix + '-' + this.rootNode.baseBoundary) + '-Part_' + this._nodeId;
1219
+ }
1220
+ /**
1221
+ * Encodes a header value for use in the generated rfc2822 email.
1222
+ *
1223
+ * @param key Header key
1224
+ * @param value Header value
1225
+ * @internal
1226
+ */
1227
+ _encodeHeaderValue(key, value) {
1228
+ key = this._normalizeHeaderKey(key);
1229
+ switch (key) {
1230
+ // Structured headers
1231
+ case 'From':
1232
+ case 'Sender':
1233
+ case 'To':
1234
+ case 'Cc':
1235
+ case 'Bcc':
1236
+ case 'Reply-To':
1237
+ return this._convertAddresses(this._parseAddresses(value));
1238
+ // values enclosed in <>
1239
+ case 'Message-ID':
1240
+ case 'In-Reply-To':
1241
+ case 'Content-Id':
1242
+ // a msg-id is structured, so an encoded word inside the angle brackets would
1243
+ // be read as literal text. drop the characters that can not appear in a header
1244
+ // at all, but leave HT alone, it separates the ids of a multi id value
1245
+ value = (value || '')
1246
+ .toString()
1247
+ .replace(/\r?\n|\r/g, ' ')
1248
+ .replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
1249
+ if (value.charAt(0) !== '<') {
1250
+ value = '<' + value;
1251
+ }
1252
+ if (value.charAt(value.length - 1) !== '>') {
1253
+ value = value + '>';
1254
+ }
1255
+ return value;
1256
+ // space separated list of values enclosed in <>
1257
+ case 'References':
1258
+ value = [].concat
1259
+ .apply([], [].concat(value || '').map(elm => {
1260
+ elm = (elm || '')
1261
+ .toString()
1262
+ .replace(/\r?\n|\r/g, ' ')
1263
+ .replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '')
1264
+ .trim();
1265
+ return elm.replace(/<[^>]*>/g, str => str.replace(/\s/g, '')).split(/\s+/);
1266
+ }))
1267
+ .map(elm => {
1268
+ if (elm.charAt(0) !== '<') {
1269
+ elm = '<' + elm;
1270
+ }
1271
+ if (elm.charAt(elm.length - 1) !== '>') {
1272
+ elm = elm + '>';
1273
+ }
1274
+ return elm;
1275
+ });
1276
+ return value.join(' ').trim();
1277
+ case 'Date':
1278
+ if (Object.prototype.toString.call(value) === '[object Date]') {
1279
+ return value.toUTCString().replace(/GMT/, '+0000');
1280
+ }
1281
+ value = (value || '').toString().replace(/\r?\n|\r/g, ' ');
1282
+ return this._encodeHeaderText(value);
1283
+ case 'Content-Type':
1284
+ case 'Content-Disposition':
1285
+ // if it includes a filename then it is already encoded
1286
+ return (value || '').toString().replace(/\r?\n|\r/g, ' ');
1287
+ default:
1288
+ value = (value || '').toString().replace(/\r?\n|\r/g, ' ');
1289
+ return this._encodeHeaderText(value);
1290
+ }
1291
+ }
1292
+ /**
1293
+ * Rebuilds address object using punycode and other adjustments
1294
+ *
1295
+ * @param addresses An array of address objects
1296
+ * @param [uniqueList] An array to be populated with addresses
1297
+ * @param [seenAddresses] Addresses already added to uniqueList, shared with recursive calls to keep deduplication linear
1298
+ * @return address string
1299
+ * @internal
1300
+ */
1301
+ _convertAddresses(addresses, uniqueList, seenAddresses) {
1302
+ const values = [];
1303
+ uniqueList = uniqueList || [];
1304
+ // Membership is checked once per address, so scanning uniqueList itself would make
1305
+ // a recipient list cost O(n^2). Groups recurse with the same set so that a nested
1306
+ // group still dedupes against the addresses collected around it, and a caller that
1307
+ // passes a partly filled list (To, then Cc, then Bcc) keeps deduping across headers.
1308
+ if (!seenAddresses) {
1309
+ seenAddresses = new Set();
1310
+ for (let i = 0; i < uniqueList.length; i++) {
1311
+ seenAddresses.add(uniqueList[i].address);
1312
+ }
1313
+ }
1314
+ [].concat(addresses || []).forEach(address => {
1315
+ if (address.address) {
1316
+ address.address = this._normalizeAddress(address.address);
1317
+ if (!address.name) {
1318
+ // an address that carries a special, be it a quoted local part or a domain
1319
+ // that could not be normalized, is only unambiguous inside angle brackets.
1320
+ // Without them a ',' or a ';' anywhere in it reads as a recipient separator
1321
+ // and the header would list more recipients than the envelope carries
1322
+ values.push(PLAIN_ADDRESS.test(address.address) ? address.address : `<${address.address}>`);
1323
+ }
1324
+ else {
1325
+ values.push(`${this._encodeAddressName(address.name)} <${address.address}>`);
1326
+ }
1327
+ if (!seenAddresses.has(address.address)) {
1328
+ seenAddresses.add(address.address);
1329
+ uniqueList.push(address);
1330
+ }
1331
+ }
1332
+ else if (address.group) {
1333
+ const groupListAddresses = (address.group.length ? this._convertAddresses(address.group, uniqueList, seenAddresses) : '').trim();
1334
+ values.push(`${this._encodeAddressName(address.name)}:${groupListAddresses};`);
1335
+ }
1336
+ });
1337
+ return values.join(', ');
1338
+ }
1339
+ /**
1340
+ * Normalizes an email address
1341
+ *
1342
+ * @param address An array of address objects
1343
+ * @return address string
1344
+ * @internal
1345
+ */
1346
+ _normalizeAddress(address) {
1347
+ address = (address || '')
1348
+ .toString()
1349
+ .replace(/[\x00-\x1F\x7F<>]+/g, ' ') // remove unallowed characters
1350
+ .trim();
1351
+ if (!address) {
1352
+ // callers use an empty value to detect a missing address
1353
+ return address;
1354
+ }
1355
+ const lastAt = address.lastIndexOf('@');
1356
+ if (lastAt < 0) {
1357
+ // Bare username, there is no domain to split off
1358
+ return this._normalizeLocalPart(address);
1359
+ }
1360
+ const user = address.substr(0, lastAt);
1361
+ const domain = address.substr(lastAt + 1);
1362
+ // Unicode in the local part is kept as is, see _normalizeLocalPart for the rest of it.
1363
+ // A domain has no quoting construct to fall back on, so whatever is not a valid domain
1364
+ // is kept as supplied and it is _convertAddresses that keeps such an address unambiguous.
1365
+ // Domains are punycoded when the local part is ASCII ('safe@jõgeva.ee' -> 'safe@xn--jgeva-dua.ee').
1366
+ // When the local part contains non-ASCII bytes the address already requires SMTPUTF8,
1367
+ // so the domain is kept (or decoded back) as UTF-8 for symmetry on both sides of '@'.
1368
+ let encodedDomain = domain;
1369
+ // A non-ASCII local part already requires SMTPUTF8, so the domain stays UTF-8 for
1370
+ // symmetry on both sides of the '@' rather than being encoded to an A-label
1371
+ const smtputf8 = /[\x80-\uFFFF]/.test(user);
1372
+ try {
1373
+ encodedDomain = normalizeDomain(domain.toLowerCase(), smtputf8);
1374
+ }
1375
+ catch (_err) {
1376
+ // keep domain as supplied
1377
+ }
1378
+ return `${this._normalizeLocalPart(user)}@${encodedDomain}`;
1379
+ }
1380
+ /**
1381
+ * Normalizes the local part of an address into a form that can be emitted as is.
1382
+ *
1383
+ * A local part is either a dot-atom or a quoted-string, anything else is not a valid
1384
+ * addr-spec. The quotes of a quoted local part get lost along the way, and a bare
1385
+ * 'user@evil.com@good.com' leaves it to the receiver which '@' splits the domain off,
1386
+ * while the split here is always at the last one. So whatever is not already one of
1387
+ * the two valid forms goes back out as a quoted-string.
1388
+ *
1389
+ * @param user Local part of an address
1390
+ * @return Local part as a dot-atom or as a quoted-string
1391
+ * @internal
1392
+ */
1393
+ _normalizeLocalPart(user) {
1394
+ if (DOT_ATOM.test(user) || QUOTED_STRING.test(user)) {
1395
+ return user;
1396
+ }
1397
+ return mimeFuncs.quoteString(user);
1398
+ }
1399
+ /**
1400
+ * If needed, mime encodes the name part
1401
+ *
1402
+ * @param name Name part of an address
1403
+ * @returns Mime word encoded string if needed
1404
+ * @internal
1405
+ */
1406
+ _encodeAddressName(name) {
1407
+ if (!/^[\w ]*$/.test(name)) {
1408
+ if (/^[\x20-\x7e]*$/.test(name)) {
1409
+ return mimeFuncs.quoteString(name);
1410
+ }
1411
+ else {
1412
+ return mimeFuncs.encodeWord(name, this._getTextEncoding(name), 52);
1413
+ }
1414
+ }
1415
+ return name;
1416
+ }
1417
+ /**
1418
+ * Encodes an unstructured header value. Such a value can only carry VCHAR and WSP, so a
1419
+ * control char or DEL has to be forced into the mime encoded word that a non-ascii value
1420
+ * would get anyway. HT stays as it is, it is valid folding whitespace here.
1421
+ *
1422
+ * @param value Header value to encode
1423
+ * @returns Mime word encoded string if needed
1424
+ * @internal
1425
+ */
1426
+ _encodeHeaderText(value) {
1427
+ return /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/.test(value)
1428
+ ? mimeFuncs.encodeWord(value, this._getTextEncoding(value), 52)
1429
+ : // encodeWords only encodes if needed, otherwise the original string is returned
1430
+ this._encodeWords(value);
1431
+ }
1432
+ /**
1433
+ * If needed, mime encodes the name part
1434
+ *
1435
+ * @param value Name part of an address
1436
+ * @returns Mime word encoded string if needed
1437
+ * @internal
1438
+ */
1439
+ _encodeWords(value) {
1440
+ // set encodeAll parameter to true even though it is against the recommendation of RFC2047,
1441
+ // by default only words that include non-ascii should be converted into encoded words
1442
+ // but some clients (eg. Zimbra) do not handle it properly and remove surrounding whitespace
1443
+ return mimeFuncs.encodeWords(value, this._getTextEncoding(value), 52, true);
1444
+ }
1445
+ /**
1446
+ * Detects best mime encoding for a text value
1447
+ *
1448
+ * @param value Value to check for
1449
+ * @return either 'Q' or 'B'
1450
+ * @internal
1451
+ */
1452
+ _getTextEncoding(value) {
1453
+ value = (value || '').toString();
1454
+ if (this.textEncoding) {
1455
+ return this.textEncoding;
1456
+ }
1457
+ // count latin alphabet symbols and 8-bit range symbols + control symbols
1458
+ // if there are more latin characters, then use quoted-printable
1459
+ // encoding, otherwise use base64
1460
+ let nonLatinLen = 0;
1461
+ let latinLen = 0;
1462
+ for (let i = 0, len = value.length; i < len; i++) {
1463
+ const code = value.charCodeAt(i);
1464
+ if ((code >= 0x00 && code <= 0x08) || code === 0x0b || code === 0x0c || (code >= 0x0e && code <= 0x1f) || code >= 0x80) {
1465
+ nonLatinLen++;
1466
+ }
1467
+ else if ((code >= 0x41 && code <= 0x5a) || (code >= 0x61 && code <= 0x7a)) {
1468
+ latinLen++;
1469
+ }
1470
+ }
1471
+ // if there are more latin symbols than binary/unicode, then prefer Q, otherwise B
1472
+ return nonLatinLen < latinLen ? 'Q' : 'B';
1473
+ }
1474
+ /**
1475
+ * Generates a message id
1476
+ *
1477
+ * @return Random Message-ID value
1478
+ * @internal
1479
+ */
1480
+ _generateMessageId() {
1481
+ return ('<' +
1482
+ [2, 2, 2, 6].reduce(
1483
+ // crux to generate UUID-like random strings
1484
+ (prev, len) => prev + '-' + node_crypto_1.default.randomBytes(len).toString('hex'), node_crypto_1.default.randomBytes(4).toString('hex')) +
1485
+ '@' +
1486
+ // try to use the domain of the FROM address or fallback to server hostname
1487
+ (this.getEnvelope().from || this.hostname || 'localhost').split('@').pop() +
1488
+ '>');
1489
+ }
1490
+ }
1491
+ exports.default = MimeNode;
1492
+ module.exports = exports.default;
1493
+ Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });