@nocobase/plugin-workflow-mailer 2.2.20 → 2.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/dist/externalVersion.js +6 -6
  2. package/dist/node_modules/joi/package.json +1 -1
  3. package/dist/node_modules/nodemailer/dist/cjs/addressparser/index.d.ts +59 -0
  4. package/dist/node_modules/nodemailer/dist/cjs/addressparser/index.js +790 -0
  5. package/dist/node_modules/nodemailer/dist/cjs/base64/index.d.ts +36 -0
  6. package/dist/node_modules/nodemailer/dist/cjs/base64/index.js +119 -0
  7. package/dist/node_modules/nodemailer/dist/cjs/dkim/index.d.ts +52 -0
  8. package/dist/node_modules/nodemailer/dist/cjs/dkim/index.js +228 -0
  9. package/dist/node_modules/nodemailer/dist/cjs/dkim/message-parser.d.ts +48 -0
  10. package/dist/node_modules/nodemailer/dist/cjs/dkim/message-parser.js +180 -0
  11. package/dist/node_modules/nodemailer/dist/cjs/dkim/relaxed-body.d.ts +36 -0
  12. package/dist/node_modules/nodemailer/dist/cjs/dkim/relaxed-body.js +148 -0
  13. package/dist/node_modules/nodemailer/dist/cjs/dkim/sign.d.ts +52 -0
  14. package/dist/node_modules/nodemailer/dist/cjs/dkim/sign.js +165 -0
  15. package/dist/node_modules/nodemailer/dist/cjs/errors.d.ts +96 -0
  16. package/dist/node_modules/nodemailer/dist/cjs/errors.js +70 -0
  17. package/dist/node_modules/nodemailer/dist/cjs/fetch/cookies.d.ts +107 -0
  18. package/dist/node_modules/nodemailer/dist/cjs/fetch/cookies.js +282 -0
  19. package/dist/node_modules/nodemailer/dist/cjs/fetch/index.d.ts +65 -0
  20. package/dist/node_modules/nodemailer/dist/cjs/fetch/index.js +384 -0
  21. package/dist/node_modules/nodemailer/dist/cjs/json-transport/index.d.ts +64 -0
  22. package/dist/node_modules/nodemailer/dist/cjs/json-transport/index.js +99 -0
  23. package/dist/node_modules/nodemailer/dist/cjs/mail-composer/index.d.ts +174 -0
  24. package/dist/node_modules/nodemailer/dist/cjs/mail-composer/index.js +613 -0
  25. package/dist/node_modules/nodemailer/dist/cjs/mailer/index.d.ts +237 -0
  26. package/dist/node_modules/nodemailer/dist/cjs/mailer/index.js +415 -0
  27. package/dist/node_modules/nodemailer/dist/cjs/mailer/mail-message.d.ts +97 -0
  28. package/dist/node_modules/nodemailer/dist/cjs/mailer/mail-message.js +342 -0
  29. package/dist/node_modules/nodemailer/dist/cjs/mime-funcs/index.d.ts +161 -0
  30. package/dist/node_modules/nodemailer/dist/cjs/mime-funcs/index.js +787 -0
  31. package/dist/node_modules/nodemailer/dist/cjs/mime-funcs/mime-types.d.ts +2 -0
  32. package/dist/node_modules/nodemailer/dist/cjs/mime-funcs/mime-types.js +2103 -0
  33. package/dist/node_modules/nodemailer/dist/cjs/mime-node/index.d.ts +333 -0
  34. package/dist/node_modules/nodemailer/dist/cjs/mime-node/index.js +1493 -0
  35. package/dist/node_modules/nodemailer/dist/cjs/mime-node/last-newline.d.ts +5 -0
  36. package/dist/node_modules/nodemailer/dist/cjs/mime-node/last-newline.js +32 -0
  37. package/dist/node_modules/nodemailer/dist/cjs/mime-node/le-unix.d.ts +9 -0
  38. package/dist/node_modules/nodemailer/dist/cjs/mime-node/le-unix.js +40 -0
  39. package/dist/node_modules/nodemailer/dist/cjs/mime-node/le-windows.d.ts +10 -0
  40. package/dist/node_modules/nodemailer/dist/cjs/mime-node/le-windows.js +47 -0
  41. package/dist/node_modules/nodemailer/dist/cjs/nodemailer.d.ts +124 -0
  42. package/dist/node_modules/nodemailer/dist/cjs/nodemailer.js +1 -0
  43. package/dist/node_modules/nodemailer/dist/cjs/package-info.d.ts +3 -0
  44. package/dist/node_modules/nodemailer/dist/cjs/package-info.js +7 -0
  45. package/dist/node_modules/nodemailer/dist/cjs/package.json +3 -0
  46. package/dist/node_modules/nodemailer/dist/cjs/punycode/index.d.ts +73 -0
  47. package/dist/node_modules/nodemailer/{lib → dist/cjs}/punycode/index.js +62 -111
  48. package/dist/node_modules/nodemailer/dist/cjs/qp/index.d.ts +32 -0
  49. package/dist/node_modules/nodemailer/dist/cjs/qp/index.js +207 -0
  50. package/dist/node_modules/nodemailer/dist/cjs/sendmail-transport/index.d.ts +69 -0
  51. package/dist/node_modules/nodemailer/dist/cjs/sendmail-transport/index.js +210 -0
  52. package/dist/node_modules/nodemailer/dist/cjs/ses-transport/index.d.ts +112 -0
  53. package/dist/node_modules/nodemailer/dist/cjs/ses-transport/index.js +252 -0
  54. package/dist/node_modules/nodemailer/dist/cjs/shared/index.d.ts +260 -0
  55. package/dist/node_modules/nodemailer/dist/cjs/shared/index.js +680 -0
  56. package/dist/node_modules/nodemailer/dist/cjs/shared/objects.d.ts +23 -0
  57. package/dist/node_modules/nodemailer/dist/cjs/shared/objects.js +43 -0
  58. package/dist/node_modules/nodemailer/dist/cjs/shared/url.d.ts +21 -0
  59. package/dist/node_modules/nodemailer/dist/cjs/shared/url.js +255 -0
  60. package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/data-stream.d.ts +15 -0
  61. package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/data-stream.js +108 -0
  62. package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/http-proxy-client.d.ts +47 -0
  63. package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/http-proxy-client.js +189 -0
  64. package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/index.d.ts +416 -0
  65. package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/index.js +1837 -0
  66. package/dist/node_modules/nodemailer/dist/cjs/smtp-pool/index.d.ts +133 -0
  67. package/dist/node_modules/nodemailer/dist/cjs/smtp-pool/index.js +569 -0
  68. package/dist/node_modules/nodemailer/dist/cjs/smtp-pool/pool-resource.d.ts +62 -0
  69. package/dist/node_modules/nodemailer/dist/cjs/smtp-pool/pool-resource.js +257 -0
  70. package/dist/node_modules/nodemailer/dist/cjs/smtp-transport/index.d.ts +156 -0
  71. package/dist/node_modules/nodemailer/dist/cjs/smtp-transport/index.js +371 -0
  72. package/dist/node_modules/nodemailer/dist/cjs/stream-transport/index.d.ts +65 -0
  73. package/dist/node_modules/nodemailer/dist/cjs/stream-transport/index.js +146 -0
  74. package/dist/node_modules/nodemailer/dist/cjs/well-known/index.d.ts +35 -0
  75. package/dist/node_modules/nodemailer/dist/cjs/well-known/index.js +41 -0
  76. package/dist/node_modules/nodemailer/dist/cjs/well-known/services.d.ts +2 -0
  77. package/dist/node_modules/nodemailer/dist/cjs/well-known/services.js +674 -0
  78. package/dist/node_modules/nodemailer/dist/cjs/xoauth2/index.d.ts +201 -0
  79. package/dist/node_modules/nodemailer/dist/cjs/xoauth2/index.js +426 -0
  80. package/dist/node_modules/nodemailer/dist/esm/addressparser/index.d.ts +59 -0
  81. package/dist/node_modules/nodemailer/{lib → dist/esm}/addressparser/index.js +361 -131
  82. package/dist/node_modules/nodemailer/dist/esm/base64/index.d.ts +36 -0
  83. package/dist/node_modules/nodemailer/{lib → dist/esm}/base64/index.js +31 -56
  84. package/dist/node_modules/nodemailer/dist/esm/dkim/index.d.ts +52 -0
  85. package/dist/node_modules/nodemailer/{lib → dist/esm}/dkim/index.js +49 -78
  86. package/dist/node_modules/nodemailer/dist/esm/dkim/message-parser.d.ts +48 -0
  87. package/dist/node_modules/nodemailer/{lib → dist/esm}/dkim/message-parser.js +64 -42
  88. package/dist/node_modules/nodemailer/dist/esm/dkim/relaxed-body.d.ts +36 -0
  89. package/dist/node_modules/nodemailer/dist/esm/dkim/relaxed-body.js +141 -0
  90. package/dist/node_modules/nodemailer/dist/esm/dkim/sign.d.ts +52 -0
  91. package/dist/node_modules/nodemailer/{lib → dist/esm}/dkim/sign.js +50 -46
  92. package/dist/node_modules/nodemailer/dist/esm/errors.d.ts +96 -0
  93. package/dist/node_modules/nodemailer/{lib → dist/esm}/errors.js +26 -18
  94. package/dist/node_modules/nodemailer/dist/esm/fetch/cookies.d.ts +107 -0
  95. package/dist/node_modules/nodemailer/dist/esm/fetch/cookies.js +242 -0
  96. package/dist/node_modules/nodemailer/dist/esm/fetch/index.d.ts +65 -0
  97. package/dist/node_modules/nodemailer/{lib → dist/esm}/fetch/index.js +80 -147
  98. package/dist/node_modules/nodemailer/dist/esm/json-transport/index.d.ts +64 -0
  99. package/dist/node_modules/nodemailer/{lib → dist/esm}/json-transport/index.js +19 -39
  100. package/dist/node_modules/nodemailer/dist/esm/mail-composer/index.d.ts +174 -0
  101. package/dist/node_modules/nodemailer/{lib → dist/esm}/mail-composer/index.js +199 -265
  102. package/dist/node_modules/nodemailer/dist/esm/mailer/index.d.ts +237 -0
  103. package/dist/node_modules/nodemailer/{lib → dist/esm}/mailer/index.js +128 -234
  104. package/dist/node_modules/nodemailer/dist/esm/mailer/mail-message.d.ts +97 -0
  105. package/dist/node_modules/nodemailer/{lib → dist/esm}/mailer/mail-message.js +62 -114
  106. package/dist/node_modules/nodemailer/dist/esm/mime-funcs/index.d.ts +161 -0
  107. package/dist/node_modules/nodemailer/dist/esm/mime-funcs/index.js +738 -0
  108. package/dist/node_modules/nodemailer/dist/esm/mime-funcs/mime-types.d.ts +2 -0
  109. package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-funcs/mime-types.js +29 -42
  110. package/dist/node_modules/nodemailer/dist/esm/mime-node/index.d.ts +333 -0
  111. package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-node/index.js +259 -372
  112. package/dist/node_modules/nodemailer/dist/esm/mime-node/last-newline.d.ts +5 -0
  113. package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-node/last-newline.js +4 -10
  114. package/dist/node_modules/nodemailer/dist/esm/mime-node/le-unix.d.ts +9 -0
  115. package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-node/le-unix.js +6 -11
  116. package/dist/node_modules/nodemailer/dist/esm/mime-node/le-windows.d.ts +10 -0
  117. package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-node/le-windows.js +6 -13
  118. package/dist/node_modules/nodemailer/dist/esm/nodemailer.d.ts +124 -0
  119. package/dist/node_modules/nodemailer/dist/esm/nodemailer.js +166 -0
  120. package/dist/node_modules/nodemailer/dist/esm/package-info.d.ts +3 -0
  121. package/dist/node_modules/nodemailer/dist/esm/package-info.js +4 -0
  122. package/dist/node_modules/nodemailer/dist/esm/package.json +3 -0
  123. package/dist/node_modules/nodemailer/dist/esm/punycode/index.d.ts +73 -0
  124. package/dist/node_modules/nodemailer/dist/esm/punycode/index.js +405 -0
  125. package/dist/node_modules/nodemailer/dist/esm/qp/index.d.ts +32 -0
  126. package/dist/node_modules/nodemailer/{lib → dist/esm}/qp/index.js +38 -67
  127. package/dist/node_modules/nodemailer/dist/esm/sendmail-transport/index.d.ts +69 -0
  128. package/dist/node_modules/nodemailer/{lib → dist/esm}/sendmail-transport/index.js +51 -101
  129. package/dist/node_modules/nodemailer/dist/esm/ses-transport/index.d.ts +112 -0
  130. package/dist/node_modules/nodemailer/dist/esm/ses-transport/index.js +212 -0
  131. package/dist/node_modules/nodemailer/dist/esm/shared/index.d.ts +260 -0
  132. package/dist/node_modules/nodemailer/{lib → dist/esm}/shared/index.js +156 -272
  133. package/dist/node_modules/nodemailer/dist/esm/shared/objects.d.ts +23 -0
  134. package/dist/node_modules/nodemailer/{lib → dist/esm}/shared/objects.js +13 -17
  135. package/dist/node_modules/nodemailer/dist/esm/shared/url.d.ts +21 -0
  136. package/dist/node_modules/nodemailer/dist/esm/shared/url.js +214 -0
  137. package/dist/node_modules/nodemailer/dist/esm/smtp-connection/data-stream.d.ts +15 -0
  138. package/dist/node_modules/nodemailer/dist/esm/smtp-connection/data-stream.js +103 -0
  139. package/dist/node_modules/nodemailer/dist/esm/smtp-connection/http-proxy-client.d.ts +47 -0
  140. package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-connection/http-proxy-client.js +49 -74
  141. package/dist/node_modules/nodemailer/dist/esm/smtp-connection/index.d.ts +416 -0
  142. package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-connection/index.js +574 -794
  143. package/dist/node_modules/nodemailer/dist/esm/smtp-pool/index.d.ts +133 -0
  144. package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-pool/index.js +172 -284
  145. package/dist/node_modules/nodemailer/dist/esm/smtp-pool/pool-resource.d.ts +62 -0
  146. package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-pool/pool-resource.js +57 -97
  147. package/dist/node_modules/nodemailer/dist/esm/smtp-transport/index.d.ts +156 -0
  148. package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-transport/index.js +81 -179
  149. package/dist/node_modules/nodemailer/dist/esm/stream-transport/index.d.ts +65 -0
  150. package/dist/node_modules/nodemailer/{lib → dist/esm}/stream-transport/index.js +36 -74
  151. package/dist/node_modules/nodemailer/dist/esm/well-known/index.d.ts +35 -0
  152. package/dist/node_modules/nodemailer/{lib → dist/esm}/well-known/index.js +5 -16
  153. package/dist/node_modules/nodemailer/dist/esm/well-known/services.d.ts +2 -0
  154. package/dist/node_modules/nodemailer/dist/esm/well-known/services.js +671 -0
  155. package/dist/node_modules/nodemailer/dist/esm/xoauth2/index.d.ts +201 -0
  156. package/dist/node_modules/nodemailer/{lib → dist/esm}/xoauth2/index.js +125 -187
  157. package/dist/node_modules/nodemailer/{lib → dist}/well-known/services.json +9 -2
  158. package/dist/node_modules/nodemailer/package.json +1 -1
  159. package/package.json +3 -3
  160. package/dist/node_modules/nodemailer/.gitattributes +0 -6
  161. package/dist/node_modules/nodemailer/.ncurc.js +0 -9
  162. package/dist/node_modules/nodemailer/.prettierignore +0 -8
  163. package/dist/node_modules/nodemailer/.prettierrc +0 -12
  164. package/dist/node_modules/nodemailer/.prettierrc.js +0 -10
  165. package/dist/node_modules/nodemailer/.release-please-config.json +0 -9
  166. package/dist/node_modules/nodemailer/eslint.config.js +0 -88
  167. package/dist/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
  168. package/dist/node_modules/nodemailer/lib/fetch/cookies.js +0 -276
  169. package/dist/node_modules/nodemailer/lib/mime-funcs/index.js +0 -671
  170. package/dist/node_modules/nodemailer/lib/nodemailer.js +0 -1
  171. package/dist/node_modules/nodemailer/lib/ses-transport/index.js +0 -266
  172. package/dist/node_modules/nodemailer/lib/shared/url.js +0 -151
  173. package/dist/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -105
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  /**
4
2
  * Restores the quoting of a local part that was read out of a quoted string.
5
3
  *
@@ -10,12 +8,12 @@
10
8
  * recipient separator once the consumer puts the address back into a header.
11
9
  *
12
10
  * This module has no dependencies so that it can ship on its own, which is why the two
13
- * grammar tests below are spelled out here instead of shared with lib/mime-node. Keeping
11
+ * grammar tests below are spelled out here instead of shared with src/mime-node. Keeping
14
12
  * only what is ambiguous quoted is deliberate, mime-node applies the stricter RFC 5321
15
13
  * dot-atom rule on top of this when it emits an address.
16
14
  *
17
- * @param {String} address Address with an unquoted local part
18
- * @return {String} Address with the local part as a quoted-string
15
+ * @param address Address with an unquoted local part
16
+ * @return Address with the local part as a quoted-string
19
17
  */
