@nocobase/plugin-notification-email 2.3.0-beta.12 → 2.3.0-beta.13

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 (48) hide show
  1. package/dist/client/index.js +1 -1
  2. package/dist/client-v2/locale.d.ts +1 -1
  3. package/dist/externalVersion.js +7 -7
  4. package/dist/node_modules/nodemailer/.ncurc.js +2 -0
  5. package/dist/node_modules/nodemailer/.prettierignore +8 -0
  6. package/dist/node_modules/nodemailer/.prettierrc +12 -0
  7. package/dist/node_modules/nodemailer/.prettierrc.js +2 -0
  8. package/dist/node_modules/nodemailer/.release-please-config.json +9 -0
  9. package/dist/node_modules/nodemailer/eslint.config.js +88 -0
  10. package/dist/node_modules/nodemailer/lib/addressparser/index.js +314 -71
  11. package/dist/node_modules/nodemailer/lib/base64/index.js +9 -13
  12. package/dist/node_modules/nodemailer/lib/dkim/index.js +17 -18
  13. package/dist/node_modules/nodemailer/lib/dkim/message-parser.js +12 -13
  14. package/dist/node_modules/nodemailer/lib/dkim/relaxed-body.js +3 -3
  15. package/dist/node_modules/nodemailer/lib/dkim/sign.js +23 -19
  16. package/dist/node_modules/nodemailer/lib/errors.js +59 -0
  17. package/dist/node_modules/nodemailer/lib/fetch/cookies.js +14 -19
  18. package/dist/node_modules/nodemailer/lib/fetch/index.js +164 -27
  19. package/dist/node_modules/nodemailer/lib/json-transport/index.js +4 -4
  20. package/dist/node_modules/nodemailer/lib/mail-composer/index.js +235 -161
  21. package/dist/node_modules/nodemailer/lib/mailer/index.js +106 -52
  22. package/dist/node_modules/nodemailer/lib/mailer/mail-message.js +106 -68
  23. package/dist/node_modules/nodemailer/lib/mime-funcs/index.js +105 -59
  24. package/dist/node_modules/nodemailer/lib/mime-funcs/mime-types.js +21 -14
  25. package/dist/node_modules/nodemailer/lib/mime-node/index.js +399 -138
  26. package/dist/node_modules/nodemailer/lib/mime-node/last-newline.js +1 -1
  27. package/dist/node_modules/nodemailer/lib/mime-node/le-unix.js +4 -7
  28. package/dist/node_modules/nodemailer/lib/mime-node/le-windows.js +1 -4
  29. package/dist/node_modules/nodemailer/lib/nodemailer.js +1 -1
  30. package/dist/node_modules/nodemailer/lib/punycode/index.js +460 -0
  31. package/dist/node_modules/nodemailer/lib/qp/index.js +33 -22
  32. package/dist/node_modules/nodemailer/lib/sendmail-transport/index.js +54 -44
  33. package/dist/node_modules/nodemailer/lib/ses-transport/index.js +110 -193
  34. package/dist/node_modules/nodemailer/lib/shared/index.js +316 -207
  35. package/dist/node_modules/nodemailer/lib/shared/objects.js +42 -0
  36. package/dist/node_modules/nodemailer/lib/shared/url.js +151 -0
  37. package/dist/node_modules/nodemailer/lib/smtp-connection/data-stream.js +3 -6
  38. package/dist/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +50 -19
  39. package/dist/node_modules/nodemailer/lib/smtp-connection/index.js +423 -218
  40. package/dist/node_modules/nodemailer/lib/smtp-pool/index.js +27 -34
  41. package/dist/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +19 -16
  42. package/dist/node_modules/nodemailer/lib/smtp-transport/index.js +66 -53
  43. package/dist/node_modules/nodemailer/lib/stream-transport/index.js +16 -7
  44. package/dist/node_modules/nodemailer/lib/well-known/index.js +7 -7
  45. package/dist/node_modules/nodemailer/lib/well-known/services.json +333 -38
  46. package/dist/node_modules/nodemailer/lib/xoauth2/index.js +105 -33
  47. package/dist/node_modules/nodemailer/package.json +1 -1
  48. package/package.json +3 -3
