imapflow 1.7.7 → 2.0.0
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 +27 -0
- package/README.md +8 -2
- package/dist/cjs/charsets.d.ts +1 -0
- package/dist/cjs/charsets.js +294 -0
- package/dist/cjs/commands/append.d.ts +22 -0
- package/dist/cjs/commands/append.js +151 -0
- package/dist/cjs/commands/authenticate.d.ts +24 -0
- package/dist/cjs/commands/authenticate.js +223 -0
- package/dist/cjs/commands/capability.d.ts +8 -0
- package/dist/cjs/commands/capability.js +32 -0
- package/dist/cjs/commands/close.d.ts +8 -0
- package/dist/cjs/commands/close.js +39 -0
- package/dist/cjs/commands/compress.d.ts +8 -0
- package/dist/cjs/commands/compress.js +56 -0
- package/dist/cjs/commands/copy.d.ts +13 -0
- package/dist/cjs/commands/copy.js +44 -0
- package/dist/cjs/commands/copyuid-parser.d.ts +11 -0
- package/dist/cjs/commands/copyuid-parser.js +32 -0
- package/dist/cjs/commands/create.d.ts +11 -0
- package/dist/cjs/commands/create.js +80 -0
- package/dist/cjs/commands/delete.d.ts +11 -0
- package/dist/cjs/commands/delete.js +40 -0
- package/dist/cjs/commands/enable.d.ts +9 -0
- package/dist/cjs/commands/enable.js +61 -0
- package/dist/cjs/commands/esearch-parser.d.ts +17 -0
- package/dist/cjs/commands/esearch-parser.js +91 -0
- package/dist/cjs/commands/expunge.d.ts +12 -0
- package/dist/cjs/commands/expunge.js +60 -0
- package/dist/cjs/commands/fetch.d.ts +30 -0
- package/dist/cjs/commands/fetch.js +241 -0
- package/dist/cjs/commands/id.d.ts +10 -0
- package/dist/cjs/commands/id.js +80 -0
- package/dist/cjs/commands/idle.d.ts +9 -0
- package/dist/cjs/commands/idle.js +347 -0
- package/dist/cjs/commands/list.d.ts +16 -0
- package/dist/cjs/commands/list.js +518 -0
- package/dist/cjs/commands/login.d.ts +11 -0
- package/dist/cjs/commands/login.js +42 -0
- package/dist/cjs/commands/logout.d.ts +8 -0
- package/dist/cjs/commands/logout.js +47 -0
- package/dist/cjs/commands/move.d.ts +13 -0
- package/dist/cjs/commands/move.js +57 -0
- package/dist/cjs/commands/namespace.d.ts +25 -0
- package/dist/cjs/commands/namespace.js +139 -0
- package/dist/cjs/commands/noop.d.ts +8 -0
- package/dist/cjs/commands/noop.js +22 -0
- package/dist/cjs/commands/quota.d.ts +10 -0
- package/dist/cjs/commands/quota.js +119 -0
- package/dist/cjs/commands/rename.d.ts +12 -0
- package/dist/cjs/commands/rename.js +48 -0
- package/dist/cjs/commands/search.d.ts +15 -0
- package/dist/cjs/commands/search.js +228 -0
- package/dist/cjs/commands/select.d.ts +25 -0
- package/dist/cjs/commands/select.js +250 -0
- package/dist/cjs/commands/starttls.d.ts +8 -0
- package/dist/cjs/commands/starttls.js +30 -0
- package/dist/cjs/commands/status-fields.d.ts +14 -0
- package/dist/cjs/commands/status-fields.js +61 -0
- package/dist/cjs/commands/status.d.ts +12 -0
- package/dist/cjs/commands/status.js +108 -0
- package/dist/cjs/commands/store.d.ts +19 -0
- package/dist/cjs/commands/store.js +93 -0
- package/dist/cjs/commands/subscribe.d.ts +9 -0
- package/dist/cjs/commands/subscribe.js +31 -0
- package/dist/cjs/commands/unsubscribe.d.ts +9 -0
- package/dist/cjs/commands/unsubscribe.js +31 -0
- package/dist/cjs/connection-deadline.d.ts +49 -0
- package/dist/cjs/connection-deadline.js +91 -0
- package/dist/cjs/errors.d.ts +83 -0
- package/dist/cjs/errors.js +13 -0
- package/dist/cjs/handler/imap-compiler.d.ts +24 -0
- package/dist/cjs/handler/imap-compiler.js +285 -0
- package/dist/cjs/handler/imap-formal-syntax.d.ts +28 -0
- package/dist/cjs/handler/imap-formal-syntax.js +121 -0
- package/dist/cjs/handler/imap-handler.d.ts +9 -0
- package/dist/cjs/handler/imap-handler.js +10 -0
- package/dist/cjs/handler/imap-parser.d.ts +16 -0
- package/dist/cjs/handler/imap-parser.js +90 -0
- package/dist/cjs/handler/imap-stream.d.ts +181 -0
- package/dist/cjs/handler/imap-stream.js +446 -0
- package/dist/cjs/handler/limits.d.ts +25 -0
- package/dist/cjs/handler/limits.js +51 -0
- package/dist/cjs/handler/parser-instance.d.ts +68 -0
- package/dist/cjs/handler/parser-instance.js +223 -0
- package/dist/cjs/handler/token-parser.d.ts +91 -0
- package/dist/cjs/handler/token-parser.js +673 -0
- package/dist/cjs/handler/types.d.ts +91 -0
- package/dist/cjs/handler/types.js +4 -0
- package/dist/cjs/imap-commands.d.ts +16 -0
- package/dist/cjs/imap-commands.js +74 -0
- package/dist/cjs/imap-flow.d.ts +676 -0
- package/dist/cjs/imap-flow.js +3949 -0
- package/dist/cjs/jp-decoder.d.ts +12 -0
- package/dist/cjs/jp-decoder.js +79 -0
- package/dist/cjs/limited-passthrough.d.ts +25 -0
- package/dist/cjs/limited-passthrough.js +54 -0
- package/dist/cjs/logger.d.ts +3 -0
- package/dist/cjs/logger.js +11 -0
- package/dist/cjs/package-info.d.ts +3 -0
- package/dist/cjs/package-info.js +7 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/proxy-connection.d.ts +33 -0
- package/dist/cjs/proxy-connection.js +392 -0
- package/dist/cjs/search-compiler.d.ts +34 -0
- package/dist/cjs/search-compiler.js +476 -0
- package/dist/cjs/special-use.d.ts +22 -0
- package/dist/cjs/special-use.js +911 -0
- package/dist/cjs/tools.d.ts +427 -0
- package/dist/cjs/tools.js +1496 -0
- package/{lib/imap-flow.d.ts → dist/cjs/types.d.ts} +386 -516
- package/dist/cjs/types.js +5 -0
- package/dist/esm/charsets.d.ts +1 -0
- package/{lib → dist/esm}/charsets.js +1 -6
- package/dist/esm/commands/append.d.ts +22 -0
- package/{lib → dist/esm}/commands/append.js +22 -52
- package/dist/esm/commands/authenticate.d.ts +24 -0
- package/{lib → dist/esm}/commands/authenticate.js +62 -87
- package/dist/esm/commands/capability.d.ts +8 -0
- package/{lib → dist/esm}/commands/capability.js +6 -9
- package/dist/esm/commands/close.d.ts +8 -0
- package/{lib → dist/esm}/commands/close.js +6 -10
- package/dist/esm/commands/compress.d.ts +8 -0
- package/{lib → dist/esm}/commands/compress.js +7 -11
- package/dist/esm/commands/copy.d.ts +13 -0
- package/{lib → dist/esm}/commands/copy.js +12 -20
- package/dist/esm/commands/copyuid-parser.d.ts +11 -0
- package/{lib → dist/esm}/commands/copyuid-parser.js +9 -15
- package/dist/esm/commands/create.d.ts +11 -0
- package/{lib → dist/esm}/commands/create.js +13 -27
- package/dist/esm/commands/delete.d.ts +11 -0
- package/{lib → dist/esm}/commands/delete.js +9 -14
- package/dist/esm/commands/enable.d.ts +9 -0
- package/{lib → dist/esm}/commands/enable.js +23 -30
- package/dist/esm/commands/esearch-parser.d.ts +17 -0
- package/dist/esm/commands/esearch-parser.js +88 -0
- package/dist/esm/commands/expunge.d.ts +12 -0
- package/{lib → dist/esm}/commands/expunge.js +17 -22
- package/dist/esm/commands/fetch.d.ts +30 -0
- package/{lib → dist/esm}/commands/fetch.js +32 -64
- package/dist/esm/commands/id.d.ts +10 -0
- package/{lib → dist/esm}/commands/id.js +17 -23
- package/dist/esm/commands/idle.d.ts +9 -0
- package/{lib → dist/esm}/commands/idle.js +47 -81
- package/dist/esm/commands/list.d.ts +16 -0
- package/{lib → dist/esm}/commands/list.js +56 -121
- package/dist/esm/commands/login.d.ts +11 -0
- package/{lib → dist/esm}/commands/login.js +10 -15
- package/dist/esm/commands/logout.d.ts +8 -0
- package/{lib → dist/esm}/commands/logout.js +9 -11
- package/dist/esm/commands/move.d.ts +13 -0
- package/{lib → dist/esm}/commands/move.js +13 -21
- package/dist/esm/commands/namespace.d.ts +25 -0
- package/{lib → dist/esm}/commands/namespace.js +34 -44
- package/dist/esm/commands/noop.d.ts +8 -0
- package/{lib → dist/esm}/commands/noop.js +6 -7
- package/dist/esm/commands/quota.d.ts +10 -0
- package/{lib → dist/esm}/commands/quota.js +18 -36
- package/dist/esm/commands/rename.d.ts +12 -0
- package/{lib → dist/esm}/commands/rename.js +10 -15
- package/dist/esm/commands/search.d.ts +15 -0
- package/{lib → dist/esm}/commands/search.js +36 -135
- package/dist/esm/commands/select.d.ts +25 -0
- package/{lib → dist/esm}/commands/select.js +33 -64
- package/dist/esm/commands/starttls.d.ts +8 -0
- package/{lib → dist/esm}/commands/starttls.js +6 -8
- package/dist/esm/commands/status-fields.d.ts +14 -0
- package/{lib → dist/esm}/commands/status-fields.js +5 -16
- package/dist/esm/commands/status.d.ts +12 -0
- package/{lib → dist/esm}/commands/status.js +18 -29
- package/dist/esm/commands/store.d.ts +19 -0
- package/{lib → dist/esm}/commands/store.js +24 -37
- package/dist/esm/commands/subscribe.d.ts +9 -0
- package/{lib → dist/esm}/commands/subscribe.js +8 -12
- package/dist/esm/commands/unsubscribe.d.ts +9 -0
- package/{lib → dist/esm}/commands/unsubscribe.js +8 -12
- package/dist/esm/connection-deadline.d.ts +49 -0
- package/{lib → dist/esm}/connection-deadline.js +14 -25
- package/dist/esm/errors.d.ts +83 -0
- package/dist/esm/errors.js +9 -0
- package/dist/esm/handler/imap-compiler.d.ts +24 -0
- package/{lib → dist/esm}/handler/imap-compiler.js +22 -80
- package/dist/esm/handler/imap-formal-syntax.d.ts +28 -0
- package/dist/esm/handler/imap-formal-syntax.js +117 -0
- package/dist/esm/handler/imap-handler.d.ts +9 -0
- package/dist/esm/handler/imap-handler.js +9 -0
- package/dist/esm/handler/imap-parser.d.ts +16 -0
- package/{lib → dist/esm}/handler/imap-parser.js +31 -44
- package/dist/esm/handler/imap-stream.d.ts +181 -0
- package/{lib → dist/esm}/handler/imap-stream.js +29 -121
- package/dist/esm/handler/limits.d.ts +25 -0
- package/{lib → dist/esm}/handler/limits.js +13 -22
- package/dist/esm/handler/parser-instance.d.ts +68 -0
- package/{lib → dist/esm}/handler/parser-instance.js +19 -47
- package/dist/esm/handler/token-parser.d.ts +91 -0
- package/{lib → dist/esm}/handler/token-parser.js +71 -155
- package/dist/esm/handler/types.d.ts +91 -0
- package/dist/esm/handler/types.js +3 -0
- package/dist/esm/imap-commands.d.ts +16 -0
- package/dist/esm/imap-commands.js +67 -0
- package/dist/esm/imap-flow.d.ts +676 -0
- package/{lib → dist/esm}/imap-flow.js +785 -1802
- package/dist/esm/jp-decoder.d.ts +12 -0
- package/{lib → dist/esm}/jp-decoder.js +6 -21
- package/dist/esm/limited-passthrough.d.ts +25 -0
- package/{lib → dist/esm}/limited-passthrough.js +7 -20
- package/dist/esm/logger.d.ts +3 -0
- package/dist/esm/logger.js +4 -0
- package/dist/esm/package-info.d.ts +3 -0
- package/dist/esm/package-info.js +4 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/proxy-connection.d.ts +33 -0
- package/{lib → dist/esm}/proxy-connection.js +56 -127
- package/dist/esm/search-compiler.d.ts +34 -0
- package/{lib → dist/esm}/search-compiler.js +54 -110
- package/dist/esm/special-use.d.ts +22 -0
- package/dist/esm/special-use.js +907 -0
- package/dist/esm/tools.d.ts +427 -0
- package/dist/esm/tools.js +1446 -0
- package/dist/esm/types.d.ts +828 -0
- package/dist/esm/types.js +4 -0
- package/package.json +60 -20
- package/.gitattributes +0 -1
- package/.github/CODE_OF_CONDUCT.md +0 -76
- package/.github/FUNDING.yml +0 -4
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- package/.github/contributing.md +0 -17
- package/.github/workflows/release.yaml +0 -36
- package/.github/workflows/stale.yml +0 -29
- package/.github/workflows/test.yml +0 -51
- package/.ncurc.js +0 -4
- package/.prettierignore +0 -4
- package/.prettierrc.js +0 -8
- package/.release-please-manifest.json +0 -3
- package/CLAUDE.md +0 -104
- package/Gruntfile.js +0 -23
- package/eslint.config.js +0 -45
- package/lib/handler/imap-formal-syntax.js +0 -189
- package/lib/handler/imap-handler.js +0 -17
- package/lib/imap-commands.js +0 -45
- package/lib/logger.js +0 -5
- package/lib/special-use.js +0 -923
- package/lib/tools.js +0 -1612
- package/release-please-config.json +0 -10
- package/test/authentication-test.js +0 -101
- package/test/auto-idle-test.js +0 -470
- package/test/bodystructure-test.js +0 -899
- package/test/charsets-test.js +0 -161
- package/test/commands-branches-test.js +0 -1095
- package/test/commands-integration-test.js +0 -11124
- package/test/commands-test.js +0 -73
- package/test/connection-edge-cases-test.js +0 -1828
- package/test/connection-test.js +0 -162
- package/test/copyuid-parser-test.js +0 -173
- package/test/fetch-generator-test.js +0 -218
- package/test/fixtures/fake-timers.js +0 -115
- package/test/fixtures/serialized-mimetorture.js +0 -2738
- package/test/fixtures/test-client.js +0 -57
- package/test/fixtures/test-tls.js +0 -8
- package/test/handler-branches-test.js +0 -310
- package/test/idle-polling-test.js +0 -518
- package/test/imap-compiler-test.js +0 -809
- package/test/imap-flow-compress-test.js +0 -166
- package/test/imap-flow-coverage-test.js +0 -612
- package/test/imap-flow-fetch-download-test.js +0 -873
- package/test/imap-flow-internals-test.js +0 -725
- package/test/imap-flow-methods-test.js +0 -889
- package/test/imap-flow-proxy-paths-test.js +0 -366
- package/test/imap-flow-secure-test.js +0 -573
- package/test/imap-flow-server-test.js +0 -1474
- package/test/imap-formal-syntax-test.js +0 -293
- package/test/imap-parser-test.js +0 -1474
- package/test/imap-stream-edge-cases-test.js +0 -666
- package/test/imap-stream-test.js +0 -177
- package/test/imapflow-test.js +0 -258
- package/test/integration/README.md +0 -52
- package/test/integration/dovecot-test.conf +0 -27
- package/test/integration/rev2-live-test.js +0 -431
- package/test/integration/run-rev2-tests.sh +0 -75
- package/test/integration-test.js +0 -83
- package/test/jp-decoder-test.js +0 -304
- package/test/limited-passthrough-test.js +0 -299
- package/test/memory-cleanup-test.js +0 -144
- package/test/memory-leak-test.js +0 -667
- package/test/parser-limits-test.js +0 -292
- package/test/proxy-connection-test.js +0 -738
- package/test/reliability-improvements-test.js +0 -548
- package/test/search-compiler-test.js +0 -1300
- package/test/search-test.js +0 -329
- package/test/special-use-test.js +0 -418
- package/test/starttls-injection-test.js +0 -181
- package/test/tag-correlation-test.js +0 -333
- package/test/timer-policy-test.js +0 -227
- package/test/token-parser-test.js +0 -456
- package/test/tools-test.js +0 -2013
- package/test/unhandled-rejection-test.js +0 -593
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = enable;
|
|
4
|
+
const tools_js_1 = require("../tools.js");
|
|
5
|
+
/**
|
|
6
|
+
* Enables IMAP extensions on the server.
|
|
7
|
+
*
|
|
8
|
+
* @param connection - IMAP connection instance
|
|
9
|
+
* @param extensionList - List of extension names to enable
|
|
10
|
+
* @returns Set of enabled extensions, false on failure, or undefined if not applicable
|
|
11
|
+
*/
|
|
12
|
+
async function enable(connection, extensionList) {
|
|
13
|
+
// ENABLE is part of base IMAP4rev2, so rev2-only servers may omit the token
|
|
14
|
+
if (!(0, tools_js_1.hasCapability)(connection, 'ENABLE') || connection.state !== connection.states.AUTHENTICATED) {
|
|
15
|
+
// nothing to do here
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
// Pre-filter: only request extensions the server actually advertised in its
|
|
19
|
+
// CAPABILITY response. Requesting unsupported extensions would cause an error.
|
|
20
|
+
// Compared case-insensitively - the capability map keeps canonical casing for
|
|
21
|
+
// some keys (e.g. IMAP4rev2).
|
|
22
|
+
let advertised = new Set([...connection.capabilities.keys()].map(capability => capability.toUpperCase()));
|
|
23
|
+
extensionList = extensionList.filter(extension => advertised.has(extension.toUpperCase()));
|
|
24
|
+
if (!extensionList.length) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
let response;
|
|
28
|
+
try {
|
|
29
|
+
let enabled = new Set();
|
|
30
|
+
response = await connection.exec('ENABLE', extensionList.map((extension) => ({ type: 'ATOM', value: extension.toUpperCase() })), {
|
|
31
|
+
untagged: {
|
|
32
|
+
// The untagged ENABLED response is a flat list of extension names
|
|
33
|
+
// (e.g., "* ENABLED CONDSTORE UTF8=ACCEPT"), NOT key-value pairs.
|
|
34
|
+
// Each attribute is a single extension identifier.
|
|
35
|
+
ENABLED: async (untagged) => {
|
|
36
|
+
if (!untagged.attributes || !untagged.attributes.length) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
untagged.attributes.forEach(attr => {
|
|
40
|
+
let value = attr.value;
|
|
41
|
+
if (value && typeof value === 'string') {
|
|
42
|
+
enabled.add(value.toUpperCase().trim());
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
// Merge instead of replace - the untagged ENABLED response only lists
|
|
49
|
+
// extensions enabled by this command (RFC 5161), so a replace would drop
|
|
50
|
+
// grants from an earlier ENABLE call
|
|
51
|
+
connection.enabled = new Set([...connection.enabled, ...enabled]);
|
|
52
|
+
response.next();
|
|
53
|
+
return connection.enabled;
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
connection.log.warn({ err, cid: connection.id });
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
module.exports = exports.default;
|
|
61
|
+
Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ImapAttributeList } from '../handler/types.js';
|
|
2
|
+
import type { ESearchResult } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parses the key-value attributes from an ESEARCH untagged response.
|
|
5
|
+
*
|
|
6
|
+
* Receives the attribute list AFTER stripping the leading (TAG "X") list
|
|
7
|
+
* and the UID atom, i.e. only the result keyword/value pairs remain.
|
|
8
|
+
*
|
|
9
|
+
* ALL and PARTIAL.messages are kept as compact sequence-set strings.
|
|
10
|
+
* Use expandRange() from tools.ts if you need to expand them.
|
|
11
|
+
* MODSEQ (RFC 7162, sent when the search used a MODSEQ criterion) is
|
|
12
|
+
* returned as a BigInt.
|
|
13
|
+
*
|
|
14
|
+
* @param attrs - Attribute array from the IMAP parser
|
|
15
|
+
* @returns ESearchResult object
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseEsearchResponse(attrs: ImapAttributeList): ESearchResult;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseEsearchResponse = parseEsearchResponse;
|
|
4
|
+
const tools_js_1 = require("../tools.js");
|
|
5
|
+
/**
|
|
6
|
+
* Parses the key-value attributes from an ESEARCH untagged response.
|
|
7
|
+
*
|
|
8
|
+
* Receives the attribute list AFTER stripping the leading (TAG "X") list
|
|
9
|
+
* and the UID atom, i.e. only the result keyword/value pairs remain.
|
|
10
|
+
*
|
|
11
|
+
* ALL and PARTIAL.messages are kept as compact sequence-set strings.
|
|
12
|
+
* Use expandRange() from tools.ts if you need to expand them.
|
|
13
|
+
* MODSEQ (RFC 7162, sent when the search used a MODSEQ criterion) is
|
|
14
|
+
* returned as a BigInt.
|
|
15
|
+
*
|
|
16
|
+
* @param attrs - Attribute array from the IMAP parser
|
|
17
|
+
* @returns ESearchResult object
|
|
18
|
+
*/
|
|
19
|
+
function parseEsearchResponse(attrs) {
|
|
20
|
+
const result = {};
|
|
21
|
+
let i = 0;
|
|
22
|
+
while (i < attrs.length) {
|
|
23
|
+
const token = attrs[i];
|
|
24
|
+
if (!token || token.type !== 'ATOM') {
|
|
25
|
+
i++;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const key = token.value.toUpperCase();
|
|
29
|
+
if (i + 1 >= attrs.length) {
|
|
30
|
+
i++;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
switch (key) {
|
|
34
|
+
// COUNT is a plain message count; MIN and MAX are sequence numbers or UIDs. All
|
|
35
|
+
// three are bounded decimal runs - isNaN() would also admit '1e400' (Infinity)
|
|
36
|
+
case 'COUNT': {
|
|
37
|
+
const n = (0, tools_js_1.parseUintValue)(attrs[++i]?.value, tools_js_1.MAX_UINT32_DIGITS);
|
|
38
|
+
if (n !== false)
|
|
39
|
+
result.count = n;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
case 'MIN': {
|
|
43
|
+
const n = (0, tools_js_1.parseUintValue)(attrs[++i]?.value, tools_js_1.MAX_UINT32_DIGITS);
|
|
44
|
+
if (n !== false)
|
|
45
|
+
result.min = n;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case 'MAX': {
|
|
49
|
+
const n = (0, tools_js_1.parseUintValue)(attrs[++i]?.value, tools_js_1.MAX_UINT32_DIGITS);
|
|
50
|
+
if (n !== false)
|
|
51
|
+
result.max = n;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
case 'MODSEQ': {
|
|
55
|
+
// RFC 7162 section 3.1.5: present when the SEARCH used a MODSEQ
|
|
56
|
+
// criterion on a CONDSTORE-enabled session. BigInt because
|
|
57
|
+
// mod-sequence values are unsigned 63-bit
|
|
58
|
+
const modseq = (0, tools_js_1.parseBigIntValue)(attrs[++i]?.value);
|
|
59
|
+
if (modseq !== false)
|
|
60
|
+
result.modseq = modseq;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case 'ALL': {
|
|
64
|
+
const allToken = attrs[++i];
|
|
65
|
+
if (allToken && typeof allToken.value === 'string') {
|
|
66
|
+
result.all = allToken.value;
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case 'PARTIAL': {
|
|
71
|
+
const listToken = attrs[++i];
|
|
72
|
+
const items = Array.isArray(listToken) ? listToken : null;
|
|
73
|
+
if (!items || items.length < 2)
|
|
74
|
+
break;
|
|
75
|
+
result.partial = {
|
|
76
|
+
range: items[0].value,
|
|
77
|
+
messages: items[1].value
|
|
78
|
+
};
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
default:
|
|
82
|
+
// Skip the value token for unknown keys to keep the stream aligned.
|
|
83
|
+
// The loop's unconditional i++ at the bottom advances past the key;
|
|
84
|
+
// this extra i++ advances past the value token.
|
|
85
|
+
i++;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
i++;
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ImapFlow } from '../imap-flow.js';
|
|
2
|
+
import type { MessageRangeOptions } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Deletes specified messages by flagging them as Deleted and expunging.
|
|
5
|
+
*
|
|
6
|
+
* @param connection - IMAP connection instance
|
|
7
|
+
* @param range - Message sequence number or UID range
|
|
8
|
+
* @param options - Expunge options
|
|
9
|
+
* @param options.uid - If true, use UID EXPUNGE when UIDPLUS is available
|
|
10
|
+
* @returns True on success, false on failure, or undefined if preconditions not met
|
|
11
|
+
*/
|
|
12
|
+
export default function expunge(connection: ImapFlow, range: string, options?: MessageRangeOptions | undefined): Promise<boolean | undefined>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = expunge;
|
|
4
|
+
const tools_js_1 = require("../tools.js");
|
|
5
|
+
/**
|
|
6
|
+
* Deletes specified messages by flagging them as Deleted and expunging.
|
|
7
|
+
*
|
|
8
|
+
* @param connection - IMAP connection instance
|
|
9
|
+
* @param range - Message sequence number or UID range
|
|
10
|
+
* @param options - Expunge options
|
|
11
|
+
* @param options.uid - If true, use UID EXPUNGE when UIDPLUS is available
|
|
12
|
+
* @returns True on success, false on failure, or undefined if preconditions not met
|
|
13
|
+
*/
|
|
14
|
+
async function expunge(connection, range, options) {
|
|
15
|
+
if (connection.state !== connection.states.SELECTED || !range) {
|
|
16
|
+
// nothing to do here
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
options = options || {};
|
|
20
|
+
// Two-step deletion process per IMAP protocol:
|
|
21
|
+
// Step 1: Mark the target messages with the \Deleted flag.
|
|
22
|
+
await connection.messageFlagsAdd(range, ['\\Deleted'], options);
|
|
23
|
+
// Step 2: Issue EXPUNGE to permanently remove \Deleted messages.
|
|
24
|
+
// With UIDPLUS (RFC 4315): "UID EXPUNGE <uids>" removes only the specified UIDs,
|
|
25
|
+
// leaving other \Deleted messages untouched, important for concurrent access.
|
|
26
|
+
// Without UIDPLUS: plain "EXPUNGE" removes ALL messages flagged \Deleted in the mailbox.
|
|
27
|
+
let byUid = options.uid && (0, tools_js_1.hasCapability)(connection, 'UIDPLUS');
|
|
28
|
+
let command = byUid ? 'UID EXPUNGE' : 'EXPUNGE';
|
|
29
|
+
let attributes = byUid ? [{ type: 'SEQUENCE', value: range }] : false;
|
|
30
|
+
let response;
|
|
31
|
+
try {
|
|
32
|
+
response = await connection.exec(command, attributes);
|
|
33
|
+
// CONDSTORE (RFC 7162): the server may return HIGHESTMODSEQ in the response code
|
|
34
|
+
// (e.g., "A OK [HIGHESTMODSEQ 9122] Expunge completed").
|
|
35
|
+
// Track this so the client can detect concurrent mailbox changes via mod-sequences.
|
|
36
|
+
let section = response.response.attributes && response.response.attributes[0] && response.response.attributes[0].section;
|
|
37
|
+
let responseCode = section && section.length && section[0] && typeof section[0].value === 'string' ? section[0].value : '';
|
|
38
|
+
if (responseCode.toUpperCase() === 'HIGHESTMODSEQ') {
|
|
39
|
+
// A response code always comes with its section, see responseCode above
|
|
40
|
+
let codeSection = section;
|
|
41
|
+
let mailbox = connection.mailbox;
|
|
42
|
+
// Bounded digit runs only: isNaN() also passes '1e5', which BigInt() rejects with
|
|
43
|
+
// a throw that the catch below would swallow, making messageDelete() report false
|
|
44
|
+
// even though the server expunged the messages.
|
|
45
|
+
let highestModseq = (0, tools_js_1.parseBigIntValue)(codeSection[1] && codeSection[1].value);
|
|
46
|
+
if (highestModseq && (!mailbox.highestModseq || highestModseq > mailbox.highestModseq)) {
|
|
47
|
+
mailbox.highestModseq = highestModseq;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
response.next();
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
await (0, tools_js_1.enhanceCommandError)(err);
|
|
55
|
+
connection.log.warn({ err, cid: connection.id });
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
module.exports = exports.default;
|
|
60
|
+
Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ImapFlow } from '../imap-flow.js';
|
|
2
|
+
import type { FetchMessageObject, FetchOptions, FetchQueryObject } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Options for the FETCH command
|
|
5
|
+
*/
|
|
6
|
+
export interface FetchCommandOptions extends Omit<FetchOptions, 'changedSince'> {
|
|
7
|
+
/** Only fetch messages changed since this modseq value */
|
|
8
|
+
changedSince?: bigint | number | string | undefined;
|
|
9
|
+
/** Callback for processing each fetched message individually. Call `next()` to release the next message */
|
|
10
|
+
onUntaggedFetch?: ((message: FetchMessageObject, next: (err?: Error | null | undefined) => void) => void) | undefined;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Result of the FETCH command
|
|
14
|
+
*/
|
|
15
|
+
export interface FetchCommandResult {
|
|
16
|
+
/** Number of untagged FETCH responses received */
|
|
17
|
+
count: number;
|
|
18
|
+
/** Formatted messages, empty when `onUntaggedFetch` consumed them */
|
|
19
|
+
list: FetchMessageObject[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Fetches emails from the server.
|
|
23
|
+
*
|
|
24
|
+
* @param connection - IMAP connection instance
|
|
25
|
+
* @param range - Message sequence number or UID range
|
|
26
|
+
* @param query - Fetch query specifying which data to retrieve (e.g., flags, envelope, bodyStructure, headers, source, bodyParts)
|
|
27
|
+
* @param options - Fetch options
|
|
28
|
+
* @returns Object with message count and list, or undefined if not in SELECTED state
|
|
29
|
+
*/
|
|
30
|
+
export default function fetch(connection: ImapFlow, range: string, query: FetchQueryObject, options?: FetchCommandOptions | undefined): Promise<FetchCommandResult | undefined>;
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = fetch;
|
|
4
|
+
const tools_js_1 = require("../tools.js");
|
|
5
|
+
/**
|
|
6
|
+
* Fetches emails from the server.
|
|
7
|
+
*
|
|
8
|
+
* @param connection - IMAP connection instance
|
|
9
|
+
* @param range - Message sequence number or UID range
|
|
10
|
+
* @param query - Fetch query specifying which data to retrieve (e.g., flags, envelope, bodyStructure, headers, source, bodyParts)
|
|
11
|
+
* @param options - Fetch options
|
|
12
|
+
* @returns Object with message count and list, or undefined if not in SELECTED state
|
|
13
|
+
*/
|
|
14
|
+
async function fetch(connection, range, query, options) {
|
|
15
|
+
if (connection.state !== connection.states.SELECTED || !range) {
|
|
16
|
+
// nothing to do here
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
options = options || {};
|
|
20
|
+
let mailbox = connection.mailbox;
|
|
21
|
+
// Use BINARY extension for fetching if supported and requested, otherwise fall back to BODY.
|
|
22
|
+
// RFC 9051 folds the FETCH side of the BINARY extension into base IMAP4rev2, so an active
|
|
23
|
+
// rev2 session can use it even without the BINARY capability token (the APPEND side is NOT
|
|
24
|
+
// folded in and stays gated on the token in append.ts)
|
|
25
|
+
const canUseBinary = connection.capabilities.has('BINARY') || (0, tools_js_1.isRev2Active)(connection);
|
|
26
|
+
const commandKey = canUseBinary && options.binary && !connection.disableBinary ? 'BINARY' : 'BODY';
|
|
27
|
+
// Retry logic for ETHROTTLE errors (server rate limiting) with exponential backoff
|
|
28
|
+
let retryCount = 0;
|
|
29
|
+
const maxRetries = 4;
|
|
30
|
+
const baseDelay = 1000; // Start with 1 second delay
|
|
31
|
+
while (retryCount < maxRetries) {
|
|
32
|
+
let messages = {
|
|
33
|
+
count: 0,
|
|
34
|
+
list: []
|
|
35
|
+
};
|
|
36
|
+
let response;
|
|
37
|
+
try {
|
|
38
|
+
/* c8 ignore next */ // range is guaranteed truthy by the early-return guard above, so the '*' fallback is unreachable
|
|
39
|
+
let attributes = [{ type: 'SEQUENCE', value: (range || '*').toString() }];
|
|
40
|
+
let queryStructure = [];
|
|
41
|
+
// Helper to build BODY.PEEK[section]<partial> or BINARY.PEEK[section]<partial> atoms.
|
|
42
|
+
// PEEK avoids marking messages as \Seen. Section identifies what to fetch (HEADER, specific part, etc.)
|
|
43
|
+
// Partial is an optional byte range [start, maxLength].
|
|
44
|
+
let setBodyPeek = (attributes, partial) => {
|
|
45
|
+
let section = [].concat(attributes || []);
|
|
46
|
+
// BINARY may only address the empty section or a numeric part specifier
|
|
47
|
+
// (RFC 3516 / RFC 9051 section-binary) - HEADER, HEADER.FIELDS, TEXT and
|
|
48
|
+
// n.MIME are invalid after BINARY and must stay BODY fetches
|
|
49
|
+
let first = section[0];
|
|
50
|
+
let binaryAddressable = !section.length || (section.length === 1 && typeof first.value === 'string' && /^\d+(\.\d+)*$/.test(first.value));
|
|
51
|
+
let bodyPeek = {
|
|
52
|
+
type: 'ATOM',
|
|
53
|
+
value: `${binaryAddressable ? commandKey : 'BODY'}.PEEK`,
|
|
54
|
+
section: section,
|
|
55
|
+
partial
|
|
56
|
+
};
|
|
57
|
+
queryStructure.push(bodyPeek);
|
|
58
|
+
};
|
|
59
|
+
// IMAP fetch macros (ALL, FAST, FULL) and standard data items map directly to IMAP atoms
|
|
60
|
+
['all', 'fast', 'full', 'uid', 'flags', 'bodyStructure', 'envelope', 'internalDate'].forEach(key => {
|
|
61
|
+
if (query[key]) {
|
|
62
|
+
queryStructure.push({ type: 'ATOM', value: key.toUpperCase() });
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
if (query.size) {
|
|
66
|
+
queryStructure.push({ type: 'ATOM', value: 'RFC822.SIZE' });
|
|
67
|
+
}
|
|
68
|
+
// Fetch full message source, optionally with byte range (start/maxLength)
|
|
69
|
+
if (query.source) {
|
|
70
|
+
let partial;
|
|
71
|
+
if (typeof query.source === 'object' && (query.source.start || query.source.maxLength)) {
|
|
72
|
+
partial = [Number(query.source.start) || 0];
|
|
73
|
+
if (query.source.maxLength && !isNaN(query.source.maxLength)) {
|
|
74
|
+
partial.push(Number(query.source.maxLength));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
setBodyPeek(null, partial);
|
|
78
|
+
}
|
|
79
|
+
// Always request a unique email ID for message deduplication.
|
|
80
|
+
// Prefer OBJECTID (RFC 8474) over Gmail's X-GM-MSGID extension.
|
|
81
|
+
if (connection.capabilities.has('OBJECTID')) {
|
|
82
|
+
queryStructure.push({ type: 'ATOM', value: 'EMAILID' });
|
|
83
|
+
}
|
|
84
|
+
else if (connection.capabilities.has('X-GM-EXT-1')) {
|
|
85
|
+
queryStructure.push({ type: 'ATOM', value: 'X-GM-MSGID' });
|
|
86
|
+
}
|
|
87
|
+
// Thread ID: OBJECTID's THREADID or Gmail's X-GM-THRID
|
|
88
|
+
if (query.threadId) {
|
|
89
|
+
if (connection.capabilities.has('OBJECTID')) {
|
|
90
|
+
queryStructure.push({ type: 'ATOM', value: 'THREADID' });
|
|
91
|
+
}
|
|
92
|
+
else if (connection.capabilities.has('X-GM-EXT-1')) {
|
|
93
|
+
queryStructure.push({ type: 'ATOM', value: 'X-GM-THRID' });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Gmail labels are only available with X-GM-EXT-1 extension
|
|
97
|
+
if (query.labels) {
|
|
98
|
+
if (connection.capabilities.has('X-GM-EXT-1')) {
|
|
99
|
+
queryStructure.push({ type: 'ATOM', value: 'X-GM-LABELS' });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// always ask for modseq if possible
|
|
103
|
+
if (connection.enabled.has('CONDSTORE') && !mailbox.noModseq) {
|
|
104
|
+
queryStructure.push({ type: 'ATOM', value: 'MODSEQ' });
|
|
105
|
+
}
|
|
106
|
+
// Always include UID in the response even if not explicitly requested,
|
|
107
|
+
// since we use it internally for message identification and tracking
|
|
108
|
+
if (!query.uid) {
|
|
109
|
+
queryStructure.push({ type: 'ATOM', value: 'UID' });
|
|
110
|
+
}
|
|
111
|
+
// Headers: fetch all headers or only specific ones via HEADER.FIELDS
|
|
112
|
+
if (query.headers) {
|
|
113
|
+
if (Array.isArray(query.headers)) {
|
|
114
|
+
setBodyPeek([{ type: 'ATOM', value: 'HEADER.FIELDS' }, query.headers.map(header => ({ type: 'ATOM', value: header }))]);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
setBodyPeek({ type: 'ATOM', value: 'HEADER' });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Fetch specific body parts by MIME part number (e.g., "1", "1.2", "2.MIME")
|
|
121
|
+
// Each part can optionally include a byte range (start/maxLength)
|
|
122
|
+
if (query.bodyParts && query.bodyParts.length) {
|
|
123
|
+
query.bodyParts.forEach(part => {
|
|
124
|
+
if (!part) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
let key;
|
|
128
|
+
let partial;
|
|
129
|
+
if (typeof part === 'object') {
|
|
130
|
+
if (!part.key || typeof part.key !== 'string') {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
key = part.key.toUpperCase();
|
|
134
|
+
if (part.start || part.maxLength) {
|
|
135
|
+
partial = [Number(part.start) || 0];
|
|
136
|
+
if (part.maxLength && !isNaN(part.maxLength)) {
|
|
137
|
+
partial.push(Number(part.maxLength));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else if (typeof part === 'string') {
|
|
142
|
+
key = part.toUpperCase();
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
setBodyPeek({ type: 'ATOM', value: key }, partial);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
// IMAP requires a single item to not be wrapped in parentheses, but
|
|
151
|
+
// multiple items must be in a list. If only one item, unwrap the array.
|
|
152
|
+
let queryAttribute = queryStructure;
|
|
153
|
+
if (queryStructure.length === 1) {
|
|
154
|
+
queryAttribute = queryStructure.pop();
|
|
155
|
+
}
|
|
156
|
+
attributes.push(queryAttribute);
|
|
157
|
+
// CONDSTORE extension: only fetch messages with modseq higher than the given value.
|
|
158
|
+
// QRESYNC adds VANISHED to also get expunged UIDs since last sync.
|
|
159
|
+
if (options.changedSince && connection.enabled.has('CONDSTORE') && !mailbox.noModseq) {
|
|
160
|
+
let changedSinceArgs = [
|
|
161
|
+
{
|
|
162
|
+
type: 'ATOM',
|
|
163
|
+
value: 'CHANGEDSINCE'
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
type: 'ATOM',
|
|
167
|
+
value: options.changedSince.toString()
|
|
168
|
+
}
|
|
169
|
+
];
|
|
170
|
+
if (options.uid && connection.enabled.has('QRESYNC')) {
|
|
171
|
+
changedSinceArgs.push({
|
|
172
|
+
type: 'ATOM',
|
|
173
|
+
value: 'VANISHED'
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
attributes.push(changedSinceArgs);
|
|
177
|
+
}
|
|
178
|
+
response = await connection.exec(options.uid ? 'UID FETCH' : 'FETCH', attributes, {
|
|
179
|
+
untagged: {
|
|
180
|
+
// Each matching message triggers an untagged FETCH response.
|
|
181
|
+
// If onUntaggedFetch callback is provided, stream messages to it one by one
|
|
182
|
+
// (useful for large result sets). Otherwise, collect all into messages.list.
|
|
183
|
+
FETCH: async (untagged) => {
|
|
184
|
+
messages.count++;
|
|
185
|
+
let formatted = await (0, tools_js_1.formatMessageResponse)(untagged, mailbox);
|
|
186
|
+
if (typeof options.onUntaggedFetch === 'function') {
|
|
187
|
+
await new Promise((resolve, reject) => {
|
|
188
|
+
options.onUntaggedFetch(formatted, err => {
|
|
189
|
+
if (err) {
|
|
190
|
+
reject(err);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
resolve();
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
messages.list.push(formatted);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
response.next();
|
|
205
|
+
return messages;
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
if (err.code === 'ETHROTTLE') {
|
|
209
|
+
// Server returned a throttle error (rate limiting). Retry with exponential backoff.
|
|
210
|
+
// Delay doubles each retry: 1s, 2s, 4s, 8s (capped at 30s).
|
|
211
|
+
// If server provides a throttleReset hint, use that if longer.
|
|
212
|
+
const backoffDelay = Math.min(baseDelay * Math.pow(2, retryCount), 30000); // Cap at 30 seconds
|
|
213
|
+
// Use throttle reset time if provided and longer than backoff. The hint is
|
|
214
|
+
// server-controlled, so the wait goes through connection.throttleWait(), which caps
|
|
215
|
+
// it and keeps the timer tracked and abortable.
|
|
216
|
+
const delay = err.throttleReset && err.throttleReset > backoffDelay ? err.throttleReset : backoffDelay;
|
|
217
|
+
connection.log.warn({
|
|
218
|
+
msg: 'Retrying throttled request with exponential backoff',
|
|
219
|
+
cid: connection.id,
|
|
220
|
+
code: err.code,
|
|
221
|
+
response: err.responseText,
|
|
222
|
+
throttleReset: err.throttleReset,
|
|
223
|
+
retryCount,
|
|
224
|
+
delayMs: delay
|
|
225
|
+
});
|
|
226
|
+
// An aborted wait means the client was closed, so give up rather than reissuing
|
|
227
|
+
// the FETCH on a connection that is already gone.
|
|
228
|
+
let aborted = await connection.throttleWait(delay);
|
|
229
|
+
if (aborted) {
|
|
230
|
+
throw connection.createNoConnectionError(connection.byeReason, { rejectedFrom: 'throttleAbort', command: 'FETCH' });
|
|
231
|
+
}
|
|
232
|
+
retryCount++;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
connection.log.warn({ err, cid: connection.id });
|
|
236
|
+
throw err;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
module.exports = exports.default;
|
|
241
|
+
Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ImapFlow } from '../imap-flow.js';
|
|
2
|
+
import type { IdInfoObject } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Sends ID info to the server and updates server info data based on the response.
|
|
5
|
+
*
|
|
6
|
+
* @param connection - IMAP connection instance
|
|
7
|
+
* @param clientInfo - Client identification key-value pairs to send to the server
|
|
8
|
+
* @returns Server information map, false on failure, or undefined if ID not supported
|
|
9
|
+
*/
|
|
10
|
+
export default function id(connection: ImapFlow, clientInfo?: IdInfoObject | null | undefined): Promise<IdInfoObject | false | undefined>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = id;
|
|
4
|
+
const tools_js_1 = require("../tools.js");
|
|
5
|
+
/**
|
|
6
|
+
* Sends ID info to the server and updates server info data based on the response.
|
|
7
|
+
*
|
|
8
|
+
* @param connection - IMAP connection instance
|
|
9
|
+
* @param clientInfo - Client identification key-value pairs to send to the server
|
|
10
|
+
* @returns Server information map, false on failure, or undefined if ID not supported
|
|
11
|
+
*/
|
|
12
|
+
// RFC 2971: The ID command exchanges client/server implementation info
|
|
13
|
+
// (name, version, vendor, etc.) for diagnostic and compatibility purposes.
|
|
14
|
+
async function id(connection, clientInfo) {
|
|
15
|
+
if (!connection.capabilities.has('ID')) {
|
|
16
|
+
// nothing to do here
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
let response;
|
|
20
|
+
try {
|
|
21
|
+
let map = {};
|
|
22
|
+
// Convert the clientInfo object into a flat array of alternating key-value strings
|
|
23
|
+
// for the IMAP wire format: ("key1" "value1" "key2" "value2" ...)
|
|
24
|
+
let formattedClientInfo = !clientInfo
|
|
25
|
+
? null
|
|
26
|
+
: Object.keys(clientInfo)
|
|
27
|
+
.map(key => [key, formatValue(key, clientInfo[key])])
|
|
28
|
+
.filter(entry => entry[1])
|
|
29
|
+
.flatMap(entry => entry);
|
|
30
|
+
if (formattedClientInfo && !formattedClientInfo.length) {
|
|
31
|
+
// value array has no elements
|
|
32
|
+
formattedClientInfo = null;
|
|
33
|
+
}
|
|
34
|
+
response = await connection.exec('ID', [formattedClientInfo], {
|
|
35
|
+
untagged: {
|
|
36
|
+
// Parse the server's ID response: a flat list of alternating key-value atoms.
|
|
37
|
+
// Even indices (i % 2 === 0) are keys, odd indices are the corresponding values.
|
|
38
|
+
ID: async (untagged) => {
|
|
39
|
+
let params = untagged.attributes && untagged.attributes[0];
|
|
40
|
+
let key;
|
|
41
|
+
(Array.isArray(params) ? params : [].concat(params || [])).forEach((val, i) => {
|
|
42
|
+
if (i % 2 === 0) {
|
|
43
|
+
key = val.value;
|
|
44
|
+
}
|
|
45
|
+
else if (typeof key === 'string' && typeof val.value === 'string') {
|
|
46
|
+
map[key.toLowerCase().trim()] = val.value;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
connection.serverInfo = map;
|
|
53
|
+
response.next();
|
|
54
|
+
return map;
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
connection.log.warn({ err, cid: connection.id });
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Formats a client info value for the ID command.
|
|
63
|
+
*
|
|
64
|
+
* @param key - The info key name
|
|
65
|
+
* @param value - The value to format
|
|
66
|
+
* @returns Formatted value string
|
|
67
|
+
*/
|
|
68
|
+
function formatValue(key, value) {
|
|
69
|
+
switch (key.toLowerCase()) {
|
|
70
|
+
case 'date':
|
|
71
|
+
// RFC 2971 requires the "date" field to use IMAP date-time format
|
|
72
|
+
// (e.g., "06-Feb-2026 12:00:00 +0000"), not ISO 8601 or other formats.
|
|
73
|
+
return (0, tools_js_1.formatDateTime)(value);
|
|
74
|
+
default:
|
|
75
|
+
// Other values are strings without newlines
|
|
76
|
+
return (value || '').toString().replace(/\s+/g, ' ');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
module.exports = exports.default;
|
|
80
|
+
Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ImapFlow } from '../imap-flow.js';
|
|
2
|
+
/**
|
|
3
|
+
* Listens for changes in the selected mailbox using IDLE or NOOP polling fallback.
|
|
4
|
+
*
|
|
5
|
+
* @param connection - IMAP connection instance
|
|
6
|
+
* @param maxIdleTime - Maximum time in milliseconds to stay in IDLE before restarting
|
|
7
|
+
* @returns Void on success, false on failure, or undefined if not in SELECTED state
|
|
8
|
+
*/
|
|
9
|
+
export default function idle(connection: ImapFlow, maxIdleTime?: number | false | undefined): Promise<void | false | undefined>;
|