@mavogel/awscdk-rootmail 0.1.27 → 0.1.28

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 (175) hide show
  1. package/.jsii +8 -8
  2. package/lib/rootmail.js +1 -1
  3. package/lib/ses-receive.js +1 -1
  4. package/node_modules/@aws-sdk/checksums/package.json +4 -4
  5. package/node_modules/@aws-sdk/client-cloudwatch-logs/dist-cjs/index.js +1 -1
  6. package/node_modules/@aws-sdk/client-cloudwatch-logs/dist-types/CloudWatchLogsClient.d.ts +2 -1
  7. package/node_modules/@aws-sdk/client-cloudwatch-logs/package.json +5 -5
  8. package/node_modules/@aws-sdk/client-route-53/dist-cjs/index.js +1 -1
  9. package/node_modules/@aws-sdk/client-route-53/dist-types/Route53Client.d.ts +2 -1
  10. package/node_modules/@aws-sdk/client-route-53/package.json +6 -6
  11. package/node_modules/@aws-sdk/client-s3/dist-cjs/index.js +1 -1
  12. package/node_modules/@aws-sdk/client-s3/dist-types/S3Client.d.ts +2 -1
  13. package/node_modules/@aws-sdk/client-s3/package.json +9 -9
  14. package/node_modules/@aws-sdk/client-ses/dist-cjs/index.js +1 -1
  15. package/node_modules/@aws-sdk/client-ses/dist-types/SESClient.d.ts +2 -1
  16. package/node_modules/@aws-sdk/client-ses/package.json +5 -5
  17. package/node_modules/@aws-sdk/client-ssm/dist-cjs/index.js +1 -1
  18. package/node_modules/@aws-sdk/client-ssm/dist-types/SSMClient.d.ts +2 -1
  19. package/node_modules/@aws-sdk/client-ssm/package.json +5 -5
  20. package/node_modules/@aws-sdk/core/package.json +3 -3
  21. package/node_modules/@aws-sdk/credential-provider-env/package.json +4 -4
  22. package/node_modules/@aws-sdk/credential-provider-http/package.json +4 -4
  23. package/node_modules/@aws-sdk/credential-provider-ini/package.json +11 -11
  24. package/node_modules/@aws-sdk/credential-provider-login/package.json +5 -5
  25. package/node_modules/@aws-sdk/credential-provider-node/package.json +9 -9
  26. package/node_modules/@aws-sdk/credential-provider-process/package.json +4 -4
  27. package/node_modules/@aws-sdk/credential-provider-sso/package.json +6 -6
  28. package/node_modules/@aws-sdk/credential-provider-web-identity/package.json +5 -5
  29. package/node_modules/@aws-sdk/middleware-sdk-route53/package.json +2 -2
  30. package/node_modules/@aws-sdk/middleware-sdk-s3/package.json +5 -5
  31. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/cognito-identity/index.js +1 -1
  32. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/signin/index.js +1 -1
  33. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso/index.js +1 -1
  34. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso-oidc/index.js +1 -1
  35. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sts/index.js +1 -1
  36. package/node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/CognitoIdentityClient.d.ts +2 -1
  37. package/node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/SigninClient.d.ts +2 -1
  38. package/node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/SSOClient.d.ts +2 -1
  39. package/node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/SSOOIDCClient.d.ts +2 -1
  40. package/node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/STSClient.d.ts +2 -1
  41. package/node_modules/@aws-sdk/nested-clients/package.json +5 -5
  42. package/node_modules/@aws-sdk/signature-v4-multi-region/package.json +2 -2
  43. package/node_modules/@aws-sdk/token-providers/package.json +5 -5
  44. package/node_modules/@aws-sdk/types/dist-types/feature-ids.d.ts +3 -0
  45. package/node_modules/@aws-sdk/types/dist-types/ts3.4/feature-ids.d.ts +3 -0
  46. package/node_modules/@aws-sdk/types/package.json +1 -1
  47. package/node_modules/@smithy/core/dist-cjs/submodules/client/index.js +15 -2
  48. package/node_modules/@smithy/core/dist-cjs/submodules/event-streams/index.browser.js +10 -0
  49. package/node_modules/@smithy/core/dist-cjs/submodules/event-streams/index.js +10 -0
  50. package/node_modules/@smithy/core/dist-cjs/submodules/protocols/index.js +5 -5
  51. package/node_modules/@smithy/core/dist-es/submodules/client/smithy-client/command.js +16 -2
  52. package/node_modules/@smithy/core/dist-es/submodules/event-streams/eventstream-codec/HeaderMarshaller.js +10 -0
  53. package/node_modules/@smithy/core/dist-es/submodules/protocols/json/codec-v2/JsonShapeSerializer2.js +5 -5
  54. package/node_modules/@smithy/core/package.json +1 -1
  55. package/node_modules/@zone-eu/mailsplit/lib/message-splitter.js +39 -27
  56. package/node_modules/@zone-eu/mailsplit/lib/mime-node.d.ts +1 -1
  57. package/node_modules/@zone-eu/mailsplit/lib/mime-node.js +38 -61
  58. package/node_modules/@zone-eu/mailsplit/lib/types.d.ts +1 -1
  59. package/node_modules/@zone-eu/mailsplit/package.json +2 -2
  60. package/node_modules/libmime/CHANGELOG.md +36 -0
  61. package/node_modules/libmime/README.md +40 -3
  62. package/node_modules/libmime/dist/cjs/charset.d.ts +36 -0
  63. package/node_modules/libmime/dist/cjs/charset.js +165 -2
  64. package/node_modules/libmime/dist/cjs/charsets.js +8 -2
  65. package/node_modules/libmime/dist/cjs/libmime.d.ts +20 -3
  66. package/node_modules/libmime/dist/cjs/libmime.js +100 -6
  67. package/node_modules/libmime/dist/cjs/types.d.ts +28 -0
  68. package/node_modules/libmime/dist/esm/charset.d.ts +36 -0
  69. package/node_modules/libmime/dist/esm/charset.js +162 -2
  70. package/node_modules/libmime/dist/esm/charsets.js +8 -2
  71. package/node_modules/libmime/dist/esm/libmime.d.ts +20 -3
  72. package/node_modules/libmime/dist/esm/libmime.js +100 -6
  73. package/node_modules/libmime/dist/esm/types.d.ts +28 -0
  74. package/node_modules/libmime/package.json +3 -3
  75. package/node_modules/mailparser/CHANGELOG.md +51 -0
  76. package/node_modules/mailparser/README.md +32 -1
  77. package/node_modules/mailparser/dist/cjs/address-header.d.ts +19 -0
  78. package/node_modules/mailparser/dist/cjs/address-header.js +185 -0
  79. package/node_modules/mailparser/dist/cjs/index.d.ts +3 -1
  80. package/node_modules/mailparser/dist/cjs/index.js +4 -2
  81. package/node_modules/mailparser/dist/cjs/mail-parser.js +36 -183
  82. package/node_modules/mailparser/dist/cjs/stream-hash.d.ts +5 -4
  83. package/node_modules/mailparser/dist/cjs/stream-hash.js +8 -5
  84. package/node_modules/mailparser/dist/cjs/types.d.ts +33 -6
  85. package/node_modules/mailparser/dist/esm/address-header.d.ts +19 -0
  86. package/node_modules/mailparser/dist/esm/address-header.js +176 -0
  87. package/node_modules/mailparser/dist/esm/index.d.ts +3 -1
  88. package/node_modules/mailparser/dist/esm/index.js +3 -2
  89. package/node_modules/mailparser/dist/esm/mail-parser.js +36 -183
  90. package/node_modules/mailparser/dist/esm/stream-hash.d.ts +5 -4
  91. package/node_modules/mailparser/dist/esm/stream-hash.js +8 -5
  92. package/node_modules/mailparser/dist/esm/types.d.ts +33 -6
  93. package/node_modules/mailparser/package.json +6 -7
  94. package/node_modules/nodemailer/CHANGELOG.md +23 -0
  95. package/node_modules/nodemailer/dist/cjs/addressparser/index.js +2 -5
  96. package/node_modules/nodemailer/dist/cjs/dkim/index.js +1 -3
  97. package/node_modules/nodemailer/dist/cjs/errors.d.ts +19 -0
  98. package/node_modules/nodemailer/dist/cjs/errors.js +14 -1
  99. package/node_modules/nodemailer/dist/cjs/fetch/cookies.js +2 -4
  100. package/node_modules/nodemailer/dist/cjs/fetch/index.js +2 -8
  101. package/node_modules/nodemailer/dist/cjs/mail-composer/index.js +42 -9
  102. package/node_modules/nodemailer/dist/cjs/mailer/mail-message.js +5 -6
  103. package/node_modules/nodemailer/dist/cjs/mime-funcs/index.d.ts +18 -1
  104. package/node_modules/nodemailer/dist/cjs/mime-funcs/index.js +43 -1
  105. package/node_modules/nodemailer/dist/cjs/mime-funcs/mime-types.js +3 -12
  106. package/node_modules/nodemailer/dist/cjs/mime-node/index.d.ts +34 -1
  107. package/node_modules/nodemailer/dist/cjs/mime-node/index.js +40 -15
  108. package/node_modules/nodemailer/dist/cjs/package-info.d.ts +1 -1
  109. package/node_modules/nodemailer/dist/cjs/package-info.js +1 -1
  110. package/node_modules/nodemailer/dist/cjs/qp/index.js +3 -12
  111. package/node_modules/nodemailer/dist/cjs/sendmail-transport/index.js +1 -3
  112. package/node_modules/nodemailer/dist/cjs/shared/index.js +2 -6
  113. package/node_modules/nodemailer/dist/cjs/smtp-connection/http-proxy-client.js +1 -1
  114. package/node_modules/nodemailer/dist/cjs/smtp-connection/index.d.ts +6 -0
  115. package/node_modules/nodemailer/dist/cjs/smtp-connection/index.js +15 -12
  116. package/node_modules/nodemailer/dist/cjs/smtp-connection/server-message-id.d.ts +14 -0
  117. package/node_modules/nodemailer/dist/cjs/smtp-connection/server-message-id.js +53 -0
  118. package/node_modules/nodemailer/dist/cjs/smtp-pool/index.d.ts +11 -0
  119. package/node_modules/nodemailer/dist/cjs/smtp-pool/index.js +25 -1
  120. package/node_modules/nodemailer/dist/cjs/smtp-pool/pool-resource.js +2 -5
  121. package/node_modules/nodemailer/dist/esm/addressparser/index.js +2 -5
  122. package/node_modules/nodemailer/dist/esm/dkim/index.js +1 -3
  123. package/node_modules/nodemailer/dist/esm/errors.d.ts +19 -0
  124. package/node_modules/nodemailer/dist/esm/errors.js +12 -0
  125. package/node_modules/nodemailer/dist/esm/fetch/cookies.js +2 -4
  126. package/node_modules/nodemailer/dist/esm/fetch/index.js +2 -8
  127. package/node_modules/nodemailer/dist/esm/mail-composer/index.js +42 -9
  128. package/node_modules/nodemailer/dist/esm/mailer/mail-message.js +5 -6
  129. package/node_modules/nodemailer/dist/esm/mime-funcs/index.d.ts +18 -1
  130. package/node_modules/nodemailer/dist/esm/mime-funcs/index.js +43 -1
  131. package/node_modules/nodemailer/dist/esm/mime-funcs/mime-types.js +3 -12
  132. package/node_modules/nodemailer/dist/esm/mime-node/index.d.ts +34 -1
  133. package/node_modules/nodemailer/dist/esm/mime-node/index.js +40 -15
  134. package/node_modules/nodemailer/dist/esm/package-info.d.ts +1 -1
  135. package/node_modules/nodemailer/dist/esm/package-info.js +1 -1
  136. package/node_modules/nodemailer/dist/esm/qp/index.js +3 -12
  137. package/node_modules/nodemailer/dist/esm/sendmail-transport/index.js +1 -3
  138. package/node_modules/nodemailer/dist/esm/shared/index.js +2 -6
  139. package/node_modules/nodemailer/dist/esm/smtp-connection/http-proxy-client.js +1 -1
  140. package/node_modules/nodemailer/dist/esm/smtp-connection/index.d.ts +6 -0
  141. package/node_modules/nodemailer/dist/esm/smtp-connection/index.js +16 -13
  142. package/node_modules/nodemailer/dist/esm/smtp-connection/server-message-id.d.ts +14 -0
  143. package/node_modules/nodemailer/dist/esm/smtp-connection/server-message-id.js +50 -0
  144. package/node_modules/nodemailer/dist/esm/smtp-pool/index.d.ts +11 -0
  145. package/node_modules/nodemailer/dist/esm/smtp-pool/index.js +25 -1
  146. package/node_modules/nodemailer/dist/esm/smtp-pool/pool-resource.js +2 -5
  147. package/node_modules/nodemailer/package.json +11 -9
  148. package/package.json +7 -7
  149. package/node_modules/mailparser/node_modules/iconv-lite/LICENSE +0 -21
  150. package/node_modules/mailparser/node_modules/iconv-lite/README.md +0 -138
  151. package/node_modules/mailparser/node_modules/iconv-lite/encodings/dbcs-codec.js +0 -532
  152. package/node_modules/mailparser/node_modules/iconv-lite/encodings/dbcs-data.js +0 -185
  153. package/node_modules/mailparser/node_modules/iconv-lite/encodings/index.js +0 -23
  154. package/node_modules/mailparser/node_modules/iconv-lite/encodings/internal.js +0 -218
  155. package/node_modules/mailparser/node_modules/iconv-lite/encodings/sbcs-codec.js +0 -75
  156. package/node_modules/mailparser/node_modules/iconv-lite/encodings/sbcs-data-generated.js +0 -451
  157. package/node_modules/mailparser/node_modules/iconv-lite/encodings/sbcs-data.js +0 -180
  158. package/node_modules/mailparser/node_modules/iconv-lite/encodings/tables/big5-added.json +0 -122
  159. package/node_modules/mailparser/node_modules/iconv-lite/encodings/tables/cp936.json +0 -264
  160. package/node_modules/mailparser/node_modules/iconv-lite/encodings/tables/cp949.json +0 -273
  161. package/node_modules/mailparser/node_modules/iconv-lite/encodings/tables/cp950.json +0 -177
  162. package/node_modules/mailparser/node_modules/iconv-lite/encodings/tables/eucjp.json +0 -182
  163. package/node_modules/mailparser/node_modules/iconv-lite/encodings/tables/gb18030-ranges.json +0 -1
  164. package/node_modules/mailparser/node_modules/iconv-lite/encodings/tables/gbk-added.json +0 -56
  165. package/node_modules/mailparser/node_modules/iconv-lite/encodings/tables/shiftjis.json +0 -125
  166. package/node_modules/mailparser/node_modules/iconv-lite/encodings/utf16.js +0 -187
  167. package/node_modules/mailparser/node_modules/iconv-lite/encodings/utf32.js +0 -314
  168. package/node_modules/mailparser/node_modules/iconv-lite/encodings/utf7.js +0 -283
  169. package/node_modules/mailparser/node_modules/iconv-lite/lib/bom-handling.js +0 -48
  170. package/node_modules/mailparser/node_modules/iconv-lite/lib/helpers/merge-exports.js +0 -13
  171. package/node_modules/mailparser/node_modules/iconv-lite/lib/index.d.ts +0 -129
  172. package/node_modules/mailparser/node_modules/iconv-lite/lib/index.js +0 -182
  173. package/node_modules/mailparser/node_modules/iconv-lite/lib/streams.js +0 -105
  174. package/node_modules/mailparser/node_modules/iconv-lite/package.json +0 -70
  175. package/node_modules/mailparser/node_modules/iconv-lite/types/encodings.d.ts +0 -425
