imapflow 2.3.0 → 2.4.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 +19 -0
- package/SECURITY.md +126 -55
- package/dist/cjs/commands/append.d.ts +1 -1
- package/dist/cjs/commands/append.js +15 -4
- package/dist/cjs/commands/compress.js +2 -2
- package/dist/cjs/commands/idle.js +2 -3
- package/dist/cjs/commands/status.js +2 -4
- package/dist/cjs/connection-deadline.d.ts +1 -1
- package/dist/cjs/connection-deadline.js +2 -4
- package/dist/cjs/download.js +22 -19
- package/dist/cjs/errors.d.ts +15 -0
- package/dist/cjs/errors.js +22 -0
- package/dist/cjs/handler/imap-compiler.d.ts +4 -4
- package/dist/cjs/handler/imap-compiler.js +18 -18
- package/dist/cjs/handler/imap-parser.js +28 -3
- package/dist/cjs/handler/imap-stream.js +9 -8
- package/dist/cjs/handler/limits.d.ts +1 -1
- package/dist/cjs/handler/limits.js +5 -7
- package/dist/cjs/handler/parser-instance.js +23 -38
- package/dist/cjs/handler/token-parser.d.ts +12 -0
- package/dist/cjs/handler/token-parser.js +44 -104
- package/dist/cjs/imap-flow.d.ts +4 -3
- package/dist/cjs/imap-flow.js +72 -72
- package/dist/cjs/package-info.d.ts +1 -1
- package/dist/cjs/package-info.js +1 -1
- package/dist/cjs/proxy-connection.js +2 -5
- package/dist/cjs/search-compiler.js +2 -3
- package/dist/cjs/tools.d.ts +2 -2
- package/dist/cjs/tools.js +8 -11
- package/dist/esm/commands/append.d.ts +1 -1
- package/dist/esm/commands/append.js +15 -4
- package/dist/esm/commands/compress.js +2 -2
- package/dist/esm/commands/idle.js +2 -3
- package/dist/esm/commands/status.js +2 -4
- package/dist/esm/connection-deadline.d.ts +1 -1
- package/dist/esm/connection-deadline.js +2 -4
- package/dist/esm/download.js +22 -19
- package/dist/esm/errors.d.ts +15 -0
- package/dist/esm/errors.js +21 -0
- package/dist/esm/handler/imap-compiler.d.ts +4 -4
- package/dist/esm/handler/imap-compiler.js +18 -18
- package/dist/esm/handler/imap-parser.js +28 -3
- package/dist/esm/handler/imap-stream.js +9 -8
- package/dist/esm/handler/limits.d.ts +1 -1
- package/dist/esm/handler/limits.js +5 -7
- package/dist/esm/handler/parser-instance.js +23 -38
- package/dist/esm/handler/token-parser.d.ts +12 -0
- package/dist/esm/handler/token-parser.js +44 -104
- package/dist/esm/imap-flow.d.ts +4 -3
- package/dist/esm/imap-flow.js +73 -73
- package/dist/esm/package-info.d.ts +1 -1
- package/dist/esm/package-info.js +1 -1
- package/dist/esm/proxy-connection.js +2 -5
- package/dist/esm/search-compiler.js +2 -3
- package/dist/esm/tools.d.ts +2 -2
- package/dist/esm/tools.js +9 -12
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.4.1](https://github.com/postalsys/imapflow/compare/v2.4.0...v2.4.1) (2026-10-14)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **download:** bump libbase64 to decode padded base64 parts in linear memory ([1c83261](https://github.com/postalsys/imapflow/commit/1c832614dda83160de1387f08e7592add65e2779))
|
|
9
|
+
|
|
10
|
+
## [2.4.0](https://github.com/postalsys/imapflow/compare/v2.3.0...v2.4.0) (2026-10-10)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* re-raise unexpected parser exceptions as coded ParserErrorInternal errors ([dda3f7e](https://github.com/postalsys/imapflow/commit/dda3f7e71e3c227479763a8c6af617fd4f57679a))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* keep append literals in sync with their data and fail downloads cleanly ([752248e](https://github.com/postalsys/imapflow/commit/752248e07fd8930cb0943287c32e3bab88484d80))
|
|
21
|
+
|
|
3
22
|
## [2.3.0](https://github.com/postalsys/imapflow/compare/v2.2.11...v2.3.0) (2026-10-08)
|
|
4
23
|
|
|
5
24
|
|
package/SECURITY.md
CHANGED
|
@@ -1,88 +1,159 @@
|
|
|
1
1
|
# Security Policy
|
|
2
2
|
|
|
3
|
-
ImapFlow is a Node.js IMAP client library. It opens TLS
|
|
4
|
-
IMAP servers, sends account credentials, and parses untrusted
|
|
5
|
-
from
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
ImapFlow is a Node.js IMAP client library. It opens TLS and cleartext
|
|
4
|
+
connections to IMAP servers, sends account credentials, and parses untrusted
|
|
5
|
+
protocol responses from servers that may be malicious or buggy. We welcome
|
|
6
|
+
responsible reports of vulnerabilities that present a credible security risk to
|
|
7
|
+
users. Please distinguish security vulnerabilities from ordinary bugs, unusual
|
|
8
|
+
edge cases, and hardening suggestions.
|
|
8
9
|
|
|
9
10
|
## Supported Versions
|
|
10
11
|
|
|
11
|
-
Security fixes are released only against the latest version. We do not
|
|
12
|
-
patches to older
|
|
13
|
-
supported way to receive security updates.
|
|
12
|
+
Security fixes are released only against the latest major version. We do not
|
|
13
|
+
backport patches to older majors, so upgrading to the current release line is
|
|
14
|
+
the supported way to receive security updates.
|
|
14
15
|
|
|
15
16
|
| Version | Supported |
|
|
16
17
|
| ------- | ------------------ |
|
|
17
|
-
|
|
|
18
|
-
| <
|
|
18
|
+
| 2.x | :white_check_mark: |
|
|
19
|
+
| < 2.0 | :x: |
|
|
19
20
|
|
|
20
|
-
If you are on an older
|
|
21
|
+
If you are on an older major, please upgrade. See the release notes at
|
|
21
22
|
<https://github.com/postalsys/imapflow/releases> before updating.
|
|
22
23
|
|
|
24
|
+
## What Qualifies as a Security Vulnerability
|
|
25
|
+
|
|
26
|
+
A security report should identify a realistic way for an attacker to cross a
|
|
27
|
+
security boundary in a supported version of ImapFlow. Unexpected behavior, a
|
|
28
|
+
failing test, or a technically correct proof of concept is not sufficient on its
|
|
29
|
+
own to establish security impact.
|
|
30
|
+
|
|
31
|
+
In particular, explain:
|
|
32
|
+
|
|
33
|
+
- **Attacker capabilities:** Who is the attacker, and what inputs, systems, or
|
|
34
|
+
responses can they control _without_ already having the privileges or secrets
|
|
35
|
+
they are trying to gain?
|
|
36
|
+
- **Attack path:** How does attacker-controlled input reach the affected code
|
|
37
|
+
through a supported public API or a realistic application integration?
|
|
38
|
+
- **Prerequisites:** What configuration and user actions are required? If the
|
|
39
|
+
attack depends on unusual options or deliberately malformed input, explain why
|
|
40
|
+
those conditions could arise in a real application.
|
|
41
|
+
- **Impact:** What confidentiality, integrity, or availability boundary is
|
|
42
|
+
crossed, and what can the attacker achieve as a result?
|
|
43
|
+
|
|
44
|
+
A vulnerability involving an optional or less common feature can still qualify
|
|
45
|
+
if these conditions are met. Conversely, the existence of a defensive check in
|
|
46
|
+
the code does not, by itself, establish that every bypass of that check has a
|
|
47
|
+
realistic security impact.
|
|
48
|
+
|
|
23
49
|
## Reporting a Vulnerability
|
|
24
50
|
|
|
25
|
-
**
|
|
26
|
-
pull requests, or discussions
|
|
51
|
+
**Do not disclose suspected exploitable vulnerabilities through public GitHub
|
|
52
|
+
issues, pull requests, or discussions before coordinating with the
|
|
53
|
+
maintainers.**
|
|
27
54
|
|
|
28
55
|
Report privately through one of the following channels:
|
|
29
56
|
|
|
30
|
-
1. **GitHub Security Advisories (preferred)
|
|
31
|
-
<https://github.com/postalsys/imapflow/security/advisories/new>.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
1. **GitHub Security Advisories (preferred):**
|
|
58
|
+
<https://github.com/postalsys/imapflow/security/advisories/new>.
|
|
59
|
+
2. **Email:** **andris@postalsys.com**, as listed in
|
|
60
|
+
[`SECURITY.txt`](SECURITY.txt). Encrypt sensitive details if possible.
|
|
61
|
+
|
|
62
|
+
### Required Information
|
|
63
|
+
|
|
64
|
+
Please provide enough information to evaluate both the defect and its security
|
|
65
|
+
impact. Reports should include:
|
|
66
|
+
|
|
67
|
+
1. The affected ImapFlow version or commit, Node.js version, and relevant
|
|
68
|
+
configuration.
|
|
69
|
+
2. A concise description of the issue and the affected public API or code path
|
|
70
|
+
(for example the IMAP response stream and parser, literal and line handling,
|
|
71
|
+
the TLS/STARTTLS upgrade, credential handling, the command compiler, or the
|
|
72
|
+
public client API. A sample server response is often the most useful
|
|
73
|
+
reproduction).
|
|
74
|
+
3. A minimal, tested reproduction, including steps to run it, expected behavior,
|
|
75
|
+
and observed behavior.
|
|
76
|
+
4. A concrete threat model: attacker control, prerequisites, trust boundary, and
|
|
77
|
+
resulting impact. Clearly distinguish what your reproduction proves from what
|
|
78
|
+
you believe might be possible.
|
|
79
|
+
5. An explanation of why the issue affects real deployments beyond a
|
|
80
|
+
deliberately constructed test setup, where this is not obvious.
|
|
81
|
+
|
|
82
|
+
A suggested fix is welcome but does not replace an explanation of
|
|
83
|
+
exploitability. A complete real-world exploit is not always necessary, but the
|
|
84
|
+
report must make a credible security impact clear.
|
|
85
|
+
|
|
86
|
+
### Reports That May Be Treated as Ordinary Bugs
|
|
87
|
+
|
|
88
|
+
The following generally do **not** qualify for private security handling unless
|
|
89
|
+
you can demonstrate an additional, meaningful security boundary crossing:
|
|
90
|
+
|
|
91
|
+
- Issues that require the attacker to control trusted application code,
|
|
92
|
+
administrator-only configuration, or the secret they supposedly extract.
|
|
93
|
+
- Problems caused solely by application-level misuse, exposed credentials, or
|
|
94
|
+
insecure deployment choices outside ImapFlow's control.
|
|
95
|
+
- Contrived proof-of-concept setups where the tester controls both sides of a
|
|
96
|
+
request, without explaining how a separate attacker could trigger the issue.
|
|
97
|
+
- Parser quirks, unexpected behavior, and bypasses of internal checks with no
|
|
98
|
+
demonstrated security impact.
|
|
99
|
+
- Speculative findings based only on static analysis or an automated tool's
|
|
100
|
+
assessment, without a verified reproduction and credible attack scenario.
|
|
101
|
+
|
|
102
|
+
These may still be valid bugs and may be considered through the normal issue
|
|
103
|
+
tracker. Please coordinate before making any potentially exploitable details
|
|
104
|
+
public. Classification as a bug does not guarantee a fix or a particular
|
|
105
|
+
priority.
|
|
106
|
+
|
|
107
|
+
### Automated and AI-Assisted Reports
|
|
108
|
+
|
|
109
|
+
Automated analysis and AI tools are welcome as research aids, but the person
|
|
110
|
+
submitting a report is responsible for verifying its claims. **Do not submit
|
|
111
|
+
bulk, unverified, or speculative AI-generated findings.** Test each finding
|
|
112
|
+
against a supported release and establish the actual security impact. Reports
|
|
113
|
+
that do not meet the requirements above may be closed without further
|
|
114
|
+
investigation or individual feedback; they can be resubmitted with stronger
|
|
115
|
+
evidence.
|
|
116
|
+
|
|
117
|
+
## Triage and Disclosure
|
|
118
|
+
|
|
119
|
+
ImapFlow is maintained by a small team, and there is **no guaranteed response or
|
|
120
|
+
resolution time**. Reports are prioritized based on demonstrated impact,
|
|
121
|
+
exploitability, affected usage, and available maintenance resources.
|
|
122
|
+
|
|
123
|
+
A private report does not automatically become a security advisory. Confirmed
|
|
124
|
+
vulnerabilities may be fixed and disclosed through GitHub Security Advisories;
|
|
125
|
+
other findings may be closed, deferred, or handled as ordinary bugs. Please do
|
|
126
|
+
not disclose details of an unresolved, potentially exploitable issue publicly
|
|
127
|
+
without coordinating with the maintainers.
|
|
128
|
+
|
|
129
|
+
## CVEs and Credit
|
|
130
|
+
|
|
131
|
+
We do not request or manage CVE identifiers. A report being acknowledged, a bug
|
|
132
|
+
being fixed, or a pull request being merged does **not** mean that ImapFlow has
|
|
133
|
+
confirmed a security vulnerability or endorsed a CVE assignment. We do not
|
|
134
|
+
guarantee advisories, CVE identifiers, or public credit for reports. For
|
|
135
|
+
confirmed vulnerabilities disclosed through an advisory, substantive reporters
|
|
136
|
+
may be credited if they wish.
|
|
63
137
|
|
|
64
138
|
## Scope
|
|
65
139
|
|
|
66
|
-
In scope: the ImapFlow library source in this repository
|
|
140
|
+
In scope: the ImapFlow library source in this repository: the IMAP response
|
|
67
141
|
stream and token parser (including handling of hostile or malformed server
|
|
68
|
-
responses, literals, line lengths, and resource
|
|
69
|
-
|
|
142
|
+
responses, literals, line lengths, and resource bounds), the TLS and STARTTLS
|
|
143
|
+
upgrade path and certificate handling, credential handling during
|
|
70
144
|
authentication, the command compiler that builds outgoing IMAP commands, and the
|
|
71
145
|
public client API.
|
|
72
146
|
|
|
73
147
|
Out of scope:
|
|
74
148
|
|
|
75
149
|
- Vulnerabilities in your own application code that uses ImapFlow.
|
|
76
|
-
- Misconfiguration of your usage
|
|
150
|
+
- Misconfiguration of your usage, for example disabling TLS certificate
|
|
77
151
|
verification (`tls.rejectUnauthorized: false`), connecting over cleartext when
|
|
78
152
|
TLS is available, or passing untrusted input directly into mailbox paths or
|
|
79
153
|
command arguments without validation.
|
|
80
|
-
- Vulnerabilities in the IMAP servers you connect to
|
|
81
|
-
dependencies (please report those to their respective maintainers; we will
|
|
82
|
-
upgrade once a fix is available).
|
|
154
|
+
- Vulnerabilities in the IMAP servers you connect to.
|
|
83
155
|
- Issues that require an already-compromised host or local access to the machine
|
|
84
156
|
running ImapFlow.
|
|
85
|
-
- Social-engineering reports
|
|
86
|
-
concrete impact.
|
|
157
|
+
- Social-engineering reports.
|
|
87
158
|
|
|
88
159
|
Thank you for helping keep ImapFlow and its users safe.
|
|
@@ -19,4 +19,4 @@ export interface AppendResult extends AppendResponseObject {
|
|
|
19
19
|
* @returns Append result with UID info if available, or undefined if preconditions not met
|
|
20
20
|
* @throws {Error} If the APPEND command fails or message exceeds APPENDLIMIT
|
|
21
21
|
*/
|
|
22
|
-
export default function append(connection: ImapFlow, destination: string | string[], content: Buffer | string, flags?: string | string[] | undefined, idate?: Date | string | false | undefined): Promise<AppendResult | undefined>;
|
|
22
|
+
export default function append(connection: ImapFlow, destination: string | string[], content: Buffer | Uint8Array | string, flags?: string | string[] | undefined, idate?: Date | string | false | undefined): Promise<AppendResult | undefined>;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = append;
|
|
4
4
|
const tools_js_1 = require("../tools.js");
|
|
5
|
+
const errors_js_1 = require("../errors.js");
|
|
5
6
|
/**
|
|
6
7
|
* Appends a message to a mailbox.
|
|
7
8
|
*
|
|
@@ -18,14 +19,24 @@ async function append(connection, destination, content, flags, idate) {
|
|
|
18
19
|
// nothing to do here
|
|
19
20
|
return;
|
|
20
21
|
}
|
|
22
|
+
// The literal size marker is announced from these bytes, so anything that is not
|
|
23
|
+
// bytes (a Readable, a plain object) is refused here instead of desyncing the session
|
|
24
|
+
let message;
|
|
21
25
|
if (typeof content === 'string') {
|
|
22
|
-
|
|
26
|
+
message = Buffer.from(content);
|
|
27
|
+
}
|
|
28
|
+
else if (content instanceof Uint8Array) {
|
|
29
|
+
// a Buffer as is, any other Uint8Array as a view over the same memory, not a copy
|
|
30
|
+
message = Buffer.isBuffer(content) ? content : Buffer.from(content.buffer, content.byteOffset, content.byteLength);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
throw (0, errors_js_1.createImapError)('Message content must be a Buffer, Uint8Array or string', 'InvalidMessageContent');
|
|
23
34
|
}
|
|
24
35
|
// APPENDLIMIT capability (RFC 7889): server may advertise the maximum message
|
|
25
36
|
// size it accepts. Check before sending to avoid a wasted round-trip.
|
|
26
37
|
if (connection.capabilities.has('APPENDLIMIT')) {
|
|
27
38
|
let appendLimit = connection.capabilities.get('APPENDLIMIT');
|
|
28
|
-
if (typeof appendLimit === 'number' && appendLimit <
|
|
39
|
+
if (typeof appendLimit === 'number' && appendLimit < message.length) {
|
|
29
40
|
let err = new Error('Message content too big for APPENDLIMIT=' + appendLimit);
|
|
30
41
|
err.serverResponseCode = 'APPENDLIMIT';
|
|
31
42
|
throw err;
|
|
@@ -82,9 +93,9 @@ async function append(connection, destination, content, flags, idate) {
|
|
|
82
93
|
// Regular literals cannot contain NUL bytes per the IMAP grammar.
|
|
83
94
|
let isLiteral8 = false;
|
|
84
95
|
if (connection.capabilities.has('BINARY') && !connection.disableBinary) {
|
|
85
|
-
isLiteral8 =
|
|
96
|
+
isLiteral8 = message.indexOf(0) >= 0;
|
|
86
97
|
}
|
|
87
|
-
attributes.push({ type: 'LITERAL', value:
|
|
98
|
+
attributes.push({ type: 'LITERAL', value: message, isLiteral8 });
|
|
88
99
|
let map = { destination };
|
|
89
100
|
if (connection.mailbox && connection.mailbox.path) {
|
|
90
101
|
map.path = connection.mailbox.path;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = compress;
|
|
4
|
+
const errors_js_1 = require("../errors.js");
|
|
4
5
|
/**
|
|
5
6
|
* Requests DEFLATE compression from the server.
|
|
6
7
|
*
|
|
@@ -36,8 +37,7 @@ async function compress(connection) {
|
|
|
36
37
|
// back its unconsumed tail on unpipe so the transport can feed it into the
|
|
37
38
|
// inflater - not something a command module can reach from here.
|
|
38
39
|
if (response.hasTrailingData) {
|
|
39
|
-
let error =
|
|
40
|
-
error.code = 'COMPRESS_TRAILING_DATA';
|
|
40
|
+
let error = (0, errors_js_1.createImapError)('Server sent data between the COMPRESS response and the compression layer switch', 'COMPRESS_TRAILING_DATA');
|
|
41
41
|
connection.log.error({ err: error, cid: connection.id });
|
|
42
42
|
// Schedule the close before releasing parser backpressure, so the buffered
|
|
43
43
|
// deflate-framed bytes cannot settle anything before teardown begins. This is
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = idle;
|
|
4
4
|
const tools_js_1 = require("../tools.js");
|
|
5
|
+
const errors_js_1 = require("../errors.js");
|
|
5
6
|
const NOOP_INTERVAL = 2 * 60 * 1000;
|
|
6
7
|
/**
|
|
7
8
|
* Marks the connection as idling on behalf of one session and returns a release function.
|
|
@@ -192,9 +193,7 @@ async function pollOnce(connection, session) {
|
|
|
192
193
|
highestModseq: true
|
|
193
194
|
});
|
|
194
195
|
if (!status) {
|
|
195
|
-
|
|
196
|
-
err.code = 'PollFailed';
|
|
197
|
-
throw err;
|
|
196
|
+
throw (0, errors_js_1.createImapError)('STATUS poll failed', 'PollFailed');
|
|
198
197
|
}
|
|
199
198
|
break;
|
|
200
199
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.default = status;
|
|
4
4
|
const tools_js_1 = require("../tools.js");
|
|
5
5
|
const status_fields_js_1 = require("./status-fields.js");
|
|
6
|
+
const errors_js_1 = require("../errors.js");
|
|
6
7
|
// STATUS fields that also refresh the live mailbox state when the queried mailbox is the
|
|
7
8
|
// currently selected one. Keyed by the output property name parseStatusList() reports.
|
|
8
9
|
const MAILBOX_UPDATERS = {
|
|
@@ -99,10 +100,7 @@ async function status(connection, path, query) {
|
|
|
99
100
|
return false;
|
|
100
101
|
});
|
|
101
102
|
if (folders && !folders.length) {
|
|
102
|
-
|
|
103
|
-
error.code = 'NotFound';
|
|
104
|
-
error.response = err;
|
|
105
|
-
throw error;
|
|
103
|
+
throw (0, errors_js_1.createImapError)(`Mailbox doesn't exist: ${path}`, 'NotFound', { response: err });
|
|
106
104
|
}
|
|
107
105
|
}
|
|
108
106
|
connection.log.warn({ err, cid: connection.id });
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ConnectionDeadline = exports.CONNECT_TIMEOUT = void 0;
|
|
4
4
|
const tools_js_1 = require("./tools.js");
|
|
5
|
+
const errors_js_1 = require("./errors.js");
|
|
5
6
|
// Default upper bound for establishing a usable transport, including DNS and proxy negotiation.
|
|
6
7
|
exports.CONNECT_TIMEOUT = 90 * 1000;
|
|
7
8
|
/**
|
|
@@ -31,10 +32,7 @@ class ConnectionDeadline {
|
|
|
31
32
|
* @returns The shared `CONNECT_TIMEOUT` error.
|
|
32
33
|
*/
|
|
33
34
|
error() {
|
|
34
|
-
|
|
35
|
-
err.code = 'CONNECT_TIMEOUT';
|
|
36
|
-
err.details = { connectionTimeout: this.timeout };
|
|
37
|
-
return err;
|
|
35
|
+
return (0, errors_js_1.createImapError)('Failed to establish connection in required time', 'CONNECT_TIMEOUT', { details: { connectionTimeout: this.timeout } });
|
|
38
36
|
}
|
|
39
37
|
/**
|
|
40
38
|
* Maps a dependency's own expiry onto the shared `CONNECT_TIMEOUT` shape, so callers see one
|
package/dist/cjs/download.js
CHANGED
|
@@ -18,6 +18,7 @@ const limited_passthrough_js_1 = require("./limited-passthrough.js");
|
|
|
18
18
|
// What a wait on the head stream listens for: it can take more input, it failed, or it went
|
|
19
19
|
// away (a consumer destroying it closes it without a 'drain')
|
|
20
20
|
const DRAIN_WAIT_EVENTS = ['drain', 'error', 'close'];
|
|
21
|
+
const errors_js_1 = require("./errors.js");
|
|
21
22
|
const tools_js_1 = require("./tools.js");
|
|
22
23
|
const isEmptySection = (value) => !value?.length;
|
|
23
24
|
/**
|
|
@@ -88,10 +89,7 @@ async function fetchExpected(client, range, query, options, expected) {
|
|
|
88
89
|
cid: client.id
|
|
89
90
|
});
|
|
90
91
|
if (attempt >= MAX_FETCH_ATTEMPTS) {
|
|
91
|
-
|
|
92
|
-
err.code = 'DownloadIncomplete';
|
|
93
|
-
err.cid = client.id;
|
|
94
|
-
throw err;
|
|
92
|
+
throw (0, errors_js_1.createImapError)('Server kept answering with data of another request', 'DownloadIncomplete', { cid: client.id });
|
|
95
93
|
}
|
|
96
94
|
}
|
|
97
95
|
}
|
|
@@ -238,6 +236,12 @@ async function downloadMessage(client, range, part, options) {
|
|
|
238
236
|
};
|
|
239
237
|
let response = await fetchExpected(client, range, query, downloadOptions, expected);
|
|
240
238
|
if (!response) {
|
|
239
|
+
// An empty answer also comes from a connection that closed while the consumer was
|
|
240
|
+
// paused between chunks (fetchOne() finds no mailbox then), and that is not a
|
|
241
|
+
// missing message: a caller that retries would skip it
|
|
242
|
+
if (!client.usable) {
|
|
243
|
+
throw client.createNoConnectionError(false, { rejectedFrom: 'download' });
|
|
244
|
+
}
|
|
241
245
|
return { response: false, chunk: false };
|
|
242
246
|
}
|
|
243
247
|
if (!uid && response.uid) {
|
|
@@ -374,13 +378,19 @@ async function downloadMessage(client, range, part, options) {
|
|
|
374
378
|
// would still pull the entire part off the wire.
|
|
375
379
|
let limiters = [];
|
|
376
380
|
let isLimited = () => limiters.some(entry => entry.limited);
|
|
381
|
+
// Every stage after the head, so a download that ends early can release all of them
|
|
382
|
+
let stages = [];
|
|
377
383
|
// Appending a stage means forwarding the current tail's errors to it before piping, so a
|
|
378
|
-
// failure anywhere reaches the stream the caller is reading
|
|
384
|
+
// failure anywhere reaches the stream the caller is reading. The stage is destroyed with
|
|
385
|
+
// the error rather than only emitting it: pipe() does not end its destination on a source
|
|
386
|
+
// error, so a consumer that only listens for 'finish' or 'close' (a plain .pipe() into a
|
|
387
|
+
// file) would otherwise wait forever.
|
|
379
388
|
let pipeStage = (stage) => {
|
|
380
389
|
output.on('error', err => {
|
|
381
|
-
stage.
|
|
390
|
+
stage.destroy(err);
|
|
382
391
|
});
|
|
383
392
|
output = output.pipe(stage);
|
|
393
|
+
stages.push(stage);
|
|
384
394
|
return stage;
|
|
385
395
|
};
|
|
386
396
|
let isTextNode = ['text/html', 'text/plain', 'text/x-amp-html'].includes(meta.contentType) || (part === '1' && !meta.contentType);
|
|
@@ -425,8 +435,8 @@ async function downloadMessage(client, range, part, options) {
|
|
|
425
435
|
// Cleanup function
|
|
426
436
|
const cleanup = () => {
|
|
427
437
|
fetchAborted = true;
|
|
428
|
-
|
|
429
|
-
|
|
438
|
+
for (let entry of [stream, ...stages]) {
|
|
439
|
+
entry.destroy();
|
|
430
440
|
}
|
|
431
441
|
};
|
|
432
442
|
// Listen for stream destruction
|
|
@@ -499,11 +509,7 @@ async function downloadMessage(client, range, part, options) {
|
|
|
499
509
|
// Loud on purpose. Everything written downstream by this point holds
|
|
500
510
|
// duplicated content, and a quiet stop is indistinguishable from a clean EOF,
|
|
501
511
|
// so the consumer would store a corrupt body believing it intact.
|
|
502
|
-
|
|
503
|
-
err.code = 'DownloadOverflow';
|
|
504
|
-
err.maxSize = maxTotalBytes;
|
|
505
|
-
err.cid = client.id;
|
|
506
|
-
throw err;
|
|
512
|
+
throw (0, errors_js_1.createImapError)('Download exceeded the expected message size', 'DownloadOverflow', { maxSize: maxTotalBytes, cid: client.id });
|
|
507
513
|
}
|
|
508
514
|
let { response, chunk } = await getNextPart();
|
|
509
515
|
// A consumer that gave up while the chunk was in flight
|
|
@@ -514,10 +520,7 @@ async function downloadMessage(client, range, part, options) {
|
|
|
514
520
|
// The message is gone mid-download (expunged by another client, or the
|
|
515
521
|
// mailbox was closed). Ending the stream here would pass the truncated body
|
|
516
522
|
// off as complete, so the consumer is told the same way as for an overflow.
|
|
517
|
-
|
|
518
|
-
err.code = 'DownloadIncomplete';
|
|
519
|
-
err.cid = client.id;
|
|
520
|
-
throw err;
|
|
523
|
+
throw (0, errors_js_1.createImapError)('Message disappeared before the download completed', 'DownloadIncomplete', { cid: client.id });
|
|
521
524
|
}
|
|
522
525
|
if (!chunk) {
|
|
523
526
|
break;
|
|
@@ -547,7 +550,7 @@ async function downloadMessage(client, range, part, options) {
|
|
|
547
550
|
fetchAllParts(head)
|
|
548
551
|
.catch(err => {
|
|
549
552
|
if (!fetchAborted && stream && !stream.destroyed) {
|
|
550
|
-
stream.
|
|
553
|
+
stream.destroy(err);
|
|
551
554
|
/* c8 ignore start */ // the else logs when a fetch error arrives after the stream was already torn down (timing-dependent)
|
|
552
555
|
}
|
|
553
556
|
else {
|
|
@@ -572,7 +575,7 @@ async function downloadMessage(client, range, part, options) {
|
|
|
572
575
|
// above rejects a promise nobody holds and takes the process down on
|
|
573
576
|
// unhandledRejection. Reaching it always means an invariant broke - the head
|
|
574
577
|
// stream kept pipeStage()'s error forwarder for the life of the download, so
|
|
575
|
-
//
|
|
578
|
+
// destroy(err) above has somewhere to go - which is why it logs at error even
|
|
576
579
|
// for a routine-looking connection code.
|
|
577
580
|
.catch(err => client.log.error({ msg: 'Failed to fail the download stream', err, cid: client.id }));
|
|
578
581
|
};
|
package/dist/cjs/errors.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export declare const ImapFlowErrorCode: {
|
|
|
29
29
|
readonly MissingServerExtension: "MissingServerExtension";
|
|
30
30
|
readonly ParserError: "ParserError";
|
|
31
31
|
readonly ParserErrorExchange: "ParserErrorExchange";
|
|
32
|
+
readonly ParserErrorInternal: "ParserErrorInternal";
|
|
32
33
|
readonly MAX_IMAP_NESTING_REACHED: "MAX_IMAP_NESTING_REACHED";
|
|
33
34
|
readonly LineTooLarge: "LineTooLarge";
|
|
34
35
|
readonly LiteralTooLarge: "LiteralTooLarge";
|
|
@@ -38,6 +39,7 @@ export declare const ImapFlowErrorCode: {
|
|
|
38
39
|
readonly InvalidTextValue: "InvalidTextValue";
|
|
39
40
|
readonly InvalidSequenceSet: "InvalidSequenceSet";
|
|
40
41
|
readonly InvalidSearchQuery: "InvalidSearchQuery";
|
|
42
|
+
readonly InvalidMessageContent: "InvalidMessageContent";
|
|
41
43
|
readonly DownloadOverflow: "DownloadOverflow";
|
|
42
44
|
readonly DownloadIncomplete: "DownloadIncomplete";
|
|
43
45
|
readonly ProxyError: "ProxyError";
|
|
@@ -48,6 +50,8 @@ export declare const ImapFlowErrorCode: {
|
|
|
48
50
|
};
|
|
49
51
|
/** One of the {@link ImapFlowErrorCode} values */
|
|
50
52
|
export type ImapFlowErrorCode = (typeof ImapFlowErrorCode)[keyof typeof ImapFlowErrorCode];
|
|
53
|
+
/** A numbered parser error code, `ParserError1` and up */
|
|
54
|
+
export type ParserErrorCode = `ParserError${number}`;
|
|
51
55
|
/**
|
|
52
56
|
* An Error raised by ImapFlow, with the extra properties the library attaches to describe
|
|
53
57
|
* the failure. Every property is optional: which ones are present depends on where the
|
|
@@ -132,3 +136,14 @@ export declare class AuthenticationFailure extends Error implements ImapFlowErro
|
|
|
132
136
|
response?: string | undefined;
|
|
133
137
|
oauthError?: any;
|
|
134
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Creates an {@link ImapFlowError} with a code and optional extra properties. The code is
|
|
141
|
+
* checked by the compiler against {@link ImapFlowErrorCode}, so a typo or an undeclared
|
|
142
|
+
* code fails the build instead of reaching consumers.
|
|
143
|
+
*
|
|
144
|
+
* @param message - The error message
|
|
145
|
+
* @param code - The error code
|
|
146
|
+
* @param props - Extra properties to set on the error
|
|
147
|
+
* @returns The error, ready to throw or reject with
|
|
148
|
+
*/
|
|
149
|
+
export declare function createImapError(message: string, code: ImapFlowErrorCode | ParserErrorCode, props?: Omit<ImapFlowError, 'name' | 'message' | 'stack' | 'code'> | undefined): ImapFlowError;
|
package/dist/cjs/errors.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AuthenticationFailure = exports.ImapFlowErrorCode = void 0;
|
|
4
|
+
exports.createImapError = createImapError;
|
|
4
5
|
/**
|
|
5
6
|
* The `code` values ImapFlow sets on the errors it raises, so they can be matched without
|
|
6
7
|
* string literals: `if (err.code === ImapFlowErrorCode.NoConnection)`.
|
|
@@ -35,6 +36,8 @@ exports.ImapFlowErrorCode = {
|
|
|
35
36
|
// response parsing and size limits
|
|
36
37
|
ParserError: 'ParserError',
|
|
37
38
|
ParserErrorExchange: 'ParserErrorExchange',
|
|
39
|
+
// an unexpected exception inside the parser, re-raised with a code; a parser bug
|
|
40
|
+
ParserErrorInternal: 'ParserErrorInternal',
|
|
38
41
|
MAX_IMAP_NESTING_REACHED: 'MAX_IMAP_NESTING_REACHED',
|
|
39
42
|
LineTooLarge: 'LineTooLarge',
|
|
40
43
|
LiteralTooLarge: 'LiteralTooLarge',
|
|
@@ -45,6 +48,7 @@ exports.ImapFlowErrorCode = {
|
|
|
45
48
|
InvalidTextValue: 'InvalidTextValue',
|
|
46
49
|
InvalidSequenceSet: 'InvalidSequenceSet',
|
|
47
50
|
InvalidSearchQuery: 'InvalidSearchQuery',
|
|
51
|
+
InvalidMessageContent: 'InvalidMessageContent',
|
|
48
52
|
// download()
|
|
49
53
|
DownloadOverflow: 'DownloadOverflow',
|
|
50
54
|
DownloadIncomplete: 'DownloadIncomplete',
|
|
@@ -66,3 +70,21 @@ class AuthenticationFailure extends Error {
|
|
|
66
70
|
}
|
|
67
71
|
}
|
|
68
72
|
exports.AuthenticationFailure = AuthenticationFailure;
|
|
73
|
+
/**
|
|
74
|
+
* Creates an {@link ImapFlowError} with a code and optional extra properties. The code is
|
|
75
|
+
* checked by the compiler against {@link ImapFlowErrorCode}, so a typo or an undeclared
|
|
76
|
+
* code fails the build instead of reaching consumers.
|
|
77
|
+
*
|
|
78
|
+
* @param message - The error message
|
|
79
|
+
* @param code - The error code
|
|
80
|
+
* @param props - Extra properties to set on the error
|
|
81
|
+
* @returns The error, ready to throw or reject with
|
|
82
|
+
*/
|
|
83
|
+
function createImapError(message, code, props) {
|
|
84
|
+
const error = new Error(message);
|
|
85
|
+
error.code = code;
|
|
86
|
+
if (props) {
|
|
87
|
+
Object.assign(error, props);
|
|
88
|
+
}
|
|
89
|
+
return error;
|
|
90
|
+
}
|
|
@@ -12,13 +12,13 @@ import type { CompilerOptions, ImapCompileInput } from './types.js';
|
|
|
12
12
|
* @param options.isLogging - If true, redacts sensitive values and truncates long strings/literals for logging purposes.
|
|
13
13
|
* @param options.literalPlus - If true, uses the LITERAL+ extension (appends "+" to literal length markers).
|
|
14
14
|
* @param options.literalMinus - If true, uses the LITERAL- extension for literals up to 4096 bytes.
|
|
15
|
-
* @returns
|
|
15
|
+
* @returns An array of Buffers (if asArray is true) or a single concatenated Buffer.
|
|
16
16
|
*/
|
|
17
17
|
declare function compiler(response: ImapCompileInput, options: CompilerOptions & {
|
|
18
18
|
asArray: true;
|
|
19
|
-
}):
|
|
19
|
+
}): Buffer[];
|
|
20
20
|
declare function compiler(response: ImapCompileInput, options?: (CompilerOptions & {
|
|
21
21
|
asArray?: false | undefined;
|
|
22
|
-
}) | undefined):
|
|
23
|
-
declare function compiler(response: ImapCompileInput, options?: CompilerOptions | undefined):
|
|
22
|
+
}) | undefined): Buffer;
|
|
23
|
+
declare function compiler(response: ImapCompileInput, options?: CompilerOptions | undefined): Buffer | Buffer[];
|
|
24
24
|
export default compiler;
|