@nocobase/plugin-workflow-mailer 2.2.17 → 2.2.18

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 (53) hide show
  1. package/dist/externalVersion.js +6 -6
  2. package/dist/node_modules/joi/dist/joi-browser.min.js +1 -1
  3. package/dist/node_modules/joi/lib/index.js +1 -1
  4. package/dist/node_modules/joi/lib/messages.js +35 -6
  5. package/dist/node_modules/joi/lib/types/keys.js +18 -1
  6. package/dist/node_modules/joi/lib/types/link.js +18 -2
  7. package/dist/node_modules/joi/lib/types/string.js +2 -2
  8. package/dist/node_modules/joi/package.json +1 -1
  9. package/dist/node_modules/nodemailer/.ncurc.js +2 -0
  10. package/dist/node_modules/nodemailer/.prettierignore +8 -0
  11. package/dist/node_modules/nodemailer/.prettierrc +12 -0
  12. package/dist/node_modules/nodemailer/.prettierrc.js +2 -0
  13. package/dist/node_modules/nodemailer/.release-please-config.json +9 -0
  14. package/dist/node_modules/nodemailer/eslint.config.js +88 -0
  15. package/dist/node_modules/nodemailer/lib/addressparser/index.js +314 -71
  16. package/dist/node_modules/nodemailer/lib/base64/index.js +9 -13
  17. package/dist/node_modules/nodemailer/lib/dkim/index.js +17 -18
  18. package/dist/node_modules/nodemailer/lib/dkim/message-parser.js +12 -13
  19. package/dist/node_modules/nodemailer/lib/dkim/relaxed-body.js +3 -3
  20. package/dist/node_modules/nodemailer/lib/dkim/sign.js +22 -18
  21. package/dist/node_modules/nodemailer/lib/errors.js +59 -0
  22. package/dist/node_modules/nodemailer/lib/fetch/cookies.js +14 -19
  23. package/dist/node_modules/nodemailer/lib/fetch/index.js +164 -27
  24. package/dist/node_modules/nodemailer/lib/json-transport/index.js +4 -4
  25. package/dist/node_modules/nodemailer/lib/mail-composer/index.js +229 -155
  26. package/dist/node_modules/nodemailer/lib/mailer/index.js +106 -54
  27. package/dist/node_modules/nodemailer/lib/mailer/mail-message.js +106 -68
  28. package/dist/node_modules/nodemailer/lib/mime-funcs/index.js +105 -59
  29. package/dist/node_modules/nodemailer/lib/mime-funcs/mime-types.js +21 -14
  30. package/dist/node_modules/nodemailer/lib/mime-node/index.js +389 -137
  31. package/dist/node_modules/nodemailer/lib/mime-node/last-newline.js +1 -1
  32. package/dist/node_modules/nodemailer/lib/mime-node/le-unix.js +4 -7
  33. package/dist/node_modules/nodemailer/lib/mime-node/le-windows.js +1 -4
  34. package/dist/node_modules/nodemailer/lib/nodemailer.js +1 -1
  35. package/dist/node_modules/nodemailer/lib/punycode/index.js +1 -1
  36. package/dist/node_modules/nodemailer/lib/qp/index.js +33 -22
  37. package/dist/node_modules/nodemailer/lib/sendmail-transport/index.js +54 -44
  38. package/dist/node_modules/nodemailer/lib/ses-transport/index.js +110 -193
  39. package/dist/node_modules/nodemailer/lib/shared/index.js +299 -240
  40. package/dist/node_modules/nodemailer/lib/shared/objects.js +42 -0
  41. package/dist/node_modules/nodemailer/lib/shared/url.js +151 -0
  42. package/dist/node_modules/nodemailer/lib/smtp-connection/data-stream.js +3 -6
  43. package/dist/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +50 -19
  44. package/dist/node_modules/nodemailer/lib/smtp-connection/index.js +415 -223
  45. package/dist/node_modules/nodemailer/lib/smtp-pool/index.js +27 -34
  46. package/dist/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +19 -16
  47. package/dist/node_modules/nodemailer/lib/smtp-transport/index.js +66 -53
  48. package/dist/node_modules/nodemailer/lib/stream-transport/index.js +16 -7
  49. package/dist/node_modules/nodemailer/lib/well-known/index.js +7 -7
  50. package/dist/node_modules/nodemailer/lib/well-known/services.json +328 -38
  51. package/dist/node_modules/nodemailer/lib/xoauth2/index.js +105 -33
  52. package/dist/node_modules/nodemailer/package.json +1 -1
  53. package/package.json +4 -4