20
18
  function _quoteLocalPart(address) {
21
19
  const lastAt = address.lastIndexOf('@');
@@ -23,39 +21,160 @@ function _quoteLocalPart(address) {
23
21
  // no domain to split off, nothing can be misrouted
24
22
  return address;
25
23
  }
26
-
27
24
  const user = address.substr(0, lastAt);
28
25
  if (/^[^\s"(),:;<>@[\\\]]+$/.test(user) || /^"(?:[^"\\]|\\[\s\S])*"$/.test(user)) {
29
26
  // a local part that carries no special reads the same with or without the quotes,
30
27
  // and one that is already a complete quoted-string needs nothing either
31
28
  return address;
32
29
  }
33
-
34
30
  return '"' + user.replace(/["\\]/g, '\\$&') + '"@' + address.substr(lastAt + 1);
35
31
  }
36
-
37
32
  /**
38
33
  * Reached for every parsed address, so it is built once rather than per call.
39
34
  */
40
35
  const HAS_WHITESPACE = /\s/;
41
-
42
36
  /**
43
37
  * An addr-spec that carries its whitespace legally, inside a quoted local part. The
44
38
  * optional tail is the malformed shape: a real mailbox with wreckage trailing it.
45
39
  */
46
40
  const QUOTED_LOCAL_ADDR = /^("(?:[^"\\]|\\[\s\S])*"@\S+)(?:\s+([\s\S]+))?$/;
47
-
48
41
  /**
49
42
  * One run holding a single '@' and no whitespace, the shape an addr-spec has to have.
50
43
  */
51
44
  const ADDR_SPEC = /^[^@\s]+@[^@\s]+$/;
52
-
53
45
  /**
54
46
  * The looser reading applied once the strict one finds nothing, which tolerates the
55
47
  * further '@' that a domain should not have but malformed headers carry anyway.
56
48
  */
57
49
  const LOOSE_ADDR_SPEC = /^[^@\s]+@\S+$/;
58
-
50
+ /**
51
+ * An addr-spec sitting inside free text, together with the whitespace around it. Sticky
52
+ * on purpose: it is run at the one offset _looseAddressStart picks rather than being let
53
+ * loose to search, see there.
54
+ */
55
+ const LOOSE_TEXT_ADDR = /\s*\b[^@\s]+@[^\s]+\b\s*/y;
56
+ /**
57
+ * The characters JS `\s` matches, which the scan below has to agree with to land on the
58
+ * same match the pattern would.
59
+ */
60
+ function _isSpaceCode(code) {
61
+ return (code === 0x20 ||
62
+ (code >= 0x09 && code <= 0x0d) ||
63
+ code === 0xa0 ||
64
+ code === 0x1680 ||
65
+ (code >= 0x2000 && code <= 0x200a) ||
66
+ code === 0x2028 ||
67
+ code === 0x2029 ||
68
+ code === 0x202f ||
69
+ code === 0x205f ||
70
+ code === 0x3000 ||
71
+ code === 0xfeff);
72
+ }
73
+ /**
74
+ * The characters JS `\w` matches without the unicode flag, the set the `\b` in
75
+ * LOOSE_TEXT_ADDR is read against. charCodeAt off either end of the string gives NaN,
76
+ * which compares false throughout, so out of range reads as the non-word the pattern
77
+ * treats them as.
78
+ */
79
+ function _isWordCode(code) {
80
+ return (code >= 0x30 && code <= 0x39) || (code >= 0x41 && code <= 0x5a) || (code >= 0x61 && code <= 0x7a) || code === 0x5f;
81
+ }
82
+ /**
83
+ * Whether `\b` holds at an offset
84
+ */
85
+ function _isBoundary(text, at) {
86
+ return _isWordCode(text.charCodeAt(at - 1)) !== _isWordCode(text.charCodeAt(at));
87
+ }
88
+ /**
89
+ * Offset of the first '@' in `text` between `from` and `to`, or -1 when the range holds none.
90
+ *
91
+ * indexOf would scan on to the end of the value, and the value here is a whole header. The
92
+ * walk below steps one whitespace delimited run at a time and only ever uses a '@' that sits
93
+ * inside the run it is on, so an unbounded probe rescans everything behind that run once per
94
+ * run and grows with the square of the header: 400KB of free text carrying no '@' took a
95
+ * quarter of a second. GHSA-v53p-9fqp-m79j took the pattern search out of this walk and left
96
+ * the probe unbounded behind it.
97
+ *
98
+ * @param text Text to look in
99
+ * @param from Offset to start at
100
+ * @param to Offset to stop before
101
+ * @return Offset of the '@', or -1
102
+ */
103
+ function _indexOfAt(text, from, to) {
104
+ for (let i = from; i < to; i++) {
105
+ if (text.charCodeAt(i) === 0x40) {
106
+ return i;
107
+ }
108
+ }
109
+ return -1;
110
+ }
111
+ /**
112
+ * Finds the offset LOOSE_TEXT_ADDR matches at, or -1 when it does not match at all.
113
+ *
114
+ * Letting the pattern search for itself is quadratic: '[^@\s]+' is retried from every
115
+ * offset and rescans the run to the next '@' each time, so 140KB of header holding no
116
+ * usable '@' blocks the event loop for about ten seconds (GHSA-v53p-9fqp-m79j). The search is also unnecessary.
117
+ * '[^@\s]+' crosses neither whitespace nor a '@', so a match can only begin at the head of
118
+ * a whitespace delimited run or just past a '@' inside one, and '[^\s]+\b' gives characters
119
+ * back until it lands on a boundary, so the only end it can take in that run is the last
120
+ * boundary in it. Both are found in one pass, and the pattern is then run at that single
121
+ * offset.
122
+ *
123
+ * @param text Free text to look in
124
+ * @return Offset to match at, or -1
125
+ */
126
+ function _looseAddressStart(text) {
127
+ const len = text.length;
128
+ let pos = 0;
129
+ while (pos < len) {
130
+ while (pos < len && _isSpaceCode(text.charCodeAt(pos))) {
131
+ pos++;
132
+ }
133
+ if (pos >= len) {
134
+ break;
135
+ }
136
+ const runStart = pos;
137
+ let runEnd = pos;
138
+ while (runEnd < len && !_isSpaceCode(text.charCodeAt(runEnd))) {
139
+ runEnd++;
140
+ }
141
+ let at = _indexOfAt(text, runStart, runEnd);
142
+ if (at >= 0) {
143
+ let lastBoundary = -1;
144
+ for (let k = runEnd; k > runStart; k--) {
145
+ if (_isBoundary(text, k)) {
146
+ lastBoundary = k;
147
+ break;
148
+ }
149
+ }
150
+ let atomStart = runStart;
151
+ while (lastBoundary >= 0 && at >= 0) {
152
+ // '[^@\s]+' has to cover a character before the '@' and '[^\s]+' one after it,
153
+ // and the boundary that ends the match has to sit past both
154
+ if (at > atomStart && runEnd > at + 1 && lastBoundary > at + 1) {
155
+ for (let start = atomStart; start < at; start++) {
156
+ if (_isBoundary(text, start)) {
157
+ if (start > runStart) {
158
+ return start;
159
+ }
160
+ // the leading '\s*' is greedy, so a match that begins at the run
161
+ // takes the whitespace in front of it along
162
+ let padded = runStart;
163
+ while (padded > 0 && _isSpaceCode(text.charCodeAt(padded - 1))) {
164
+ padded--;
165
+ }
166
+ return padded;
167
+ }
168
+ }
169
+ }
170
+ atomStart = at + 1;
171
+ at = _indexOfAt(text, atomStart, runEnd);
172
+ }
173
+ }
174
+ pos = runEnd;
175
+ }
176
+ return -1;
177
+ }
59
178
  /**
60
179
  * Recovers the addr-spec from an angle-addr that came back holding unquoted whitespace.
61
180
  *
@@ -75,28 +194,26 @@ const LOOSE_ADDR_SPEC = /^[^@\s]+@\S+$/;
75
194
  * A quoted local part is left alone: RFC 5321 allows whitespace inside it, so
76
195
  * '<"user name"@example.com>' is well formed and means exactly what it says.
77
196
  *
78
- * @param {Object} data Collected address parts, mutated in place
197
+ * @param data Collected address parts, mutated in place
79
198
  */
80
199
  function _recoverAddrSpec(data) {
81
200
  if (!HAS_WHITESPACE.test(data.address)) {
82
201
  return;
83
202
  }
84
-
85
203
  let address;
86
204
  let rest;
87
-
88
205
  const quoted = data.address.match(QUOTED_LOCAL_ADDR);
89
206
  if (quoted) {
90
207
  if (!quoted[2]) {
91
208
  // the whitespace sits inside the quoted local part, this is a well formed mailbox
92
209
  return;
93
210
  }
94
-
95
211
  // a real mailbox with wreckage trailing it, so peel the addr-spec off whole rather
96
212
  // than splitting into the quotes
97
213
  address = quoted[1];
98
214
  rest = [quoted[2]];
99
- } else {
215
+ }
216
+ else {
100
217
  if (data.address.indexOf('"') >= 0) {
101
218
  // Splitting on whitespace loses track of where the quoted string starts and ends,
102
219
  // and this module does not take addresses out of quoted strings: the run picked out
@@ -105,36 +222,105 @@ function _recoverAddrSpec(data) {
105
222
  // left is wreckage either way and the original is the honest answer
106
223
  return;
107
224
  }
108
-
109
225
  const parts = data.address.split(/\s+/);
110
-
111
226
  let addrIndex = parts.findIndex(part => ADDR_SPEC.test(part));
112
227
  if (addrIndex < 0) {
113
228
  addrIndex = parts.findIndex(part => LOOSE_ADDR_SPEC.test(part));
114
229
  }
115
-
116
230
  if (addrIndex < 0) {
117
231
  // nothing in there reads as an address, there is no better answer than the original
118
232
  return;
119
233
  }
120
-
121
234
  address = parts.splice(addrIndex, 1)[0];
122
235
  rest = parts;
123
236
  }
124
-
125
237
  data.address = address;
126
238
  data.text = [data.text]
127
239
  .concat(rest)
128
240
  .filter(part => part)
129
241
  .join(' ');
130
242
  }
131
-
243
+ /**
244
+ * Takes the RFC 5322 comments out of the contents of an angle-addr.
245
+ *
246
+ * The tokenizer tracks a single open operator, so once '<' is open a '(' is plain text and
247
+ * a comment inside the brackets reached the address verbatim: 'Name <user@example.com(x)evil.com>'
248
+ * put 'user@example.com(x)evil.com' into the envelope, a value that is no mailbox and that a
249
+ * receiver stripping the comment reads as 'user@example.comevil.com' (GHSA-g73g-hqqh-jr95).
250
+ * A comment is folding whitespace, so it is read as a space that splits two atoms and as
251
+ * nothing next to an '@', the same rule the token walk applies outside the brackets. Quoted
252
+ * strings and domain literals are copied through untouched, a '(' in there is not a comment.
253
+ *
254
+ * @param address Contents of the angle brackets
255
+ * @return The address with the comments removed, and the text of the comments
256
+ */
257
+ function _stripAddressComments(address) {
258
+ const comments = [];
259
+ let result = '';
260
+ let comment = '';
261
+ let depth = 0;
262
+ let closer = '';
263
+ // carried along rather than read back off the growing result, which would flatten it on
264
+ // every comment (see lastChars in _handleAddress)
265
+ let lastChar = '';
266
+ for (let i = 0, len = address.length; i < len; i++) {
267
+ const chr = address.charAt(i);
268
+ if (depth) {
269
+ if (chr === '\\' && i < len - 1) {
270
+ comment += address.charAt(++i);
271
+ }
272
+ else if (chr === '(') {
273
+ depth++;
274
+ comment += chr;
275
+ }
276
+ else if (chr === ')' && !--depth) {
277
+ comments.push(comment.trim());
278
+ comment = '';
279
+ if (lastChar !== '@' && address.charAt(i + 1) !== '@') {
280
+ result += ' ';
281
+ lastChar = ' ';
282
+ }
283
+ }
284
+ else {
285
+ comment += chr;
286
+ }
287
+ continue;
288
+ }
289
+ if (closer) {
290
+ if (chr === '\\' && closer === '"' && i < len - 1) {
291
+ result += chr + address.charAt(++i);
292
+ lastChar = address.charAt(i);
293
+ continue;
294
+ }
295
+ if (chr === closer) {
296
+ closer = '';
297
+ }
298
+ }
299
+ else if (chr === '"') {
300
+ closer = '"';
301
+ }
302
+ else if (chr === '[') {
303
+ closer = ']';
304
+ }
305
+ else if (chr === '(') {
306
+ depth = 1;
307
+ continue;
308
+ }
309
+ result += chr;
310
+ lastChar = chr;
311
+ }
312
+ if (depth) {
313
+ // an unterminated comment runs to the end of the address
314
+ comments.push(comment.trim());
315
+ }
316
+ return { address: result.trim(), comments: comments.filter(text => text) };
317
+ }
132
318
  /**
133
319
  * Converts tokens for a single address into an address object
134
320
  *
135
- * @param {Array} tokens Tokens object
136
- * @param {Number} depth Current recursion depth for nested group protection
137
- * @return {Object} Address object
321
+ * @param tokens Tokens object
322
+ * @param depth Current recursion depth for nested group protection
323
+ * @return Address object
138
324
  */
139
325
  function _handleAddress(tokens, depth) {
140
326
  let isGroup = false;
@@ -148,7 +334,12 @@ function _handleAddress(tokens, depth) {
148
334
  textWasQuoted: []
149
335
  };
150
336
  let insideQuotes = false;
151
-
337
+ // Last character of the run each state is currently accumulating. Reading it back off
338
+ // the accumulator with slice(-1) makes the engine flatten the whole growing string on
339
+ // every token, which is quadratic over an address built from many comment-joined atoms
340
+ // (GHSA-prgh-xp8r-p3m5). A run only ever grows by the token appended below, so the
341
+ // character is carried along instead of re-read.
342
+ const lastChars = { address: '', comment: '', group: '', text: '' };
152
343
  // Filter out <addresses>, (comments) and regular text
153
344
  for (let i = 0, len = tokens.length; i < len; i++) {
154
345
  const token = tokens[i];
@@ -177,66 +368,98 @@ function _handleAddress(tokens, depth) {
177
368
  insideQuotes = false;
178
369
  break;
179
370
  }
180
- } else if (token.value) {
371
+ }
372
+ else if (token.value) {
373
+ // An empty quoted string is dropped by the tokenizer, leaving no text token of its
374
+ // own for textWasQuoted to be recorded on, so the pair of quote operators right in
375
+ // front of this token is all that is left of it and the run it opens carries the
376
+ // quoting instead. Without this '""@example.com' reads as the bare '@example.com',
377
+ // the quotes never go back on, and the value is no longer an addr-spec a trailing
378
+ // comment can be peeled off of. It only ever opens a run: a run that already holds
379
+ // material collected outside the quotes is not a quoted string, whatever follows it
380
+ const prevPrevToken = i > 1 ? tokens[i - 2] : null;
381
+ const opensAfterEmptyQuotedString = prevToken?.type === 'operator' &&
382
+ prevToken.value === '"' &&
383
+ !!prevToken.noBreak &&
384
+ prevPrevToken?.type === 'operator' &&
385
+ prevPrevToken.value === '"';
181
386
  if (state === 'address') {
182
387
  // Handle unquoted name that includes a "<".
183
388
  // Apple Mail truncates everything between an unexpected < and an address.
184
389
  token.value = token.value.replace(/^[^<]*<\s*/, '');
185
390
  }
186
-
187
391
  // A comment is folding whitespace. It may sit inside an addr-spec, on either side
188
392
  // of the '@', but it cannot join two atoms into one: gluing across it would read
189
393
  // 'user@example.com(x)evil.com' as the single domain 'example.comevil.com' and
190
394
  // deliver to a domain the sender never named.
191
395
  const parts = data[state];
192
- const joins =
193
- prevToken &&
396
+ const joins = prevToken &&
194
397
  prevToken.noBreak &&
195
398
  parts.length &&
196
- (prevToken.value !== ')' || parts[parts.length - 1].slice(-1) === '@' || token.value.charAt(0) === '@');
197
-
399
+ (prevToken.value !== ')' || lastChars[state] === '@' || token.value.charAt(0) === '@');
198
400
  if (joins) {
199
401
  data[state][data[state].length - 1] += token.value;
402
+ if (token.value) {
403
+ lastChars[state] = token.value.charAt(token.value.length - 1);
404
+ }
200
405
  if (state === 'text' && insideQuotes) {
201
406
  data.textWasQuoted[data.textWasQuoted.length - 1] = true;
202
407
  }
203
- } else {
408
+ }
409
+ else {
204
410
  data[state].push(token.value);
411
+ lastChars[state] = token.value.charAt(token.value.length - 1);
205
412
  if (state === 'text') {
206
- data.textWasQuoted.push(insideQuotes);
413
+ data.textWasQuoted.push(insideQuotes || opensAfterEmptyQuotedString);
207
414
  }
208
415
  }
209
416
  }
210
417
  }
211
-
212
418
  // If there is no text but a comment, replace the two
213
419
  if (!data.text.length && data.comment.length) {
214
420
  data.text = data.comment;
215
421
  data.comment = [];
216
422
  }
217
-
218
423
  if (isGroup) {
219
424
  // http://tools.ietf.org/html/rfc2822#appendix-A.1.3
220
425
  data.text = data.text.join(' ');
221
-
222
426
  // Parse group members, but flatten any nested groups (RFC 5322 doesn't allow nesting)
223
427
  let groupMembers = [];
224
428
  if (data.group.length) {
225
- const parsedGroup = addressparser(data.group.join(','), { _depth: depth + 1 });
429
+ const parsedGroup = _parseAddressList(data.group.join(','), depth + 1);
226
430
  parsedGroup.forEach(member => {
227
431
  if (member.group) {
228
432
  groupMembers = groupMembers.concat(member.group);
229
- } else {
433
+ }
434
+ else {
230
435
  groupMembers.push(member);
231
436
  }
232
437
  });
233
438
  }
234
-
235
439
  addresses.push({
236
440
  name: data.text || '',
237
441
  group: groupMembers
238
442
  });
239
- } else {
443
+ }
444
+ else {
445
+ // Comments come out of the angle-addr before anything asks whether one was found, so
446
+ // that brackets holding nothing but a comment read the same as empty ones
447
+ const addressComments = [];
448
+ const addressParts = [];
449
+ for (const part of data.address) {
450
+ if (part.indexOf('(') < 0) {
451
+ addressParts.push(part);
452
+ continue;
453
+ }
454
+ const stripped = _stripAddressComments(part);
455
+ for (const comment of stripped.comments) {
456
+ addressComments.push(comment);
457
+ }
458
+ if (stripped.address) {
459
+ addressParts.push(stripped.address);
460
+ }
461
+ }
462
+ data.address = addressParts;
240
463
  // If no address was found, try to detect one from regular text
241
464
  if (!data.address.length && data.text.length) {
242
465
  for (let i = data.text.length - 1; i >= 0; i--) {
@@ -249,23 +472,25 @@ function _handleAddress(tokens, depth) {
249
472
  break;
250
473
  }
251
474
  }
252
-
253
475
  // Try a looser regex match if strict match found nothing
254
476
  if (!data.address.length) {
255
477
  let extracted = false;
256
478
  for (let i = data.text.length - 1; i >= 0; i--) {
257
479
  // Security: Do not extract email addresses from quoted strings
258
480
  if (!data.textWasQuoted[i]) {
259
- data.text[i] = data.text[i]
260
- .replace(/\s*\b[^@\s]+@[^\s]+\b\s*/, match => {
261
- if (!extracted) {
262
- data.address = [match.trim()];
263
- extracted = true;
264
- return ' ';
265
- }
266
- return match;
267
- })
268
- .trim();
481
+ const part = data.text[i];
482
+ let remainder = part;
483
+ const at = _looseAddressStart(part);
484
+ if (at >= 0) {
485
+ LOOSE_TEXT_ADDR.lastIndex = at;
486
+ const match = LOOSE_TEXT_ADDR.exec(part);
487
+ if (match) {
488
+ data.address = [match[0].trim()];
489
+ extracted = true;
490
+ remainder = part.slice(0, at) + ' ' + part.slice(at + match[0].length);
491
+ }
492
+ }
493
+ data.text[i] = remainder.trim();
269
494
  if (extracted) {
270
495
  break;
271
496
  }
@@ -273,56 +498,65 @@ function _handleAddress(tokens, depth) {
273
498
  }
274
499
  }
275
500
  }
276
-
277
501
  // If there's still no text but a comment exists, replace the two
278
502
  if (!data.text.length && data.comment.length) {
279
503
  data.text = data.comment;
280
504
  data.comment = [];
281
505
  }
282
-
283
506
  // Keep only the first address occurrence, push others to regular text
284
507
  if (data.address.length > 1) {
285
508
  data.text = data.text.concat(data.address.splice(1));
286
509
  }
287
-
288
510
  // An address is only taken from unquoted text, so anything left in the text at this
289
511
  // point that still has to serve as the address carries its quoting in this flag
290
512
  const addressFromQuotedText = !data.address.length && data.textWasQuoted.some(wasQuoted => wasQuoted);
291
-
292
513
  // Join values with spaces
293
514
  data.text = data.text.join(' ');
294
515
  data.address = data.address.join(' ');
295
-
516
+ if (addressFromQuotedText && data.text.indexOf('@') >= 0) {
517
+ // A quoted run with no '@' in it is a display name and nothing else, so it stays
518
+ // where it is. Moving it over anyway made a comment the mailbox was read from:
519
+ // '"Display Name" <(a comment)>' handed on 'Display Name' as the address with the
520
+ // comment as the name, and a quoted name holding a ',' or a ';' became an address
521
+ // that reads as two recipients once a consumer writes it back into a header.
522
+ //
523
+ // The mailbox is still sitting in the text, so it moves over here and is quoted
524
+ // before the recovery below rather than after it. Anything else the text holds
525
+ // came along with it: a comment ends the domain but leaves the atoms behind it in
526
+ // the same text, and '"user"@example.com(x)evil.com' was handed on as the address
527
+ // 'user@example.com evil.com', a second domain riding into the envelope recipient
528
+ // on a value that is no addr-spec at all (GHSA-g57g-f23g-4646). Putting the quotes
529
+ // back first is what lets the recovery tell the whitespace an addr-spec may carry
530
+ // from the wreckage trailing one, as only a quoted local part may hold whitespace
531
+ data.address = _quoteLocalPart(data.text);
532
+ data.text = '';
533
+ }
296
534
  _recoverAddrSpec(data);
297
-
535
+ // a comment names the mailbox only when nothing else does, as one outside the brackets
536
+ if (!data.text && addressComments.length) {
537
+ data.text = addressComments.join(' ');
538
+ }
298
539
  const address = {
299
540
  address: data.address || data.text || '',
300
541
  name: data.text || data.address || ''
301
542
  };
302
-
303
543
  if (address.address === address.name) {
304
544
  if (/@/.test(address.address || '')) {
305
545
  address.name = '';
306
- } else {
546
+ }
547
+ else {
307
548
  address.address = '';
308
549
  }
309
550
  }
310
-
311
- if (addressFromQuotedText && address.address) {
312
- address.address = _quoteLocalPart(address.address);
313
- }
314
-
315
551
  addresses.push(address);
316
552
  }
317
-
318
553
  return addresses;
319
554
  }
320
-
321
555
  /**
322
556
  * Creates a Tokenizer object for tokenizing address field strings
323
557
  *
324
558
  * @constructor
325
- * @param {String} str Address field string
559
+ * @param str Address field string
326
560
  */
327
561
  class Tokenizer {
328
562
  constructor(str) {
@@ -332,7 +566,7 @@ class Tokenizer {
332
566
  this.node = null;
333
567
  this.escaped = false;
334
568
  this.inDomainLiteral = false;
335
-
569
+ this.afterAt = false;
336
570
  this.list = [];
337
571
  /**
338
572
  * Operator tokens and which tokens are expected to end the sequence
@@ -352,85 +586,92 @@ class Tokenizer {
352
586
  ';': ''
353
587
  };
354
588
  }
355
-
356
589
  /**
357
590
  * Tokenizes the original input string
358
591
  *
359
- * @return {Array} An array of operator|text tokens
592
+ * @return An array of operator|text tokens
360
593
  */
361
594
  tokenize() {
362
595
  const list = [];
363
-
364
596
  for (let i = 0, len = this.str.length; i < len; i++) {
365
597
  const chr = this.str.charAt(i);
366
598
  const nextChr = i < len - 1 ? this.str.charAt(i + 1) : null;
367
599
  this.checkChar(chr, nextChr);
368
600
  }
369
-
370
601
  this.list.forEach(node => {
371
602
  node.value = (node.value || '').toString().trim();
372
603
  if (node.value) {
373
604
  list.push(node);
374
605
  }
375
606
  });
376
-
377
607
  return list;
378
608
  }
379
-
380
609
  /**
381
610
  * Checks if a character is an operator or text and acts accordingly
382
611
  *
383
- * @param {String} chr Character from the address field
612
+ * @param chr Character from the address field
613
+ * @param nextChr Character following chr, null at the end of the field
384
614
  */
385
615
  checkChar(chr, nextChr) {
386
- // Track RFC 5322 domain-literals ("[" *dtext "]"). Operator characters such
387
- // as the ":" of an IPv6 address-literal (user@[IPv6:2001:db8::1]) are dtext
388
- // and must not be treated as the group delimiter while inside the brackets.
389
- // Quoted strings and comments are handled separately via operatorExpecting,
390
- // so only enter this state when no operator is open. The list separators ","
391
- // and ";" are the exception: they always end the literal (and split the
392
- // address list) so that an unclosed "[" cannot swallow later recipients.
616
+ // Track RFC 5322 domain-literals ("[" *dtext "]"). The ":" of an IPv6
617
+ // address-literal (user@[IPv6:2001:db8::1]) is dtext and must not be treated as
618
+ // the group delimiter while inside the brackets. That is the only operator the
619
+ // literal hides: letting it hide the others as well meant a "[" could turn the
620
+ // comment, quoted string or angle-addr after it into text and pick a different
621
+ // mailbox, '[ ( ] <a@victim.com> ) <b@evil.com>' yielded a@victim.com where
622
+ // RFC 5322 reads b@evil.com. A "[" also only opens a literal right after the "@"
623
+ // of an addr-spec, so the group delimiter of a display name holding one stays
624
+ // intact. Quoted strings and comments are handled separately via
625
+ // operatorExpecting, so only enter this state when no operator is open. The list
626
+ // separators "," and ";" always end the literal (and split the address list) so
627
+ // that an unclosed "[" cannot swallow later recipients.
393
628
  if (!this.escaped && !this.operatorExpecting) {
394
- if (!this.inDomainLiteral && chr === '[') {
629
+ if (!this.inDomainLiteral && chr === '[' && this.afterAt) {
395
630
  this.inDomainLiteral = true;
396
- } else if (this.inDomainLiteral && (chr === ']' || chr === ',' || chr === ';')) {
631
+ }
632
+ else if (this.inDomainLiteral && (chr === ']' || chr === ',' || chr === ';')) {
397
633
  this.inDomainLiteral = false;
398
634
  }
399
635
  }
400
-
401
636
  if (this.escaped) {
402
637
  // ignore next condition blocks
403
- } else if (chr === this.operatorExpecting) {
638
+ }
639
+ else if (chr === this.operatorExpecting) {
404
640
  this.node = {
405
641
  type: 'operator',
406
642
  value: chr
407
643
  };
408
-
409
644
  if (nextChr && ![' ', '\t', '\r', '\n', ',', ';'].includes(nextChr)) {
410
645
  this.node.noBreak = true;
411
646
  }
412
-
413
647
  this.list.push(this.node);
414
648
  this.node = null;
649
+ if (chr !== ')') {
650
+ // a comment is folding whitespace, it does not part the "@" from the "["
651
+ this.afterAt = false;
652
+ }
415
653
  this.operatorExpecting = '';
416
654
  this.escaped = false;
417
-
418
655
  return;
419
- } else if (!this.operatorExpecting && !this.inDomainLiteral && chr in this.operators) {
656
+ }
657
+ else if (!this.operatorExpecting && !(this.inDomainLiteral && chr === ':') && chr in this.operators) {
420
658
  this.node = {
421
659
  type: 'operator',
422
660
  value: chr
423
661
  };
424
662
  this.list.push(this.node);
425
663
  this.node = null;
664
+ if (chr !== '(') {
665
+ this.afterAt = false;
666
+ }
426
667
  this.operatorExpecting = this.operators[chr];
427
668
  this.escaped = false;
428
669
  return;
429
- } else if (['"', "'"].includes(this.operatorExpecting) && chr === '\\') {
670
+ }
671
+ else if (['"', "'"].includes(this.operatorExpecting) && chr === '\\') {
430
672
  this.escaped = true;
431
673
  return;
432
674
  }
433
-
434
675
  if (!this.node) {
435
676
  this.node = {
436
677
  type: 'text',
@@ -438,76 +679,65 @@ class Tokenizer {
438
679
  };
439
680
  this.list.push(this.node);
440
681
  }
441
-
442
682
  if (chr === '\n') {
443
683
  // Convert newlines to spaces. Carriage return is ignored as \r and \n usually
444
684
  // go together anyway and there already is a WS for \n. Lone \r means something is fishy.
445
685
  chr = ' ';
446
686
  }
447
-
448
687
  if (chr.charCodeAt(0) >= 0x21 || [' ', '\t'].includes(chr)) {
449
688
  // skip command bytes
450
689
  this.node.value += chr;
690
+ // text inside a quoted string or a comment is not part of an addr-spec. Tracked
691
+ // as it goes rather than read back off the value (see lastChars in _handleAddress)
692
+ if (!this.operatorExpecting && chr !== ' ' && chr !== '\t') {
693
+ this.afterAt = chr === '@';
694
+ }
451
695
  }
452
-
453
696
  this.escaped = false;
454
697
  }
455
698
  }
456
-
457
699
  /**
458
700
  * Maximum recursion depth for parsing nested groups.
459
701
  * RFC 5322 doesn't allow nested groups, so this is a safeguard against
460
702
  * malicious input that could cause stack overflow.
461
703
  */
462
704
  const MAX_NESTED_GROUP_DEPTH = 50;
463
-
464
705
  /**
465
- * Parses structured e-mail addresses from an address field
466
- *
467
- * Example:
468
- *
469
- * 'Name <address@domain>'
470
- *
471
- * will be converted to
706
+ * Parses an address list, recursing into the groups it holds.
472
707
  *
473
- * [{name: 'Name', address: 'address@domain'}]
708
+ * The depth is threaded through the calls rather than carried in an options object, so a
709
+ * caller supplied one can not seed it and lift the recursion limit: `{ _depth: -1e9 }`
710
+ * bought a billion levels of nesting and turned the guard below into the stack overflow it
711
+ * is there to prevent.
474
712
  *
475
- * @param {String} str Address field
476
- * @param {Object} options Optional options object
477
- * @param {Number} options._depth Internal recursion depth counter (do not set manually)
478
- * @return {Array} An array of address objects
713
+ * @param str Address field
714
+ * @param depth Current recursion depth for nested group protection
715
+ * @return An array of address objects
479
716
  */
480
- function addressparser(str, options) {
481
- options = options || {};
482
- const depth = options._depth || 0;
483
-
717
+ function _parseAddressList(str, depth) {
484
718
  // Prevent stack overflow from deeply nested groups (DoS protection)
485
719
  if (depth > MAX_NESTED_GROUP_DEPTH) {
486
720
  return [];
487
721
  }
488
-
489
722
  const tokenizer = new Tokenizer(str);
490
723
  const tokens = tokenizer.tokenize();
491
-
492
724
  const addresses = [];
493
725
  let address = [];
494
- let parsedAddresses = [];
495
-
726
+ const parsedAddresses = [];
496
727
  tokens.forEach(token => {
497
728
  if (token.type === 'operator' && (token.value === ',' || token.value === ';')) {
498
729
  if (address.length) {
499
730
  addresses.push(address);
500
731
  }
501
732
  address = [];
502
- } else {
733
+ }
734
+ else {
503
735
  address.push(token);
504
736
  }
505
737
  });
506
-
507
738
  if (address.length) {
508
739
  addresses.push(address);
509
740
  }
510
-
511
741
  addresses.forEach(addr => {
512
742
  const handled = _handleAddress(addr, depth);
513
743
  // Appended in place. Rebuilding the accumulator with concat() would copy every
@@ -516,7 +746,6 @@ function addressparser(str, options) {
516
746
  parsedAddresses.push(handled[i]);
517
747
  }
518
748
  });
519
-
520
749
  // Merge fragments produced when unquoted display names contain commas.
521
750
  // "Joe Foo, PhD <joe@example.com>" is split on the comma into
522
751
  // [{name:"Joe Foo", address:""}, {name:"PhD", address:"joe@example.com"}].
@@ -529,16 +758,19 @@ function addressparser(str, options) {
529
758
  const next = mergedAddresses.length ? mergedAddresses[mergedAddresses.length - 1] : null;
530
759
  if (next && current.address === '' && current.name && !current.group && next.address && next.name) {
531
760
  next.name = current.name + ', ' + next.name;
532
- } else {
761
+ }
762
+ else {
533
763
  mergedAddresses.push(current);
534
764
  }
535
765
  }
536
766
  mergedAddresses.reverse();
537
- parsedAddresses = mergedAddresses;
538
-
539
- if (options.flatten) {
767
+ return mergedAddresses;
768
+ }
769
+ function addressparser(str, options) {
770
+ const parsedAddresses = _parseAddressList(str, 0);
771
+ if (options?.flatten) {
540
772
  const flatAddresses = [];
541
- const walkAddressList = list => {
773
+ const walkAddressList = (list) => {
542
774
  list.forEach(entry => {
543
775
  if (entry.group) {
544
776
  return walkAddressList(entry.group);
@@ -549,8 +781,6 @@ function addressparser(str, options) {
549
781
  walkAddressList(parsedAddresses);
550
782
  return flatAddresses;
551
783
  }
552
-
553
784
  return parsedAddresses;
554
785
  }
555
-
556
- module.exports = addressparser;
786
+ export default addressparser;