@mavogel/awscdk-rootmail 0.1.27 → 0.1.29

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 +9 -9
  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 +8 -8
  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
@@ -45,6 +45,26 @@ const libcharset = __importStar(require("./charset.js"));
45
45
  const mimetypes = __importStar(require("./mimetypes.js"));
46
46
  const STAGE_KEY = 0x1001;
47
47
  const STAGE_VALUE = 0x1002;
48
+ /**
49
+ * Offset of the ")" that closes the rfc822 comment opening at `start`, or -1 when the
50
+ * comment never closes. Comments nest, and a backslash escapes the character after it
51
+ */
52
+ const findCommentEnd = (str, start) => {
53
+ let depth = 0;
54
+ for (let i = start, len = str.length; i < len; i++) {
55
+ let chr = str.charAt(i);
56
+ if (chr === '\\') {
57
+ i++;
58
+ }
59
+ else if (chr === '(') {
60
+ depth++;
61
+ }
62
+ else if (chr === ')' && --depth === 0) {
63
+ return i;
64
+ }
65
+ }
66
+ return -1;
67
+ };
48
68
  // Sets an own property on a plain object. A regular assignment with a
49
69
  // '__proto__' key would silently modify the object's prototype instead of
50
70
  // creating an own property, so it needs special handling.
@@ -61,6 +81,9 @@ const setOwnProperty = (obj, key, value) => {
61
81
  obj[key] = value;
62
82
  }
63
83
  };
84
+ // Header names decodeHeaders() leaves out with the skipPrototypeKeys option. Keys are lowercased,
85
+ // so of these only '__proto__' and 'constructor' can ever match
86
+ const PROTOTYPE_KEYS = new Set(Object.getOwnPropertyNames(Object.prototype));
64
87
  // Whitespace that separates the tokens of a header value. Kept as a char comparison,
65
88
  // a regex test would be run for every single char of every parsed header
66
89
  const isWSP = (chr) => chr === ' ' || chr === '\t' || chr === '\r' || chr === '\n' || chr === '\f' || chr === '\v';
