@mavogel/awscdk-rootmail 0.1.14 → 0.1.16

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 (168) hide show
  1. package/.jsii +10 -10
  2. package/lib/rootmail.js +1 -1
  3. package/lib/ses-receive.js +1 -1
  4. package/node_modules/@aws-sdk/checksums/package.json +3 -3
  5. package/node_modules/@aws-sdk/client-cloudwatch-logs/dist-cjs/index.js +23 -1
  6. package/node_modules/@aws-sdk/client-cloudwatch-logs/dist-es/models/CloudWatchLogsServiceException.js +1 -0
  7. package/node_modules/@aws-sdk/client-cloudwatch-logs/dist-es/models/errors.js +21 -0
  8. package/node_modules/@aws-sdk/client-cloudwatch-logs/dist-types/models/CloudWatchLogsServiceException.d.ts +1 -0
  9. package/node_modules/@aws-sdk/client-cloudwatch-logs/dist-types/models/errors.d.ts +21 -0
  10. package/node_modules/@aws-sdk/client-cloudwatch-logs/dist-types/ts3.4/models/CloudWatchLogsServiceException.d.ts +1 -0
  11. package/node_modules/@aws-sdk/client-cloudwatch-logs/dist-types/ts3.4/models/errors.d.ts +21 -0
  12. package/node_modules/@aws-sdk/client-cloudwatch-logs/package.json +4 -4
  13. package/node_modules/@aws-sdk/client-route-53/dist-cjs/index.js +71 -1
  14. package/node_modules/@aws-sdk/client-route-53/dist-es/models/Route53ServiceException.js +1 -0
  15. package/node_modules/@aws-sdk/client-route-53/dist-es/models/errors.js +69 -0
  16. package/node_modules/@aws-sdk/client-route-53/dist-types/models/Route53ServiceException.d.ts +1 -0
  17. package/node_modules/@aws-sdk/client-route-53/dist-types/models/errors.d.ts +69 -0
  18. package/node_modules/@aws-sdk/client-route-53/dist-types/ts3.4/models/Route53ServiceException.d.ts +1 -0
  19. package/node_modules/@aws-sdk/client-route-53/dist-types/ts3.4/models/errors.d.ts +69 -0
  20. package/node_modules/@aws-sdk/client-route-53/package.json +4 -4
  21. package/node_modules/@aws-sdk/client-s3/dist-cjs/index.js +23 -1
  22. package/node_modules/@aws-sdk/client-s3/dist-es/models/S3ServiceException.js +1 -0
  23. package/node_modules/@aws-sdk/client-s3/dist-es/models/errors.js +21 -0
  24. package/node_modules/@aws-sdk/client-s3/dist-types/models/S3ServiceException.d.ts +1 -0
  25. package/node_modules/@aws-sdk/client-s3/dist-types/models/errors.d.ts +21 -0
  26. package/node_modules/@aws-sdk/client-s3/dist-types/ts3.4/models/S3ServiceException.d.ts +1 -0
  27. package/node_modules/@aws-sdk/client-s3/dist-types/ts3.4/models/errors.d.ts +21 -0
  28. package/node_modules/@aws-sdk/client-s3/package.json +8 -8
  29. package/node_modules/@aws-sdk/client-ses/dist-cjs/index.js +36 -1
  30. package/node_modules/@aws-sdk/client-ses/dist-es/models/SESServiceException.js +1 -0
  31. package/node_modules/@aws-sdk/client-ses/dist-es/models/errors.js +34 -0
  32. package/node_modules/@aws-sdk/client-ses/dist-types/models/SESServiceException.d.ts +1 -0
  33. package/node_modules/@aws-sdk/client-ses/dist-types/models/errors.d.ts +34 -0
  34. package/node_modules/@aws-sdk/client-ses/dist-types/ts3.4/models/SESServiceException.d.ts +1 -0
  35. package/node_modules/@aws-sdk/client-ses/dist-types/ts3.4/models/errors.d.ts +34 -0
  36. package/node_modules/@aws-sdk/client-ses/package.json +4 -4
  37. package/node_modules/@aws-sdk/client-ssm/dist-cjs/index.js +142 -1
  38. package/node_modules/@aws-sdk/client-ssm/dist-es/models/SSMServiceException.js +1 -0
  39. package/node_modules/@aws-sdk/client-ssm/dist-es/models/errors.js +140 -0
  40. package/node_modules/@aws-sdk/client-ssm/dist-types/models/SSMServiceException.d.ts +1 -0
  41. package/node_modules/@aws-sdk/client-ssm/dist-types/models/errors.d.ts +140 -0
  42. package/node_modules/@aws-sdk/client-ssm/dist-types/ts3.4/models/SSMServiceException.d.ts +1 -0
  43. package/node_modules/@aws-sdk/client-ssm/dist-types/ts3.4/models/errors.d.ts +140 -0
  44. package/node_modules/@aws-sdk/client-ssm/package.json +4 -4
  45. package/node_modules/@aws-sdk/core/package.json +3 -3
  46. package/node_modules/@aws-sdk/credential-provider-env/package.json +3 -3
  47. package/node_modules/@aws-sdk/credential-provider-http/package.json +3 -3
  48. package/node_modules/@aws-sdk/credential-provider-ini/package.json +10 -10
  49. package/node_modules/@aws-sdk/credential-provider-login/package.json +4 -4
  50. package/node_modules/@aws-sdk/credential-provider-node/package.json +8 -8
  51. package/node_modules/@aws-sdk/credential-provider-process/package.json +3 -3
  52. package/node_modules/@aws-sdk/credential-provider-sso/package.json +5 -5
  53. package/node_modules/@aws-sdk/credential-provider-web-identity/package.json +4 -4
  54. package/node_modules/@aws-sdk/middleware-sdk-s3/package.json +4 -4
  55. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/cognito-identity/index.js +11 -1
  56. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/signin/index.js +6 -1
  57. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso/index.js +6 -1
  58. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso-oidc/index.js +13 -1
  59. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sts/index.js +9 -1
  60. package/node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/models/CognitoIdentityServiceException.js +1 -0
  61. package/node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/models/errors.js +9 -0
  62. package/node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/models/SigninServiceException.js +1 -0
  63. package/node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/models/errors.js +4 -0
  64. package/node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/models/SSOServiceException.js +1 -0
  65. package/node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/models/errors.js +4 -0
  66. package/node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/models/SSOOIDCServiceException.js +1 -0
  67. package/node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/models/errors.js +11 -0
  68. package/node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/models/STSServiceException.js +1 -0
  69. package/node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/models/errors.js +7 -0
  70. package/node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/models/CognitoIdentityServiceException.d.ts +1 -0
  71. package/node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/models/errors.d.ts +9 -0
  72. package/node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/models/SigninServiceException.d.ts +1 -0
  73. package/node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/models/errors.d.ts +4 -0
  74. package/node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/models/SSOServiceException.d.ts +1 -0
  75. package/node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/models/errors.d.ts +4 -0
  76. package/node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/models/SSOOIDCServiceException.d.ts +1 -0
  77. package/node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/models/errors.d.ts +11 -0
  78. package/node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/models/STSServiceException.d.ts +1 -0
  79. package/node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/models/errors.d.ts +7 -0
  80. package/node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/models/CognitoIdentityServiceException.d.ts +1 -0
  81. package/node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/models/errors.d.ts +9 -0
  82. package/node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/models/SigninServiceException.d.ts +1 -0
  83. package/node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/models/errors.d.ts +4 -0
  84. package/node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/models/SSOServiceException.d.ts +1 -0
  85. package/node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/models/errors.d.ts +4 -0
  86. package/node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/models/SSOOIDCServiceException.d.ts +1 -0
  87. package/node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/models/errors.d.ts +11 -0
  88. package/node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/models/STSServiceException.d.ts +1 -0
  89. package/node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/models/errors.d.ts +7 -0
  90. package/node_modules/@aws-sdk/nested-clients/package.json +4 -4
  91. package/node_modules/@aws-sdk/signature-v4-multi-region/package.json +2 -2
  92. package/node_modules/@aws-sdk/token-providers/package.json +4 -4
  93. package/node_modules/@smithy/core/dist-cjs/submodules/client/index.js +33 -11
  94. package/node_modules/@smithy/core/dist-cjs/submodules/endpoints/index.browser.js +2 -1
  95. package/node_modules/@smithy/core/dist-cjs/submodules/endpoints/index.js +2 -1
  96. package/node_modules/@smithy/core/dist-cjs/submodules/schema/index.js +3 -0
  97. package/node_modules/@smithy/core/dist-es/submodules/client/smithy-client/exceptions.js +33 -11
  98. package/node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/cache/EndpointCache.js +2 -1
  99. package/node_modules/@smithy/core/dist-es/submodules/schema/TypeRegistry.js +3 -0
  100. package/node_modules/@smithy/core/dist-types/submodules/client/smithy-client/exceptions.d.ts +1 -0
  101. package/node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/exceptions.d.ts +1 -0
  102. package/node_modules/@smithy/core/package.json +1 -1
  103. package/node_modules/@smithy/signature-v4/dist-cjs/index.js +4 -1
  104. package/node_modules/@smithy/signature-v4/dist-es/getCanonicalHeaders.js +4 -1
  105. package/node_modules/@smithy/signature-v4/package.json +3 -3
  106. package/node_modules/mailparser/.release-please-manifest.json +1 -1
  107. package/node_modules/mailparser/CHANGELOG.md +7 -0
  108. package/node_modules/mailparser/package.json +2 -2
  109. package/node_modules/nodemailer/CHANGELOG.md +27 -0
  110. package/node_modules/nodemailer/dist/cjs/base64/index.d.ts +3 -0
  111. package/node_modules/nodemailer/dist/cjs/base64/index.js +80 -41
  112. package/node_modules/nodemailer/dist/cjs/dkim/index.js +43 -29
  113. package/node_modules/nodemailer/dist/cjs/dkim/message-parser.js +2 -2
  114. package/node_modules/nodemailer/dist/cjs/errors.d.ts +17 -0
  115. package/node_modules/nodemailer/dist/cjs/errors.js +12 -1
  116. package/node_modules/nodemailer/dist/cjs/fetch/index.js +23 -3
  117. package/node_modules/nodemailer/dist/cjs/mailer/index.js +23 -7
  118. package/node_modules/nodemailer/dist/cjs/mailer/mail-message.d.ts +5 -0
  119. package/node_modules/nodemailer/dist/cjs/mailer/mail-message.js +20 -0
  120. package/node_modules/nodemailer/dist/cjs/mime-node/index.d.ts +1 -1
  121. package/node_modules/nodemailer/dist/cjs/mime-node/index.js +165 -105
  122. package/node_modules/nodemailer/dist/cjs/nodemailer.js +12 -9
  123. package/node_modules/nodemailer/dist/cjs/package-info.d.ts +1 -1
  124. package/node_modules/nodemailer/dist/cjs/package-info.js +1 -1
  125. package/node_modules/nodemailer/dist/cjs/qp/index.d.ts +2 -0
  126. package/node_modules/nodemailer/dist/cjs/qp/index.js +103 -33
  127. package/node_modules/nodemailer/dist/cjs/sendmail-transport/index.js +54 -14
  128. package/node_modules/nodemailer/dist/cjs/shared/index.d.ts +32 -1
  129. package/node_modules/nodemailer/dist/cjs/shared/index.js +212 -115
  130. package/node_modules/nodemailer/dist/cjs/smtp-connection/http-proxy-client.d.ts +3 -1
  131. package/node_modules/nodemailer/dist/cjs/smtp-connection/http-proxy-client.js +88 -68
  132. package/node_modules/nodemailer/dist/cjs/smtp-connection/index.d.ts +7 -1
  133. package/node_modules/nodemailer/dist/cjs/smtp-connection/index.js +348 -136
  134. package/node_modules/nodemailer/dist/cjs/smtp-pool/index.d.ts +3 -0
  135. package/node_modules/nodemailer/dist/cjs/smtp-pool/index.js +7 -1
  136. package/node_modules/nodemailer/dist/cjs/smtp-pool/pool-resource.js +106 -17
  137. package/node_modules/nodemailer/dist/cjs/smtp-transport/index.js +8 -2
  138. package/node_modules/nodemailer/dist/esm/base64/index.d.ts +3 -0
  139. package/node_modules/nodemailer/dist/esm/base64/index.js +80 -41
  140. package/node_modules/nodemailer/dist/esm/dkim/index.js +43 -29
  141. package/node_modules/nodemailer/dist/esm/dkim/message-parser.js +2 -2
  142. package/node_modules/nodemailer/dist/esm/errors.d.ts +17 -0
  143. package/node_modules/nodemailer/dist/esm/errors.js +10 -0
  144. package/node_modules/nodemailer/dist/esm/fetch/index.js +23 -3
  145. package/node_modules/nodemailer/dist/esm/mailer/index.js +23 -7
  146. package/node_modules/nodemailer/dist/esm/mailer/mail-message.d.ts +5 -0
  147. package/node_modules/nodemailer/dist/esm/mailer/mail-message.js +20 -0
  148. package/node_modules/nodemailer/dist/esm/mime-node/index.d.ts +1 -1
  149. package/node_modules/nodemailer/dist/esm/mime-node/index.js +166 -106
  150. package/node_modules/nodemailer/dist/esm/nodemailer.js +12 -9
  151. package/node_modules/nodemailer/dist/esm/package-info.d.ts +1 -1
  152. package/node_modules/nodemailer/dist/esm/package-info.js +1 -1
  153. package/node_modules/nodemailer/dist/esm/qp/index.d.ts +2 -0
  154. package/node_modules/nodemailer/dist/esm/qp/index.js +103 -33
  155. package/node_modules/nodemailer/dist/esm/sendmail-transport/index.js +54 -14
  156. package/node_modules/nodemailer/dist/esm/shared/index.d.ts +32 -1
  157. package/node_modules/nodemailer/dist/esm/shared/index.js +207 -115
  158. package/node_modules/nodemailer/dist/esm/smtp-connection/http-proxy-client.d.ts +3 -1
  159. package/node_modules/nodemailer/dist/esm/smtp-connection/http-proxy-client.js +88 -68
  160. package/node_modules/nodemailer/dist/esm/smtp-connection/index.d.ts +7 -1
  161. package/node_modules/nodemailer/dist/esm/smtp-connection/index.js +348 -136
  162. package/node_modules/nodemailer/dist/esm/smtp-pool/index.d.ts +3 -0
  163. package/node_modules/nodemailer/dist/esm/smtp-pool/index.js +7 -1
  164. package/node_modules/nodemailer/dist/esm/smtp-pool/pool-resource.js +106 -17
  165. package/node_modules/nodemailer/dist/esm/smtp-transport/index.js +8 -2
  166. package/node_modules/nodemailer/package.json +1 -1
  167. package/node_modules/uuid/package.json +17 -17
  168. package/package.json +9 -9