@@ -1,16 +1,14 @@
1
1
  import mailsplit from '@zone-eu/mailsplit';
2
2
  import FlowedDecoder from '@zone-eu/mailsplit/lib/flowed-decoder.js';
3
- import encodingJapanese from 'encoding-japanese';
4
3
  import he from 'he';
5
4
  import { htmlToText } from 'html-to-text';
6
- import iconv from 'iconv-lite';
7
5
  import libmime from 'libmime';
8
6
  import linkifyIt from 'linkify-it';
9
7
  import addressparser from 'nodemailer/lib/addressparser';
10
- import punycode from 'punycode.js';
11
8
  import { Transform } from 'node:stream';
12
9
  import tlds from './tlds.cjs';
13
10
  import StreamHash from './stream-hash.js';
11
+ import { decodeAddresses, formatAddressesHTML, formatAddressesText } from './address-header.js';
14
12
  const { Splitter, ChunkedPassthrough } = mailsplit;
15
13
  // Bounds for link detection in text bodies. Message content is untrusted and
16
14
  // linkify-it backtracks heavily on crafted input: matching a host is quadratic
@@ -72,49 +70,6 @@ linkify.add('@', {
72
70
  match.url = 'https://twitter.com/' + match.url.replace(/^@/, '');
73
71
  }
74
72
  });