@@ -298,6 +321,18 @@ class Libmime {
298
321
  normalizeCharset(charset) {
299
322
  return libcharset.normalizeCharset(charset);
300
323
  }
324
+ /**
325
+ * Creates a Transform stream that decodes a body in the given charset into UTF-8 Buffers,
326
+ * resolving the charset name and handling the Japanese charsets the same way header
327
+ * decoding does. An unknown or refused charset (UTF-7, base64, hex) is decoded as UTF-8,
328
+ * and the stream says so with `supported` set to false.
329
+ *
330
+ * @param {String} [charset='UTF-8'] Charset the input is encoded in
331
+ * @return {DecodeStream} Transform stream with `charset` and `supported` properties
332
+ */
333
+ createDecodeStream(charset) {
334
+ return libcharset.createDecodeStream(charset);
335
+ }
301
336
  /**
302
337
  * Decodes the inner payload of a single mime encoded-word into a unicode string.
303
338
  * Expects the three components of `=?charset?encoding?text?=` already separated.
@@ -456,9 +491,11 @@ class Libmime {
456
491
  * header might have its own rules (eg. formatted email addresses and such)
457
492
  *
458
493
  * @param {String} headers Headers string
494
+ * @param {Object} [options] Options
495
+ * @param {Boolean} [options.skipPrototypeKeys] Leave out headers named like an Object.prototype member, eg. `__proto__` and `constructor`
459
496
  * @return {Object} An object of headers, where header keys are object keys and every value is an Array of the values for that key
460
497
  */
461
- decodeHeaders(headers) {
498
+ decodeHeaders(headers, options) {
462
499
  let lines = headers.split(/\r?\n|\r/), headersObj = {}, header, i, len;
463
500
  // Empty lines in front of the block are not part of any header, a caller might
464
501
  // include the line break that separates a mime part boundary from its headers
@@ -484,6 +521,9 @@ class Libmime {
484
521
  lines = unfolded;
485
522
  for (i = 0, len = lines.length; i < len; i++) {
486
523
  header = this.decodeHeader(lines[i]);
524
+ if (options?.skipPrototypeKeys && PROTOTYPE_KEYS.has(header.key)) {
525
+ continue;
526
+ }
487
527
  // own properties only, an attacker controlled key such as '__proto__' or 'toString' would
488
528
  // otherwise resolve to an inherited Object.prototype member
489
529
  if (!Object.hasOwn(headersObj, header.key)) {
@@ -552,9 +592,12 @@ class Libmime {
552
592
  * decoded, so a caller that runs decodeWords() over the params has to leave them out
553
593
  *
554
594
  * @param {String} str Header value
595
+ * @param {Object} [options] Options, see ParseHeaderValueOptions
555
596
  * @return {Object} Header value as a parsed structure
556
597
  */
557
- parseHeaderValue(str) {
598
+ parseHeaderValue(str, options) {
599
+ // Cleared again by an unclosed "(", see the loop
600
+ let stripComments = !!options?.stripComments;
558
601
  let response = {
559
602
  value: '',
560
603
  params: {}
@@ -574,10 +617,23 @@ class Libmime {
574
617
  let valueEnd = 0;
575
618
  let stage = STAGE_VALUE;
576
619
  let quote = false;
620
+ // A quoted string has closed and the ";" that ends the parameter has not come yet
621
+ let quoteClosed = false;
577
622
  let escaped = false;
578
623
  let chr;
624
+ // Whether a "(" would continue the unquoted token of a parameter value, which it does
625
+ // right behind the "=" and behind a character of the token: `filename=Invoice(1).pdf`
626
+ // names the file and `boundary=(c)b` the boundary `(c)b`, as they do for Python and
627
+ // postal-mime. Anywhere else, in the type, in a parameter name, behind whitespace or
628
+ // behind a quoted string, a "(" opens a comment
629
+ let tokenOpen = false;
579
630
  // Stores the collected value, either as the default value, as the value for the
580
- // current key, or as a key that has no value at all. Runs once per parameter
631
+ // current key, or as a key that has no value at all. Runs once per parameter.
632
+ //
633
+ // A parameter given twice keeps its first value, as it does for Python and for
634
+ // postal-mime. Letting the last one win read `boundary=b; boundary=c` as a multipart
635
+ // of the parts under `--c`, which the other readers leave in the epilogue of the
636
+ // parts under `--b`
581
637
  let commit = () => {
582
638
  let collected = value.substring(0, valueEnd);
583
639
  value = '';
@@ -585,21 +641,42 @@ class Libmime {
585
641
  if (stage === STAGE_KEY) {
586
642
  // key without a value, see emptykey:
587
643
  // Header-Key: somevalue; key=value; emptykey
588
- if (collected) {
589
- setOwnProperty(response.params, collected.toLowerCase(), '');
644
+ let name = collected.toLowerCase();
645
+ if (name && !Object.hasOwn(response.params, name)) {
646
+ setOwnProperty(response.params, name, '');
590
647
  }
591
648
  }
592
649
  else if (key === false) {
593
650
  // default value
594
651
  response.value = collected;
595
652
  }
596
- else {
653
+ else if (!Object.hasOwn(response.params, key)) {
597
654
  // subkey value
598
655
  setOwnProperty(response.params, key, collected);
599
656
  }
600
657
  };
601
658
  for (let i = 0, len = str.length; i < len; i++) {
602
659
  chr = str.charAt(i);
660
+ // With stripComments, an rfc822 comment stands anywhere outside a quoted string
661
+ // but right behind a token it would continue. Read as content, `boundary=b (comment)`
662
+ // named a boundary no delimiter line matches, so the parts that Python and
663
+ // postal-mime find stayed hidden from a splitter built on this
664
+ if (stripComments && chr === '(' && !quote && !escaped && !tokenOpen) {
665
+ let end = findCommentEnd(str, i);
666
+ if (end < 0 && str.indexOf(';', i) < 0) {
667
+ // A comment that never closes runs to the end of the value, as it does for
668
+ // Python. One that a parameter follows is content instead, with everything
669
+ // after it, so that the parameter survives, as postal-mime keeps it
670
+ end = len - 1;
671
+ }
672
+ if (end >= 0) {
673
+ i = end;
674
+ continue;
675
+ }
676
+ // Looking for a closing ")" again would scan a run of unclosed "(" to its end
677
+ // once for each of them
678
+ stripComments = false;
679
+ }
603
680
  switch (stage) {
604
681
  case STAGE_KEY:
605
682
  if (chr === '=') {
@@ -607,6 +684,7 @@ class Libmime {
607
684
  value = '';
608
685
  valueEnd = 0;
609
686
  stage = STAGE_VALUE;
687
+ tokenOpen = true;
610
688
  }
611
689
  else if (chr === ';') {
612
690
  // key without a value, eg. the "inline" in "attachment; inline; filename=a.txt"
@@ -623,9 +701,19 @@ class Libmime {
623
701
  }
624
702
  break;
625
703
  case STAGE_VALUE:
704
+ if (quoteClosed && chr !== ';') {
705
+ // Nothing behind a closed quoted string reaches the value: a parameter
706
+ // value is a token or a quoted string, not both (rfc2045 section 5.1),
707
+ // so what follows one is junk up to the ";" that ends the parameter, as
708
+ // Python and postal-mime read it. Read on, `boundary="b"c` named a
709
+ // boundary bc that no delimiter line of the message matches
710
+ escaped = false;
711
+ break;
712
+ }
626
713
  if (escaped) {
627
714
  value += chr;
628
715
  valueEnd = value.length;
716
+ tokenOpen = key !== false && !quote;
629
717
  }
630
718
  else if (chr === '\\') {
631
719
  escaped = true;
@@ -633,20 +721,26 @@ class Libmime {
633
721
  }
634
722
  else if (chr === '"') {
635
723
  // every char between the quotes is significant, including whitespace
724
+ quoteClosed = quote;
636
725
  quote = !quote;
726
+ tokenOpen = false;
637
727
  }
638
728
  else if (!quote && chr === ';') {
639
729
  commit();
640
730
  stage = STAGE_KEY;
731
+ tokenOpen = false;
732
+ quoteClosed = false;
641
733
  }
642
734
  else if (!quote && isWSP(chr)) {
643
735
  if (value.length) {
644
736
  value += chr;
645
737
  }
738
+ tokenOpen = false;
646
739
  }
647
740
  else {
648
741
  value += chr;
649
742
  valueEnd = value.length;
743
+ tokenOpen = key !== false && !quote;
650
744
  }
651
745
  escaped = false;
652
746
  break;
@@ -15,6 +15,34 @@ export interface HeaderLine {
15
15
  * Parsed header block, every key holds the values of all the header lines with that key
16
16
  */
17
17
  export type HeaderObject = Record<string, string[]>;
18
+ /**
19
+ * Options of decodeHeaders()
20
+ */
21
+ export interface DecodeHeadersOptions {
22
+ /**
23
+ * Leave out the headers whose name is also the name of an Object.prototype member, such as
24
+ * `__proto__` and `constructor`. By default they are kept as own properties of the result,
25
+ * where a lookup with the `in` operator or a merge into another object can not tell them
26
+ * apart from the inherited members
27
+ */
28
+ skipPrototypeKeys?: boolean | undefined;
29
+ }
30
+ /**
31
+ * Options of parseHeaderValue()
32
+ */
33
+ export interface ParseHeaderValueOptions {
34
+ /**
35
+ * Drop the RFC 822 comments outside quoted strings. The MIME headers (Content-Type,
36
+ * Content-Disposition) allow them and they are not part of the value, so `boundary=b (comment)`
37
+ * names the boundary `b`. A parenthesis right behind the `=` of a parameter or behind a token
38
+ * of its value continues the token, as it does for Python and postal-mime: `filename=Invoice(1).pdf`
39
+ * keeps its name and `boundary=(c)b` names the boundary `(c)b`. A comment that never closes runs to the end
40
+ * of the value, unless a parameter follows it, in which case it is content and the parameter
41
+ * survives. By default comments are kept as they are, since a header such as
42
+ * Authentication-Results carries information in them
43
+ */
44
+ stripComments?: boolean | undefined;
45
+ }
18
46
  /**
19
47
  * Header value with key=value parameters as returned by parseHeaderValue()
20
48
  */
@@ -1,4 +1,6 @@
1
1
  import { Buffer } from 'node:buffer';
2
+ import { Transform } from 'node:stream';
3
+ import type { TransformCallback } from 'node:stream';
2
4
  /**
3
5
  * Encodes an unicode string into an Buffer object as UTF-8
4
6
  *
@@ -34,10 +36,44 @@ export declare function convert(data: string | Buffer, fromCharset?: string): Bu
34
36
  * @return Canonicalized charset name
35
37
  */
36
38
  export declare function normalizeCharset(charset?: unknown): string;
39
+ /**
40
+ * Transform stream that decodes bytes in a charset into UTF-8 Buffers, see createDecodeStream()
41
+ */
42
+ export declare class DecodeStream extends Transform {
43
+ /** Canonical name of the charset the input was declared in, what normalizeCharset() gives */
44
+ readonly charset: string;
45
+ /**
46
+ * false when the declared charset is unknown, or one that is refused (UTF-7, base64, hex). The
47
+ * input is then decoded as UTF-8, the same fallback decode() applies, so a caller that must
48
+ * not relabel such bytes as UTF-8 checks this before piping anything through
49
+ */
50
+ readonly supported: boolean;
51
+ private decoder;
52
+ private chunks;
53
+ private highSurrogate;
54
+ constructor(charset?: unknown);
55
+ _transform(chunk: Buffer, _encoding: BufferEncoding, done: TransformCallback): void;
56
+ _flush(done: TransformCallback): void;
57
+ private pushText;
58
+ }
59
+ /**
60
+ * Creates a Transform stream that decodes bytes in the given charset into UTF-8 Buffers. The
61
+ * charset name is resolved with normalizeCharset(), and the output is what decode() gives for
62
+ * the whole input, however it is split into chunks: the Japanese charsets iconv-lite does not
63
+ * support are decoded with encoding-japanese (collected until the input ends, as that decoder
64
+ * is not incremental), and an unknown or refused charset (UTF-7, base64, hex) is decoded as
65
+ * UTF-8 with `supported` set to false on the stream.
66
+ *
67
+ * @param [charset='UTF-8'] Charset the input is encoded in
68
+ * @return Transform stream with `charset` and `supported` properties
69
+ */
70
+ export declare function createDecodeStream(charset?: unknown): DecodeStream;
37
71
  declare const _default: {
38
72
  encode: typeof encode;
39
73
  decode: typeof decode;
40
74
  convert: typeof convert;
41
75
  normalizeCharset: typeof normalizeCharset;
76
+ createDecodeStream: typeof createDecodeStream;
77
+ DecodeStream: typeof DecodeStream;
42
78
  };
43
79
  export default _default;
@@ -1,4 +1,6 @@
1
1
  import { Buffer } from 'node:buffer';
2
+ import { Transform } from 'node:stream';
3
+ import { StringDecoder } from 'node:string_decoder';
2
4
  import iconv from 'iconv-lite';
3
5
  import encodingJapanese from 'encoding-japanese';
4
6
  import charsets from './charsets.js';
@@ -22,6 +24,8 @@ const isUnsupportedCodec = (name) => {
22
24
  return false;
23
25
  }
24
26
  };
27
+ // Charsets iconv-lite does not support, decoded with encoding-japanese instead
28
+ const JAPANESE_CHARSET = /^jis|^iso-?2022-?jp|^EUCJP/i;
25
29
  // Own entries only, so that a name such as '__proto__' or 'constructor' does not resolve to an
26
30
  // inherited Object.prototype member
27
31
  const CHARSET_ALIASES = new Map(Object.entries(charsets));
@@ -54,7 +58,7 @@ export function decode(buf, fromCharset) {
54
58
  return buf.toString();
55
59
  }
56
60
  try {
57
- if (/^jis|^iso-?2022-?jp|^EUCJP/i.test(fromCharset)) {
61
+ if (JAPANESE_CHARSET.test(fromCharset)) {
58
62
  if (typeof buf === 'string') {
59
63
  buf = Buffer.from(buf);
60
64
  }
@@ -122,4 +126,160 @@ export function normalizeCharset(charset) {
122
126
  // updated pass
123
127
  return CHARSET_ALIASES.get(name) || name.toUpperCase();
124
128
  }
125
- export default { encode, decode, convert, normalizeCharset };
129
+ // The decoder all of iconv-lite's double byte codecs share
130
+ const DBCS_DECODER = iconv.getCodec('gbk').decoder;
131
+ // The UTF-16 and UTF-32 decoders without a byte order in the name read it from a BOM or guess it
132
+ // from the first 100 characters, but a stream guesses as soon as it has 16 or 32 bytes. Holding
133
+ // back this many bytes makes the stream guess from the same characters as decode()
134
+ const BYTE_ORDER_GUESSING = new Map([
135
+ [iconv.getCodec('utf-16'), 200],
136
+ [iconv.getCodec('utf-32'), 400]
137
+ ]);
138
+ // iconv-lite 0.7 sizes what a DBCS decoder's write() returns by the length of the chunk, so when
139
+ // the chunk finishes or rejects a character the previous one started, the output can outgrow
140
+ // that size and its end is cut off: an astral GB18030 character split in two loses its low
141
+ // surrogate, a GB18030 lead byte followed by a space loses everything after it. The input is
142
+ // therefore cut where no character can be unfinished, right after a byte below 0x30. No lead,
143
+ // trail or GB18030 digit byte is that low, so such a byte always decodes as itself and the
144
+ // parse starts afresh after it. Every cut is decoded as a whole, with nothing carried over, and
145
+ // the bytes after the last such byte wait for the next chunk
146
+ class LowByteCutDecoder {
147
+ constructor(charset) {
148
+ this.pending = [];
149
+ this.charset = charset;
150
+ }
151
+ write(chunk) {
152
+ let cut = chunk.length;
153
+ while (cut > 0 && chunk[cut - 1] >= 0x30) {
154
+ cut--;
155
+ }
156
+ if (!cut) {
157
+ // copied, the caller may reuse the chunk it wrote
158
+ this.pending.push(Buffer.from(chunk));
159
+ return '';
160
+ }
161
+ let text = iconv.decode(Buffer.concat([...this.pending, chunk.subarray(0, cut)]), this.charset);
162
+ this.pending = [Buffer.from(chunk.subarray(cut))];
163
+ return text;
164
+ }
165
+ end() {
166
+ let rest = Buffer.concat(this.pending);
167
+ this.pending = [];
168
+ return rest.length ? iconv.decode(rest, this.charset) : undefined;
169
+ }
170
+ }
171
+ // Passes the input on once its first `size` bytes are in, see BYTE_ORDER_GUESSING
172
+ class HeadBufferedDecoder {
173
+ constructor(decoder, size) {
174
+ this.head = [];
175
+ this.headLength = 0;
176
+ this.decoder = decoder;
177
+ this.size = size;
178
+ }
179
+ write(chunk) {
180
+ if (!this.head) {
181
+ return this.decoder.write(chunk);
182
+ }
183
+ // copied, the caller may reuse the chunk it wrote
184
+ this.head.push(Buffer.from(chunk));
185
+ this.headLength += chunk.length;
186
+ return this.headLength < this.size ? '' : this.releaseHead();
187
+ }
188
+ end() {
189
+ return (this.head ? this.releaseHead() : '') + (this.decoder.end() || '');
190
+ }
191
+ releaseHead() {
192
+ let head = Buffer.concat(this.head || []);
193
+ this.head = null;
194
+ return this.decoder.write(head);
195
+ }
196
+ }
197
+ // Picks the decoder for a charset iconv-lite knows
198
+ function createChunkDecoder(charset) {
199
+ let codec = iconv.getCodec(charset);
200
+ if (codec.decoder === DBCS_DECODER) {
201
+ return new LowByteCutDecoder(charset);
202
+ }
203
+ let headSize = BYTE_ORDER_GUESSING.get(codec);
204
+ return headSize ? new HeadBufferedDecoder(iconv.getDecoder(charset), headSize) : iconv.getDecoder(charset);
205
+ }
206
+ /**
207
+ * Transform stream that decodes bytes in a charset into UTF-8 Buffers, see createDecodeStream()
208
+ */
209
+ export class DecodeStream extends Transform {
210
+ constructor(charset) {
211
+ super();
212
+ // null for the Japanese charsets, which are collected and decoded when the input ends
213
+ this.decoder = null;
214
+ this.chunks = [];
215
+ this.highSurrogate = '';
216
+ this.charset = normalizeCharset(charset || 'UTF-8');
217
+ if (UTF8_COMPATIBLE.test(this.charset)) {
218
+ this.supported = true;
219
+ this.decoder = new StringDecoder('utf-8');
220
+ }
221
+ else if (isUnsupportedCodec(this.charset) || !(JAPANESE_CHARSET.test(this.charset) || iconv.encodingExists(this.charset))) {
222
+ this.supported = false;
223
+ this.decoder = new StringDecoder('utf-8');
224
+ }
225
+ else {
226
+ this.supported = true;
227
+ // encoding-japanese has no incremental decoder and ISO-2022-JP is stateful, so the
228
+ // Japanese charsets are left without one
229
+ this.decoder = JAPANESE_CHARSET.test(this.charset) ? null : createChunkDecoder(this.charset);
230
+ }
231
+ }
232
+ _transform(chunk, _encoding, done) {
233
+ if (!this.decoder) {
234
+ this.chunks.push(chunk);
235
+ return done();
236
+ }
237
+ try {
238
+ this.pushText(this.decoder.write(chunk), false);
239
+ }
240
+ catch (err) {
241
+ return done(err);
242
+ }
243
+ done();
244
+ }
245
+ _flush(done) {
246
+ try {
247
+ this.pushText(this.decoder ? this.decoder.end() : decode(Buffer.concat(this.chunks), this.charset), true);
248
+ }
249
+ catch (err) {
250
+ return done(err);
251
+ }
252
+ this.chunks = [];
253
+ done();
254
+ }
255
+ // A decoder may return the two halves of a surrogate pair from separate writes, and a lone
256
+ // half would turn into U+FFFD on its way to UTF-8, so a trailing high surrogate waits for
257
+ // the rest of its character
258
+ pushText(text, final) {
259
+ text = this.highSurrogate + (text || '');
260
+ this.highSurrogate = '';
261
+ let last = text.charCodeAt(text.length - 1);
262
+ if (!final && last >= 0xd800 && last <= 0xdbff) {
263
+ this.highSurrogate = text.slice(-1);
264
+ text = text.slice(0, -1);
265
+ }
266
+ if (text) {
267
+ this.push(Buffer.from(text, 'utf-8'));
268
+ }
269
+ }
270
+ }
271
+ /**
272
+ * Creates a Transform stream that decodes bytes in the given charset into UTF-8 Buffers. The
273
+ * charset name is resolved with normalizeCharset(), and the output is what decode() gives for
274
+ * the whole input, however it is split into chunks: the Japanese charsets iconv-lite does not
275
+ * support are decoded with encoding-japanese (collected until the input ends, as that decoder
276
+ * is not incremental), and an unknown or refused charset (UTF-7, base64, hex) is decoded as
277
+ * UTF-8 with `supported` set to false on the stream.
278
+ *
279
+ * @param [charset='UTF-8'] Charset the input is encoded in
280
+ * @return Transform stream with `charset` and `supported` properties
281
+ */
282
+ export function createDecodeStream(charset) {
283
+ return new DecodeStream(charset);
284
+ }
285
+ export default { encode, decode, convert, normalizeCharset, createDecodeStream, DecodeStream };
@@ -111,6 +111,10 @@ const charsets = {
111
111
  mac: 'macintosh',
112
112
  macintosh: 'macintosh',
113
113
  'x-mac-roman': 'macintosh',
114
+ // the x-mac-* labels mail clients send, under the names iconv-lite knows them by
115
+ 'x-mac-ce': 'maccenteuro',
116
+ 'x-mac-cyrillic': 'maccyrillic',
117
+ 'x-mac-ukrainian': 'macukraine',
114
118
  'dos-874': 'windows-874',
115
119
  'iso-8859-11': 'windows-874',
116
120
  'iso8859-11': 'windows-874',
@@ -178,7 +182,7 @@ const charsets = {
178
182
  'x-gbk': 'GBK',
179
183
  gb18030: 'gb18030',
180
184
  big5: 'Big5',
181
- 'big5-hkscs': 'Big5',
185
+ 'big5-hkscs': 'Big5-HKSCS',
182
186
  'cn-big5': 'Big5',
183
187
  csbig5: 'Big5',
184
188
  'x-x-big5': 'Big5',
@@ -204,7 +208,9 @@ const charsets = {
204
208
  ksc_5601: 'EUC-KR',
205
209
  'windows-949': 'EUC-KR',
206
210
  'utf-16be': 'UTF-16BE',
207
- 'utf-16': 'UTF-16LE',
211
+ // iconv-lite's UTF-16 decoder reads the byte order from a BOM and guesses it from the
212
+ // content without one, falling back to little endian
213
+ 'utf-16': 'UTF-16',
208
214
  'utf-16le': 'UTF-16LE'
209
215
  };
210
216
  export default charsets;
@@ -1,5 +1,6 @@
1
1
  import { Buffer } from 'node:buffer';
2
- import type { HeaderLine, HeaderObject, HeaderParam, LibmimeConfig, ParsedHeaderValue, StructuredHeaderValue } from './types.js';
2
+ import * as libcharset from './charset.js';
3
+ import type { DecodeHeadersOptions, HeaderLine, HeaderObject, HeaderParam, LibmimeConfig, ParsedHeaderValue, ParseHeaderValueOptions, StructuredHeaderValue } from './types.js';
3
4
  import type * as types from './types.js';
4
5
  export type * from './types.js';
5
6
  export declare class Libmime {
@@ -70,6 +71,16 @@ export declare class Libmime {
70
71
  * @return {String} Canonical charset name
71
72
  */
72
73
  normalizeCharset(charset?: unknown): string;
74
+ /**
75
+ * Creates a Transform stream that decodes a body in the given charset into UTF-8 Buffers,
76
+ * resolving the charset name and handling the Japanese charsets the same way header
77
+ * decoding does. An unknown or refused charset (UTF-7, base64, hex) is decoded as UTF-8,
78
+ * and the stream says so with `supported` set to false.
79
+ *
80
+ * @param {String} [charset='UTF-8'] Charset the input is encoded in
81
+ * @return {DecodeStream} Transform stream with `charset` and `supported` properties
82
+ */
83
+ createDecodeStream(charset?: unknown): libcharset.DecodeStream;
73
84
  /**
74
85
  * Decodes the inner payload of a single mime encoded-word into a unicode string.
75
86
  * Expects the three components of `=?charset?encoding?text?=` already separated.
@@ -112,9 +123,11 @@ export declare class Libmime {
112
123
  * header might have its own rules (eg. formatted email addresses and such)
113
124
  *
114
125
  * @param {String} headers Headers string
126
+ * @param {Object} [options] Options
127
+ * @param {Boolean} [options.skipPrototypeKeys] Leave out headers named like an Object.prototype member, eg. `__proto__` and `constructor`
115
128
  * @return {Object} An object of headers, where header keys are object keys and every value is an Array of the values for that key
116
129
  */
117
- decodeHeaders(headers: string): HeaderObject;
130
+ decodeHeaders(headers: string, options?: DecodeHeadersOptions): HeaderObject;
118
131
  /**
119
132
  * Joins parsed header value together as 'value; param1=value1; param2=value2'
120
133
  * PS: We are following RFC 822 for the list of special characters that we need to keep in quotes.
@@ -140,9 +153,10 @@ export declare class Libmime {
140
153
  * decoded, so a caller that runs decodeWords() over the params has to leave them out
141
154
  *
142
155
  * @param {String} str Header value
156
+ * @param {Object} [options] Options, see ParseHeaderValueOptions
143
157
  * @return {Object} Header value as a parsed structure
144
158
  */
145
- parseHeaderValue(str: string): ParsedHeaderValue;
159
+ parseHeaderValue(str: string, options?: ParseHeaderValueOptions): ParsedHeaderValue;
146
160
  /**
147
161
  * Encodes a string or an Buffer to an UTF-8 Parameter Value Continuation encoding (rfc2231)
148
162
  * Useful for splitting long parameter values.
@@ -207,12 +221,15 @@ declare const libmime: Libmime & {
207
221
  type LibmimeInstance = Libmime;
208
222
  declare namespace libmime {
209
223
  type Libmime = LibmimeInstance;
224
+ type DecodeStream = libcharset.DecodeStream;
225
+ type DecodeHeadersOptions = types.DecodeHeadersOptions;
210
226
  type HeaderLine = types.HeaderLine;
211
227
  type HeaderObject = types.HeaderObject;
212
228
  type HeaderParam = types.HeaderParam;
213
229
  type LibmimeConfig = types.LibmimeConfig;
214
230
  type MimeWordEncoding = types.MimeWordEncoding;
215
231
  type ParsedHeaderValue = types.ParsedHeaderValue;
232
+ type ParseHeaderValueOptions = types.ParseHeaderValueOptions;
216
233
  type StructuredHeader = types.StructuredHeader;
217
234
  type StructuredHeaderValue = types.StructuredHeaderValue;
218
235
  }