@@ -8,6 +8,7 @@ import { isProtoKey, copyOwnKeys } from './objects.js';
8
8
  import dns from 'node:dns';
9
9
  import net from 'node:net';
10
10
  import os from 'node:os';
11
+ import { finished } from 'node:stream';
11
12
  // re-exported for the callers that already depend on this module, see ./objects
12
13
  export { isProtoKey, copyOwnKeys };
13
14
  const DNS_TTL = 5 * 60 * 1000;
@@ -21,14 +22,24 @@ export const _resetCacheCleanup = () => {
21
22
  lastCacheCleanup = 0;
22
23
  };
23
24
  export let networkInterfaces;
24
- try {
25
- networkInterfaces = os.networkInterfaces();
26
- }
27
- catch (_err) {
28
- // fails on some systems
29
- }
25
+ let networkInterfacesRead = false;
26
+ // Read on first use rather than at load, so that importing the module does not ask for
27
+ // the interface table (Deno prompts for --allow-sys on it)
28
+ /** @internal */
29
+ export const _readNetworkInterfaces = () => {
30
+ if (!networkInterfacesRead) {
31
+ networkInterfacesRead = true;
32
+ try {
33
+ networkInterfaces = os.networkInterfaces();
34
+ }
35
+ catch (_err) {
36
+ // fails on some systems
37
+ }
38
+ }
39
+ return networkInterfaces;
40
+ };
30
41
  const isFamilySupported = (family, allowInternal) => {
31
- const addresses = Object.values(networkInterfaces || {}).flat();
42
+ const addresses = Object.values(_readNetworkInterfaces() || {}).flat();
32
43
  if (!addresses.length) {
33
44
  // hope for the best. Runtimes without an interface table (Cloudflare
34
45
  // Workers) report an empty object rather than throwing
@@ -39,7 +50,8 @@ const isFamilySupported = (family, allowInternal) => {
39
50
  const resolve = (family, hostname, options, callback) => {
40
51
  options = options || {};
41
52
  if (!isFamilySupported(family, options.allowInternalNetworkInterfaces)) {
42
- return callback(null, []);
53
+ callback(null, []);
54
+ return null;
43
55
  }
44
56
  const dnsResolver = dns.Resolver ? new dns.Resolver(options) : dns;
45
57
  dnsResolver['resolve' + family](hostname, (err, addresses) => {
@@ -58,6 +70,7 @@ const resolve = (family, hostname, options, callback) => {
58
70
  }
59
71
  return callback(null, Array.isArray(addresses) ? addresses : [].concat(addresses || []));
60
72
  });
73
+ return dns.Resolver ? dnsResolver : null;
61
74
  };
62
75
  export const dnsCache = new Map();
63
76
  const formatDNSValue = (value, extra) => {
@@ -121,115 +134,139 @@ export const resolveHostname = (options, callback) => {
121
134
  }));
122
135
  }
123
136
  }