@@ -4,6 +4,7 @@
4
4
 
5
5
  const MimeNode = require('../mime-node');
6
6
  const mimeFuncs = require('../mime-funcs');
7
+ const { parseDataURI, copyOwnKeys } = require('../shared');
7
8
 
8
9
  /**
9
10
  * Creates the object for composing a MimeNode instance out from the mail options
@@ -31,7 +32,11 @@ class MailComposer {
31
32
 
32
33
  // Compose MIME tree
33
34
  if (this.mail.raw) {
34
- this.message = new MimeNode('message/rfc822', { newline: this.mail.newline }).setRaw(this.mail.raw);
35
+ this.message = new MimeNode('message/rfc822', {
36
+ newline: this.mail.newline,
37
+ disableUrlAccess: this.mail.disableUrlAccess,
38
+ disableFileAccess: this.mail.disableFileAccess
39
+ }).setRaw(this.mail.raw);
35
40
  } else if (this._useMixed) {
36
41
  this.message = this._createMixed();
37
42
  } else if (this._useAlternative) {
@@ -58,7 +63,7 @@ class MailComposer {
58
63
 
59
64
  // Add headers to the root node, always overrides custom headers
60
65
  ['from', 'sender', 'to', 'cc', 'bcc', 'reply-to', 'in-reply-to', 'references', 'subject', 'message-id', 'date'].forEach(header => {
61
- let key = header.replace(/-(\w)/g, (o, c) => c.toUpperCase());
66
+ const key = header.replace(/-(\w)/g, (o, c) => c.toUpperCase());
62
67
  if (this.mail[key]) {
63
68
  this.message.setHeader(header, this.mail[key]);
64
69
  }
@@ -82,23 +87,36 @@ class MailComposer {
82
87
  * @returns {Object} An object of arrays (`related` and `attached`)
83
88
  */