@@ -1,52 +1,211 @@
1
1
  'use strict';
2
2
 
3
+ /**
4
+ * Restores the quoting of a local part that was read out of a quoted string.
5
+ *
6
+ * RFC 5321 allows '@' inside a quoted local part, so handing '"user@evil.com"@good.com'
7
+ * on as the bare 'user@evil.com@good.com' leaves it to the consumer which '@' splits the
8
+ * domain off. Getting that wrong is a misrouting vector, so the quotes go back on. The
9
+ * same holds for the other specials: a ',' or a ';' that loses its quotes reads as a
10
+ * recipient separator once the consumer puts the address back into a header.
11
+ *
12
+ * 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
14
+ * only what is ambiguous quoted is deliberate, mime-node applies the stricter RFC 5321
15
+ * dot-atom rule on top of this when it emits an address.
16
+ *
17
+ * @param {String} address Address with an unquoted local part
18
+ * @return {String} Address with the local part as a quoted-string
19
+ */
20
+ function _quoteLocalPart(address) {
21
+ const lastAt = address.lastIndexOf('@');
22
+ if (lastAt < 0) {
23
+ // no domain to split off, nothing can be misrouted
24
+ return address;
25
+ }
26
+
27
+ const user = address.substr(0, lastAt);
28
+ if (/^[^\s"(),:;<>@[\\\]]+$/.test(user) || /^"(?:[^"\\]|\\[\s\S])*"$/.test(user)) {
29
+ // a local part that carries no special reads the same with or without the quotes,
30
+ // and one that is already a complete quoted-string needs nothing either
31
+ return address;
32
+ }
33
+
34
+ return '"' + user.replace(/["\\]/g, '\\$&') + '"@' + address.substr(lastAt + 1);
35
+ }
36
+
37
+ /**
38
+ * Reached for every parsed address, so it is built once rather than per call.
39
+ */
40
+ const HAS_WHITESPACE = /\s/;
41
+
42
+ /**
43
+ * An addr-spec that carries its whitespace legally, inside a quoted local part. The
44
+ * optional tail is the malformed shape: a real mailbox with wreckage trailing it.
45
+ */
46
+ const QUOTED_LOCAL_ADDR = /^("(?:[^"\\]|\\[\s\S])*"@\S+)(?:\s+([\s\S]+))?$/;
47
+
48
+ /**
49
+ * One run holding a single '@' and no whitespace, the shape an addr-spec has to have.
50
+ */
51
+ const ADDR_SPEC = /^[^@\s]+@[^@\s]+$/;
52
+
53
+ /**
54
+ * The looser reading applied once the strict one finds nothing, which tolerates the
55
+ * further '@' that a domain should not have but malformed headers carry anyway.
56
+ */
57
+ const LOOSE_ADDR_SPEC = /^[^@\s]+@\S+$/;
58
+
59
+ /**
60
+ * Recovers the addr-spec from an angle-addr that came back holding unquoted whitespace.
61
+ *
62
+ * A malformed header can put more than a mailbox between the angle brackets, most often
63
+ * because the generator wrote the recipient twice: '<user@example.com user@example.com>'
64
+ * or '<example.com user@example.com>'. Whitespace is not addr-spec, so the whole run can
65
+ * never be a mailbox anyone could deliver to, and passing it on as the address loses the
66
+ * recipient that is sitting right there in the header.
67
+ *
68
+ * The run that still reads as an addr-spec is kept and whatever is left over becomes
69
+ * display text rather than being dropped. Candidates are read strictly first and then
70
+ * under the looser grammar, the same two tiers the unquoted-text branch below applies to
71
+ * the same problem, so that '<a@b@c.com junk>' and a bare 'a@b@c.com junk' agree on the
72
+ * recipient. When several runs qualify the first wins, which is what that branch's looser
73
+ * tier does within a token.
74
+ *
75
+ * A quoted local part is left alone: RFC 5321 allows whitespace inside it, so
76
+ * '<"user name"@example.com>' is well formed and means exactly what it says.
77
+ *
78
+ * @param {Object} data Collected address parts, mutated in place
79
+ */
80
+ function _recoverAddrSpec(data) {
81
+ if (!HAS_WHITESPACE.test(data.address)) {
82
+ return;
83
+ }
84
+
85
+ let address;
86
+ let rest;
87
+
88
+ const quoted = data.address.match(QUOTED_LOCAL_ADDR);
89
+ if (quoted) {
90
+ if (!quoted[2]) {
91
+ // the whitespace sits inside the quoted local part, this is a well formed mailbox
92
+ return;
93
+ }
94
+
95
+ // a real mailbox with wreckage trailing it, so peel the addr-spec off whole rather
96
+ // than splitting into the quotes
97
+ address = quoted[1];
98
+ rest = [quoted[2]];
99
+ } else {
100
+ if (data.address.indexOf('"') >= 0) {
101
+ // Splitting on whitespace loses track of where the quoted string starts and ends,
102
+ // and this module does not take addresses out of quoted strings: the run picked out
103
+ // of '<junk "user@evil.com b"@good.com>' would be an address from the domain the
104
+ // quotes were hiding. Every well formed shape was already handled above, so what is
105
+ // left is wreckage either way and the original is the honest answer
106
+ return;
107
+ }
108
+
109
+ const parts = data.address.split(/\s+/);
110
+
111
+ let addrIndex = parts.findIndex(part => ADDR_SPEC.test(part));
112
+ if (addrIndex < 0) {
113
+ addrIndex = parts.findIndex(part => LOOSE_ADDR_SPEC.test(part));
114
+ }
115
+
116
+ if (addrIndex < 0) {
117
+ // nothing in there reads as an address, there is no better answer than the original
118
+ return;
119
+ }
120
+
121
+ address = parts.splice(addrIndex, 1)[0];
122
+ rest = parts;
123
+ }
124
+
125
+ data.address = address;
126
+ data.text = [data.text]
127
+ .concat(rest)
128
+ .filter(part => part)
129
+ .join(' ');
130
+ }
131
+
3
132
  /**
4
133
  * Converts tokens for a single address into an address object
5
134
  *
6
135
  * @param {Array} tokens Tokens object
136
+ * @param {Number} depth Current recursion depth for nested group protection
7
137
  * @return {Object} Address object
8
138
  */
9
- function _handleAddress(tokens) {
10
- let token;
139
+ function _handleAddress(tokens, depth) {
11
140
  let isGroup = false;
12
141
  let state = 'text';
13
- let address;
14
- let addresses = [];
15
- let data = {
142
+ const addresses = [];
143
+ const data = {
16
144
  address: [],
17
145
  comment: [],
18
146
  group: [],
19
- text: []
147
+ text: [],
148
+ textWasQuoted: []
20
149
  };
21
- let i;
22
- let len;
150
+ let insideQuotes = false;
23
151
 
24
152
  // Filter out <addresses>, (comments) and regular text
25
- for (i = 0, len = tokens.length; i < len; i++) {
26
- token = tokens[i];
153
+ for (let i = 0, len = tokens.length; i < len; i++) {
154
+ const token = tokens[i];
155
+ const prevToken = i ? tokens[i - 1] : null;
27
156
  if (token.type === 'operator') {
28
157
  switch (token.value) {
29
158
  case '<':
30
159
  state = 'address';
160
+ insideQuotes = false;
31
161
  break;
32
162
  case '(':
33
163
  state = 'comment';
164
+ insideQuotes = false;
34
165
  break;
35
166
  case ':':
36
167
  state = 'group';
37
168
  isGroup = true;
169
+ insideQuotes = false;
170
+ break;
171
+ case '"':
172
+ insideQuotes = !insideQuotes;
173
+ state = 'text';
38
174
  break;
39
175
  default:
40
176
  state = 'text';
177
+ insideQuotes = false;
178
+ break;
41
179
  }
42
180
  } else if (token.value) {
43
181
  if (state === 'address') {
44
- // handle use case where unquoted name includes a "<"
45
- // Apple Mail truncates everything between an unexpected < and an address
46
- // and so will we
182
+ // Handle unquoted name that includes a "<".
183
+ // Apple Mail truncates everything between an unexpected < and an address.
47
184
  token.value = token.value.replace(/^[^<]*<\s*/, '');
48
185
  }
49
- data[state].push(token.value);
186
+
187
+ // A comment is folding whitespace. It may sit inside an addr-spec, on either side
188
+ // of the '@', but it cannot join two atoms into one: gluing across it would read
189
+ // 'user@example.com(x)evil.com' as the single domain 'example.comevil.com' and
190
+ // deliver to a domain the sender never named.
191
+ const parts = data[state];
192
+ const joins =
193
+ prevToken &&
194
+ prevToken.noBreak &&
195
+ parts.length &&
196
+ (prevToken.value !== ')' || parts[parts.length - 1].slice(-1) === '@' || token.value.charAt(0) === '@');
197
+
198
+ if (joins) {
199
+ data[state][data[state].length - 1] += token.value;
200
+ if (state === 'text' && insideQuotes) {
201
+ data.textWasQuoted[data.textWasQuoted.length - 1] = true;
202
+ }
203
+ } else {
204
+ data[state].push(token.value);
205
+ if (state === 'text') {
206
+ data.textWasQuoted.push(insideQuotes);
207
+ }
208
+ }
50
209
  }
51
210
  }
52
211
 
@@ -59,74 +218,101 @@ function _handleAddress(tokens) {
59
218
  if (isGroup) {
60
219
  // http://tools.ietf.org/html/rfc2822#appendix-A.1.3
61
220
  data.text = data.text.join(' ');
221
+
222
+ // Parse group members, but flatten any nested groups (RFC 5322 doesn't allow nesting)
223
+ let groupMembers = [];
224
+ if (data.group.length) {
225
+ const parsedGroup = addressparser(data.group.join(','), { _depth: depth + 1 });
226
+ parsedGroup.forEach(member => {
227
+ if (member.group) {
228
+ groupMembers = groupMembers.concat(member.group);
229
+ } else {
230
+ groupMembers.push(member);
231
+ }
232
+ });
233
+ }
234
+
62
235
  addresses.push({
63
- name: data.text || (address && address.name),
64
- group: data.group.length ? addressparser(data.group.join(',')) : []
236
+ name: data.text || '',
237
+ group: groupMembers
65
238
  });
66
239
  } else {
67
240
  // If no address was found, try to detect one from regular text
68
241
  if (!data.address.length && data.text.length) {
69
- for (i = data.text.length - 1; i >= 0; i--) {
70
- if (data.text[i].match(/^[^@\s]+@[^@\s]+$/)) {
242
+ for (let i = data.text.length - 1; i >= 0; i--) {
243
+ // Security: Do not extract email addresses from quoted strings.
244
+ // RFC 5321 allows @ inside quoted local-parts like "user@domain"@example.com.
245
+ // Extracting emails from quoted text leads to misrouting vulnerabilities.
246
+ if (!data.textWasQuoted[i] && ADDR_SPEC.test(data.text[i])) {
71
247
  data.address = data.text.splice(i, 1);
248
+ data.textWasQuoted.splice(i, 1);
72
249
  break;
73
250
  }
74
251
  }
75
252
 
76
- let _regexHandler = function (address) {
77
- if (!data.address.length) {
78
- data.address = [address.trim()];
79
- return ' ';
80
- } else {
81
- return address;
82
- }
83
- };
84
-
85
- // still no address
253
+ // Try a looser regex match if strict match found nothing
86
254
  if (!data.address.length) {
87
- for (i = data.text.length - 1; i >= 0; i--) {
88
- // fixed the regex to parse email address correctly when email address has more than one @
89
- data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^\s]+\b\s*/, _regexHandler).trim();
90
- if (data.address.length) {
91
- break;
255
+ let extracted = false;
256
+ for (let i = data.text.length - 1; i >= 0; i--) {
257
+ // Security: Do not extract email addresses from quoted strings
258
+ 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();
269
+ if (extracted) {
270
+ break;
271
+ }
92
272
  }
93
273
  }
94
274
  }
95
275
  }
96
276
 
97
- // If there's still is no text but a comment exixts, replace the two
277
+ // If there's still no text but a comment exists, replace the two
98
278
  if (!data.text.length && data.comment.length) {
99
279
  data.text = data.comment;
100
280
  data.comment = [];
101
281
  }
102
282
 
103
- // Keep only the first address occurence, push others to regular text
283
+ // Keep only the first address occurrence, push others to regular text
104
284
  if (data.address.length > 1) {
105
285
  data.text = data.text.concat(data.address.splice(1));
106
286
  }
107
287
 
288
+ // An address is only taken from unquoted text, so anything left in the text at this
289
+ // point that still has to serve as the address carries its quoting in this flag
290
+ const addressFromQuotedText = !data.address.length && data.textWasQuoted.some(wasQuoted => wasQuoted);
291
+
108
292
  // Join values with spaces
109
293
  data.text = data.text.join(' ');
110
294
  data.address = data.address.join(' ');
111
295
 
112
- if (!data.address && isGroup) {
113
- return [];
114
- } else {
115
- address = {
116
- address: data.address || data.text || '',
117
- name: data.text || data.address || ''
118
- };
296
+ _recoverAddrSpec(data);
119
297
 
120
- if (address.address === address.name) {
121
- if ((address.address || '').match(/@/)) {
122
- address.name = '';
123
- } else {
124
- address.address = '';
125
- }
298
+ const address = {
299
+ address: data.address || data.text || '',
300
+ name: data.text || data.address || ''
301
+ };
302
+
303
+ if (address.address === address.name) {
304
+ if (/@/.test(address.address || '')) {
305
+ address.name = '';
306
+ } else {
307
+ address.address = '';
126
308
  }
309
+ }
127
310
 
128
- addresses.push(address);
311
+ if (addressFromQuotedText && address.address) {
312
+ address.address = _quoteLocalPart(address.address);
129
313
  }
314
+
315
+ addresses.push(address);
130
316
  }
131
317
 
132
318
  return addresses;
@@ -145,6 +331,7 @@ class Tokenizer {
145
331
  this.operatorExpecting = '';
146
332
  this.node = null;
147
333
  this.escaped = false;
334
+ this.inDomainLiteral = false;
148
335
 
149
336
  this.list = [];
150
337
  /**
@@ -172,11 +359,12 @@ class Tokenizer {
172
359
  * @return {Array} An array of operator|text tokens
173
360
  */
174
361
  tokenize() {
175
- let chr,
176
- list = [];
362
+ const list = [];
363
+
177
364
  for (let i = 0, len = this.str.length; i < len; i++) {
178
- chr = this.str.charAt(i);
179
- this.checkChar(chr);
365
+ const chr = this.str.charAt(i);
366
+ const nextChr = i < len - 1 ? this.str.charAt(i + 1) : null;
367
+ this.checkChar(chr, nextChr);
180
368
  }
181
369
 
182
370
  this.list.forEach(node => {
@@ -194,7 +382,22 @@ class Tokenizer {
194
382
  *
195
383
  * @param {String} chr Character from the address field
196
384
  */
197
- checkChar(chr) {
385
+ 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.
393
+ if (!this.escaped && !this.operatorExpecting) {
394
+ if (!this.inDomainLiteral && chr === '[') {
395
+ this.inDomainLiteral = true;
396
+ } else if (this.inDomainLiteral && (chr === ']' || chr === ',' || chr === ';')) {
397
+ this.inDomainLiteral = false;
398
+ }
399
+ }
400
+
198
401
  if (this.escaped) {
199
402
  // ignore next condition blocks
200
403
  } else if (chr === this.operatorExpecting) {
@@ -202,12 +405,18 @@ class Tokenizer {
202
405
  type: 'operator',
203
406
  value: chr
204
407
  };
408
+
409
+ if (nextChr && ![' ', '\t', '\r', '\n', ',', ';'].includes(nextChr)) {
410
+ this.node.noBreak = true;
411
+ }
412
+
205
413
  this.list.push(this.node);
206
414
  this.node = null;
207
415
  this.operatorExpecting = '';
208
416
  this.escaped = false;
417
+
209
418
  return;
210
- } else if (!this.operatorExpecting && chr in this.operators) {
419
+ } else if (!this.operatorExpecting && !this.inDomainLiteral && chr in this.operators) {
211
420
  this.node = {
212
421
  type: 'operator',
213
422
  value: chr
@@ -245,6 +454,13 @@ class Tokenizer {
245
454
  }
246
455
  }
247
456
 
457
+ /**
458
+ * Maximum recursion depth for parsing nested groups.
459
+ * RFC 5322 doesn't allow nested groups, so this is a safeguard against
460
+ * malicious input that could cause stack overflow.
461
+ */
462
+ const MAX_NESTED_GROUP_DEPTH = 50;
463
+
248
464
  /**
249
465
  * Parses structured e-mail addresses from an address field
250
466
  *
@@ -257,15 +473,23 @@ class Tokenizer {
257
473
  * [{name: 'Name', address: 'address@domain'}]
258
474
  *
259
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)
260
478
  * @return {Array} An array of address objects
261
479
  */
262
480
  function addressparser(str, options) {
263
481
  options = options || {};
482
+ const depth = options._depth || 0;
483
+
484
+ // Prevent stack overflow from deeply nested groups (DoS protection)
485
+ if (depth > MAX_NESTED_GROUP_DEPTH) {
486
+ return [];
487
+ }
264
488
 
265
- let tokenizer = new Tokenizer(str);
266
- let tokens = tokenizer.tokenize();
489
+ const tokenizer = new Tokenizer(str);
490
+ const tokens = tokenizer.tokenize();
267
491
 
268
- let addresses = [];
492
+ const addresses = [];
269
493
  let address = [];
270
494
  let parsedAddresses = [];
271
495
 
@@ -284,30 +508,49 @@ function addressparser(str, options) {
284
508
  addresses.push(address);
285
509
  }
286
510
 
287
- addresses.forEach(address => {
288
- address = _handleAddress(address);
289
- if (address.length) {
290
- parsedAddresses = parsedAddresses.concat(address);
511
+ addresses.forEach(addr => {
512
+ const handled = _handleAddress(addr, depth);
513
+ // Appended in place. Rebuilding the accumulator with concat() would copy every
514
+ // entry collected so far on each address, making a flat list cost O(n^2).
515
+ for (let i = 0; i < handled.length; i++) {
516
+ parsedAddresses.push(handled[i]);
291
517
  }
292
518
  });
293
519
 
520
+ // Merge fragments produced when unquoted display names contain commas.
521
+ // "Joe Foo, PhD <joe@example.com>" is split on the comma into
522
+ // [{name:"Joe Foo", address:""}, {name:"PhD", address:"joe@example.com"}].
523
+ // Recombine: a name-only entry followed by an entry with both name and address.
524
+ // Walked back to front so that a run of fragments folds into one entry in a single
525
+ // pass. Splicing each fragment out of the list instead would cost O(n^2).
526
+ const mergedAddresses = [];
527
+ for (let i = parsedAddresses.length - 1; i >= 0; i--) {
528
+ const current = parsedAddresses[i];
529
+ const next = mergedAddresses.length ? mergedAddresses[mergedAddresses.length - 1] : null;
530
+ if (next && current.address === '' && current.name && !current.group && next.address && next.name) {
531
+ next.name = current.name + ', ' + next.name;
532
+ } else {
533
+ mergedAddresses.push(current);
534
+ }
535
+ }
536
+ mergedAddresses.reverse();
537
+ parsedAddresses = mergedAddresses;
538
+
294
539
  if (options.flatten) {
295
- let addresses = [];
296
- let walkAddressList = list => {
297
- list.forEach(address => {
298
- if (address.group) {
299
- return walkAddressList(address.group);
300
- } else {
301
- addresses.push(address);
540
+ const flatAddresses = [];
541
+ const walkAddressList = list => {
542
+ list.forEach(entry => {
543
+ if (entry.group) {
544
+ return walkAddressList(entry.group);
302
545
  }
546
+ flatAddresses.push(entry);
303
547
  });
304
548
  };
305
549
  walkAddressList(parsedAddresses);
306
- return addresses;
550
+ return flatAddresses;
307
551
  }
308
552
 
309
553
  return parsedAddresses;
310
554
  }
311
555
 
312
- // expose to the world
313
556
  module.exports = addressparser;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const Transform = require('stream').Transform;
3
+ const { Transform } = require('stream');
4
4
 
5
5
  /**
6
6
  * Encodes a Buffer into a base64 encoded string
@@ -31,14 +31,12 @@ function wrap(str, lineLength) {
31
31
  return str;
32
32
  }
33
33
 
34
- let result = [];
34
+ const result = [];
35
35
  let pos = 0;
36
- let chunkLength = lineLength * 1024;
36
+ const chunkLength = lineLength * 1024;
37
+ const wrapRegex = new RegExp('.{' + lineLength + '}', 'g');
37
38
  while (pos < str.length) {
38
- let wrappedLines = str
39
- .substr(pos, chunkLength)
40
- .replace(new RegExp('.{' + lineLength + '}', 'g'), '$&\r\n')
41
- .trim();
39
+ const wrappedLines = str.substr(pos, chunkLength).replace(wrapRegex, '$&\r\n').trim();
42
40
  result.push(wrappedLines);
43
41
  pos += chunkLength;
44
42
  }
@@ -56,7 +54,6 @@ function wrap(str, lineLength) {
56
54
  class Encoder extends Transform {
57
55
  constructor(options) {
58
56
  super();
59
- // init Transform
60
57
  this.options = options || {};
61
58
 
62
59
  if (this.options.lineLength !== false) {
@@ -99,15 +96,15 @@ class Encoder extends Transform {
99
96
  b64 = wrap(b64, this.options.lineLength);
100
97
 
101
98
  // remove last line as it is still most probably incomplete
102
- let lastLF = b64.lastIndexOf('\n');
99
+ const lastLF = b64.lastIndexOf('\n');
103
100
  if (lastLF < 0) {
104
101
  this._curLine = b64;
105
102
  b64 = '';
106
103
  } else if (lastLF === b64.length - 1) {
107
104
  this._curLine = '';
108
105
  } else {
109
- this._curLine = b64.substr(lastLF + 1);
110
- b64 = b64.substr(0, lastLF + 1);
106
+ this._curLine = b64.substring(lastLF + 1);
107
+ b64 = b64.substring(0, lastLF + 1);
111
108
  }
112
109
  }
113
110
 
@@ -127,14 +124,13 @@ class Encoder extends Transform {
127
124
  if (this._curLine) {
128
125
  this._curLine = wrap(this._curLine, this.options.lineLength);
129
126
  this.outputBytes += this._curLine.length;
130
- this.push(this._curLine, 'ascii');
127
+ this.push(Buffer.from(this._curLine, 'ascii'));
131
128
  this._curLine = '';
132
129
  }
133
130
  done();
134
131
  }
135
132
  }
136
133
 
137
- // expose to the world
138
134
  module.exports = {
139
135
  encode,
140
136
  wrap,