75
- // Lists the parameters a header value gives in RFC 2231 form (name*=, name*0= or
76
- // name*0*=). libmime decodes those while parsing the header, so the result must not go
77
- // through decodeWords as well: filename*=utf-8''a%3D%3Futf-8%3FQ%3F%3D2Eexe%3F%3D would
78
- // otherwise come out as "a.exe" while a parser that decodes once sees the encoded word.
79
- function rfc2231ParamNames(value) {
80
- let names = new Set();
81
- let quote = false;
82
- let escaped = false;
83
- let start = 0;
84
- let addSegment = (end) => {
85
- let segment = value.substring(start, end);
86
- let eq = segment.indexOf('=');
87
- if (eq < 0) {
88
- return;
89
- }
90
- let match = segment
91
- .substring(0, eq)
92
- .trim()
93
- .toLowerCase()
94
- .match(/^(.+?)\*(\d+\*?)?$/);
95
- if (match) {
96
- names.add(match[1]);
97
- }
98
- };
99
- for (let i = 0; i < value.length; i++) {
100
- let chr = value.charAt(i);
101
- if (escaped) {
102
- escaped = false;
103
- }
104
- else if (chr === '\\') {
105
- escaped = true;
106
- }
107
- else if (chr === '"') {
108
- quote = !quote;
109
- }
110
- else if (chr === ';' && !quote) {
111
- addSegment(i);
112
- start = i + 1;
113
- }
114
- }
115
- addSegment(value.length);
116
- return names;
117
- }
118
73
  // Deepest element nesting html-to-text is asked to convert. htmlparser2 keeps its open