84
89
  getAttachments(findRelated) {
85
- let icalEvent, eventObject;
86
- let attachments = [].concat(this.mail.attachments || []).map((attachment, i) => {
87
- let data;
88
- let isMessageNode = /^message\//i.test(attachment.contentType);
89
-
90
+ let eventObject;
91
+ const attachments = [].concat(this.mail.attachments || []).map((attachment, i) => {
90
92
  if (/^data:/i.test(attachment.path || attachment.href)) {
91
93
  attachment = this._processDataUrl(attachment);
92
94
  }
93
95
 
94
- let contentType = attachment.contentType || mimeFuncs.detectMimeType(attachment.filename || attachment.path || attachment.href || 'bin');
95
- let isImage = /^image\//i.test(contentType);
96
- let contentDisposition = attachment.contentDisposition || (isMessageNode || (isImage && attachment.cid) ? 'inline' : 'attachment');
96
+ const contentType =
97
+ attachment.contentType || mimeFuncs.detectMimeType(attachment.filename || attachment.path || attachment.href || 'bin');
98
+
99
+ const isImage = /^image\//i.test(contentType);
100
+ const isMessageNode = /^message\//i.test(contentType);
101
+
102
+ const contentDisposition =
103
+ attachment.contentDisposition || (isMessageNode || (isImage && attachment.cid) ? 'inline' : 'attachment');
97
104
 
98
- data = {
105
+ let contentTransferEncoding;
106
+ if ('contentTransferEncoding' in attachment) {
107
+ // also contains `false`, to set
108
+ contentTransferEncoding = attachment.contentTransferEncoding;
109
+ } else if (isMessageNode) {
110
+ // the content might include non-ASCII bytes but at this point we do not know it yet
111
+ contentTransferEncoding = '8bit';
112
+ } else {
113
+ contentTransferEncoding = 'base64'; // the default
114
+ }
115
+
116
+ const data = {
99
117
  contentType,
100
118
  contentDisposition,
101
- contentTransferEncoding: 'contentTransferEncoding' in attachment ? attachment.contentTransferEncoding : 'base64'
119
+ contentTransferEncoding
102
120
  };
103
121
 
104
122
  if (attachment.filename) {
@@ -128,7 +146,8 @@ class MailComposer {
128
146
  } else if (attachment.href) {
129
147
  data.content = {
130
148
  href: attachment.href,
131
- httpHeaders: attachment.httpHeaders
149
+ httpHeaders: attachment.httpHeaders,
150
+ tls: attachment.tls
132
151
  };
133
152
  } else {
134
153
  data.content = attachment.content || '';
@@ -146,21 +165,7 @@ class MailComposer {
146
165
  });
147
166
 
148
167
  if (this.mail.icalEvent) {
149
- if (
150
- typeof this.mail.icalEvent === 'object' &&
151
- (this.mail.icalEvent.content || this.mail.icalEvent.path || this.mail.icalEvent.href || this.mail.icalEvent.raw)
152
- ) {
153
- icalEvent = this.mail.icalEvent;
154
- } else {
155
- icalEvent = {
156
- content: this.mail.icalEvent
157
- };
158
- }
159
-
160
- eventObject = {};
161
- Object.keys(icalEvent).forEach(key => {
162
- eventObject[key] = icalEvent[key];
163
- });
168
+ eventObject = Object.assign({}, this._getIcalEvent());
164
169
 
165
170
  eventObject.contentType = 'application/ics';
166
171
  if (!eventObject.headers) {
@@ -176,12 +181,75 @@ class MailComposer {
176
181
  attached: attachments.concat(eventObject || []),
177
182
  related: []
178
183
  };
179
- } else {
180
- return {
181
- attached: attachments.filter(attachment => !attachment.cid).concat(eventObject || []),
182
- related: attachments.filter(attachment => !!attachment.cid)
183
- };
184
184
  }
185
+
186
+ return {
187
+ attached: attachments.filter(attachment => !attachment.cid).concat(eventObject || []),
188
+ related: attachments.filter(attachment => !!attachment.cid)
189
+ };
190
+ }
191
+
192
+ /**
193
+ * Returns the icalEvent value with `path`/`href`/data uri input normalized into
194
+ * a `content` entry, the same way as for regular attachments. The same event is
195
+ * included twice (as a text/calendar alternative and as an application/ics
196
+ * attachment), so the shared content object is marked to be resolved just once
197
+ * and the buffered result is reused by the second node.
198
+ *
199
+ * @returns {Object} Normalized icalEvent data
200
+ */
201
+ _getIcalEvent() {
202
+ if (!this._icalEvent) {
203
+ let icalEvent;
204
+ if (
205
+ typeof this.mail.icalEvent === 'object' &&
206
+ (this.mail.icalEvent.content || this.mail.icalEvent.path || this.mail.icalEvent.href || this.mail.icalEvent.raw)
207
+ ) {
208
+ // an own "__proto__" key would make the copy inherit path/href from caller
209
+ // data, and the mapping below then replaces the content the caller did set
210
+ icalEvent = copyOwnKeys({}, this.mail.icalEvent);
211
+ } else {
212
+ icalEvent = {
213
+ content: this.mail.icalEvent
214
+ };
215
+ }
216
+
217
+ if (/^data:/i.test(icalEvent.path || icalEvent.href)) {
218
+ icalEvent = this._processDataUrl(icalEvent);
219
+ }
220
+
221
+ if (/^https?:\/\//i.test(icalEvent.path)) {
222
+ icalEvent.href = icalEvent.path;
223
+ icalEvent.path = undefined;
224
+ }
225
+
226
+ if (!icalEvent.raw) {
227
+ // map file path and URL values into `content`, otherwise the content
228
+ // nodes would render an empty body
229
+ if (icalEvent.path) {
230
+ icalEvent.content = {
231
+ path: icalEvent.path
232
+ };
233
+ icalEvent.path = undefined;
234
+ } else if (icalEvent.href) {
235
+ icalEvent.content = {
236
+ href: icalEvent.href,
237
+ httpHeaders: icalEvent.httpHeaders
238
+ };
239
+ icalEvent.href = undefined;
240
+ }
241
+ }
242
+
243
+ if (icalEvent.content && typeof icalEvent.content === 'object') {
244
+ // we are going to have the same attachment twice, so mark this to be
245
+ // resolved just once
246
+ icalEvent.content._resolve = true;
247
+ }
248
+
249
+ this._icalEvent = icalEvent;
250
+ }
251
+
252
+ return this._icalEvent;
185
253
  }
186
254
 
187
255
  /**
@@ -190,16 +258,14 @@ class MailComposer {
190
258
  * @returns {Array} An array of alternative elements. Includes the `text` and `html` values as well
191
259
  */
192
260
  getAlternatives() {
193
- let alternatives = [],
194
- text,
195
- html,
196
- watchHtml,
197
- amp,
198
- icalEvent,
199
- eventObject;
261
+ const alternatives = [];
262
+ let text, html, watchHtml, amp, eventObject;
200
263
 
201
264
  if (this.mail.text) {
202
- if (typeof this.mail.text === 'object' && (this.mail.text.content || this.mail.text.path || this.mail.text.href || this.mail.text.raw)) {
265
+ if (
266
+ typeof this.mail.text === 'object' &&
267
+ (this.mail.text.content || this.mail.text.path || this.mail.text.href || this.mail.text.raw)
268
+ ) {
203
269
  text = this.mail.text;
204
270
  } else {
205
271
  text = {
@@ -224,7 +290,10 @@ class MailComposer {
224
290
  }
225
291
 
226
292
  if (this.mail.amp) {
227
- if (typeof this.mail.amp === 'object' && (this.mail.amp.content || this.mail.amp.path || this.mail.amp.href || this.mail.amp.raw)) {
293
+ if (
294
+ typeof this.mail.amp === 'object' &&
295
+ (this.mail.amp.content || this.mail.amp.path || this.mail.amp.href || this.mail.amp.raw)
296
+ ) {
228
297
  amp = this.mail.amp;
229
298
  } else {
230
299
  amp = {
@@ -236,37 +305,21 @@ class MailComposer {
236
305
 
237
306
  // NB! when including attachments with a calendar alternative you might end up in a blank screen on some clients
238
307
  if (this.mail.icalEvent) {
239
- if (
240
- typeof this.mail.icalEvent === 'object' &&
241
- (this.mail.icalEvent.content || this.mail.icalEvent.path || this.mail.icalEvent.href || this.mail.icalEvent.raw)
242
- ) {
243
- icalEvent = this.mail.icalEvent;
244
- } else {
245
- icalEvent = {
246
- content: this.mail.icalEvent
247
- };
248
- }
249
-
250
- eventObject = {};
251
- Object.keys(icalEvent).forEach(key => {
252
- eventObject[key] = icalEvent[key];
253
- });
254
-
255
- if (eventObject.content && typeof eventObject.content === 'object') {
256
- // we are going to have the same attachment twice, so mark this to be
257
- // resolved just once
258
- eventObject.content._resolve = true;
259
- }
308
+ eventObject = Object.assign({}, this._getIcalEvent());
260
309
 
261
310
  eventObject.filename = false;
262
- eventObject.contentType = 'text/calendar; charset=utf-8; method=' + (eventObject.method || 'PUBLISH').toString().trim().toUpperCase();
311
+ eventObject.contentType =
312
+ 'text/calendar; charset=utf-8; method=' + (eventObject.method || 'PUBLISH').toString().trim().toUpperCase();
263
313
  if (!eventObject.headers) {
264
314
  eventObject.headers = {};
265
315
  }
266
316
  }
267
317
 
268
318
  if (this.mail.html) {
269
- if (typeof this.mail.html === 'object' && (this.mail.html.content || this.mail.html.path || this.mail.html.href || this.mail.html.raw)) {
319
+ if (
320
+ typeof this.mail.html === 'object' &&
321
+ (this.mail.html.content || this.mail.html.path || this.mail.html.href || this.mail.html.raw)
322
+ ) {
270
323
  html = this.mail.html;
271
324
  } else {
272
325
  html = {
@@ -284,14 +337,14 @@ class MailComposer {
284
337
  .concat(eventObject || [])
285
338
  .concat(this.mail.alternatives || [])
286
339
  .forEach(alternative => {
287
- let data;
288
-
289
340
  if (/^data:/i.test(alternative.path || alternative.href)) {
290
341
  alternative = this._processDataUrl(alternative);
291
342
  }
292
343
 
293
- data = {
294
- contentType: alternative.contentType || mimeFuncs.detectMimeType(alternative.filename || alternative.path || alternative.href || 'txt'),
344
+ const data = {
345
+ contentType:
346
+ alternative.contentType ||
347
+ mimeFuncs.detectMimeType(alternative.filename || alternative.path || alternative.href || 'txt'),
295
348
  contentTransferEncoding: alternative.contentTransferEncoding
296
349
  };
297
350
 
@@ -340,26 +393,22 @@ class MailComposer {
340
393
  * @returns {Object} MimeNode node element
341
394
  */
342
395
  _createMixed(parentNode) {
343
- let node;
344
-
345
- if (!parentNode) {
346
- node = new MimeNode('multipart/mixed', {
347
- baseBoundary: this.mail.baseBoundary,
348
- textEncoding: this.mail.textEncoding,
349
- boundaryPrefix: this.mail.boundaryPrefix,
350
- disableUrlAccess: this.mail.disableUrlAccess,
351
- disableFileAccess: this.mail.disableFileAccess,
352
- normalizeHeaderKey: this.mail.normalizeHeaderKey,
353
- newline: this.mail.newline
354
- });
355
- } else {
356
- node = parentNode.createChild('multipart/mixed', {
357
- disableUrlAccess: this.mail.disableUrlAccess,
358
- disableFileAccess: this.mail.disableFileAccess,
359
- normalizeHeaderKey: this.mail.normalizeHeaderKey,
360
- newline: this.mail.newline
361
- });
362
- }
396
+ const node = parentNode
397
+ ? parentNode.createChild('multipart/mixed', {
398
+ disableUrlAccess: this.mail.disableUrlAccess,
399
+ disableFileAccess: this.mail.disableFileAccess,
400
+ normalizeHeaderKey: this.mail.normalizeHeaderKey,
401
+ newline: this.mail.newline
402
+ })
403
+ : new MimeNode('multipart/mixed', {
404
+ baseBoundary: this.mail.baseBoundary,
405
+ textEncoding: this.mail.textEncoding,
406
+ boundaryPrefix: this.mail.boundaryPrefix,
407
+ disableUrlAccess: this.mail.disableUrlAccess,
408
+ disableFileAccess: this.mail.disableFileAccess,
409
+ normalizeHeaderKey: this.mail.normalizeHeaderKey,
410
+ newline: this.mail.newline
411
+ });
363
412
 
364
413
  if (this._useAlternative) {
365
414
  this._createAlternative(node);
@@ -388,26 +437,22 @@ class MailComposer {
388
437
  * @returns {Object} MimeNode node element
389
438
  */
390
439
  _createAlternative(parentNode) {
391
- let node;
392
-
393
- if (!parentNode) {
394
- node = new MimeNode('multipart/alternative', {
395
- baseBoundary: this.mail.baseBoundary,
396
- textEncoding: this.mail.textEncoding,
397
- boundaryPrefix: this.mail.boundaryPrefix,
398
- disableUrlAccess: this.mail.disableUrlAccess,
399
- disableFileAccess: this.mail.disableFileAccess,
400
- normalizeHeaderKey: this.mail.normalizeHeaderKey,
401
- newline: this.mail.newline
402
- });
403
- } else {
404
- node = parentNode.createChild('multipart/alternative', {
405
- disableUrlAccess: this.mail.disableUrlAccess,
406
- disableFileAccess: this.mail.disableFileAccess,
407
- normalizeHeaderKey: this.mail.normalizeHeaderKey,
408
- newline: this.mail.newline
409
- });
410
- }
440
+ const node = parentNode
441
+ ? parentNode.createChild('multipart/alternative', {
442
+ disableUrlAccess: this.mail.disableUrlAccess,
443
+ disableFileAccess: this.mail.disableFileAccess,
444
+ normalizeHeaderKey: this.mail.normalizeHeaderKey,
445
+ newline: this.mail.newline
446
+ })
447
+ : new MimeNode('multipart/alternative', {
448
+ baseBoundary: this.mail.baseBoundary,
449
+ textEncoding: this.mail.textEncoding,
450
+ boundaryPrefix: this.mail.boundaryPrefix,
451
+ disableUrlAccess: this.mail.disableUrlAccess,
452
+ disableFileAccess: this.mail.disableFileAccess,
453
+ normalizeHeaderKey: this.mail.normalizeHeaderKey,
454
+ newline: this.mail.newline
455
+ });
411
456
 
412
457
  this._alternatives.forEach(alternative => {
413
458
  if (this._useRelated && this._htmlNode === alternative) {
@@ -427,26 +472,22 @@ class MailComposer {
427
472
  * @returns {Object} MimeNode node element
428
473
  */
429
474
  _createRelated(parentNode) {
430
- let node;
431
-
432
- if (!parentNode) {
433
- node = new MimeNode('multipart/related; type="text/html"', {
434
- baseBoundary: this.mail.baseBoundary,
435
- textEncoding: this.mail.textEncoding,
436
- boundaryPrefix: this.mail.boundaryPrefix,
437
- disableUrlAccess: this.mail.disableUrlAccess,
438
- disableFileAccess: this.mail.disableFileAccess,
439
- normalizeHeaderKey: this.mail.normalizeHeaderKey,
440
- newline: this.mail.newline
441
- });
442
- } else {
443
- node = parentNode.createChild('multipart/related; type="text/html"', {
444
- disableUrlAccess: this.mail.disableUrlAccess,
445
- disableFileAccess: this.mail.disableFileAccess,
446
- normalizeHeaderKey: this.mail.normalizeHeaderKey,
447
- newline: this.mail.newline
448
- });
449
- }
475
+ const node = parentNode
476
+ ? parentNode.createChild('multipart/related; type="text/html"', {
477
+ disableUrlAccess: this.mail.disableUrlAccess,
478
+ disableFileAccess: this.mail.disableFileAccess,
479
+ normalizeHeaderKey: this.mail.normalizeHeaderKey,
480
+ newline: this.mail.newline
481
+ })
482
+ : new MimeNode('multipart/related; type="text/html"', {
483
+ baseBoundary: this.mail.baseBoundary,
484
+ textEncoding: this.mail.textEncoding,
485
+ boundaryPrefix: this.mail.boundaryPrefix,
486
+ disableUrlAccess: this.mail.disableUrlAccess,
487
+ disableFileAccess: this.mail.disableFileAccess,
488
+ normalizeHeaderKey: this.mail.normalizeHeaderKey,
489
+ newline: this.mail.newline
490
+ });
450
491
 
451
492
  this._createContentNode(node, this._htmlNode);
452
493
 
@@ -466,33 +507,30 @@ class MailComposer {
466
507
  element = element || {};
467
508
  element.content = element.content || '';
468
509
 
469
- let node;
470
- let encoding = (element.encoding || 'utf8')
510
+ const encoding = (element.encoding || 'utf8')
471
511
  .toString()
472
512
  .toLowerCase()
473
513
  .replace(/[-_\s]/g, '');
474
514
 
475
- if (!parentNode) {
476
- node = new MimeNode(element.contentType, {
477
- filename: element.filename,
478
- baseBoundary: this.mail.baseBoundary,
479
- textEncoding: this.mail.textEncoding,
480
- boundaryPrefix: this.mail.boundaryPrefix,
481
- disableUrlAccess: this.mail.disableUrlAccess,
482
- disableFileAccess: this.mail.disableFileAccess,
483
- normalizeHeaderKey: this.mail.normalizeHeaderKey,
484
- newline: this.mail.newline
485
- });
486
- } else {
487
- node = parentNode.createChild(element.contentType, {
488
- filename: element.filename,
489
- textEncoding: this.mail.textEncoding,
490
- disableUrlAccess: this.mail.disableUrlAccess,
491
- disableFileAccess: this.mail.disableFileAccess,
492
- normalizeHeaderKey: this.mail.normalizeHeaderKey,
493
- newline: this.mail.newline
494
- });
495
- }
515
+ const node = parentNode
516
+ ? parentNode.createChild(element.contentType, {
517
+ filename: element.filename,
518
+ textEncoding: this.mail.textEncoding,
519
+ disableUrlAccess: this.mail.disableUrlAccess,
520
+ disableFileAccess: this.mail.disableFileAccess,
521
+ normalizeHeaderKey: this.mail.normalizeHeaderKey,
522
+ newline: this.mail.newline
523
+ })
524
+ : new MimeNode(element.contentType, {
525
+ filename: element.filename,
526
+ baseBoundary: this.mail.baseBoundary,
527
+ textEncoding: this.mail.textEncoding,
528
+ boundaryPrefix: this.mail.boundaryPrefix,
529
+ disableUrlAccess: this.mail.disableUrlAccess,
530
+ disableFileAccess: this.mail.disableFileAccess,
531
+ normalizeHeaderKey: this.mail.normalizeHeaderKey,
532
+ newline: this.mail.newline
533
+ });
496
534
 
497
535
  // add custom headers
498
536
  if (element.headers) {
@@ -537,12 +575,54 @@ class MailComposer {
537
575
  * @return {Object} Parsed element
538
576
  */
539
577
  _processDataUrl(element) {
540
- let parts = (element.path || element.href).match(/^data:((?:[^;]*;)*(?:[^,]*)),(.*)$/i);
541
- if (!parts) {
578
+ const dataUrl = element.path || element.href;
579
+
580
+ // Early validation to prevent ReDoS
581
+ if (!dataUrl || typeof dataUrl !== 'string') {
582
+ return element;
583
+ }
584
+
585
+ if (!dataUrl.startsWith('data:')) {
542
586
  return element;
543
587
  }
544
588
 
545
- element.content = /\bbase64$/i.test(parts[1]) ? Buffer.from(parts[2], 'base64') : Buffer.from(decodeURIComponent(parts[2]));
589
+ if (dataUrl.length > 52428800) {
590
+ // 52428800 chars = 50MB limit for data URL string (~37.5MB decoded image)
591
+ // Extract content type before rejecting to preserve MIME type
592
+ let detectedType = 'application/octet-stream';
593
+ const commaPos = dataUrl.indexOf(',');
594
+
595
+ if (commaPos > 0 && commaPos < 200) {
596
+ // Parse header safely with size limit
597
+ const header = dataUrl.substring(5, commaPos); // skip 'data:'
598
+ const parts = header.split(';');
599
+ if (parts[0] && parts[0].includes('/')) {
600
+ detectedType = parts[0].trim();
601
+ }
602
+ }
603
+
604
+ // Return empty content for excessively long data URLs
605
+ return Object.assign(copyOwnKeys({}, element), {
606
+ path: false,
607
+ href: false,
608
+ content: Buffer.alloc(0),
609
+ contentType: element.contentType || detectedType
610
+ });
611
+ }
612
+
613
+ let parsedDataUri;
614
+ try {
615
+ parsedDataUri = parseDataURI(dataUrl);
616
+ } catch (_err) {
617
+ return element;
618
+ }
619
+
620
+ if (!parsedDataUri) {
621
+ return element;
622
+ }
623
+
624
+ element.content = parsedDataUri.data;
625
+ element.contentType = element.contentType || parsedDataUri.contentType;
546
626
 
547
627
  if ('path' in element) {
548
628
  element.path = false;
@@ -552,12 +632,6 @@ class MailComposer {
552
632
  element.href = false;
553
633
  }
554
634
 
555
- parts[1].split(';').forEach(item => {
556
- if (/^\w+\/[^/]+$/i.test(item)) {
557
- element.contentType = element.contentType || item.toLowerCase();
558
- }
559
- });
560
-
561
635
  return element;
562
636
  }
563
637
  }