nodemailer 10.0.16 → 10.1.1
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.
- package/CHANGELOG.md +34 -0
- package/SECURITY.md +105 -29
- package/dist/cjs/base64/index.d.ts +3 -0
- package/dist/cjs/base64/index.js +80 -41
- package/dist/cjs/dkim/index.js +43 -29
- package/dist/cjs/dkim/message-parser.js +2 -2
- package/dist/cjs/errors.d.ts +17 -0
- package/dist/cjs/errors.js +12 -1
- package/dist/cjs/fetch/index.js +39 -13
- package/dist/cjs/mailer/index.js +23 -7
- package/dist/cjs/mailer/mail-message.d.ts +5 -0
- package/dist/cjs/mailer/mail-message.js +20 -0
- package/dist/cjs/mime-node/index.d.ts +1 -1
- package/dist/cjs/mime-node/index.js +165 -105
- package/dist/cjs/nodemailer.js +12 -9
- package/dist/cjs/package-info.d.ts +1 -1
- package/dist/cjs/package-info.js +1 -1
- package/dist/cjs/qp/index.d.ts +2 -0
- package/dist/cjs/qp/index.js +103 -33
- package/dist/cjs/sendmail-transport/index.js +54 -14
- package/dist/cjs/shared/index.d.ts +32 -1
- package/dist/cjs/shared/index.js +212 -115
- package/dist/cjs/smtp-connection/http-proxy-client.d.ts +3 -1
- package/dist/cjs/smtp-connection/http-proxy-client.js +88 -68
- package/dist/cjs/smtp-connection/index.d.ts +7 -1
- package/dist/cjs/smtp-connection/index.js +348 -136
- package/dist/cjs/smtp-pool/index.d.ts +3 -0
- package/dist/cjs/smtp-pool/index.js +7 -1
- package/dist/cjs/smtp-pool/pool-resource.js +106 -17
- package/dist/cjs/smtp-transport/index.js +8 -2
- package/dist/esm/base64/index.d.ts +3 -0
- package/dist/esm/base64/index.js +80 -41
- package/dist/esm/dkim/index.js +43 -29
- package/dist/esm/dkim/message-parser.js +2 -2
- package/dist/esm/errors.d.ts +17 -0
- package/dist/esm/errors.js +10 -0
- package/dist/esm/fetch/index.js +39 -13
- package/dist/esm/mailer/index.js +23 -7
- package/dist/esm/mailer/mail-message.d.ts +5 -0
- package/dist/esm/mailer/mail-message.js +20 -0
- package/dist/esm/mime-node/index.d.ts +1 -1
- package/dist/esm/mime-node/index.js +166 -106
- package/dist/esm/nodemailer.js +12 -9
- package/dist/esm/package-info.d.ts +1 -1
- package/dist/esm/package-info.js +1 -1
- package/dist/esm/qp/index.d.ts +2 -0
- package/dist/esm/qp/index.js +103 -33
- package/dist/esm/sendmail-transport/index.js +54 -14
- package/dist/esm/shared/index.d.ts +32 -1
- package/dist/esm/shared/index.js +207 -115
- package/dist/esm/smtp-connection/http-proxy-client.d.ts +3 -1
- package/dist/esm/smtp-connection/http-proxy-client.js +88 -68
- package/dist/esm/smtp-connection/index.d.ts +7 -1
- package/dist/esm/smtp-connection/index.js +348 -136
- package/dist/esm/smtp-pool/index.d.ts +3 -0
- package/dist/esm/smtp-pool/index.js +7 -1
- package/dist/esm/smtp-pool/pool-resource.js +106 -17
- package/dist/esm/smtp-transport/index.js +8 -2
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## [10.1.1](https://github.com/nodemailer/nodemailer/compare/v10.1.0...v10.1.1) (2026-10-14)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **fetch:** strip credentials on any cross-origin redirect ([a15360c](https://github.com/nodemailer/nodemailer/commit/a15360cd2cd12e4670e96ab0b7290e8c862391cf))
|
|
9
|
+
|
|
10
|
+
## [10.1.0](https://github.com/nodemailer/nodemailer/compare/v10.0.16...v10.1.0) (2026-10-10)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* harden message streams, SMTP connections and the connection pool ([8009da6](https://github.com/nodemailer/nodemailer/commit/8009da6e10bb8e10d7f0948a25d65895fdcd64f1))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* do not read the environment or the interface table on import ([094179d](https://github.com/nodemailer/nodemailer/commit/094179d176d2ebe7a28742411ad427d3c87a3981))
|
|
21
|
+
* end the SMTP session on any 421 reply and drive the envelope through one step ([5638ac1](https://github.com/nodemailer/nodemailer/commit/5638ac1914342460df270f736e7ceb0a844cf83a))
|
|
22
|
+
* keep ERR_ACCESS_DENIED from the Node.js permission model ([8b33a3d](https://github.com/nodemailer/nodemailer/commit/8b33a3d4fedee3431b97da7da12af954c3c8dceb))
|
|
23
|
+
* let the proxy handshake and the SMTP connection share one deadline ([91ed683](https://github.com/nodemailer/nodemailer/commit/91ed683f7a1e9ff8c56e24fb76facf16af08b162))
|
|
24
|
+
* **mime-node:** decode an SMTPUTF8 domain from its A-label form ([e436f4d](https://github.com/nodemailer/nodemailer/commit/e436f4dccbc8b0fb57b4747ebaafa5bb035c15cb))
|
|
25
|
+
* **mime-node:** finish an encoded part only after its output was read ([ec97d8f](https://github.com/nodemailer/nodemailer/commit/ec97d8f679a1d9d47bbebb03ea064f5fa6f58d01))
|
|
26
|
+
* **qp:** encode a lone CR or LF in binary quoted-printable parts ([42b91d9](https://github.com/nodemailer/nodemailer/commit/42b91d9a0fe18eea412c9774c5c142adf5b3efc7))
|
|
27
|
+
* **smtp-connection:** count the wait for the greeting against connectionTimeout ([bdcd046](https://github.com/nodemailer/nodemailer/commit/bdcd0462cab163eca3c34058d09b9981ab671c47))
|
|
28
|
+
* **smtp-connection:** drop the connection when DATA is taken after a refused sender ([f90fb04](https://github.com/nodemailer/nodemailer/commit/f90fb045bc24f96a128710791b2fa443e64f55fd))
|
|
29
|
+
* **smtp-connection:** refuse a second connect() call ([24ba2e0](https://github.com/nodemailer/nodemailer/commit/24ba2e0847ecb8202540f1c5e53483bd119ae4f2))
|
|
30
|
+
* turn Nagle off for SMTP sockets and report an early sendmail exit as such ([fa9b8d3](https://github.com/nodemailer/nodemailer/commit/fa9b8d300bf642cd9e11f6732ebb312c09868677))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Performance Improvements
|
|
34
|
+
|
|
35
|
+
* encode base64 and quoted-printable without per-chunk delays ([fd377bb](https://github.com/nodemailer/nodemailer/commit/fd377bbf559363860aedbedba6f107348fc152d2))
|
|
36
|
+
|
|
3
37
|
## [10.0.16](https://github.com/nodemailer/nodemailer/compare/v10.0.15...v10.0.16) (2026-10-07)
|
|
4
38
|
|
|
5
39
|
|
package/SECURITY.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# Security Policy
|
|
2
2
|
|
|
3
|
-
Nodemailer is a widely deployed, zero-dependency
|
|
4
|
-
reports
|
|
3
|
+
Nodemailer is a widely deployed, zero-dependency email library. We welcome
|
|
4
|
+
responsible reports of vulnerabilities that present a credible security risk to
|
|
5
|
+
users. Please distinguish security vulnerabilities from ordinary bugs, unusual
|
|
6
|
+
edge cases, and hardening suggestions.
|
|
5
7
|
|
|
6
8
|
## Supported Versions
|
|
7
9
|
|
|
@@ -17,45 +19,119 @@ the supported way to receive security updates.
|
|
|
17
19
|
If you are on an older major, please upgrade. See the migration notes at
|
|
18
20
|
<https://nodemailer.com/> before updating.
|
|
19
21
|
|
|
20
|
-
##
|
|
22
|
+
## What Qualifies as a Security Vulnerability
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
A security report should identify a realistic way for an attacker to cross a
|
|
25
|
+
security boundary in a supported version of Nodemailer. Unexpected behavior,
|
|
26
|
+
a failing test, or a technically correct proof of concept is not sufficient on
|
|
27
|
+
its own to establish security impact.
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
In particular, explain:
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
- **Attacker capabilities:** Who is the attacker, and what inputs, systems, or
|
|
32
|
+
responses can they control _without_ already having the privileges or secrets
|
|
33
|
+
they are trying to gain?
|
|
34
|
+
- **Attack path:** How does attacker-controlled input reach the affected code
|
|
35
|
+
through a supported Nodemailer API or a realistic application integration?
|
|
36
|
+
- **Prerequisites:** What configuration and user actions are required? If the
|
|
37
|
+
attack depends on unusual options or deliberately malformed input, explain
|
|
38
|
+
why those conditions could arise in a real application.
|
|
39
|
+
- **Impact:** What confidentiality, integrity, or availability boundary is
|
|
40
|
+
crossed, and what can the attacker achieve as a result?
|
|
32
41
|
|
|
33
|
-
|
|
42
|
+
A vulnerability involving an optional or less common feature can still qualify
|
|
43
|
+
if these conditions are met. Conversely, the existence of a defensive check in
|
|
44
|
+
the code does not, by itself, establish that every bypass of that check has a
|
|
45
|
+
realistic security impact.
|
|
34
46
|
|
|
35
|
-
|
|
36
|
-
- The component involved (e.g. SMTP connection, address parsing, MIME/header
|
|
37
|
-
generation, DKIM).
|
|
38
|
-
- A clear description of the issue and its impact (e.g. header/SMTP command
|
|
39
|
-
injection, information disclosure, DoS).
|
|
40
|
-
- A minimal proof of concept or reproduction steps.
|
|
41
|
-
- Any suggested remediation, if you have one.
|
|
47
|
+
## Reporting a Vulnerability
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
Accepted issues are fixed in a new release and coordinated through a GitHub
|
|
46
|
-
Security Advisory, and reporters who wish to be named are credited.
|
|
49
|
+
**Do not disclose suspected exploitable vulnerabilities through public GitHub
|
|
50
|
+
issues, pull requests, or discussions before coordinating with the maintainer.**
|
|
47
51
|
|
|
48
|
-
|
|
52
|
+
Report privately through one of the following channels:
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
1. **GitHub Security Advisories (preferred):**
|
|
55
|
+
<https://github.com/nodemailer/nodemailer/security/advisories/new>.
|
|
56
|
+
2. **Email:** **andris@reinman.eu**, as listed in
|
|
57
|
+
[`SECURITY.txt`](SECURITY.txt). Encrypt sensitive details if possible.
|
|
58
|
+
|
|
59
|
+
### Required Information
|
|
60
|
+
|
|
61
|
+
Please provide enough information to evaluate both the defect and its security
|
|
62
|
+
impact. Reports should include:
|
|
63
|
+
|
|
64
|
+
1. The affected Nodemailer version or commit, Node.js version, and relevant
|
|
65
|
+
configuration.
|
|
66
|
+
2. A concise description of the issue and the affected public API or code path.
|
|
67
|
+
3. A minimal, tested reproduction, including steps to run it, expected behavior,
|
|
68
|
+
and observed behavior.
|
|
69
|
+
4. A concrete threat model: attacker control, prerequisites, trust boundary, and
|
|
70
|
+
resulting impact. Clearly distinguish what your reproduction proves from what
|
|
71
|
+
you believe might be possible.
|
|
72
|
+
5. An explanation of why the issue affects applications beyond a deliberately
|
|
73
|
+
constructed test setup, where this is not obvious.
|
|
74
|
+
|
|
75
|
+
A suggested fix is welcome but does not replace an explanation of exploitability.
|
|
76
|
+
A complete real-world exploit is not always necessary, but the report must make
|
|
77
|
+
a credible security impact clear.
|
|
78
|
+
|
|
79
|
+
### Reports That May Be Treated as Ordinary Bugs
|
|
80
|
+
|
|
81
|
+
The following generally do **not** qualify for private security handling unless
|
|
82
|
+
you can demonstrate an additional, meaningful security boundary crossing:
|
|
83
|
+
|
|
84
|
+
- Issues that require the attacker to control trusted application code,
|
|
85
|
+
administrator-only configuration, or the secret they supposedly extract.
|
|
86
|
+
- Problems caused solely by application-level misuse, exposed credentials, or
|
|
87
|
+
insecure deployment choices outside Nodemailer's control.
|
|
88
|
+
- Contrived proof-of-concept setups where the tester controls both sides of a
|
|
89
|
+
request, without explaining how a separate attacker could trigger the issue.
|
|
90
|
+
- Parser quirks, unexpected behavior, and bypasses of internal checks with no
|
|
91
|
+
demonstrated security impact.
|
|
92
|
+
- Speculative findings based only on static analysis or an automated tool's
|
|
93
|
+
assessment, without a verified reproduction and credible attack scenario.
|
|
94
|
+
|
|
95
|
+
These may still be valid bugs and may be considered through the normal issue
|
|
96
|
+
tracker. Please coordinate before making any potentially exploitable details
|
|
97
|
+
public. Classification as a bug does not guarantee a fix or a particular
|
|
98
|
+
priority.
|
|
99
|
+
|
|
100
|
+
### Automated and AI-Assisted Reports
|
|
101
|
+
|
|
102
|
+
Automated analysis and AI tools are welcome as research aids, but the person
|
|
103
|
+
submitting a report is responsible for verifying its claims. **Do not submit
|
|
104
|
+
bulk, unverified, or speculative AI-generated findings.** Test each finding
|
|
105
|
+
against a supported release and establish the actual security impact. Reports
|
|
106
|
+
that do not meet the requirements above may be closed without further
|
|
107
|
+
investigation or individual feedback; they can be resubmitted with stronger
|
|
108
|
+
evidence.
|
|
109
|
+
|
|
110
|
+
## Triage and Disclosure
|
|
111
|
+
|
|
112
|
+
Nodemailer is maintained by a single person, and there is **no guaranteed
|
|
113
|
+
response or resolution time**. Reports are prioritized based on demonstrated
|
|
114
|
+
impact, exploitability, affected usage, and available maintenance resources.
|
|
115
|
+
|
|
116
|
+
A private report does not automatically become a security advisory. Confirmed
|
|
117
|
+
vulnerabilities may be fixed and disclosed through GitHub Security Advisories;
|
|
118
|
+
other findings may be closed, deferred, or handled as ordinary bugs. Please do
|
|
119
|
+
not disclose details of an unresolved, potentially exploitable issue publicly
|
|
120
|
+
without coordinating with the maintainer.
|
|
121
|
+
|
|
122
|
+
## CVEs and Credit
|
|
123
|
+
|
|
124
|
+
We do not request or manage CVE identifiers. A report being acknowledged, a bug
|
|
125
|
+
being fixed, or a pull request being merged does **not** mean that Nodemailer
|
|
126
|
+
has confirmed a security vulnerability or endorsed a CVE assignment. We do not
|
|
127
|
+
guarantee advisories, CVE identifiers, or public credit for reports. For
|
|
128
|
+
confirmed vulnerabilities disclosed through an advisory, substantive reporters
|
|
129
|
+
may be credited if they wish.
|
|
54
130
|
|
|
55
131
|
## Scope
|
|
56
132
|
|
|
57
133
|
In scope: the `nodemailer` package source in this repository: message and MIME
|
|
58
|
-
generation, SMTP/LMTP client
|
|
134
|
+
generation, SMTP/LMTP client behavior, address parsing, header handling, DKIM
|
|
59
135
|
signing, and the bundled transports.
|
|
60
136
|
|
|
61
137
|
Out of scope: vulnerabilities in your own application code, misconfiguration of
|
|
@@ -24,6 +24,9 @@ export interface EncoderOptions {
|
|
|
24
24
|
/**
|
|
25
25
|
* Creates a transform stream for encoding data to base64 encoding
|
|
26
26
|
*
|
|
27
|
+
* The output is the same as `wrap(encode(input), lineLength)` no matter how the input is split
|
|
28
|
+
* into chunks: every line but the last one ends with a line break, the last one does not
|
|
29
|
+
*
|
|
27
30
|
* @constructor
|
|
28
31
|
* @param options Stream options
|
|
29
32
|
* @param [options.lineLength=76] Maximum length for lines, set to false to disable wrapping
|
package/dist/cjs/base64/index.js
CHANGED
|
@@ -16,6 +16,47 @@ function encode(buffer) {
|
|
|
16
16
|
}
|
|
17
17
|
return buffer.toString('base64');
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Turns a line length option into a whole number of characters, the default for anything unusable
|
|
21
|
+
*/
|
|
22
|
+
function normalizeLineLength(lineLength) {
|
|
23
|
+
const length = Math.floor(Number(lineLength));
|
|
24
|
+
return Number.isFinite(length) && length >= 1 ? length : 76;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Splits the bytes of `src` into lines of `lineLength` bytes, each followed by a line break. With
|
|
28
|
+
* `final` set the last line, which may be shorter, gets no line break; otherwise only complete
|
|
29
|
+
* lines are taken and the rest is left for the caller
|
|
30
|
+
*
|
|
31
|
+
* @param src Bytes to wrap
|
|
32
|
+
* @param lineLength Line length
|
|
33
|
+
* @param final Whether `src` ends the output
|
|
34
|
+
* @returns The wrapped bytes and the number of trailing bytes not taken
|
|
35
|
+
*/
|
|
36
|
+
function wrapBuffer(src, lineLength, final) {
|
|
37
|
+
const lines = Math.ceil(src.length / lineLength);
|
|
38
|
+
// the last line waits for more data unless this is the end: whether it gets a line break
|
|
39
|
+
// depends on whether anything follows it
|
|
40
|
+
const complete = Math.max(lines - 1, 0);
|
|
41
|
+
let rest = src.length - complete * lineLength;
|
|
42
|
+
const output = Buffer.allocUnsafe(complete * (lineLength + 2) + (final ? rest : 0));
|
|
43
|
+
let to = 0;
|
|
44
|
+
for (let from = 0; from < complete * lineLength; from += lineLength) {
|
|
45
|
+
src.copy(output, to, from, from + lineLength);
|
|
46
|
+
to += lineLength;
|
|
47
|
+
output[to++] = 0x0d;
|
|
48
|
+
output[to++] = 0x0a;
|
|
49
|
+
}
|
|
50
|
+
if (final) {
|
|
51
|
+
to += src.copy(output, to, complete * lineLength);
|
|
52
|
+
rest = 0;
|
|
53
|
+
}
|
|
54
|
+
if (to !== output.length) {
|
|
55
|
+
// never hand out bytes of the unfilled allocation
|
|
56
|
+
throw new Error('Unexpected wrapped length');
|
|
57
|
+
}
|
|
58
|
+
return { output, rest };
|
|
59
|
+
}
|
|
19
60
|
/**
|
|
20
61
|
* Adds soft line breaks to a base64 string
|
|
21
62
|
*
|
|
@@ -44,6 +85,9 @@ function wrap(str, lineLength) {
|
|
|
44
85
|
/**
|
|
45
86
|
* Creates a transform stream for encoding data to base64 encoding
|
|
46
87
|
*
|
|
88
|
+
* The output is the same as `wrap(encode(input), lineLength)` no matter how the input is split
|
|
89
|
+
* into chunks: every line but the last one ends with a line break, the last one does not
|
|
90
|
+
*
|
|
47
91
|
* @constructor
|
|
48
92
|
* @param options Stream options
|
|
49
93
|
* @param [options.lineLength=76] Maximum length for lines, set to false to disable wrapping
|
|
@@ -53,66 +97,61 @@ class Encoder extends node_stream_1.Transform {
|
|
|
53
97
|
super();
|
|
54
98
|
this.options = options || {};
|
|
55
99
|
if (this.options.lineLength !== false) {
|
|
56
|
-
this.options.lineLength = this.options.lineLength
|
|
100
|
+
this.options.lineLength = normalizeLineLength(this.options.lineLength);
|
|
57
101
|
}
|
|
58
102
|
this._curLine = '';
|
|
59
103
|
this._remainingBytes = false;
|
|
60
104
|
this.inputBytes = 0;
|
|
61
105
|
this.outputBytes = 0;
|
|
62
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Emits the encoded characters `b64` that follow the current line, keeping what can not be
|
|
109
|
+
* emitted yet as the new current line
|
|
110
|
+
*
|
|
111
|
+
* @internal
|
|
112
|
+
*/
|
|
113
|
+
_emit(b64, final) {
|
|
114
|
+
const src = Buffer.from(this._curLine + b64, 'latin1');
|
|
115
|
+
if (!src.length) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
let output = src;
|
|
119
|
+
if (this.options.lineLength) {
|
|
120
|
+
const wrapped = wrapBuffer(src, this.options.lineLength, final);
|
|
121
|
+
output = wrapped.output;
|
|
122
|
+
this._curLine = wrapped.rest ? src.toString('latin1', src.length - wrapped.rest) : '';
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
this._curLine = '';
|
|
126
|
+
}
|
|
127
|
+
if (output.length) {
|
|
128
|
+
this.outputBytes += output.length;
|
|
129
|
+
this.push(output);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
63
132
|
/** @internal */
|
|
64
133
|
_transform(chunk, encoding, done) {
|
|
65
134
|
let buf = encoding !== 'buffer' ? Buffer.from(chunk, encoding) : chunk;
|
|
66
135
|
if (!buf || !buf.length) {
|
|
67
|
-
|
|
68
|
-
return;
|
|
136
|
+
return done();
|
|
69
137
|
}
|
|
70
138
|
this.inputBytes += buf.length;
|
|
71
|
-
if (this._remainingBytes
|
|
139
|
+
if (this._remainingBytes) {
|
|
72
140
|
buf = Buffer.concat([this._remainingBytes, buf], this._remainingBytes.length + buf.length);
|
|
73
141
|
this._remainingBytes = false;
|
|
74
142
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
else {
|
|
80
|
-
this._remainingBytes = false;
|
|
81
|
-
}
|
|
82
|
-
let b64 = this._curLine + encode(buf);
|
|
83
|
-
if (this.options.lineLength) {
|
|
84
|
-
b64 = wrap(b64, this.options.lineLength);
|
|
85
|
-
// remove last line as it is still most probably incomplete
|
|
86
|
-
const lastLF = b64.lastIndexOf('\n');
|
|
87
|
-
if (lastLF < 0) {
|
|
88
|
-
this._curLine = b64;
|
|
89
|
-
b64 = '';
|
|
90
|
-
}
|
|
91
|
-
else if (lastLF === b64.length - 1) {
|
|
92
|
-
this._curLine = '';
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
this._curLine = b64.substring(lastLF + 1);
|
|
96
|
-
b64 = b64.substring(0, lastLF + 1);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
if (b64) {
|
|
100
|
-
this.outputBytes += b64.length;
|
|
101
|
-
this.push(Buffer.from(b64, 'ascii'));
|
|
143
|
+
const extra = buf.length % 3;
|
|
144
|
+
if (extra) {
|
|
145
|
+
this._remainingBytes = buf.subarray(buf.length - extra);
|
|
146
|
+
buf = buf.subarray(0, buf.length - extra);
|
|
102
147
|
}
|
|
103
|
-
|
|
148
|
+
this._emit(encode(buf), false);
|
|
149
|
+
done();
|
|
104
150
|
}
|
|
105
151
|
/** @internal */
|
|
106
152
|
_flush(done) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
if (this._curLine) {
|
|
111
|
-
this._curLine = wrap(this._curLine, this.options.lineLength);
|
|
112
|
-
this.outputBytes += this._curLine.length;
|
|
113
|
-
this.push(Buffer.from(this._curLine, 'ascii'));
|
|
114
|
-
this._curLine = '';
|
|
115
|
-
}
|
|
153
|
+
this._emit(this._remainingBytes ? encode(this._remainingBytes) : '', true);
|
|
154
|
+
this._remainingBytes = false;
|
|
116
155
|
done();
|
|
117
156
|
}
|
|
118
157
|
}
|
package/dist/cjs/dkim/index.js
CHANGED
|
@@ -37,51 +37,67 @@ class DKIMSigner {
|
|
|
37
37
|
this.output = output;
|
|
38
38
|
this.output.usingCache = false;
|
|
39
39
|
this.hasErrored = false;
|
|
40
|
-
this.input.on('error', err =>
|
|
40
|
+
this.input.on('error', err => this.fail(err));
|
|
41
|
+
// A consumer that goes away before the signed message was read in full destroys the
|
|
42
|
+
// output. Stop reading the input and drop the cache file instead of leaving them open
|
|
43
|
+
this.output.once('close', () => {
|
|
44
|
+
if (this.output.writableFinished) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
41
47
|
this.hasErrored = true;
|
|
48
|
+
this.input.unpipe();
|
|
49
|
+
this.input.destroy();
|
|
42
50
|
this.cleanup();
|
|
43
|
-
output.emit('error', err);
|
|
44
51
|
});
|
|
45
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Ends the output with an error and releases the cache file
|
|
55
|
+
*/
|
|
56
|
+
fail(err) {
|
|
57
|
+
this.hasErrored = true;
|
|
58
|
+
this.cleanup();
|
|
59
|
+
this.output.destroy(err);
|
|
60
|
+
}
|
|
46
61
|
cleanup() {
|
|
47
62
|
if (!this.cache || !this.cachePath) {
|
|
48
63
|
return;
|
|
49
64
|
}
|
|
65
|
+
const cache = this.cache;
|
|
66
|
+
this.cache = false;
|
|
67
|
+
cache.destroy();
|
|
50
68
|
node_fs_1.default.unlink(this.cachePath, () => false);
|
|
51
69
|
}
|
|
52
70
|
createReadCache() {
|
|
53
71
|
// pipe remainings to cache file
|
|
54
72
|
this.cache = node_fs_1.default.createReadStream(this.cachePath);
|
|
55
|
-
this.cache.once('error', err =>
|
|
56
|
-
this.cleanup();
|
|
57
|
-
this.output.emit('error', err);
|
|
58
|
-
});
|
|
73
|
+
this.cache.once('error', err => this.fail(err));
|
|
59
74
|
this.cache.once('close', () => {
|
|
60
75
|
this.cleanup();
|
|
61
76
|
});
|
|
62
77
|
this.cache.pipe(this.output);
|
|
63
78
|
}
|
|
64
79
|
sendNextChunk() {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
80
|
+
while (!this.hasErrored) {
|
|
81
|
+
if (this.readPos >= this.chunks.length) {
|
|
82
|
+
if (!this.cache) {
|
|
83
|
+
this.output.end();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
return this.createReadCache();
|
|
87
|
+
}
|
|
88
|
+
const chunk = this.chunks[this.readPos++];
|
|
89
|
+
if (this.output.write(chunk) === false) {
|
|
90
|
+
this.output.once('drain', () => {
|
|
91
|
+
this.sendNextChunk();
|
|
92
|
+
});
|
|
71
93
|
return;
|
|
72
94
|
}
|
|
73
|
-
return this.createReadCache();
|
|
74
|
-
}
|
|
75
|
-
const chunk = this.chunks[this.readPos++];
|
|
76
|
-
if (this.output.write(chunk) === false) {
|
|
77
|
-
this.output.once('drain', () => {
|
|
78
|
-
this.sendNextChunk();
|
|
79
|
-
});
|
|
80
|
-
return;
|
|
81
95
|
}
|
|
82
|
-
setImmediate(() => this.sendNextChunk());
|
|
83
96
|
}
|
|
84
97
|
sendSignedOutput() {
|
|
98
|
+
if (this.hasErrored) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
85
101
|
let keyPos = 0;
|
|
86
102
|
const signNextKey = () => {
|
|
87
103
|
if (keyPos >= this.keys.length) {
|
|
@@ -101,9 +117,7 @@ class DKIMSigner {
|
|
|
101
117
|
});
|
|
102
118
|
}
|
|
103
119
|
catch (err) {
|
|
104
|
-
this.
|
|
105
|
-
this.cleanup();
|
|
106
|
-
this.output.emit('error', err);
|
|
120
|
+
this.fail(err);
|
|
107
121
|
return;
|
|
108
122
|
}
|
|
109
123
|
if (dkimField) {
|
|
@@ -122,7 +136,6 @@ class DKIMSigner {
|
|
|
122
136
|
// pipe remainings to cache file
|
|
123
137
|
this.cache = node_fs_1.default.createWriteStream(this.cachePath);
|
|
124
138
|
this.cache.once('error', err => {
|
|
125
|
-
this.cleanup();
|
|
126
139
|
// drain input
|
|
127
140
|
this.relaxedBody.unpipe(this.cache);
|
|
128
141
|
this.relaxedBody.on('readable', () => {
|
|
@@ -130,11 +143,12 @@ class DKIMSigner {
|
|
|
130
143
|
// do nothing
|
|
131
144
|
}
|
|
132
145
|
});
|
|
133
|
-
this.
|
|
134
|
-
// emit error
|
|
135
|
-
this.output.emit('error', err);
|
|
146
|
+
this.fail(err);
|
|
136
147
|
});
|
|
137
148
|
this.cache.once('close', () => {
|
|
149
|
+
if (this.hasErrored) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
138
152
|
this.sendSignedOutput();
|
|
139
153
|
});
|
|
140
154
|
this.relaxedBody.removeAllListeners('readable');
|
|
@@ -211,7 +225,7 @@ class DKIM {
|
|
|
211
225
|
catch (_E) {
|
|
212
226
|
// the body hash is created here, an unknown hashAlgo throws inside this timer
|
|
213
227
|
// where nothing else could catch it
|
|
214
|
-
|
|
228
|
+
signer.fail(sign_js_1.default.unsupportedHashAlgoError(signer.hashAlgo));
|
|
215
229
|
return;
|
|
216
230
|
}
|
|
217
231
|
if (writeValue) {
|
|
@@ -107,7 +107,7 @@ class MessageParser extends node_stream_1.Transform {
|
|
|
107
107
|
const chunk = data.slice(headerPos);
|
|
108
108
|
this.bodySize += chunk.length;
|
|
109
109
|
// this would be the first chunk of data sent downstream
|
|
110
|
-
|
|
110
|
+
this.push(chunk);
|
|
111
111
|
}
|
|
112
112
|
return false;
|
|
113
113
|
}
|
|
@@ -136,7 +136,7 @@ class MessageParser extends node_stream_1.Transform {
|
|
|
136
136
|
this.bodySize += chunk.length;
|
|
137
137
|
this.push(chunk);
|
|
138
138
|
}
|
|
139
|
-
|
|
139
|
+
callback();
|
|
140
140
|
}
|
|
141
141
|
/** @internal */
|
|
142
142
|
_flush(callback) {
|
package/dist/cjs/errors.d.ts
CHANGED
|
@@ -61,6 +61,19 @@ export declare const EPROXY = "EPROXY";
|
|
|
61
61
|
export declare const EFILEACCESS = "EFILEACCESS";
|
|
62
62
|
export declare const EURLACCESS = "EURLACCESS";
|
|
63
63
|
export declare const EFETCH = "EFETCH";
|
|
64
|
+
/**
|
|
65
|
+
* Tells whether the error ended the connection rather than rejected what was sent over it
|
|
66
|
+
*/
|
|
67
|
+
export declare const isTransientError: (err: {
|
|
68
|
+
code?: string | undefined;
|
|
69
|
+
responseCode?: number | undefined;
|
|
70
|
+
}) => boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Code Node.js sets on an error when its permission model (`--permission`) denies an
|
|
73
|
+
* operation. It is not replaced with the Nodemailer code of the failing step (`ESOCKET`,
|
|
74
|
+
* `EDNS`, `EFETCH`, ...), so a missing grant such as `--allow-net` stays recognizable.
|
|
75
|
+
*/
|
|
76
|
+
export declare const ERR_ACCESS_DENIED = "ERR_ACCESS_DENIED";
|
|
64
77
|
/**
|
|
65
78
|
* An Error together with the properties Nodemailer attaches to the errors it
|
|
66
79
|
* hands to callers. Every property is optional, the set that is present
|
|
@@ -84,6 +97,10 @@ export interface NodemailerError extends NodeJS.ErrnoException {
|
|
|
84
97
|
rejected?: string[] | undefined;
|
|
85
98
|
/** Per-recipient errors for the rejected addresses */
|
|
86
99
|
rejectedErrors?: NodemailerError[] | undefined;
|
|
100
|
+
/** The code the error had before Nodemailer set its own, such as ECONNREFUSED for an ESOCKET error */
|
|
101
|
+
originalCode?: string | undefined;
|
|
102
|
+
/** Which wait an ETIMEDOUT error ended: connecting, the greeting, a TLS upgrade or an idle socket */
|
|
103
|
+
timeoutType?: 'CONNECT_TIMEOUT' | 'GREETING_TIMEOUT' | 'UPGRADE_TIMEOUT' | 'SOCKET_TIMEOUT' | undefined;
|
|
87
104
|
}
|
|
88
105
|
/**
|
|
89
106
|
* Node style callback: called with an error, or with null and the result
|
package/dist/cjs/errors.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* err.code = errors.ECONNECTION;
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
exports.EFETCH = exports.EURLACCESS = exports.EFILEACCESS = exports.EPROXY = exports.ECONFIG = exports.ESES = exports.ESENDMAIL = exports.EMAXRECIPIENTS = exports.EMAXLIMIT = exports.EOAUTH2 = exports.ENOAUTH = exports.EAUTH = exports.ESTREAM = exports.EMESSAGE = exports.EENVELOPE = exports.EPROTOCOL = exports.EREQUIRETLS = exports.ETLS = exports.EDNS = exports.ESOCKET = exports.ETIMEDOUT = exports.ECONNECTION = exports.ERROR_CODES = void 0;
|
|
13
|
+
exports.ERR_ACCESS_DENIED = exports.isTransientError = exports.EFETCH = exports.EURLACCESS = exports.EFILEACCESS = exports.EPROXY = exports.ECONFIG = exports.ESES = exports.ESENDMAIL = exports.EMAXRECIPIENTS = exports.EMAXLIMIT = exports.EOAUTH2 = exports.ENOAUTH = exports.EAUTH = exports.ESTREAM = exports.EMESSAGE = exports.EENVELOPE = exports.EPROTOCOL = exports.EREQUIRETLS = exports.ETLS = exports.EDNS = exports.ESOCKET = exports.ETIMEDOUT = exports.ECONNECTION = exports.ERROR_CODES = void 0;
|
|
14
14
|
/**
|
|
15
15
|
* Error code descriptions for documentation and debugging
|
|
16
16
|
*/
|
|
@@ -68,3 +68,14 @@ exports.EPROXY = 'EPROXY';
|
|
|
68
68
|
exports.EFILEACCESS = 'EFILEACCESS';
|
|
69
69
|
exports.EURLACCESS = 'EURLACCESS';
|
|
70
70
|
exports.EFETCH = 'EFETCH';
|
|
71
|
+
/**
|
|
72
|
+
* Tells whether the error ended the connection rather than rejected what was sent over it
|
|
73
|
+
*/
|
|
74
|
+
const isTransientError = (err) => err.responseCode === 421 || err.code === exports.ECONNECTION || err.code === exports.ESOCKET || err.code === exports.ETIMEDOUT;
|
|
75
|
+
exports.isTransientError = isTransientError;
|
|
76
|
+
/**
|
|
77
|
+
* Code Node.js sets on an error when its permission model (`--permission`) denies an
|
|
78
|
+
* operation. It is not replaced with the Nodemailer code of the failing step (`ESOCKET`,
|
|
79
|
+
* `EDNS`, `EFETCH`, ...), so a missing grant such as `--allow-net` stays recognizable.
|
|
80
|
+
*/
|
|
81
|
+
exports.ERR_ACCESS_DENIED = 'ERR_ACCESS_DENIED';
|