124
- // Resolve both IPv4 and IPv6 addresses for fallback support
137
+ // The timeout limits the lookup as a whole. It is also handed to the resolver, but there
138
+ // it applies to every query attempt on its own, and the resolver retries a few times
139
+ let responded = false;
140
+ let deadline;
141
+ const respond = (err, result) => {
142
+ if (responded) {
143
+ return;
144
+ }
145
+ responded = true;
146
+ clearTimeout(deadline);
147
+ callback(err, result);
148
+ };
149
+ // a stale cached value is still better than no value at all
150
+ const respondCached = (error) => {
151
+ if (!cached) {
152
+ return false;
153
+ }
154
+ dnsCache.set(host, {
155
+ value: cached.value,
156
+ expires: Date.now() + (options.dnsTtl || DNS_TTL)
157
+ });
158
+ respond(null, formatDNSValue(cached.value, {
159
+ servername,
160
+ cached: true,
161
+ error
162
+ }));
163
+ return true;
164
+ };
165
+ const resolvers = [];
166
+ const timeout = Number(options.timeout) || 0;
167
+ if (timeout > 0) {
168
+ deadline = setTimeout(() => {
169
+ for (const resolver of resolvers) {
170
+ if (typeof resolver.cancel === 'function') {
171
+ resolver.cancel();
172
+ }
173
+ }
174
+ const err = new Error('DNS lookup for ' + host + ' timed out');
175
+ err.code = dns.TIMEOUT;
176
+ if (!respondCached(err)) {
177
+ respond(err);
178
+ }
179
+ }, timeout);
180
+ }
181
+ // Resolve both IPv4 and IPv6 addresses for fallback support, at the same time
125
182
  let ipv4Addresses = [];
