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,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Public data shapes of the ImapFlow API. Every optional property is declared with an
|
|
3
|
+
// explicit `| undefined` so that a consumer compiling with exactOptionalPropertyTypes
|
|
4
|
+
// can still pass a value that may be undefined.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const resolveCharset: (charset: string) => string | null;
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
// Subset of the IANA Character Sets registry (https://www.iana.org/assignments/character-sets/).
|
|
4
2
|
// Used to validate and resolve charset names found in MIME Content-Type parameters.
|
|
5
3
|
// This list covers the most commonly encountered charsets in email messages.
|
|
@@ -262,9 +260,7 @@ const CHARACTER_SETS = [
|
|
|
262
260
|
'TIS-620',
|
|
263
261
|
'CP50220'
|
|
264
262
|
];
|
|
265
|
-
|
|
266
263
|
const CHARSET_MAP = new Map();
|
|
267
|
-
|
|
268
264
|
// Build a lookup map with normalized keys for fuzzy charset resolution.
|
|
269
265
|
// Normalization strategy:
|
|
270
266
|
// 1. Strip all underscores, hyphens, and spaces, then lowercase (e.g., "ISO-8859-1" -> "iso88591")
|
|
@@ -284,12 +280,11 @@ CHARACTER_SETS.forEach(entry => {
|
|
|
284
280
|
CHARSET_MAP.set(modifiedKey, entry);
|
|
285
281
|
}
|
|
286
282
|
});
|
|
287
|
-
|
|
288
283
|
// Resolves a charset name to its canonical IANA form using case-insensitive,
|
|
289
284
|
// symbol-stripping normalization. For example, "WIN-1252", "windows_1252",
|
|
290
285
|
// and "WINDOWS-1252" all resolve to "windows-1252".
|
|
291
286
|
// Returns null if the charset is not recognized.
|
|
292
|
-
|
|
287
|
+
export const resolveCharset = (charset) => {
|
|
293
288
|
let key = charset.replace(/[_-\s]/g, '').toLowerCase();
|
|
294
289
|
return CHARSET_MAP.get(key) ?? null;
|
|
295
290
|
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ImapFlow } from '../imap-flow.js';
|
|
2
|
+
import type { AppendResponseObject } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Result of an APPEND: the destination, the selected mailbox path at the time of the append,
|
|
5
|
+
* and the UID information the server reported
|
|
6
|
+
*/
|
|
7
|
+
export interface AppendResult extends AppendResponseObject {
|
|
8
|
+
/** Path of the currently selected mailbox, if any */
|
|
9
|
+
path?: string | undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Appends a message to a mailbox.
|
|
13
|
+
*
|
|
14
|
+
* @param connection - IMAP connection instance
|
|
15
|
+
* @param destination - Destination mailbox path
|
|
16
|
+
* @param content - Message content (RFC 822 format)
|
|
17
|
+
* @param flags - Message flags to set on the appended message
|
|
18
|
+
* @param idate - Internal date to set for the message
|
|
19
|
+
* @returns Append result with UID info if available, or undefined if preconditions not met
|
|
20
|
+
* @throws {Error} If the APPEND command fails or message exceeds APPENDLIMIT
|
|
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>;
|
|
@@ -1,39 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const {
|
|
4
|
-
formatFlag,
|
|
5
|
-
canUseFlag,
|
|
6
|
-
formatDateTime,
|
|
7
|
-
normalizePath,
|
|
8
|
-
encodePath,
|
|
9
|
-
comparePaths,
|
|
10
|
-
enhanceCommandError,
|
|
11
|
-
parseBigIntValue,
|
|
12
|
-
parseUintValue,
|
|
13
|
-
MAX_UINT32_DIGITS
|
|
14
|
-
} = require('../tools.js');
|
|
15
|
-
|
|
1
|
+
import { formatFlag, canUseFlag, formatDateTime, normalizePath, encodePath, comparePaths, enhanceCommandError, parseBigIntValue, parseUintValue, MAX_UINT32_DIGITS } from '../tools.js';
|
|
16
2
|
/**
|
|
17
3
|
* Appends a message to a mailbox.
|
|
18
4
|
*
|
|
19
|
-
* @param
|
|
20
|
-
* @param
|
|
21
|
-
* @param
|
|
22
|
-
* @param
|
|
23
|
-
* @param
|
|
24
|
-
* @returns
|
|
5
|
+
* @param connection - IMAP connection instance
|
|
6
|
+
* @param destination - Destination mailbox path
|
|
7
|
+
* @param content - Message content (RFC 822 format)
|
|
8
|
+
* @param flags - Message flags to set on the appended message
|
|
9
|
+
* @param idate - Internal date to set for the message
|
|
10
|
+
* @returns Append result with UID info if available, or undefined if preconditions not met
|
|
25
11
|
* @throws {Error} If the APPEND command fails or message exceeds APPENDLIMIT
|
|
26
12
|
*/
|
|
27
|
-
|
|
13
|
+
export default async function append(connection, destination, content, flags, idate) {
|
|
28
14
|
if (![connection.states.AUTHENTICATED, connection.states.SELECTED].includes(connection.state) || !destination) {
|
|
29
15
|
// nothing to do here
|
|
30
16
|
return;
|
|
31
17
|
}
|
|
32
|
-
|
|
33
18
|
if (typeof content === 'string') {
|
|
34
19
|
content = Buffer.from(content);
|
|
35
20
|
}
|
|
36
|
-
|
|
37
21
|
// APPENDLIMIT capability (RFC 7889): server may advertise the maximum message
|
|
38
22
|
// size it accepts. Check before sending to avoid a wasted round-trip.
|
|
39
23
|
if (connection.capabilities.has('APPENDLIMIT')) {
|
|
@@ -44,34 +28,26 @@ module.exports = async (connection, destination, content, flags, idate) => {
|
|
|
44
28
|
throw err;
|
|
45
29
|
}
|
|
46
30
|
}
|
|
47
|
-
|
|
48
31
|
destination = normalizePath(connection, destination);
|
|
49
|
-
|
|
50
32
|
// If appending to the currently selected mailbox, we can listen for the
|
|
51
33
|
// untagged EXISTS response to capture the new message's sequence number.
|
|
52
34
|
let expectExists = comparePaths(connection, connection.mailbox.path, destination);
|
|
53
|
-
|
|
54
35
|
// Validate and format flags. Only flags allowed by the mailbox's permanentFlags are included.
|
|
55
36
|
flags = (Array.isArray(flags) ? flags : [].concat(flags || []))
|
|
56
37
|
.map(flag => flag && formatFlag(flag.toString()))
|
|
57
|
-
.filter(flag => flag && canUseFlag(connection.mailbox, flag));
|
|
58
|
-
|
|
38
|
+
.filter((flag) => !!flag && canUseFlag(connection.mailbox, flag));
|
|
59
39
|
// APPEND command format: APPEND <mailbox> [<flags>] [<date-time>] <literal>
|
|
60
40
|
let attributes = [{ type: 'ATOM', value: encodePath(connection, destination) }];
|
|
61
|
-
|
|
62
41
|
// Internal date: the date the server should record for this message.
|
|
63
42
|
// Must be quoted (STRING type) per the IMAP date-time grammar.
|
|
64
43
|
idate = idate ? formatDateTime(idate) : false;
|
|
65
|
-
|
|
66
44
|
// Flags and date are optional; flags must come before date if both are present
|
|
67
45
|
if (flags.length || idate) {
|
|
68
46
|
attributes.push(flags.map(flag => ({ type: 'ATOM', value: flag })));
|
|
69
47
|
}
|
|
70
|
-
|
|
71
48
|
if (idate) {
|
|
72
49
|
attributes.push({ type: 'STRING', value: idate });
|
|
73
50
|
}
|
|
74
|
-
|
|
75
51
|
// BINARY extension (RFC 3516): if the message content contains NUL bytes,
|
|
76
52
|
// use literal8 syntax (~{size}\r\n) instead of regular literal ({size}\r\n).
|
|
77
53
|
// Regular literals cannot contain NUL bytes per the IMAP grammar.
|
|
@@ -79,17 +55,14 @@ module.exports = async (connection, destination, content, flags, idate) => {
|
|
|
79
55
|
if (connection.capabilities.has('BINARY') && !connection.disableBinary) {
|
|
80
56
|
isLiteral8 = content.indexOf(Buffer.from([0])) >= 0;
|
|
81
57
|
}
|
|
82
|
-
|
|
83
58
|
attributes.push({ type: 'LITERAL', value: content, isLiteral8 });
|
|
84
|
-
|
|
85
59
|
let map = { destination };
|
|
86
60
|
if (connection.mailbox && connection.mailbox.path) {
|
|
87
61
|
map.path = connection.mailbox.path;
|
|
88
62
|
}
|
|
89
|
-
|
|
90
63
|
// Handler for untagged EXISTS: captures the new message count which gives
|
|
91
64
|
// us the sequence number of the appended message (it's the latest message).
|
|
92
|
-
const handleExistsUpdate = untagged => {
|
|
65
|
+
const handleExistsUpdate = (untagged) => {
|
|
93
66
|
// The count is written into the live mailbox state below, so it has to clear the
|
|
94
67
|
// same bar untaggedExists() applies: a digit run long enough to coerce to Infinity
|
|
95
68
|
// would make resolveRange('*') compile to the literal string "Infinity" and break
|
|
@@ -99,34 +72,33 @@ module.exports = async (connection, destination, content, flags, idate) => {
|
|
|
99
72
|
return;
|
|
100
73
|
}
|
|
101
74
|
map.seq = seq;
|
|
102
|
-
|
|
103
75
|
// Update the connection's mailbox state and emit 'exists' event if the
|
|
104
76
|
// count changed (notifies listeners about the new message).
|
|
105
77
|
if (expectExists) {
|
|
106
|
-
let
|
|
78
|
+
let mailbox = connection.mailbox;
|
|
79
|
+
let prevCount = mailbox.exists;
|
|
107
80
|
if (map.seq !== prevCount) {
|
|
108
|
-
|
|
81
|
+
mailbox.exists = map.seq;
|
|
109
82
|
connection.emit('exists', {
|
|
110
|
-
path:
|
|
83
|
+
path: mailbox.path,
|
|
111
84
|
count: map.seq,
|
|
112
85
|
prevCount
|
|
113
86
|
});
|
|
114
87
|
}
|
|
115
88
|
}
|
|
116
89
|
};
|
|
117
|
-
|
|
118
90
|
let response;
|
|
119
91
|
try {
|
|
120
92
|
response = await connection.exec('APPEND', attributes, {
|
|
121
93
|
// Only listen for EXISTS if we're appending to the currently selected mailbox
|
|
122
94
|
untagged: expectExists ? { EXISTS: handleExistsUpdate } : false
|
|
123
95
|
});
|
|
124
|
-
|
|
125
96
|
// UIDPLUS (RFC 4315): the server may include APPENDUID response code in
|
|
126
97
|
// the tagged OK. Format: [APPENDUID <uidValidity> <uid>]
|
|
127
98
|
let section = response.response.attributes && response.response.attributes[0] && response.response.attributes[0].section;
|
|
128
99
|
if (section && section.length) {
|
|
129
|
-
let
|
|
100
|
+
let first = section[0];
|
|
101
|
+
let responseCode = first && typeof first.value === 'string' ? first.value : '';
|
|
130
102
|
if (responseCode.toUpperCase() === 'APPENDUID') {
|
|
131
103
|
// Bounded digit runs only: isNaN() also passes '1e5', which BigInt() rejects
|
|
132
104
|
// with a throw - and this catch rethrows, so the append would reject after the
|
|
@@ -141,9 +113,7 @@ module.exports = async (connection, destination, content, flags, idate) => {
|
|
|
141
113
|
}
|
|
142
114
|
}
|
|
143
115
|
}
|
|
144
|
-
|
|
145
116
|
response.next();
|
|
146
|
-
|
|
147
117
|
// If we didn't get an EXISTS during APPEND (some servers don't send it
|
|
148
118
|
// until the next command), issue a NOOP to flush pending notifications.
|
|
149
119
|
if (expectExists && !map.seq) {
|
|
@@ -153,24 +123,24 @@ module.exports = async (connection, destination, content, flags, idate) => {
|
|
|
153
123
|
comment: 'Sequence not found from APPEND output'
|
|
154
124
|
});
|
|
155
125
|
response.next();
|
|
156
|
-
}
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
157
128
|
connection.log.warn({ err, cid: connection.id });
|
|
158
129
|
}
|
|
159
130
|
}
|
|
160
|
-
|
|
161
131
|
// If we have a sequence number but no UID (server doesn't support UIDPLUS),
|
|
162
132
|
// look up the UID via SEARCH to provide a consistent result to the caller.
|
|
163
133
|
if (map.seq && !map.uid) {
|
|
164
134
|
let list = await connection.search({ seq: map.seq }, { uid: true });
|
|
165
|
-
if (list && list.length) {
|
|
135
|
+
if (Array.isArray(list) && list.length) {
|
|
166
136
|
map.uid = list[0];
|
|
167
137
|
}
|
|
168
138
|
}
|
|
169
|
-
|
|
170
139
|
return map;
|
|
171
|
-
}
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
172
142
|
await enhanceCommandError(err);
|
|
173
143
|
connection.log.warn({ err, cid: connection.id });
|
|
174
144
|
throw err;
|
|
175
145
|
}
|
|
176
|
-
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ImapFlow } from '../imap-flow.js';
|
|
2
|
+
/**
|
|
3
|
+
* Credentials for the AUTHENTICATE command
|
|
4
|
+
*/
|
|
5
|
+
export interface AuthenticateCredentials {
|
|
6
|
+
/** OAuth2 access token for OAUTHBEARER/XOAUTH2 authentication */
|
|
7
|
+
accessToken?: string | undefined;
|
|
8
|
+
/** Password for PLAIN or LOGIN authentication */
|
|
9
|
+
password?: string | undefined;
|
|
10
|
+
/** Force a specific login method (e.g., 'AUTH=PLAIN', 'AUTH=LOGIN') */
|
|
11
|
+
loginMethod?: string | undefined;
|
|
12
|
+
/** Authorization identity for PLAIN authentication */
|
|
13
|
+
authzid?: string | undefined;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Authenticates user using the best available method.
|
|
17
|
+
*
|
|
18
|
+
* @param connection - IMAP connection instance
|
|
19
|
+
* @param username - The username to authenticate with
|
|
20
|
+
* @param credentials - Authentication credentials
|
|
21
|
+
* @returns The authenticated username, or undefined if already authenticated
|
|
22
|
+
* @throws {Error} If no supported authentication mechanism is available or if authentication fails
|
|
23
|
+
*/
|
|
24
|
+
export default function authenticate(connection: ImapFlow, username: string, { accessToken, password, loginMethod, authzid }: AuthenticateCredentials): Promise<string | undefined>;
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { getStatusCode, getErrorText } = require('../tools.js');
|
|
4
|
-
|
|
1
|
+
import { getStatusCode, getErrorText } from '../tools.js';
|
|
5
2
|
/**
|
|
6
3
|
* Handles authentication errors by enriching the error object with server response details.
|
|
7
4
|
*
|
|
8
|
-
* @param
|
|
9
|
-
* @param
|
|
10
|
-
* @returns
|
|
5
|
+
* @param err - The original authentication error
|
|
6
|
+
* @param errorResponse - Optional OAuth error response from the server
|
|
7
|
+
* @returns The enriched error; the caller is expected to throw it
|
|
11
8
|
*/
|
|
12
9
|
async function handleAuthError(err, errorResponse) {
|
|
13
10
|
let errorCode = getStatusCode(err.response);
|
|
@@ -21,21 +18,19 @@ async function handleAuthError(err, errorResponse) {
|
|
|
21
18
|
}
|
|
22
19
|
return err;
|
|
23
20
|
}
|
|
24
|
-
|
|
25
21
|
/**
|
|
26
22
|
* Authenticates using OAuth (OAUTHBEARER or XOAUTH2).
|
|
27
23
|
*
|
|
28
|
-
* @param
|
|
29
|
-
* @param
|
|
30
|
-
* @param
|
|
31
|
-
* @returns
|
|
24
|
+
* @param connection - IMAP connection instance
|
|
25
|
+
* @param username - The username to authenticate with
|
|
26
|
+
* @param accessToken - The OAuth2 access token
|
|
27
|
+
* @returns The authenticated username
|
|
32
28
|
* @throws {Error} If authentication fails
|
|
33
29
|
*/
|
|
34
30
|
async function authOauth(connection, username, accessToken) {
|
|
35
31
|
let oauthbearer;
|
|
36
32
|
let command;
|
|
37
33
|
let breaker;
|
|
38
|
-
|
|
39
34
|
if (connection.capabilities.has('AUTH=OAUTHBEARER')) {
|
|
40
35
|
// OAUTHBEARER payload per RFC 7628: fields separated by \x01 (SASL GS2 framing).
|
|
41
36
|
// Format: "n,a=<user>," \x01 "host=..." \x01 "port=..." \x01 "auth=Bearer <token>" \x01 \x01
|
|
@@ -54,9 +49,10 @@ async function authOauth(connection, username, accessToken) {
|
|
|
54
49
|
''
|
|
55
50
|
].join('\x01');
|
|
56
51
|
command = 'OAUTHBEARER';
|
|
57
|
-
// "AQ==" is base64 for \x01
|
|
52
|
+
// "AQ==" is base64 for \x01, sent as the error continuation to abort the SASL exchange
|
|
58
53
|
breaker = 'AQ==';
|
|
59
|
-
}
|
|
54
|
+
}
|
|
55
|
+
else if (connection.capabilities.has('AUTH=XOAUTH') || connection.capabilities.has('AUTH=XOAUTH2')) {
|
|
60
56
|
// XOAUTH2 payload (Google-specific): simpler format, also \x01-delimited.
|
|
61
57
|
// Format: "user=<user>" \x01 "auth=Bearer <token>" \x01 \x01
|
|
62
58
|
oauthbearer = [`user=${username}`, `auth=Bearer ${accessToken}`, '', ''].join('\x01');
|
|
@@ -64,54 +60,47 @@ async function authOauth(connection, username, accessToken) {
|
|
|
64
60
|
// Empty breaker: XOAUTH2 expects an empty response to abort the SASL exchange
|
|
65
61
|
breaker = '';
|
|
66
62
|
}
|
|
67
|
-
|
|
68
63
|
let errorResponse = false;
|
|
69
64
|
try {
|
|
70
|
-
let response = await connection.exec(
|
|
71
|
-
'
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
{
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
cid: connection.id
|
|
89
|
-
});
|
|
90
|
-
}
|
|
65
|
+
let response = await connection.exec('AUTHENTICATE', [
|
|
66
|
+
{ type: 'ATOM', value: command },
|
|
67
|
+
{ type: 'ATOM', value: Buffer.from(oauthbearer).toString('base64'), sensitive: true }
|
|
68
|
+
], {
|
|
69
|
+
// Server sends a "+" continuation if auth fails, with a base64 JSON error payload.
|
|
70
|
+
// We decode it for diagnostics, then send the breaker to terminate the exchange.
|
|
71
|
+
onPlusTag: async (resp) => {
|
|
72
|
+
if (resp.attributes && resp.attributes[0] && resp.attributes[0].type === 'TEXT') {
|
|
73
|
+
try {
|
|
74
|
+
errorResponse = JSON.parse(Buffer.from(resp.attributes[0].value, 'base64').toString());
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
connection.log.debug({
|
|
78
|
+
msg: 'Failed to parse OAuth error response',
|
|
79
|
+
errorResponse: resp.attributes[0].value,
|
|
80
|
+
err,
|
|
81
|
+
cid: connection.id
|
|
82
|
+
});
|
|
91
83
|
}
|
|
92
|
-
|
|
93
|
-
connection.log.debug({ src: 'c', msg: breaker, comment: `Error response for ${command}`, cid: connection.id });
|
|
94
|
-
connection.write(breaker);
|
|
95
84
|
}
|
|
85
|
+
connection.log.debug({ src: 'c', msg: breaker, comment: `Error response for ${command}`, cid: connection.id });
|
|
86
|
+
connection.write(breaker);
|
|
96
87
|
}
|
|
97
|
-
);
|
|
88
|
+
});
|
|
98
89
|
response.next();
|
|
99
|
-
|
|
100
90
|
connection.authCapabilities.set(`AUTH=${command}`, true);
|
|
101
|
-
|
|
102
91
|
return username;
|
|
103
|
-
}
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
104
94
|
throw await handleAuthError(err, errorResponse);
|
|
105
95
|
}
|
|
106
96
|
}
|
|
107
|
-
|
|
108
97
|
/**
|
|
109
98
|
* Authenticates using the SASL LOGIN mechanism.
|
|
110
99
|
*
|
|
111
|
-
* @param
|
|
112
|
-
* @param
|
|
113
|
-
* @param
|
|
114
|
-
* @returns
|
|
100
|
+
* @param connection - IMAP connection instance
|
|
101
|
+
* @param username - The username to authenticate with
|
|
102
|
+
* @param password - The password to authenticate with
|
|
103
|
+
* @returns The authenticated username
|
|
115
104
|
* @throws {Error} If authentication fails
|
|
116
105
|
*/
|
|
117
106
|
async function authLogin(connection, username, password) {
|
|
@@ -120,47 +109,45 @@ async function authLogin(connection, username, password) {
|
|
|
120
109
|
// SASL LOGIN is a challenge-response mechanism: the server sends base64-encoded
|
|
121
110
|
// prompts ("Username:" and "Password:") and the client responds with base64-encoded values.
|
|
122
111
|
let response = await connection.exec('AUTHENTICATE', [{ type: 'ATOM', value: 'LOGIN' }], {
|
|
123
|
-
onPlusTag: async resp => {
|
|
112
|
+
onPlusTag: async (resp) => {
|
|
124
113
|
if (resp.attributes && resp.attributes[0] && resp.attributes[0].type === 'TEXT') {
|
|
125
114
|
// Decode the server's base64 challenge to determine what it's asking for.
|
|
126
115
|
// Strip trailing colons and null bytes (\x00) that some servers append to the prompt.
|
|
127
116
|
let question = Buffer.from(resp.attributes[0].value, 'base64')
|
|
128
117
|
.toString()
|
|
129
118
|
.toLowerCase()
|
|
130
|
-
.replace(/[:\x00]*$/, '');
|
|
131
|
-
|
|
119
|
+
.replace(/[:\x00]*$/, '');
|
|
132
120
|
if (question === 'username' || question === 'user name') {
|
|
133
121
|
let encodedUsername = Buffer.from(username).toString('base64');
|
|
134
122
|
connection.log.debug({ src: 'c', msg: encodedUsername, comment: `Encoded username for AUTH=LOGIN`, cid: connection.id });
|
|
135
123
|
connection.write(encodedUsername);
|
|
136
|
-
}
|
|
124
|
+
}
|
|
125
|
+
else if (question === 'password') {
|
|
137
126
|
connection.log.debug({ src: 'c', msg: '(* value hidden *)', comment: `Encoded password for AUTH=LOGIN`, cid: connection.id });
|
|
138
127
|
connection.write(Buffer.from(password).toString('base64'));
|
|
139
|
-
}
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
140
130
|
throw new Error(`Unknown LOGIN question "${question}"`);
|
|
141
131
|
}
|
|
142
132
|
}
|
|
143
133
|
}
|
|
144
134
|
});
|
|
145
|
-
|
|
146
135
|
response.next();
|
|
147
|
-
|
|
148
136
|
connection.authCapabilities.set(`AUTH=LOGIN`, true);
|
|
149
|
-
|
|
150
137
|
return username;
|
|
151
|
-
}
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
152
140
|
throw await handleAuthError(err, errorResponse);
|
|
153
141
|
}
|
|
154
142
|
}
|
|
155
|
-
|
|
156
143
|
/**
|
|
157
144
|
* Authenticates using the SASL PLAIN mechanism.
|
|
158
145
|
*
|
|
159
|
-
* @param
|
|
160
|
-
* @param
|
|
161
|
-
* @param
|
|
162
|
-
* @param
|
|
163
|
-
* @returns
|
|
146
|
+
* @param connection - IMAP connection instance
|
|
147
|
+
* @param username - The authentication identity (authcid)
|
|
148
|
+
* @param password - The password to authenticate with
|
|
149
|
+
* @param authzid - Optional authorization identity to impersonate
|
|
150
|
+
* @returns The authorized identity (authzid if provided, otherwise username)
|
|
164
151
|
* @throws {Error} If authentication fails
|
|
165
152
|
*/
|
|
166
153
|
async function authPlain(connection, username, password, authzid) {
|
|
@@ -183,61 +170,49 @@ async function authPlain(connection, username, password, authzid) {
|
|
|
183
170
|
connection.write(encodedResponse);
|
|
184
171
|
}
|
|
185
172
|
});
|
|
186
|
-
|
|
187
173
|
response.next();
|
|
188
|
-
|
|
189
174
|
connection.authCapabilities.set(`AUTH=PLAIN`, true);
|
|
190
|
-
|
|
191
175
|
// Return the identity we're authorized as (authzid if provided, otherwise username)
|
|
192
176
|
return authzid || username;
|
|
193
|
-
}
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
194
179
|
throw await handleAuthError(err, errorResponse);
|
|
195
180
|
}
|
|
196
181
|
}
|
|
197
|
-
|
|
198
182
|
/**
|
|
199
183
|
* Authenticates user using the best available method.
|
|
200
184
|
*
|
|
201
|
-
* @param
|
|
202
|
-
* @param
|
|
203
|
-
* @param
|
|
204
|
-
* @
|
|
205
|
-
* @param {string} [credentials.password] - Password for PLAIN or LOGIN authentication
|
|
206
|
-
* @param {string} [credentials.loginMethod] - Force a specific login method (e.g., 'AUTH=PLAIN', 'AUTH=LOGIN')
|
|
207
|
-
* @param {string} [credentials.authzid] - Authorization identity for PLAIN authentication
|
|
208
|
-
* @returns {Promise<string|undefined>} The authenticated username, or undefined if already authenticated
|
|
185
|
+
* @param connection - IMAP connection instance
|
|
186
|
+
* @param username - The username to authenticate with
|
|
187
|
+
* @param credentials - Authentication credentials
|
|
188
|
+
* @returns The authenticated username, or undefined if already authenticated
|
|
209
189
|
* @throws {Error} If no supported authentication mechanism is available or if authentication fails
|
|
210
190
|
*/
|
|
211
|
-
|
|
191
|
+
export default async function authenticate(connection, username, { accessToken, password, loginMethod, authzid }) {
|
|
212
192
|
if (connection.state !== connection.states.NOT_AUTHENTICATED) {
|
|
213
193
|
// nothing to do here
|
|
214
194
|
return;
|
|
215
195
|
}
|
|
216
|
-
|
|
217
196
|
// Authentication method selection order:
|
|
218
|
-
// 1. OAuth (OAUTHBEARER > XOAUTH2)
|
|
197
|
+
// 1. OAuth (OAUTHBEARER > XOAUTH2), preferred when an accessToken is provided,
|
|
219
198
|
// as it avoids transmitting passwords entirely.
|
|
220
|
-
// 2. SASL PLAIN
|
|
199
|
+
// 2. SASL PLAIN, preferred over LOGIN because it supports authzid (impersonation)
|
|
221
200
|
// and sends credentials in a single round trip.
|
|
222
|
-
// 3. SASL LOGIN
|
|
201
|
+
// 3. SASL LOGIN, the fallback; an older challenge-response mechanism (two round trips).
|
|
223
202
|
// If loginMethod is explicitly set, it overrides the automatic capability-based selection.
|
|
224
|
-
|
|
225
203
|
if (accessToken) {
|
|
226
204
|
// AUTH=OAUTHBEARER and AUTH=XOAUTH in the context of OAuth2 or very similar so we can handle these together
|
|
227
205
|
if (connection.capabilities.has('AUTH=OAUTHBEARER') || connection.capabilities.has('AUTH=XOAUTH') || connection.capabilities.has('AUTH=XOAUTH2')) {
|
|
228
206
|
return await authOauth(connection, username, accessToken);
|
|
229
207
|
}
|
|
230
208
|
}
|
|
231
|
-
|
|
232
209
|
if (password) {
|
|
233
210
|
if ((!loginMethod && connection.capabilities.has('AUTH=PLAIN')) || loginMethod === 'AUTH=PLAIN') {
|
|
234
211
|
return await authPlain(connection, username, password, authzid);
|
|
235
212
|
}
|
|
236
|
-
|
|
237
213
|
if ((!loginMethod && connection.capabilities.has('AUTH=LOGIN')) || loginMethod === 'AUTH=LOGIN') {
|
|
238
214
|
return await authLogin(connection, username, password);
|
|
239
215
|
}
|
|
240
216
|
}
|
|
241
|
-
|
|
242
217
|
throw new Error('Unsupported authentication mechanism');
|
|
243
|
-
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ImapFlow } from '../imap-flow.js';
|
|
2
|
+
/**
|
|
3
|
+
* Refreshes capabilities from server.
|
|
4
|
+
*
|
|
5
|
+
* @param connection - IMAP connection instance
|
|
6
|
+
* @returns Server capabilities map, or false on failure
|
|
7
|
+
*/
|
|
8
|
+
export default function capability(connection: ImapFlow): Promise<Map<string, boolean | number> | false>;
|
|
@@ -1,30 +1,27 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Refreshes capabilities from server.
|
|
5
3
|
*
|
|
6
|
-
* @param
|
|
7
|
-
* @returns
|
|
4
|
+
* @param connection - IMAP connection instance
|
|
5
|
+
* @returns Server capabilities map, or false on failure
|
|
8
6
|
*/
|
|
9
7
|
// Capabilities are normally received and updated by the global response handler
|
|
10
8
|
// (e.g., from the server greeting or after authentication). This explicit CAPABILITY
|
|
11
9
|
// command is only needed when capabilities must be refreshed on demand, such as
|
|
12
10
|
// after STARTTLS or when the server signals a capability change.
|
|
13
|
-
|
|
11
|
+
export default async function capability(connection) {
|
|
14
12
|
if (connection.capabilities.size && !connection.expectCapabilityUpdate) {
|
|
15
13
|
return connection.capabilities;
|
|
16
14
|
}
|
|
17
|
-
|
|
18
15
|
let response;
|
|
19
16
|
try {
|
|
20
17
|
// The actual parsing of the untagged CAPABILITY response is handled by the
|
|
21
18
|
// global handler, not here. We just trigger the server to send it.
|
|
22
19
|
response = await connection.exec('CAPABILITY');
|
|
23
|
-
|
|
24
20
|
response.next();
|
|
25
21
|
return connection.capabilities;
|
|
26
|
-
}
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
27
24
|
connection.log.warn({ err, cid: connection.id });
|
|
28
25
|
return false;
|
|
29
26
|
}
|
|
30
|
-
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ImapFlow } from '../imap-flow.js';
|
|
2
|
+
/**
|
|
3
|
+
* Closes the currently selected mailbox.
|
|
4
|
+
*
|
|
5
|
+
* @param connection - IMAP connection instance
|
|
6
|
+
* @returns True on success, false on failure, or undefined if not in SELECTED state
|
|
7
|
+
*/
|
|
8
|
+
export default function close(connection: ImapFlow): Promise<boolean | undefined>;
|
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Closes the currently selected mailbox.
|
|
5
3
|
*
|
|
6
|
-
* @param
|
|
7
|
-
* @returns
|
|
4
|
+
* @param connection - IMAP connection instance
|
|
5
|
+
* @returns True on success, false on failure, or undefined if not in SELECTED state
|
|
8
6
|
*/
|
|
9
|
-
|
|
7
|
+
export default async function close(connection) {
|
|
10
8
|
if (connection.state !== connection.states.SELECTED) {
|
|
11
9
|
// nothing to do here
|
|
12
10
|
return;
|
|
13
11
|
}
|
|
14
|
-
|
|
15
12
|
let response;
|
|
16
13
|
try {
|
|
17
14
|
// IMAP CLOSE (RFC 3501 6.4.2): permanently removes all messages flagged \Deleted
|
|
@@ -19,20 +16,19 @@ module.exports = async connection => {
|
|
|
19
16
|
// Unlike EXPUNGE, CLOSE does not send individual untagged EXPUNGE responses.
|
|
20
17
|
response = await connection.exec('CLOSE');
|
|
21
18
|
response.next();
|
|
22
|
-
|
|
23
19
|
// Transition from SELECTED back to AUTHENTICATED state.
|
|
24
20
|
// Clear mailbox metadata so subsequent operations know no mailbox is selected.
|
|
25
21
|
let currentMailbox = connection.mailbox;
|
|
26
22
|
connection.mailbox = false;
|
|
27
23
|
connection.currentSelectCommand = false;
|
|
28
24
|
connection.state = connection.states.AUTHENTICATED;
|
|
29
|
-
|
|
30
25
|
if (currentMailbox) {
|
|
31
26
|
connection.emit('mailboxClose', currentMailbox);
|
|
32
27
|
}
|
|
33
28
|
return true;
|
|
34
|
-
}
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
35
31
|
connection.log.warn({ err, cid: connection.id });
|
|
36
32
|
return false;
|
|
37
33
|
}
|
|
38
|
-
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ImapFlow } from '../imap-flow.js';
|
|
2
|
+
/**
|
|
3
|
+
* Requests DEFLATE compression from the server.
|
|
4
|
+
*
|
|
5
|
+
* @param connection - IMAP connection instance
|
|
6
|
+
* @returns True if compression was enabled, false otherwise
|
|
7
|
+
*/
|
|
8
|
+
export default function compress(connection: ImapFlow): Promise<boolean>;
|