nodemailer 10.0.13 → 10.0.15

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 (51) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/cjs/addressparser/index.d.ts +0 -1
  3. package/dist/cjs/addressparser/index.js +55 -17
  4. package/dist/cjs/dkim/message-parser.js +28 -4
  5. package/dist/cjs/dkim/sign.d.ts +1 -0
  6. package/dist/cjs/dkim/sign.js +1 -0
  7. package/dist/cjs/errors.d.ts +3 -3
  8. package/dist/cjs/mailer/index.d.ts +7 -1
  9. package/dist/cjs/mime-funcs/index.d.ts +2 -1
  10. package/dist/cjs/mime-funcs/index.js +197 -83
  11. package/dist/cjs/mime-node/index.js +2 -1
  12. package/dist/cjs/nodemailer.d.ts +13 -2
  13. package/dist/cjs/package-info.d.ts +1 -1
  14. package/dist/cjs/package-info.js +1 -1
  15. package/dist/cjs/punycode/index.js +1 -0
  16. package/dist/cjs/ses-transport/index.d.ts +6 -1
  17. package/dist/cjs/shared/index.d.ts +25 -1
  18. package/dist/cjs/shared/index.js +1 -0
  19. package/dist/cjs/smtp-connection/http-proxy-client.d.ts +11 -3
  20. package/dist/cjs/smtp-connection/http-proxy-client.js +17 -7
  21. package/dist/cjs/smtp-connection/index.d.ts +1 -1
  22. package/dist/cjs/smtp-connection/index.js +15 -1
  23. package/dist/cjs/smtp-pool/index.d.ts +6 -1
  24. package/dist/cjs/smtp-transport/index.d.ts +6 -1
  25. package/dist/cjs/well-known/services.js +6 -0
  26. package/dist/esm/addressparser/index.d.ts +0 -1
  27. package/dist/esm/addressparser/index.js +55 -17
  28. package/dist/esm/dkim/message-parser.js +28 -4
  29. package/dist/esm/dkim/sign.d.ts +1 -0
  30. package/dist/esm/dkim/sign.js +1 -0
  31. package/dist/esm/errors.d.ts +3 -3
  32. package/dist/esm/mailer/index.d.ts +7 -1
  33. package/dist/esm/mime-funcs/index.d.ts +2 -1
  34. package/dist/esm/mime-funcs/index.js +197 -83
  35. package/dist/esm/mime-node/index.js +2 -1
  36. package/dist/esm/nodemailer.d.ts +13 -2
  37. package/dist/esm/package-info.d.ts +1 -1
  38. package/dist/esm/package-info.js +1 -1
  39. package/dist/esm/punycode/index.js +1 -0
  40. package/dist/esm/ses-transport/index.d.ts +6 -1
  41. package/dist/esm/shared/index.d.ts +25 -1
  42. package/dist/esm/shared/index.js +1 -0
  43. package/dist/esm/smtp-connection/http-proxy-client.d.ts +11 -3
  44. package/dist/esm/smtp-connection/http-proxy-client.js +17 -7
  45. package/dist/esm/smtp-connection/index.d.ts +1 -1
  46. package/dist/esm/smtp-connection/index.js +15 -1
  47. package/dist/esm/smtp-pool/index.d.ts +6 -1
  48. package/dist/esm/smtp-transport/index.d.ts +6 -1
  49. package/dist/esm/well-known/services.js +6 -0
  50. package/dist/well-known/services.json +7 -0
  51. package/package.json +8 -8
@@ -614,7 +614,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
614
614
  *
615
615
  * @param envelope Envelope object, {from: addr, to: [addr]}
616
616
  * @param message String, Buffer or a Stream
617
- * @param callback Callback to return once sending is completed
617
+ * @param done Callback to return once sending is completed
618
618
  */