119
74
  // element stack at the front of an array, so parsing alone is quadratic in the nesting
120
75
  // depth (200k nested <b> took 9 s), and html-to-text's recursive walk overflows the call
@@ -184,6 +139,14 @@ function exceedsHtmlNestingDepth(html, maxDepth) {
184
139
  }
185
140
  return false;
186
141
  }
142
+ // Charsets a body is never decoded from, matched the way iconv-lite and node-iconv compare names
143
+ // (case and punctuation ignored). UTF-7 can carry markup past a filter that looks for "<" (a body
144
+ // of +ADw-script+AD4- decodes to <script>), base64 and hex are transfer encodings. libmime's decode
145
+ // stream refuses the same names, this check applies the refusal to the native Iconv option as well
146
+ const REFUSED_CHARSET = /^(?:(?:cs)?unicode11)?utf7(?:imap)?$|^base64$|^hex$/;
147
+ function isRefusedCharset(charset) {
148
+ return REFUSED_CHARSET.test((charset || '').toLowerCase().replace(/[^a-z0-9]/g, ''));
149
+ }
187
150
  class IconvDecoder extends Transform {
188
151
  constructor(Iconv, charset) {
189
152
  super();
@@ -213,41 +176,6 @@ class IconvDecoder extends Transform {
213
176
  this.stream.end();
214
177
  }
215
178
  }
216
- class JPDecoder extends Transform {
217
- constructor(charset) {
218
- super();
219
- this.charset = charset;
220
- this.chunks = [];
221
- this.chunklen = 0;
222
- }
223
- _transform(chunk, encoding, done) {
224
- if (typeof chunk === 'string') {
225
- chunk = Buffer.from(chunk, encoding);
226
- }
227
- this.chunks.push(chunk);
228
- this.chunklen += chunk.length;
229
- done();
230
- }
231
- _flush(done) {
232
- let input = Buffer.concat(this.chunks, this.chunklen);
233
- try {
234
- let output = encodingJapanese.convert(input, {
235
- to: 'UNICODE', // to_encoding
236
- from: this.charset, // from_encoding
237
- type: 'string'
238
- });
239
- if (typeof output === 'string') {
240
- output = Buffer.from(output);
241
- }
242
- this.push(output);
243
- }
244
- catch {
245
- // keep as is on errors
246
- this.push(input);
247
- }
248
- done();
249
- }
250
- }
251
179
  /**
252
180
  * A lower level, streaming e-mail parser. It is a Transform stream: write the raw message in and
253
181
  * read the parsed content out. It emits:
@@ -323,6 +251,10 @@ class MailParser extends Transform {
323
251
  // create wrapper
324
252
  return {
325
253
  decodeStream(charset) {
254
+ if (isRefusedCharset(charset)) {
255
+ // the same refusal as without the Iconv option, see below
256
+ return libmime.createDecodeStream(charset);
257
+ }
326
258
  // node-iconv resolves its own charset aliases and needs charset-specific
327
259
  // fixups (see IconvDecoder, eg. ks_c_5601-1987), so pass the charset through as is
328
260
  return new IconvDecoder(Iconv, charset);
@@ -332,13 +264,12 @@ class MailParser extends Transform {
332
264
  else {
333
265
  return {
334
266
  decodeStream(charset) {
335
- // resolve charset aliases (eg. iso-8859-8-i -> ISO-8859-8) via libmime's canonical table
336
- charset = libmime.normalizeCharset((charset || 'ascii').toString());
337
- if (/^jis|^iso-?2022-?jp/i.test(charset)) {
338
- // special case not supported by iconv-lite
339
- return new JPDecoder(charset);
340
- }
341
- return iconv.decodeStream(charset);
267
+ // libmime resolves charset aliases (eg. iso-8859-8-i -> ISO-8859-8), decodes the
268
+ // Japanese charsets iconv-lite lacks and keeps a multibyte character split across
269
+ // chunks intact. A charset it does not know or refuses (UTF-7, which would turn
270
+ // +ADw-script+AD4- into <script>, and the base64 and hex transfer encodings) is
271
+ // decoded as UTF-8 with invalid bytes replaced, the same as encoded words get
272
+ return libmime.createDecodeStream(charset || 'ascii');
342
273
  }
343
274
  };
344
275
  }
@@ -441,9 +372,18 @@ class MailParser extends Transform {
441
372
  case 'content-type':
442
373
  case 'content-disposition':
443
374
  case 'dkim-signature': {
444
- // a parameter given in RFC 2231 form is decoded by parseHeaderValue already
445
- let rfc2231 = rfc2231ParamNames(value);
446
- value = this.libmime.parseHeaderValue(value);
375
+ // The comments of a MIME header are not part of its value, so they are
376
+ // dropped as mailsplit drops them for the structure: `boundary=b (comment)`
377
+ // names the boundary `b`. A DKIM-Signature is a tag list without comment
378
+ // syntax, so a parenthesis in a tag value is data
379
+ value = this.libmime.parseHeaderValue(value, { stripComments: key !== 'dkim-signature' });
380
+ // A parameter given in RFC 2231 form was decoded by parseHeaderValue already,
381
+ // which names it in decodedParams, and must not go through decodeWords as
382
+ // well: filename*=utf-8''a%3D%3Futf-8%3FQ%3F%3D2Eexe%3F%3D would otherwise come
383
+ // out as a.exe while a parser that decodes once sees the encoded word. A
384
+ // second scan of the raw header for the name used to decide this, and read
385
+ // `(c)filename*=` as `(c)filename` once the comment was dropped from the real one
386
+ let rfc2231 = new Set(value.decodedParams);
447
387
  if (value.value) {
448
388
  value.value = this.libmime.decodeWords(value.value);
449
389
  }
@@ -676,52 +616,7 @@ class MailParser extends Transform {
676
616
  }
677
617
  /** @internal */
678
618
  decodeAddresses(addresses) {
679
- for (let i = 0; i < addresses.length; i++) {
680
- let address = addresses[i];
681
- address.name = (address.name || '').toString().trim();
682
- // An encoded word decodes into the display name and nothing else, whatever it
683
- // decodes to. RFC 2047 section 5 keeps encoded words out of the addr-spec, so a
684
- // name that decodes to "CEO" <ceo@bank.com> is still only a name: parsing it
685
- // again would report an address the header never carried
686
- if (address.name) {
687
- try {
688
- address.name = this.libmime.decodeWords(address.name);
689
- }
690
- catch {
691
- //ignore, keep as is
692
- }
693
- }
694
- // Security fix: detect RFC 2047 encoded-words in the address field.
695
- // Per RFC 2047 Section 5, encoded-words MUST NOT appear in addr-spec.
696
- // If found, decode and validate; reject if result is not a simple local@domain.
697
- if (address.address && /[=]\?[^?]+\?[BbQq]\?[^?]*\?[=]/.test(address.address)) {
698
- try {
699
- let decodedAddr = this.libmime.decodeWords(address.address);
700
- if (/^[^\s@]+@[^\s@]+$/.test(decodedAddr) && !/[=]\?/.test(decodedAddr)) {
701
- address.address = decodedAddr;
702
- }
703
- else {
704
- address.address = '';
705
- }
706
- }
707
- catch {
708
- address.address = '';
709
- }
710
- }
711
- if (address.address && /@xn--/.test(address.address)) {
712
- try {
713
- address.address =
714
- address.address.slice(0, address.address.lastIndexOf('@') + 1) +
715
- punycode.toUnicode(address.address.slice(address.address.lastIndexOf('@') + 1));
716
- }
717
- catch {
718
- // Not a valid punycode string; keep as is
719
- }
720
- }
721
- if (address.group) {
722
- this.decodeAddresses(address.group);
723
- }
724
- }
619
+ decodeAddresses(addresses, this.libmime);
725
620
  }
726
621
  /** @internal */
727
622
  createNode(node) {
@@ -1003,7 +898,8 @@ class MailParser extends Transform {
1003
898
  this.waitUntilAttachmentEnd = false;
1004
899
  }
1005
900
  };
1006
- let algo = this.options.checksumAlgo || 'md5';
901
+ // false skips hashing, checksum is then left unset
902
+ let algo = this.options.checksumAlgo === false ? false : this.options.checksumAlgo || 'md5';
1007
903
  let hasher;
1008
904
  try {
1009
905
  hasher = new StreamHash(attachment, algo);
@@ -1156,56 +1052,13 @@ class MailParser extends Transform {
1156
1052
  * Formats parsed addresses for an HTML context, every value HTML-escaped.
1157
1053
  */
1158
1054
  getAddressesHTML(value) {
1159
- let formatSingleLevel = (addresses) => addresses
1160
- .map(address => {
1161
- let str = '<span class="mp_address_group">';
1162
- if (address.name) {
1163
- str += '<span class="mp_address_name">' + he.encode(address.name) + (address.group ? ': ' : '') + '</span>';
1164
- }
1165
- if (address.address) {
1166
- let link = '<a href="mailto:' + he.encode(address.address) + '" class="mp_address_email">' + he.encode(address.address) + '</a>';
1167
- if (address.name) {
1168
- str += ' &lt;' + link + '&gt;';
1169
- }
1170
- else {
1171
- str += link;
1172
- }
1173
- }
1174
- if (address.group) {
1175
- str += formatSingleLevel(address.group) + ';';
1176
- }
1177
- return str + '</span>';
1178
- })
1179
- .join(', ');
1180
- return formatSingleLevel([].concat(value || []));
1055
+ return formatAddressesHTML(value);
1181
1056
  }
1182
1057
  /**
1183
1058
  * Formats parsed addresses as plain text, display names quoted.
1184
1059
  */
1185
1060
  getAddressesText(value) {
1186
- let formatSingleLevel = (addresses) => addresses
1187
- .map(address => {
1188
- let str = '';
1189
- if (address.name) {
1190
- // a quoted-string, so a quote or backslash inside the name is escaped
1191
- str += `"${address.name.replace(/["\\]/g, '\\$&')}"` + (address.group ? ': ' : '');
1192
- }
1193
- if (address.address) {
1194
- let link = address.address;
1195
- if (address.name) {
1196
- str += ' <' + link + '>';
1197
- }
1198
- else {
1199
- str += link;
1200
- }
1201
- }
1202
- if (address.group) {
1203
- str += formatSingleLevel(address.group) + ';';
1204
- }
1205
- return str;
1206
- })
1207
- .join(', ');
1208
- return formatSingleLevel([].concat(value || []));
1061
+ return formatAddressesText(value);
1209
1062
  }
1210
1063
  /**
1211
1064
  * Replaces `cid:` links to image attachments in the HTML content. Call it once the parser has
@@ -10,14 +10,15 @@ export interface HashTarget {
10
10
  }
11
11
  /**
12
12
  * A pass-through stream that records the checksum and byte size of what flows through it on the
13
- * given object once the stream ends. Throws when the hash algorithm is not available.
13
+ * given object once the stream ends. Throws when the hash algorithm is not available. With `algo`
14
+ * set to `false` nothing is hashed and only the size is recorded.
14
15
  */
15
16
  declare class StreamHash extends Transform {
16
17
  attachment: HashTarget;
17
- algo: string;
18
- hash: crypto.Hash;
18
+ algo: string | false;
19
+ hash: crypto.Hash | null;
19
20
  byteCount: number;
20
- constructor(attachment: HashTarget, algo?: string);
21
+ constructor(attachment: HashTarget, algo?: string | false);
21
22
  _transform(chunk: Buffer, encoding: BufferEncoding, done: TransformCallback): void;
22
23
  _flush(done: TransformCallback): void;
23
24
  }
@@ -2,23 +2,26 @@ import crypto from 'node:crypto';
2
2
  import { Transform } from 'node:stream';
3
3
  /**
4
4
  * A pass-through stream that records the checksum and byte size of what flows through it on the
5
- * given object once the stream ends. Throws when the hash algorithm is not available.
5
+ * given object once the stream ends. Throws when the hash algorithm is not available. With `algo`
6
+ * set to `false` nothing is hashed and only the size is recorded.
6
7
  */
7
8
  class StreamHash extends Transform {
8
9
  constructor(attachment, algo) {
9
10
  super();
10
11
  this.attachment = attachment;
11
- this.algo = (algo || 'md5').toLowerCase();
12
- this.hash = crypto.createHash(this.algo);
12
+ this.algo = algo === false ? false : (algo || 'md5').toLowerCase();
13
+ this.hash = this.algo === false ? null : crypto.createHash(this.algo);
13
14
  this.byteCount = 0;
14
15
  }
15
16
  _transform(chunk, encoding, done) {
16
- this.hash.update(chunk);
17
+ this.hash?.update(chunk);
17
18
  this.byteCount += chunk.length;
18
19
  done(null, chunk);
19
20
  }
20
21
  _flush(done) {
21
- this.attachment.checksum = this.hash.digest('hex');
22
+ if (this.hash) {
23
+ this.attachment.checksum = this.hash.digest('hex');
24
+ }
22
25
  this.attachment.size = this.byteCount;
23
26
  done();
24
27
  }
@@ -37,6 +37,24 @@ export interface AddressObject {
37
37
  /** The addresses formatted as plain text, display names quoted. */
38
38
  text: string;
39
39
  }
40
+ /**
41
+ * Options for `parseAddressHeader`.
42
+ */
43
+ export interface ParseAddressHeaderOptions {
44
+ /**
45
+ * Replace every group with its members, so the result lists mailboxes only and the group names
46
+ * are dropped.
47
+ * @default false
48
+ */
49
+ flatten?: boolean;
50
+ /**
51
+ * Decode an internationalized (punycode) domain to Unicode. Set to false to keep the domain as
52
+ * it appears in the header, for an address that is going to be used to send mail, such as an
53
+ * SMTP envelope, where the ASCII form is the one every server accepts.
54
+ * @default true
55
+ */
56
+ decodePunycode?: boolean;
57
+ }
40
58
  /**
41
59
  * One entry of the combined `list` header, built from a `List-*` header such as `List-Unsubscribe`.
42
60
  */
@@ -122,7 +140,10 @@ export interface AttachmentStream extends AttachmentCommon {
122
140
  * message. Must be called for every attachment. Set to `null` once called.
123
141
  */
124
142
  release: (() => void) | null;
125
- /** The hex encoded checksum of the content (md5 unless `checksumAlgo` says otherwise). Set once `content` has ended. */
143
+ /**
144
+ * The hex encoded checksum of the content (md5 unless `checksumAlgo` says otherwise). Set once
145
+ * `content` has ended, never when `checksumAlgo` is `false`.
146
+ */
126
147
  checksum?: string;
127
148
  /** The size of the decoded content in bytes. Set once `content` has ended. */
128
149
  size?: number;
@@ -135,7 +156,11 @@ export interface Attachment extends AttachmentCommon {
135
156
  content: Buffer;
136
157
  /** Always `null`, `simpleParser` has released the attachment already. */
137
158
  release: null;
138
- /** The hex encoded checksum of the content (md5 unless `checksumAlgo` says otherwise). */
159
+ /**
160
+ * The hex encoded checksum of the content (md5 unless `checksumAlgo` says otherwise). Not set when
161
+ * `checksumAlgo` is `false`: the type keeps it a string so code written for the default does not
162
+ * have to check it, so a caller that turns hashing off must not read it.
163
+ */
139
164
  checksum: string;
140
165
  /** The size of the decoded content in bytes. */
141
166
  size: number;
@@ -211,16 +236,18 @@ export interface MailParserOptions {
211
236
  */
212
237
  keepDeliveryStatus?: boolean;
213
238
  /**
214
- * Decode character sets with the native `iconv` module instead of `iconv-lite`. Pass the
215
- * `Iconv` class: `{ Iconv: require('iconv').Iconv }`.
239
+ * Decode the character sets of text bodies with the native `iconv` module instead of libmime's
240
+ * decoder (`iconv-lite`). Pass the `Iconv` class: `{ Iconv: require('iconv').Iconv }`. Bodies
241
+ * declared as UTF-7 are never decoded from it either way, see the note on charsets in the README.
216
242
  */
217
243
  Iconv?: IconvConstructor;
218
244
  /**
219
245
  * The hash algorithm for attachment checksums, any name `crypto.createHash()` accepts. An algorithm
220
- * that is not available makes the parser emit an error.
246
+ * that is not available makes the parser emit an error. `false` skips hashing attachments
247
+ * altogether, and their `checksum` is then not set (`size` still is).
221
248
  * @default 'md5'
222
249
  */
223
- checksumAlgo?: string;
250
+ checksumAlgo?: string | false;
224
251
  /**
225
252
  * The longest the URLs `updateImageLinks()` writes into the HTML may be in total, in characters.
226
253
  * References past the limit are left as `cid:` links.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mailparser",
3
- "version": "3.10.0",
3
+ "version": "3.12.4",
4
4
  "description": "Parse e-mails",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -92,14 +92,12 @@
92
92
  ],
93
93
  "license": "MIT",
94
94
  "dependencies": {
95
- "@zone-eu/mailsplit": "5.4.27",
96
- "encoding-japanese": "2.4.0",
95
+ "@zone-eu/mailsplit": "5.4.29",
97
96
  "he": "1.2.0",
98
97
  "html-to-text": "10.0.1",
99
- "iconv-lite": "0.7.3",
100
- "libmime": "5.5.2",
98
+ "libmime": "5.6.3",
101
99
  "linkify-it": "5.0.2",
102
- "nodemailer": "10.1.7",
100
+ "nodemailer": "10.2.1",
103
101
  "punycode.js": "2.3.1",
104
102
  "tlds": "1.261.0"
105
103
  },
@@ -110,10 +108,11 @@
110
108
  "eslint-config-prettier": "10.1.8",
111
109
  "globals": "17.13.0",
112
110
  "iconv": "3.0.1",
111
+ "iconv-lite": "0.7.3",
113
112
  "prettier": "3.9.10",
114
113
  "tsx": "4.23.15",
115
114
  "typescript": "6.0.3",
116
- "typescript-eslint": "8.71.2"
115
+ "typescript-eslint": "8.72.0"
117
116
  },
118
117
  "repository": {
119
118
  "type": "git",
@@ -1,5 +1,28 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## [10.2.1](https://github.com/nodemailer/nodemailer/compare/v10.2.0...v10.2.1) (2026-10-22)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * drop the comments of a Content-Type or Content-Disposition value with the stripComments option ([b3052b7](https://github.com/nodemailer/nodemailer/commit/b3052b7fe2e49b51dd66d6c7814a072e885f1ac8))
9
+
10
+ ## [10.2.0](https://github.com/nodemailer/nodemailer/compare/v10.1.7...v10.2.0) (2026-10-21)
11
+
12
+
13
+ ### Features
14
+
15
+ * add info.serverMessageId from the final 250 reply ([108dddd](https://github.com/nodemailer/nodemailer/commit/108dddd88d9d71f708630b7aba7fa2a7d2f14076))
16
+ * add pendingCount() to the SMTP pool ([6cffb70](https://github.com/nodemailer/nodemailer/commit/6cffb7037269a6012d992c263a8927fcaf0563f2))
17
+ * make header value encoding public with MimeNode encodeHeaderValue() ([3b048c7](https://github.com/nodemailer/nodemailer/commit/3b048c7a0fb60f946985b09b3143b0680194e47b))
18
+ * mark SMTP errors as permanent or transient with err.permanent ([6a20e2a](https://github.com/nodemailer/nodemailer/commit/6a20e2ae109eeb5662235d72d1e5a2d38515997a))
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * label an ASCII message/rfc822 attachment 7bit instead of 8bit ([ad66fa1](https://github.com/nodemailer/nodemailer/commit/ad66fa1a048135867a629bcb7b6dc308ee06a945))
24
+ * report the recipients an LMTP server refuses after DATA in rejectedErrors ([dd64eb3](https://github.com/nodemailer/nodemailer/commit/dd64eb3171e3ab5629828084d3944a7859790968))
25
+
3
26
  ## [10.1.7](https://github.com/nodemailer/nodemailer/compare/v10.1.6...v10.1.7) (2026-10-18)
4
27
 
5
28
 
@@ -693,9 +693,7 @@ function _handleAddress(tokens, from, to) {
693
693
  // A run that is a quoted local part with its '@' and domain is an addr-spec of
694
694
  // its own, with the quoting put back on, and takes its turn like any other
695
695
  const local = data.quotedLocalAt[i];
696
- const quotedLocal = data.textWasQuoted[i] && local >= 0 && QUOTED_LOCAL_DOMAIN.test(data.text[i].slice(local))
697
- ? _quoteLocalPart(data.text[i])
698
- : '';
696
+ const quotedLocal = data.textWasQuoted[i] && local >= 0 && QUOTED_LOCAL_DOMAIN.test(data.text[i].slice(local)) ? _quoteLocalPart(data.text[i]) : '';
699
697
  if (quotedLocal || (!data.textWasQuoted[i] && _isAddrSpec(candidates[i]))) {
700
698
  data.address = [quotedLocal || candidates[i]];
701
699
  data.text.splice(i, 1);
@@ -1020,8 +1018,7 @@ class Tokenizer {
1020
1018
  return;
1021
1019
  }
1022
1020
  if (this.node < 0) {
1023
- this.node = this.list.push((this.operatorExpecting === '"' || this.operatorExpecting === ')' ? TOKEN_ESCAPES : 0) |
1024
- (this.escaped ? TOKEN_STARTS_ESCAPED : 0), i, i);
1021
+ this.node = this.list.push((this.operatorExpecting === '"' || this.operatorExpecting === ')' ? TOKEN_ESCAPES : 0) | (this.escaped ? TOKEN_STARTS_ESCAPED : 0), i, i);
1025
1022
  }
1026
1023
  if (chr === '\n') {
1027
1024
  // Convert newlines to spaces. Carriage return is ignored as \r and \n usually
@@ -25,9 +25,7 @@ class DKIMSigner {
25
25
  this.chunks = [];
26
26
  this.chunklen = 0;
27
27
  this.readPos = 0;
28
- this.cachePath = this.cacheDir
29
- ? node_path_1.default.join(this.cacheDir, 'message.' + Date.now() + '-' + node_crypto_1.default.randomBytes(14).toString('hex'))
30
- : false;
28
+ this.cachePath = this.cacheDir ? node_path_1.default.join(this.cacheDir, 'message.' + Date.now() + '-' + node_crypto_1.default.randomBytes(14).toString('hex')) : false;
31
29
  this.cache = false;
32
30
  this.headers = false;
33
31
  this.bodyHash = false;
@@ -68,6 +68,18 @@ export declare const isTransientError: (err: {
68
68
  code?: string | undefined;
69
69
  responseCode?: number | undefined;
70
70
  }) => boolean;
71
+ /**
72
+ * Tells whether an SMTP reply code is a permanent rejection, one that retrying the same
73
+ * message would get again. RFC 5321 makes every 5yz reply a permanent negative completion
74
+ * and every 4yz reply a transient one. 503 (bad sequence of commands) is the exception: it
75
+ * says the session was in the wrong state for the command rather than that the message was
76
+ * refused, so the same message can go through on a new connection. 2yz and 3yz replies are
77
+ * only an error when the client expected something else, and are not permanent either.
78
+ *
79
+ * @param responseCode SMTP reply code
80
+ * @returns true for a permanent rejection
81
+ */
82
+ export declare const isPermanentResponseCode: (responseCode: number) => boolean;
71
83
  /**
72
84
  * Code Node.js sets on an error when its permission model (`--permission`) denies an
73
85
  * operation. It is not replaced with the Nodemailer code of the failing step (`ESOCKET`,
@@ -89,6 +101,13 @@ export interface NodemailerError extends NodeJS.ErrnoException {
89
101
  response?: string | undefined;
90
102
  /** Numeric SMTP response code */
91
103
  responseCode?: number | undefined;
104
+ /**
105
+ * Set together with responseCode, from the reply class: true when the server rejected the
106
+ * message for good (5xx except 503), false when the same message may be accepted on a later
107
+ * attempt (4xx, 503, or an unexpected 2xx or 3xx). Not set on an error that carries no SMTP
108
+ * reply, such as a connection failure or missing credentials, see isPermanentResponseCode
109
+ */
110
+ permanent?: boolean | undefined;
92
111
  /** URL of the resource that could not be fetched */
93
112
  sourceUrl?: string | undefined;
94
113
  /** Recipient address the error applies to */
@@ -10,7 +10,7 @@
10
10
  * err.code = errors.ECONNECTION;
11
11
  */
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.ERR_ACCESS_DENIED = exports.isTransientError = exports.EFETCH = exports.EURLACCESS = exports.EFILEACCESS = exports.EPROXY = exports.ECONFIG = exports.ESES = exports.ESENDMAIL = exports.EMAXRECIPIENTS = exports.EMAXLIMIT = exports.EOAUTH2 = exports.ENOAUTH = exports.EAUTH = exports.ESTREAM = exports.EMESSAGE = exports.EENVELOPE = exports.EPROTOCOL = exports.EREQUIRETLS = exports.ETLS = exports.EDNS = exports.ESOCKET = exports.ETIMEDOUT = exports.ECONNECTION = exports.ERROR_CODES = void 0;
13
+ exports.ERR_ACCESS_DENIED = exports.isPermanentResponseCode = exports.isTransientError = exports.EFETCH = exports.EURLACCESS = exports.EFILEACCESS = exports.EPROXY = exports.ECONFIG = exports.ESES = exports.ESENDMAIL = exports.EMAXRECIPIENTS = exports.EMAXLIMIT = exports.EOAUTH2 = exports.ENOAUTH = exports.EAUTH = exports.ESTREAM = exports.EMESSAGE = exports.EENVELOPE = exports.EPROTOCOL = exports.EREQUIRETLS = exports.ETLS = exports.EDNS = exports.ESOCKET = exports.ETIMEDOUT = exports.ECONNECTION = exports.ERROR_CODES = void 0;
14
14
  /**
15
15
  * Error code descriptions for documentation and debugging
16
16
  */
@@ -73,6 +73,19 @@ exports.EFETCH = 'EFETCH';
73
73
  */
74
74
  const isTransientError = (err) => err.responseCode === 421 || err.code === exports.ECONNECTION || err.code === exports.ESOCKET || err.code === exports.ETIMEDOUT;
75
75
  exports.isTransientError = isTransientError;
76
+ /**
77
+ * Tells whether an SMTP reply code is a permanent rejection, one that retrying the same
78
+ * message would get again. RFC 5321 makes every 5yz reply a permanent negative completion
79
+ * and every 4yz reply a transient one. 503 (bad sequence of commands) is the exception: it
80
+ * says the session was in the wrong state for the command rather than that the message was
81
+ * refused, so the same message can go through on a new connection. 2yz and 3yz replies are
82
+ * only an error when the client expected something else, and are not permanent either.
83
+ *
84
+ * @param responseCode SMTP reply code
85
+ * @returns true for a permanent rejection
86
+ */
87
+ const isPermanentResponseCode = (responseCode) => responseCode >= 500 && responseCode < 600 && responseCode !== 503;
88
+ exports.isPermanentResponseCode = isPermanentResponseCode;
76
89
  /**
77
90
  * Code Node.js sets on an error when its permission model (`--permission`) denies an
78
91
  * operation. It is not replaced with the Nodemailer code of the failing step (`ESOCKET`,
@@ -198,16 +198,14 @@ class Cookies {
198
198
  // check if hostname matches
199
199
  // .foo.com also matches subdomains, foo.com does not
200
200
  if (urlparts.hostname !== cookie.domain &&
201
- (cookie.domain.charAt(0) !== '.' ||
202
- ('.' + urlparts.hostname).substr(-cookie.domain.length) !== cookie.domain)) {
201
+ (cookie.domain.charAt(0) !== '.' || ('.' + urlparts.hostname).substr(-cookie.domain.length) !== cookie.domain)) {
203
202
  return false;
204
203
  }
205
204
  // check if the request path path-matches the cookie path (RFC 6265 section 5.1.4):
206
205
  // identical paths match, otherwise the cookie path must be a directory prefix
207
206
  const pathname = urlparts.pathname || '/';
208
207
  const cookiePath = cookie.path;
209
- const pathMatches = pathname === cookiePath ||
210
- (pathname.startsWith(cookiePath) && (cookiePath.endsWith('/') || pathname.charAt(cookiePath.length) === '/'));
208
+ const pathMatches = pathname === cookiePath || (pathname.startsWith(cookiePath) && (cookiePath.endsWith('/') || pathname.charAt(cookiePath.length) === '/'));
211
209
  if (!pathMatches) {
212
210
  return false;
213
211
  }
@@ -262,11 +262,7 @@ function nmfetch(url, options) {
262
262
  }
263
263
  });
264
264
  }
265
- if (parsed.protocol === 'https:' &&
266
- parsed.hostname &&
267
- parsed.hostname !== reqOptions.host &&
268
- !node_net_1.default.isIP(parsed.hostname) &&
269
- !reqOptions.servername) {
265
+ if (parsed.protocol === 'https:' && parsed.hostname && parsed.hostname !== reqOptions.host && !node_net_1.default.isIP(parsed.hostname) && !reqOptions.servername) {
270
266
  reqOptions.servername = parsed.hostname;
271
267
  }
272
268
  try {
@@ -369,9 +365,7 @@ function nmfetch(url, options) {
369
365
  // downgrade would also put them on the wire in cleartext). Strip sensitive
370
366
  // request headers so an attacker who controls the redirect target cannot
371
367
  // harvest them.
372
- const crossOrigin = redirectParsed.protocol !== parsed.protocol ||
373
- redirectParsed.hostname !== parsed.hostname ||
374
- fetchPort(redirectParsed) !== fetchPort(parsed);
368
+ const crossOrigin = redirectParsed.protocol !== parsed.protocol || redirectParsed.hostname !== parsed.hostname || fetchPort(redirectParsed) !== fetchPort(parsed);
375
369
  if (options.headers && crossOrigin) {
376
370
  const sensitive = ['authorization', 'cookie', 'proxy-authorization'];
377
371
  Object.keys(options.headers).forEach(key => {