126
183
  let ipv6Addresses = [];
127
184
  let ipv4Error = null;
128
185
  let ipv6Error = null;
129
- resolve(4, options.host, options, (err, addresses) => {
130
- if (err) {
131
- ipv4Error = err;
186
+ let pending = 2;
187
+ const onResolved = () => {
188
+ if (--pending || responded) {
189
+ return;
132
190
  }
133
- else {
134
- ipv4Addresses = addresses || [];
191
+ // Combine addresses: IPv4 first, then IPv6
192
+ const allAddresses = ipv4Addresses.concat(ipv6Addresses);
193
+ if (allAddresses.length) {
194
+ const value = {
195
+ addresses: allAddresses
196
+ };
197
+ dnsCache.set(host, {
198
+ value,
199
+ expires: Date.now() + (options.dnsTtl || DNS_TTL)
200
+ });
201
+ return respond(null, formatDNSValue(value, {
202
+ servername,
203
+ cached: false
204
+ }));
135
205
  }
136
- resolve(6, host, options, (err, addresses) => {
137
- if (err) {
138
- ipv6Error = err;
139
- }
140
- else {
141
- ipv6Addresses = addresses || [];
142
- }
143
- // Combine addresses: IPv4 first, then IPv6
144
- const allAddresses = ipv4Addresses.concat(ipv6Addresses);
145
- if (allAddresses.length) {
206
+ // No addresses from resolve4/resolve6, try dns.lookup as fallback
207
+ if (ipv4Error && ipv6Error && respondCached(ipv4Error)) {
208
+ // Both resolvers had errors
209
+ return;
210
+ }
211
+ try {
212
+ dns.lookup(host, { all: true }, (err, addresses) => {
213
+ if (err) {
214
+ if (respondCached(err)) {
215
+ return;
216
+ }
217
+ return respond(err);
218
+ }
219
+ // Get all supported addresses from dns.lookup
220
+ const supportedAddresses = addresses
221
+ ? addresses.filter(addr => isFamilySupported(addr.family)).map(addr => addr.address)
222
+ : [];
223
+ if (addresses && addresses.length && !supportedAddresses.length) {
224
+ // there are addresses but none can be used
225
+ console.warn(`Failed to resolve IPv${addresses[0].family} addresses with current network`);
226
+ }
227
+ if (!supportedAddresses.length && cached) {
228
+ // nothing was found, fallback to cached value
229
+ return respond(null, formatDNSValue(cached.value, {
230
+ servername,
231
+ cached: true
232
+ }));
233
+ }
146
234
  const value = {
147
- addresses: allAddresses
235
+ addresses: supportedAddresses.length ? supportedAddresses : [host]
148
236
  };
149
237
  dnsCache.set(host, {
150
238
  value,
151
239
  expires: Date.now() + (options.dnsTtl || DNS_TTL)
152
240
  });
153
- return callback(null, formatDNSValue(value, {
241
+ return respond(null, formatDNSValue(value, {
154
242
  servername,
155
243
  cached: false
156
244
  }));
245
+ });
246
+ }
247
+ catch (lookupErr) {
248
+ if (respondCached(lookupErr)) {
249
+ return;
157
250
  }
158
- // No addresses from resolve4/resolve6, try dns.lookup as fallback
159
- if (ipv4Error && ipv6Error) {
160
- // Both resolvers had errors
161
- if (cached) {
162
- dnsCache.set(host, {
163
- value: cached.value,
164
- expires: Date.now() + (options.dnsTtl || DNS_TTL)
165
- });
166
- return callback(null, formatDNSValue(cached.value, {
167
- servername,
168
- cached: true,
169
- error: ipv4Error
170
- }));
171
- }
172
- }
173
- try {
174
- dns.lookup(host, { all: true }, (err, addresses) => {
175
- if (err) {
176
- if (cached) {
177
- dnsCache.set(host, {
178
- value: cached.value,
179
- expires: Date.now() + (options.dnsTtl || DNS_TTL)
180
- });
181
- return callback(null, formatDNSValue(cached.value, {
182
- servername,
183
- cached: true,
184
- error: err
185
- }));
186
- }
187
- return callback(err);
188
- }
189
- // Get all supported addresses from dns.lookup
190
- const supportedAddresses = addresses
191
- ? addresses.filter(addr => isFamilySupported(addr.family)).map(addr => addr.address)
192
- : [];
193
- if (addresses && addresses.length && !supportedAddresses.length) {
194
- // there are addresses but none can be used
195
- console.warn(`Failed to resolve IPv${addresses[0].family} addresses with current network`);
196
- }
197
- if (!supportedAddresses.length && cached) {
198
- // nothing was found, fallback to cached value
199
- return callback(null, formatDNSValue(cached.value, {
200
- servername,
201
- cached: true
202
- }));
203
- }
204
- const value = {
205
- addresses: supportedAddresses.length ? supportedAddresses : [host]
206
- };
207
- dnsCache.set(host, {
208
- value,
209
- expires: Date.now() + (options.dnsTtl || DNS_TTL)
210
- });
211
- return callback(null, formatDNSValue(value, {
212
- servername,
213
- cached: false
214
- }));
215
- });
251
+ return respond(ipv4Error || ipv6Error || lookupErr);
252
+ }
253
+ };
254
+ for (const family of [4, 6]) {
255
+ const resolver = resolve(family, host, options, (err, addresses) => {
256
+ if (family === 4) {
257
+ ipv4Error = err;
258
+ ipv4Addresses = addresses || [];
216
259
  }
217
- catch (lookupErr) {
218
- if (cached) {
219
- dnsCache.set(host, {
220
- value: cached.value,
221
- expires: Date.now() + (options.dnsTtl || DNS_TTL)
222
- });
223
- return callback(null, formatDNSValue(cached.value, {
224
- servername,
225
- cached: true,
226
- error: lookupErr
227
- }));
228
- }
229
- return callback(ipv4Error || ipv6Error || lookupErr);
260
+ else {
261
+ ipv6Error = err;
262
+ ipv6Addresses = addresses || [];
230
263
  }
264
+ onResolved();
231
265
  });
232
- });
266
+ if (resolver) {
267
+ resolvers.push(resolver);
268
+ }
269
+ }
233
270
  };
234
271
  /**
235
272
  * Parses connection url to a structured configuration object
@@ -548,43 +585,98 @@ export const encodeXText = (str) => {
548
585
  }
549
586
  return result;
550
587
  };
588
+ // The first error a stream emitted, see recordStreamErrors
589
+ const streamErrors = new WeakMap();
590
+ /**
591
+ * Keeps the first error a content stream emits before it is read, so the error is reported
592
+ * when the stream is read instead of being thrown as unhandled. The listener stays attached
593
+ * for good, a stream that emits 'error' more than once never throws the later ones either
594
+ *
595
+ * @param stream Readable stream
596
+ */
597
+ export function recordStreamErrors(stream) {
598
+ stream.on('error', err => {
599
+ if (!streamErrors.has(stream)) {
600
+ streamErrors.set(stream, err);
601
+ }
602
+ });
603
+ }
604
+ /**
605
+ * Destroys a value if it is a readable stream that was not destroyed yet
606
+ *
607
+ * @param value Any content value
608
+ */
609
+ export function destroyStream(value) {
610
+ const stream = value;
611
+ if (stream && typeof stream.pipe === 'function' && typeof stream.destroy === 'function' && !stream.destroyed) {
612
+ stream.destroy();
613
+ }
614
+ }
615
+ /**
616
+ * Tells why a stream can not be read from start to end anymore. A stream that ended or was
617
+ * destroyed before anyone read it would never emit 'end' to a new reader, or, when piped, would
618
+ * end the destination right away and turn into an empty value without any error
619
+ *
620
+ * @param stream Readable stream
621
+ * @returns The error the stream failed with, an ESTREAM error, or null when it can be read
622
+ */
623
+ export function unreadableStreamError(stream) {
624
+ const err = streamErrors.get(stream) || stream.errored;
625
+ if (err) {
626
+ return err;
627
+ }
628
+ if (stream.readableEnded || stream.destroyed) {
629
+ const unreadable = new Error('Content stream was already read or destroyed');
630
+ unreadable.code = errors.ESTREAM;
631
+ return unreadable;
632
+ }
633
+ return null;
634
+ }
551
635
  /**
552
636
  * Streams a stream value into a Buffer
553
637
  *
554
638
  * @param stream Readable stream
555
639
  * @param callback Callback function with (err, value)
556
640
  */
557
- function resolveStream(stream, callback) {
641
+ export function resolveStream(stream, callback) {
558
642
  let responded = false;
559
- const chunks = [];
560
- let chunklen = 0;
561
- stream.on('error', err => {
643
+ const respond = (err, value) => {
562
644
  if (responded) {
563
645
  return;
564
646
  }
565
647
  responded = true;
566
- callback(err);
567
- });
568
- stream.on('readable', () => {
569
- let chunk;
570
- while ((chunk = stream.read()) !== null) {
571
- chunks.push(chunk);
572
- chunklen += chunk.length;
648
+ callback(err, value);
649
+ };
650
+ const unreadable = unreadableStreamError(stream);
651
+ if (unreadable) {
652
+ // absorbs a later 'error' from the stream, there is nobody left to report it to
653
+ stream.on('error', () => false);
654
+ setImmediate(() => respond(unreadable));
655
+ return;
656
+ }
657
+ const chunks = [];
658
+ let chunklen = 0;
659
+ stream.on('data', (chunk) => {
660
+ if (typeof chunk === 'string') {
661
+ chunk = Buffer.from(chunk);
573
662
  }
663
+ chunks.push(chunk);
664
+ chunklen += chunk.length;
574
665
  });
575
- stream.on('end', () => {
576
- if (responded) {
577
- return;
666
+ // finished() also reports a stream that is destroyed before 'end', which would otherwise
667
+ // leave the callback waiting forever
668
+ finished(stream, { writable: false }, err => {
669
+ if (err) {
670
+ return respond(err);
578
671
  }
579
- responded = true;
580
672
  let value;
581
673
  try {
582
674
  value = Buffer.concat(chunks, chunklen);
583
675
  }
584
676
  catch (E) {
585
- return callback(E);
677
+ return respond(E);
586
678
  }
587
- callback(null, value);
679
+ respond(null, value);
588
680
  });
589
681
  }
590
682
  /**
@@ -9,6 +9,8 @@ import type { Callback } from '../errors.js';
9
9
  export interface HttpProxyClientOptions {
10
10
  /** Set to false to accept a proxy certificate that fails validation (e.g. self-signed) */
11
11
  rejectUnauthorized?: boolean | undefined;
12
+ /** Time in milliseconds the CONNECT handshake may take, defaults to httpProxyClient.timeout or 30 seconds */
13
+ timeout?: number | undefined;
12
14
  }
13
15
  /**
14
16
  * Receives the proxied socket once the CONNECT handshake has succeeded, or the error that prevented it
@@ -38,7 +40,7 @@ declare function httpProxyClient(proxyUrl: string, destinationPort: number | str
38
40
  */
39
41
  declare function httpProxyClient(proxyUrl: string, destinationPort: number | string, destinationHost: string, tlsOptions: HttpProxyClientOptions | undefined, callback: HttpProxyClientCallback): void;
40
42
  /**
41
- * Socket timeout in milliseconds while the CONNECT handshake is in progress, defaults to 30 seconds.
43
+ * Time in milliseconds the CONNECT handshake may take when the call does not set one, defaults to 30 seconds.
42
44
  * Settable on the function itself, the same way the CommonJS module exposed it.
43
45
  */
44
46
  declare namespace httpProxyClient {
@@ -8,6 +8,8 @@ import * as errors from '../errors.js';
8
8
  // Cap the CONNECT response we buffer before the header terminator, so a proxy that
9
9
  // never sends \r\n\r\n cannot grow memory unboundedly before the socket times out.
10
10
  const MAX_RESPONSE_HEADER_BYTES = 64 * 1024;
11
+ // URL hostnames keep the brackets around an IPv6 literal, socket options and net.isIPv6 take it without
12
+ const unbracket = (host) => typeof host === 'string' && host.startsWith('[') && host.endsWith(']') ? host.slice(1, -1) : host;
11
13
  function httpProxyClient(proxyUrl, destinationPort, destinationHost, tlsOptions, callback) {
12
14
  if (typeof tlsOptions === 'function') {
13
15
  callback = tlsOptions;
@@ -27,6 +29,8 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, tlsOptions,
27
29
  return;
28
30
  }
29
31
  const proxy = urllib.parse(proxyUrl);
32
+ // the CONNECT request line and the Host header take an IPv6 destination in brackets
33
+ const authority = (net.isIPv6(unbracket(destinationHost)) ? '[' + unbracket(destinationHost) + ']' : destinationHost) + ':' + destinationPort;
30
34
  const connectOptions = {
31
35
  host: proxy.hostname,
32
36
  port: Number(proxy.port) ? Number(proxy.port) : proxy.protocol === 'https:' ? 443 : 80
@@ -42,15 +46,27 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, tlsOptions,
42
46
  else {
43
47
  connect = net.connect.bind(net);
44
48
  }
49
+ // The handshake is bounded as a whole, a proxy that keeps sending a byte now and then can
50
+ // not hold the connection open past it
51
+ const timeout = Number(tlsOptions.timeout) || httpProxyClient.timeout || 30 * 1000;
45
52
  let socket;
46
- // Error harness for initial connection. Once connection is established, the responsibility
47
- // to handle errors is passed to whoever uses this socket
53
+ // Single settlement path for the handshake: every temporary listener and the timer are
54
+ // dropped exactly once. Once the tunnel is up, the responsibility to handle errors is passed
55
+ // to whoever uses this socket
48
56
  let finished = false;
49
- const tempSocketErr = (err) => {
57
+ let timer;
58
+ const cleanup = () => {
59
+ clearTimeout(timer);
60
+ socket.removeListener('data', onSocketData);
61
+ socket.removeListener('error', fail);
62
+ socket.removeListener('close', onEarlyClose);
63
+ };
64
+ function fail(err) {
50
65
  if (finished) {
51
66
  return;
52
67
  }
53
68
  finished = true;
69
+ cleanup();
54
70
  try {
55
71
  socket.destroy();
56
72
  }
@@ -58,18 +74,72 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, tlsOptions,
58
74
  // ignore
59
75
  }
60
76
  done(err);
61
- };
62
- const timeoutErr = () => {
63
- const err = new Error('Proxy socket timed out');
64
- err.code = 'ETIMEDOUT';
65
- tempSocketErr(err);
66
- };
77
+ }
78
+ function onEarlyClose() {
79
+ const err = new Error('Proxy closed the connection before the tunnel was established');
80
+ err.code = errors.EPROXY;
81
+ fail(err);
82
+ }
83
+ // The response is collected as chunks and only the bytes that just arrived, together
84
+ // with the three before them, are searched for the end of the headers. Appending to a
85
+ // string and searching all of it again re-read the whole response on every chunk.
86
+ const chunks = [];
87
+ let received = 0;
88
+ let tail = '';
89
+ function onSocketData(chunk) {
90
+ if (finished) {
91
+ return;
92
+ }
93
+ const window = tail + chunk.toString('binary');
94
+ const windowEnd = window.indexOf('\r\n\r\n');
95
+ chunks.push(chunk);
96
+ received += chunk.length;
97
+ tail = window.slice(-3);
98
+ if (windowEnd < 0) {
99
+ if (received > MAX_RESPONSE_HEADER_BYTES) {
100
+ const err = new Error('Proxy response headers too large');
101
+ err.code = errors.EPROXY;
102
+ fail(err);
103
+ }
104
+ return;
105
+ }
106
+ // Stop reading before anything is put back. A socket that keeps flowing would emit the
107
+ // bytes after the headers, a greeting the proxy sent together with its own response,
108
+ // before the next owner of the socket has a listener for them
109
+ socket.removeListener('data', onSocketData);
110
+ socket.pause();
111
+ const headerEnd = received - window.length + windowEnd;
112
+ const response = Buffer.concat(chunks, received);
113
+ if (response.length > headerEnd + 4) {
114
+ socket.unshift(response.subarray(headerEnd + 4));
115
+ }
116
+ // check response code
117
+ const match = response.toString('binary', 0, headerEnd).match(/^HTTP\/\d+\.\d+ (\d+)/i);
118
+ if (!match || (match[1] || '').charAt(0) !== '2') {
119
+ const err = new Error('Invalid response from proxy' + ((match && ': ' + match[1]) || ''));
120
+ err.code = errors.EPROXY;
121
+ return fail(err);
122
+ }
123
+ // proxy connection is now established
124
+ finished = true;
125
+ cleanup();
126
+ // A fresh socket starts flowing once something listens for 'data', a paused one would
127
+ // not. Keep that behaviour for the next owner of the socket
128
+ const resumeOnData = (event) => {
129
+ if (event === 'data') {
130
+ socket.removeListener('newListener', resumeOnData);
131
+ socket.resume();
132
+ }
133
+ };
134
+ socket.on('newListener', resumeOnData);
135
+ return done(null, socket);
136
+ }
67
137
  socket = connect(connectOptions, () => {
68
138
  if (finished) {
69
139
  return;
70
140
  }
71
141
  const reqHeaders = {
72
- Host: destinationHost + ':' + destinationPort,
142
+ Host: authority,
73
143
  Connection: 'close'
74
144
  };
75
145
  if (proxy.auth) {
@@ -78,9 +148,7 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, tlsOptions,
78
148
  socket.write(
79
149
  // HTTP method
80
150
  'CONNECT ' +
81
- destinationHost +
82
- ':' +
83
- destinationPort +
151
+ authority +
84
152
  ' HTTP/1.1\r\n' +
85
153
  // HTTP request headers
86
154
  Object.keys(reqHeaders)
@@ -88,62 +156,14 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, tlsOptions,
88
156
  .join('\r\n') +
89
157
  // End request
90
158
  '\r\n\r\n');
91
- // The response is collected as chunks and only the bytes that just arrived, together
92
- // with the three before them, are searched for the end of the headers. Appending to a
93
- // string and searching all of it again re-read the whole response on every chunk.
94
- const chunks = [];
95
- let received = 0;
96
- let tail = '';
97
- const onSocketData = (chunk) => {
98
- let match;
99
- if (finished) {
100
- return;
101
- }
102
- const window = tail + chunk.toString('binary');
103
- const windowEnd = window.indexOf('\r\n\r\n');
104
- chunks.push(chunk);
105
- received += chunk.length;
106
- tail = window.slice(-3);
107
- if (windowEnd >= 0) {
108
- socket.removeListener('data', onSocketData);
109
- const headerEnd = received - window.length + windowEnd;
110
- const response = Buffer.concat(chunks, received).toString('binary');
111
- const headers = response.substr(0, headerEnd);
112
- const remainder = response.substr(headerEnd + 4);
113
- if (remainder) {
114
- socket.unshift(Buffer.from(remainder, 'binary'));
115
- }
116
- // proxy connection is now established
117
- finished = true;
118
- // check response code
119
- match = headers.match(/^HTTP\/\d+\.\d+ (\d+)/i);
120
- if (!match || (match[1] || '').charAt(0) !== '2') {
121
- try {
122
- socket.destroy();
123
- }
124
- catch (_E) {
125
- // ignore
126
- }
127
- const err = new Error('Invalid response from proxy' + ((match && ': ' + match[1]) || ''));
128
- err.code = errors.EPROXY;
129
- return done(err);
130
- }
131
- socket.removeListener('error', tempSocketErr);
132
- socket.removeListener('timeout', timeoutErr);
133
- socket.setTimeout(0);
134
- return done(null, socket);
135
- }
136
- if (received > MAX_RESPONSE_HEADER_BYTES) {
137
- socket.removeListener('data', onSocketData);
138
- const err = new Error('Proxy response headers too large');
139
- err.code = errors.EPROXY;
140
- return tempSocketErr(err);
141
- }
142
- };
143
159
  socket.on('data', onSocketData);
144
160
  });
145
- socket.setTimeout(httpProxyClient.timeout || 30 * 1000);
146
- socket.on('timeout', timeoutErr);
147
- socket.once('error', tempSocketErr);
161
+ timer = setTimeout(() => {
162
+ const err = new Error('Proxy socket timed out');
163
+ err.code = errors.ETIMEDOUT;
164
+ fail(err);
165
+ }, timeout);
166
+ socket.once('error', fail);
167
+ socket.once('close', onEarlyClose);
148
168
  }
149
169
  export default httpProxyClient;
@@ -3,7 +3,7 @@ import net from 'node:net';
3
3
  import tls from 'node:tls';
4
4
  import { type Readable } from 'node:stream';
5
5
  import * as shared from '../shared/index.js';
6
- import type { Callback, NodemailerError } from '../errors.js';
6
+ import { type Callback, type NodemailerError } from '../errors.js';
7
7
  import type XOAuth2 from '../xoauth2/index.js';
8
8
  import type { XOAuth2Options } from '../xoauth2/index.js';
9
9
  /**
@@ -275,6 +275,12 @@ export interface SMTPConnectionConnectOptions extends tls.ConnectionOptions {
275
275
  allowInternalNetworkInterfaces?: boolean | undefined;
276
276
  /** DNS lookup timeout in ms */
277
277
  timeout?: number | undefined;
278
+ /** Try the resolved addresses in turn, see net.connect */
279
+ autoSelectFamily?: boolean | undefined;
280
+ /** Time in ms an address gets before the next one is tried, see net.connect */
281
+ autoSelectFamilyAttemptTimeout?: number | undefined;
282
+ /** Hands the resolved addresses to net.connect */
283
+ lookup?: net.LookupFunction | undefined;
278
284
  }
279
285
  /**
280
286
  * A queued handler for the next server response