619
619
  send(envelope, message, done) {
620
620
  // ensure that the callback is only called once. The public callback type has a
@@ -827,6 +827,8 @@ class SMTPConnection extends node_events_1.EventEmitter {
827
827
  * @event
828
828
  * @param err Error object
829
829
  * @param type Error name
830
+ * @param data Server response that triggered the error, false if there is none
831
+ * @param command SMTP command that was in flight
830
832
  * @internal
831
833
  */
832
834
  _onError(err, type, data, command) {
@@ -1095,6 +1097,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
1095
1097
  * {from:'...', to:['...']}
1096
1098
  * or
1097
1099
  * {from:{address:'...',name:'...'}, to:[address:'...',name:'...']}
1100
+ * @param callback Callback to run once the envelope is processed
1098
1101
  * @internal
1099
1102
  */
1100
1103
  _setEnvelope(envelope, callback) {
@@ -1489,6 +1492,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
1489
1492
  * hosts invalidly use a longer message than VXNlcm5hbWU6
1490
1493
  *
1491
1494
  * @param str Message from the server
1495
+ * @param callback Callback to run once the authentication sequence completes
1492
1496
  * @internal
1493
1497
  */
1494
1498
  _actionAUTH_LOGIN_USER(str, callback) {
@@ -1510,6 +1514,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
1510
1514
  * base64 encoded again.
1511
1515
  *
1512
1516
  * @param str Message from the server
1517
+ * @param callback Callback to run once the authentication sequence completes
1513
1518
  * @internal
1514
1519
  */
1515
1520
  _actionAUTH_CRAM_MD5(str, callback) {
@@ -1534,6 +1539,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
1534
1539
  * the user can be considered logged in. Start waiting for a message to send
1535
1540
  *
1536
1541
  * @param str Message from the server
1542
+ * @param callback Callback to run once the authentication sequence completes
1537
1543
  * @internal
1538
1544
  */
1539
1545
  _actionAUTH_CRAM_MD5_PASS(str, callback) {
@@ -1555,6 +1561,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
1555
1561
  * response needs to be base64 encoded password.
1556
1562
  *
1557
1563
  * @param str Message from the server
1564
+ * @param callback Callback to run once the authentication sequence completes
1558
1565
  * @internal
1559
1566
  */
1560
1567
  _actionAUTH_LOGIN_PASS(str, callback) {
@@ -1574,6 +1581,8 @@ class SMTPConnection extends node_events_1.EventEmitter {
1574
1581
  * the user can be considered logged in. Start waiting for a message to send
1575
1582
  *
1576
1583
  * @param str Message from the server
1584
+ * @param isRetry True if this is a retry after a failed login, or the callback itself
1585
+ * @param [callback] Callback to run once the authentication sequence completes
1577
1586
  * @internal
1578
1587
  */
1579
1588
  _actionAUTHComplete(str, isRetry, callback) {
@@ -1616,6 +1625,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
1616
1625
  * Handle response for a MAIL FROM: command
1617
1626
  *
1618
1627
  * @param str Message from the server
1628
+ * @param callback Callback to run once the envelope is processed
1619
1629
  * @internal
1620
1630
  */
1621
1631
  _actionMAIL(str, callback) {
@@ -1644,6 +1654,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
1644
1654
  * Handle response for a RCPT TO: command
1645
1655
  *
1646
1656
  * @param str Message from the server
1657
+ * @param callback Callback to run once the envelope is processed
1647
1658
  * @internal
1648
1659
  */
1649
1660
  _actionRCPT(str, callback) {
@@ -1695,6 +1706,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
1695
1706
  * Handle response for a DATA command
1696
1707
  *
1697
1708
  * @param str Message from the server
1709
+ * @param callback Callback to run once the envelope is processed
1698
1710
  * @internal
1699
1711
  */
1700
1712
  _actionDATA(str, callback) {
@@ -1721,6 +1733,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
1721
1733
  * We expect a single response that defines if the sending succeeded or failed
1722
1734
  *
1723
1735
  * @param str Message from the server
1736
+ * @param callback Callback to run with the final send result
1724
1737
  * @internal
1725
1738
  */
1726
1739
  _actionSMTPStream(str, callback) {
@@ -1737,6 +1750,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
1737
1750
  * @param recipient The recipient this response applies to
1738
1751
  * @param final Is this the final recipient?
1739
1752
  * @param str Message from the server
1753
+ * @param callback Callback to run with the final send result
1740
1754
  * @internal
1741
1755
  */
1742
1756
  _actionLMTPStream(recipient, final, str, callback) {
@@ -110,9 +110,14 @@ declare class SMTPPool extends EventEmitter {
110
110
  /**
111
111
  * Verifies SMTP configuration
112
112
  *
113
- * @param callback Callback function
113
+ * @returns Promise that resolves to true if the configuration is usable
114
114
  */
115
115
  verify(): Promise<true>;
116
+ /**
117
+ * Verifies SMTP configuration
118
+ *
119
+ * @param callback Callback function
120
+ */
116
121
  verify(callback: VerifyCallback): void;
117
122
  }
118
123
  /**
@@ -125,9 +125,14 @@ declare class SMTPTransport extends EventEmitter {
125
125
  /**
126
126
  * Verifies SMTP configuration
127
127
  *
128
- * @param callback Callback function
128
+ * @returns Promise that resolves to true if the configuration is usable
129
129
  */
130
130
  verify(): Promise<true>;
131
+ /**
132
+ * Verifies SMTP configuration
133
+ *
134
+ * @param callback Callback function
135
+ */
131
136
  verify(callback: VerifyCallback): void;
132
137
  /**
133
138
  * Releases resources
@@ -314,6 +314,12 @@ exports.services = {
314
314
  "host": "mailosaur.io",
315
315
  "port": 25
316
316
  },
317
+ "MailSenpai": {
318
+ "description": "MailSenpai (SMTP Senpai, EU)",
319
+ "host": "relay.mailsenpai.com",
320
+ "port": 2525,
321
+ "secure": false
322
+ },
317
323
  "Mailtrap": {
318
324
  "description": "Mailtrap",
319
325
  "host": "live.smtp.mailtrap.io",
@@ -39,7 +39,6 @@ export type Address = MailboxAddress | GroupAddress;
39
39
  *
40
40
  * @param str Address field
41
41
  * @param options Optional options object
42
- * @param options._depth Internal recursion depth counter (do not set manually)
43
42
  * @return An array of address objects
44
43
  */
45
44
  declare function addressparser(str: string | null | undefined, options: AddressParserOptions & {
@@ -426,7 +426,7 @@ function _handleAddress(tokens, depth) {
426
426
  // Parse group members, but flatten any nested groups (RFC 5322 doesn't allow nesting)
427
427
  let groupMembers = [];
428
428
  if (data.group.length) {
429
- const parsedGroup = addressparser(data.group.join(','), { _depth: depth + 1 });
429
+ const parsedGroup = _parseAddressList(data.group.join(','), depth + 1);
430
430
  parsedGroup.forEach(member => {
431
431
  if (member.group) {
432
432
  groupMembers = groupMembers.concat(member.group);
@@ -513,7 +513,13 @@ function _handleAddress(tokens, depth) {
513
513
  // Join values with spaces
514
514
  data.text = data.text.join(' ');
515
515
  data.address = data.address.join(' ');
516
- if (addressFromQuotedText && data.text) {
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
+ //
517
523
  // The mailbox is still sitting in the text, so it moves over here and is quoted
518
524
  // before the recovery below rather than after it. Anything else the text holds
519
525
  // came along with it: a comment ends the domain but leaves the atoms behind it in
@@ -560,6 +566,7 @@ class Tokenizer {
560
566
  this.node = null;
561
567
  this.escaped = false;
562
568
  this.inDomainLiteral = false;
569
+ this.afterAt = false;
563
570
  this.list = [];
564
571
  /**
565
572
  * Operator tokens and which tokens are expected to end the sequence
@@ -603,17 +610,23 @@ class Tokenizer {
603
610
  * Checks if a character is an operator or text and acts accordingly
604
611
  *
605
612
  * @param chr Character from the address field
613
+ * @param nextChr Character following chr, null at the end of the field
606
614
  */
607
615
  checkChar(chr, nextChr) {
608
- // Track RFC 5322 domain-literals ("[" *dtext "]"). Operator characters such
609
- // as the ":" of an IPv6 address-literal (user@[IPv6:2001:db8::1]) are dtext
610
- // and must not be treated as the group delimiter while inside the brackets.
611
- // Quoted strings and comments are handled separately via operatorExpecting,
612
- // so only enter this state when no operator is open. The list separators ","
613
- // and ";" are the exception: they always end the literal (and split the
614
- // 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.
615
628
  if (!this.escaped && !this.operatorExpecting) {
616
- if (!this.inDomainLiteral && chr === '[') {
629
+ if (!this.inDomainLiteral && chr === '[' && this.afterAt) {
617
630
  this.inDomainLiteral = true;
618
631
  }
619
632
  else if (this.inDomainLiteral && (chr === ']' || chr === ',' || chr === ';')) {
@@ -633,17 +646,24 @@ class Tokenizer {
633
646
  }
634
647
  this.list.push(this.node);
635
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
+ }
636
653
  this.operatorExpecting = '';
637
654
  this.escaped = false;
638
655
  return;
639
656
  }
640
- else if (!this.operatorExpecting && !this.inDomainLiteral && chr in this.operators) {
657
+ else if (!this.operatorExpecting && !(this.inDomainLiteral && chr === ':') && chr in this.operators) {
641
658
  this.node = {
642
659
  type: 'operator',
643
660
  value: chr
644
661
  };
645
662
  this.list.push(this.node);
646
663
  this.node = null;
664
+ if (chr !== '(') {
665
+ this.afterAt = false;
666
+ }
647
667
  this.operatorExpecting = this.operators[chr];
648
668
  this.escaped = false;
649
669
  return;
@@ -667,6 +687,11 @@ class Tokenizer {
667
687
  if (chr.charCodeAt(0) >= 0x21 || [' ', '\t'].includes(chr)) {
668
688
  // skip command bytes
669
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
+ }
670
695
  }
671
696
  this.escaped = false;
672
697
  }
@@ -677,9 +702,19 @@ class Tokenizer {
677
702
  * malicious input that could cause stack overflow.
678
703
  */
679
704
  const MAX_NESTED_GROUP_DEPTH = 50;
680
- function addressparser(str, options) {
681
- options = options || {};
682
- const depth = options._depth || 0;
705
+ /**
706
+ * Parses an address list, recursing into the groups it holds.
707
+ *
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.
712
+ *
713
+ * @param str Address field
714
+ * @param depth Current recursion depth for nested group protection
715
+ * @return An array of address objects
716
+ */
717
+ function _parseAddressList(str, depth) {
683
718
  // Prevent stack overflow from deeply nested groups (DoS protection)
684
719
  if (depth > MAX_NESTED_GROUP_DEPTH) {
685
720
  return [];
@@ -688,7 +723,7 @@ function addressparser(str, options) {
688
723
  const tokens = tokenizer.tokenize();
689
724
  const addresses = [];
690
725
  let address = [];
691
- let parsedAddresses = [];
726
+ const parsedAddresses = [];
692
727
  tokens.forEach(token => {
693
728
  if (token.type === 'operator' && (token.value === ',' || token.value === ';')) {
694
729
  if (address.length) {
@@ -729,8 +764,11 @@ function addressparser(str, options) {
729
764
  }
730
765
  }
731
766
  mergedAddresses.reverse();
732
- parsedAddresses = mergedAddresses;
733
- if (options.flatten) {
767
+ return mergedAddresses;
768
+ }
769
+ function addressparser(str, options) {
770
+ const parsedAddresses = _parseAddressList(str, 0);
771
+ if (options?.flatten) {
734
772
  const flatAddresses = [];
735
773
  const walkAddressList = (list) => {
736
774
  list.forEach(entry => {
@@ -1,4 +1,31 @@
1
1
  import { Transform } from 'node:stream';
2
+ /**
3
+ * Drops the SP and HTAB from both ends of a field name.
4
+ *
5
+ * An index scan rather than `/^[ \t]+|[ \t]+$/g`, which retries the trailing branch at
6
+ * every offset of a blank run that is followed by other text: the run is consumed greedily,
7
+ * `$` fails, and the engine gives the characters back one at a time before moving on to
8
+ * start the same walk one offset further in. A field name carrying a 128 KiB run of spaces
9
+ * between two atoms took about 6.7 seconds to trim, and the cost grows with its square.
10
+ *
11
+ * Not `.trim()`, which takes the other Unicode spaces along: an NBSP in front of a field
12
+ * name would come off and select the name into the signed set, where the relaxed
13
+ * canonicalization in sign.ts deliberately leaves everything but SP and HTAB alone.
14
+ *
15
+ * @param str Field name to trim
16
+ * @return The field name without the surrounding SP and HTAB
17
+ */
18
+ function _trimFieldName(str) {
19
+ let start = 0;
20
+ let end = str.length;
21
+ while (start < end && (str.charCodeAt(start) === 0x20 || str.charCodeAt(start) === 0x09)) {
22
+ start++;
23
+ }
24
+ while (end > start && (str.charCodeAt(end - 1) === 0x20 || str.charCodeAt(end - 1) === 0x09)) {
25
+ end--;
26
+ }
27
+ return str.slice(start, end);
28
+ }
2
29
  /**
3
30
  * MessageParser instance is a transform stream that separates message headers
4
31
  * from the rest of the body. Headers are emitted with the 'headers' event. Message
@@ -141,10 +168,7 @@ class MessageParser extends Transform {
141
168
  return lines
142
169
  .filter(line => /[^ \t\r]/.test(line))
143
170
  .map(line => ({
144
- key: line
145
- .substr(0, line.indexOf(':'))
146
- .replace(/^[ \t]+|[ \t]+$/g, '')
147
- .toLowerCase(),
171
+ key: _trimFieldName(line.substr(0, line.indexOf(':'))).toLowerCase(),
148
172
  line
149
173
  }));
150
174
  }
@@ -36,6 +36,7 @@ export interface DKIMRelaxedHeaders {
36
36
  * Returns DKIM signature header line
37
37
  *
38
38
  * @param headers Parsed headers object from MessageParser
39
+ * @param hashAlgo Hash algorithm the body hash was calculated with, for example "sha256"
39
40
  * @param bodyHash Base64 encoded hash of the message
40
41
  * @param options DKIM options
41
42
  * @param options.domainName Domain name to be signed for
@@ -14,6 +14,7 @@ function unsupportedHashAlgoError(hashAlgo) {
14
14
  * Returns DKIM signature header line
15
15
  *
16
16
  * @param headers Parsed headers object from MessageParser
17
+ * @param hashAlgo Hash algorithm the body hash was calculated with, for example "sha256"
17
18
  * @param bodyHash Base64 encoded hash of the message
18
19
  * @param options DKIM options
19
20
  * @param options.domainName Domain name to be signed for
@@ -65,11 +65,11 @@ export declare const EFETCH = "EFETCH";
65
65
  * An Error together with the properties Nodemailer attaches to the errors it
66
66
  * hands to callers. Every property is optional, the set that is present
67
67
  * depends on where the error originated. Socket level errors keep the errno
68
- * and syscall fields Node.js sets on them.
68
+ * and syscall fields Node.js sets on them. `code` (see ERROR_CODES) is
69
+ * inherited from ErrnoException, not redeclared, so it follows the
70
+ * consumer's @types/node.
69
71
  */
70
72
  export interface NodemailerError extends NodeJS.ErrnoException {
71
- /** Nodemailer error code, see ERROR_CODES */
72
- code?: string | undefined;
73
73
  /** SMTP command that was in flight when the server replied with an error */
74
74
  command?: string | undefined;
75
75
  /** Raw SMTP server response */
@@ -193,9 +193,15 @@ declare class Mail<out T = SentMessageInfo, out D extends TransportOptions = Tra
193
193
  * Sends an email using the preselected transport object
194
194
  *
195
195
  * @param data E-data description
196
- * @param callback Callback to run once the sending succeeded or failed
196
+ * @returns Promise that resolves with the result of the send
197
197
  */
198
198
  sendMail(data: SendMailOptions): Promise<T>;
199
+ /**
200
+ * Sends an email using the preselected transport object
201
+ *
202
+ * @param data E-data description
203
+ * @param callback Callback to run once the sending succeeded or failed
204
+ */
199
205
  sendMail(data: SendMailOptions, callback: SendMailCallback<T>): void;
200
206
  getVersionString(): string;
201
207
  /**
@@ -59,6 +59,7 @@ export declare function quoteString(value?: string): string;
59
59
  * no need to encode the values in any way. If the value is plaintext but has
60
60
  * longer lines then allowed, then use format=flowed
61
61
  *
62
+ * @param str Multi line string to check
62
63
  * @param lineLength Max line length to check for
63
64
  * @returns Returns true if there is at least one line longer than lineLength chars
64
65
  */
@@ -100,9 +101,9 @@ export declare function buildHeaderValue(structured: StructuredHeaderValue): str
100
101
  * title*0*=utf-8''unicode
101
102
  * title*1*=%20string
102
103
  *
104
+ * @param key Parameter name the generated keys are built from, for example title
103
105
  * @param data String to be encoded
104
106
  * @param [maxLength=50] Max length for generated chunks
105
- * @param [fromCharset='UTF-8'] Source sharacter set
106
107
  * @return A list of encoded keys and headers
107
108
  */
108
109
  export declare function buildHeaderParam(key: string, data: string | Buffer, maxLength?: number): EncodedHeaderParam[];