imapflow 1.7.8 → 2.0.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 +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 +520 -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 +3956 -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} +387 -517
- 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 +60 -123
- 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 +769 -1790
- 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 -101
- 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 -909
- 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 -661
|
@@ -1,30 +1,27 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Requests DEFLATE compression from the server.
|
|
5
3
|
*
|
|
6
|
-
* @param
|
|
7
|
-
* @returns
|
|
4
|
+
* @param connection - IMAP connection instance
|
|
5
|
+
* @returns True if compression was enabled, false otherwise
|
|
8
6
|
*/
|
|
9
7
|
// COMPRESS=DEFLATE (RFC 4978): enables zlib compression on the IMAP connection
|
|
10
8
|
// to reduce bandwidth. Once enabled, all subsequent data in both directions is compressed.
|
|
11
|
-
|
|
9
|
+
export default async function compress(connection) {
|
|
12
10
|
// Skip if the server doesn't support COMPRESS=DEFLATE, or if compression
|
|
13
11
|
// is already active (connection._inflate exists) to avoid double-compression.
|
|
14
12
|
if (!connection.capabilities.has('COMPRESS=DEFLATE') || connection._inflate) {
|
|
15
13
|
// nothing to do here
|
|
16
14
|
return false;
|
|
17
15
|
}
|
|
18
|
-
|
|
19
16
|
let response;
|
|
20
17
|
try {
|
|
21
18
|
response = await connection.exec('COMPRESS', [{ type: 'ATOM', value: 'DEFLATE' }]);
|
|
22
|
-
}
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
23
21
|
// The server declined (NO/BAD): nothing switched, staying uncompressed is safe.
|
|
24
22
|
connection.log.warn({ err, cid: connection.id });
|
|
25
23
|
return false;
|
|
26
24
|
}
|
|
27
|
-
|
|
28
25
|
// Everything after the tagged OK is already deflate-framed (RFC 4978 section 4) -
|
|
29
26
|
// the server switches at the OK, so declining the upgrade at this point is not a
|
|
30
27
|
// protocol option. The socket stays piped into the plaintext parser until the
|
|
@@ -42,14 +39,13 @@ module.exports = async connection => {
|
|
|
42
39
|
// Schedule the close before releasing parser backpressure, so the buffered
|
|
43
40
|
// deflate-framed bytes cannot settle anything before teardown begins. This is
|
|
44
41
|
// why the decision lives here rather than at the connection layer the way the
|
|
45
|
-
// STARTTLS guard does (starttls.
|
|
42
|
+
// STARTTLS guard does (starttls.ts records a flag, upgradeToSTARTTLS decides):
|
|
46
43
|
// only the command module holds the response before its backpressure release,
|
|
47
44
|
// so only it can order teardown ahead of that release.
|
|
48
45
|
connection.closeAfter();
|
|
49
46
|
response.next();
|
|
50
47
|
throw error;
|
|
51
48
|
}
|
|
52
|
-
|
|
53
49
|
response.next();
|
|
54
50
|
return true;
|
|
55
|
-
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ImapFlow } from '../imap-flow.js';
|
|
2
|
+
import type { CopyResponseObject, MessageRangeOptions } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Copies messages from the current mailbox to another mailbox.
|
|
5
|
+
*
|
|
6
|
+
* @param connection - IMAP connection instance
|
|
7
|
+
* @param range - Message sequence number or UID range
|
|
8
|
+
* @param destination - Destination mailbox path
|
|
9
|
+
* @param options - Copy options
|
|
10
|
+
* @param options.uid - If true, use UID COPY instead of COPY
|
|
11
|
+
* @returns Copy result with UID mapping if available, false on failure, or undefined if preconditions not met
|
|
12
|
+
*/
|
|
13
|
+
export default function copy(connection: ImapFlow, range: string, destination: string | string[], options?: MessageRangeOptions | undefined): Promise<CopyResponseObject | false | undefined>;
|
|
@@ -1,47 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { normalizePath, encodePath, enhanceCommandError } = require('../tools.js');
|
|
4
|
-
const { parseCopyUid } = require('./copyuid-parser.js');
|
|
5
|
-
|
|
1
|
+
import { normalizePath, encodePath, enhanceCommandError } from '../tools.js';
|
|
2
|
+
import { parseCopyUid } from './copyuid-parser.js';
|
|
6
3
|
/**
|
|
7
4
|
* Copies messages from the current mailbox to another mailbox.
|
|
8
5
|
*
|
|
9
|
-
* @param
|
|
10
|
-
* @param
|
|
11
|
-
* @param
|
|
12
|
-
* @param
|
|
13
|
-
* @param
|
|
14
|
-
* @returns
|
|
6
|
+
* @param connection - IMAP connection instance
|
|
7
|
+
* @param range - Message sequence number or UID range
|
|
8
|
+
* @param destination - Destination mailbox path
|
|
9
|
+
* @param options - Copy options
|
|
10
|
+
* @param options.uid - If true, use UID COPY instead of COPY
|
|
11
|
+
* @returns Copy result with UID mapping if available, false on failure, or undefined if preconditions not met
|
|
15
12
|
*/
|
|
16
|
-
|
|
13
|
+
export default async function copy(connection, range, destination, options) {
|
|
17
14
|
if (connection.state !== connection.states.SELECTED || !range || !destination) {
|
|
18
15
|
// nothing to do here
|
|
19
16
|
return;
|
|
20
17
|
}
|
|
21
|
-
|
|
22
18
|
options = options || {};
|
|
23
19
|
destination = normalizePath(connection, destination);
|
|
24
|
-
|
|
25
20
|
let attributes = [
|
|
26
21
|
{ type: 'SEQUENCE', value: range },
|
|
27
22
|
{ type: 'ATOM', value: encodePath(connection, destination) }
|
|
28
23
|
];
|
|
29
|
-
|
|
30
24
|
let response;
|
|
31
25
|
try {
|
|
32
26
|
response = await connection.exec(options.uid ? 'UID COPY' : 'COPY', attributes);
|
|
33
27
|
response.next();
|
|
34
|
-
|
|
35
28
|
let map = { path: connection.mailbox.path, destination };
|
|
36
|
-
|
|
37
29
|
// UIDPLUS (RFC 4315): the server may include a COPYUID response code in the
|
|
38
30
|
// tagged OK response, providing a mapping from source UIDs to destination UIDs.
|
|
39
31
|
parseCopyUid(response.response, map);
|
|
40
|
-
|
|
41
32
|
return map;
|
|
42
|
-
}
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
43
35
|
await enhanceCommandError(err);
|
|
44
36
|
connection.log.warn({ err, cid: connection.id });
|
|
45
37
|
return false;
|
|
46
38
|
}
|
|
47
|
-
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ImapResponse } from '../handler/types.js';
|
|
2
|
+
import type { CopyResponseObject } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parses COPYUID response code from an IMAP response (RFC 4315).
|
|
5
|
+
* Used by both COPY and MOVE commands to extract the UID mapping
|
|
6
|
+
* from source mailbox to destination mailbox.
|
|
7
|
+
*
|
|
8
|
+
* @param response - IMAP response object with attributes
|
|
9
|
+
* @param map - Result map to populate with uidValidity and uidMap
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseCopyUid(response: ImapResponse, map: CopyResponseObject): void;
|
|
@@ -1,35 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { expandRange, parseBigIntValue } = require('../tools.js');
|
|
4
|
-
|
|
1
|
+
import { expandRange, parseBigIntValue } from '../tools.js';
|
|
5
2
|
/**
|
|
6
3
|
* Parses COPYUID response code from an IMAP response (RFC 4315).
|
|
7
4
|
* Used by both COPY and MOVE commands to extract the UID mapping
|
|
8
5
|
* from source mailbox to destination mailbox.
|
|
9
6
|
*
|
|
10
|
-
* @param
|
|
11
|
-
* @param
|
|
7
|
+
* @param response - IMAP response object with attributes
|
|
8
|
+
* @param map - Result map to populate with uidValidity and uidMap
|
|
12
9
|
*/
|
|
13
|
-
function parseCopyUid(response, map) {
|
|
10
|
+
export function parseCopyUid(response, map) {
|
|
14
11
|
let section = response.attributes && response.attributes[0] && response.attributes[0].section;
|
|
15
12
|
let responseCode = section && section.length && section[0] && typeof section[0].value === 'string' ? section[0].value : '';
|
|
16
|
-
|
|
17
13
|
if (responseCode !== 'COPYUID') {
|
|
18
14
|
return;
|
|
19
15
|
}
|
|
20
|
-
|
|
16
|
+
// A COPYUID code always comes with its section, see responseCode above
|
|
17
|
+
let codeSection = section;
|
|
21
18
|
// Only a bounded pure digit string is accepted: isNaN() also passes values like "1e5" or
|
|
22
19
|
// "Infinity", which BigInt() then rejects with a throw that loses the uidMap.
|
|
23
|
-
let uidValidity = parseBigIntValue(
|
|
20
|
+
let uidValidity = parseBigIntValue(codeSection[1] && codeSection[1].value);
|
|
24
21
|
if (uidValidity !== false) {
|
|
25
22
|
map.uidValidity = uidValidity;
|
|
26
23
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let destinationUids = section[3] && typeof section[3].value === 'string' ? expandRange(section[3].value) : false;
|
|
24
|
+
const sourceUids = codeSection[2] && typeof codeSection[2].value === 'string' ? expandRange(codeSection[2].value) : false;
|
|
25
|
+
const destinationUids = codeSection[3] && typeof codeSection[3].value === 'string' ? expandRange(codeSection[3].value) : false;
|
|
30
26
|
if (sourceUids && destinationUids && sourceUids.length === destinationUids.length) {
|
|
31
27
|
map.uidMap = new Map(sourceUids.map((uid, i) => [uid, destinationUids[i]]));
|
|
32
28
|
}
|
|
33
29
|
}
|
|
34
|
-
|
|
35
|
-
module.exports = { parseCopyUid };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ImapFlow } from '../imap-flow.js';
|
|
2
|
+
import type { MailboxCreateResponse } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a new mailbox and subscribes to it.
|
|
5
|
+
*
|
|
6
|
+
* @param connection - IMAP connection instance
|
|
7
|
+
* @param path - Mailbox path to create
|
|
8
|
+
* @returns Object with path and creation status, or undefined if preconditions not met
|
|
9
|
+
* @throws If the CREATE command fails (except when mailbox already exists)
|
|
10
|
+
*/
|
|
11
|
+
export default function create(connection: ImapFlow, path: string | string[]): Promise<MailboxCreateResponse | undefined>;
|
|
@@ -1,40 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { encodePath, normalizePath, getStatusCode, enhanceCommandError } = require('../tools.js');
|
|
4
|
-
|
|
1
|
+
import { encodePath, normalizePath, getStatusCode, enhanceCommandError } from '../tools.js';
|
|
5
2
|
/**
|
|
6
3
|
* Creates a new mailbox and subscribes to it.
|
|
7
4
|
*
|
|
8
|
-
* @param
|
|
9
|
-
* @param
|
|
10
|
-
* @returns
|
|
11
|
-
* @throws
|
|
5
|
+
* @param connection - IMAP connection instance
|
|
6
|
+
* @param path - Mailbox path to create
|
|
7
|
+
* @returns Object with path and creation status, or undefined if preconditions not met
|
|
8
|
+
* @throws If the CREATE command fails (except when mailbox already exists)
|
|
12
9
|
*/
|
|
13
|
-
|
|
10
|
+
export default async function create(connection, path) {
|
|
14
11
|
if (![connection.states.AUTHENTICATED, connection.states.SELECTED].includes(connection.state)) {
|
|
15
12
|
// nothing to do here
|
|
16
13
|
return;
|
|
17
14
|
}
|
|
18
|
-
|
|
19
15
|
path = normalizePath(connection, path);
|
|
20
|
-
|
|
21
16
|
let response;
|
|
22
17
|
try {
|
|
23
18
|
let map = {
|
|
24
19
|
path
|
|
25
20
|
};
|
|
26
21
|
response = await connection.exec('CREATE', [{ type: 'ATOM', value: encodePath(connection, path) }]);
|
|
27
|
-
|
|
28
22
|
// Parse the response code section (e.g., [MAILBOXID (<id>)]) from the tagged OK response.
|
|
29
23
|
// IMAP response code attributes are structured as alternating key-value pairs.
|
|
30
|
-
let section =
|
|
31
|
-
response.response.attributes &&
|
|
24
|
+
let section = response.response.attributes &&
|
|
32
25
|
response.response.attributes[0] &&
|
|
33
26
|
response.response.attributes[0].section &&
|
|
34
27
|
response.response.attributes[0].section.length
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
? response.response.attributes[0].section
|
|
29
|
+
: false;
|
|
38
30
|
if (section) {
|
|
39
31
|
let key;
|
|
40
32
|
section.forEach((attribute, i) => {
|
|
@@ -43,11 +35,9 @@ module.exports = async (connection, path) => {
|
|
|
43
35
|
key = attribute && typeof attribute.value === 'string' ? attribute.value : false;
|
|
44
36
|
return;
|
|
45
37
|
}
|
|
46
|
-
|
|
47
38
|
if (!key) {
|
|
48
39
|
return;
|
|
49
40
|
}
|
|
50
|
-
|
|
51
41
|
let value;
|
|
52
42
|
switch (key.toLowerCase()) {
|
|
53
43
|
case 'mailboxid':
|
|
@@ -55,25 +45,22 @@ module.exports = async (connection, path) => {
|
|
|
55
45
|
value = Array.isArray(attribute) && attribute[0] && typeof attribute[0].value === 'string' ? attribute[0].value : false;
|
|
56
46
|
break;
|
|
57
47
|
}
|
|
58
|
-
|
|
59
48
|
if (key && value) {
|
|
60
49
|
map[key] = value;
|
|
61
50
|
}
|
|
62
51
|
});
|
|
63
52
|
}
|
|
64
|
-
|
|
65
53
|
map.created = true;
|
|
66
54
|
response.next();
|
|
67
|
-
|
|
68
55
|
// Auto-subscribe after creation so the new mailbox appears in LSUB listings
|
|
69
56
|
// and is visible to clients that only show subscribed folders.
|
|
70
57
|
await connection.run('SUBSCRIBE', path);
|
|
71
|
-
|
|
72
58
|
return map;
|
|
73
|
-
}
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
74
61
|
let errorCode = getStatusCode(err.response);
|
|
75
62
|
// ALREADYEXISTS (RFC 5530) means the mailbox already exists on the server.
|
|
76
|
-
// This is not a true error
|
|
63
|
+
// This is not a true error, we return created:false to indicate nothing was created.
|
|
77
64
|
if (errorCode === 'ALREADYEXISTS') {
|
|
78
65
|
// no need to do anything, mailbox already exists
|
|
79
66
|
return {
|
|
@@ -81,9 +68,8 @@ module.exports = async (connection, path) => {
|
|
|
81
68
|
created: false
|
|
82
69
|
};
|
|
83
70
|
}
|
|
84
|
-
|
|
85
71
|
await enhanceCommandError(err);
|
|
86
72
|
connection.log.warn({ err, cid: connection.id });
|
|
87
73
|
throw err;
|
|
88
74
|
}
|
|
89
|
-
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ImapFlow } from '../imap-flow.js';
|
|
2
|
+
import type { MailboxDeleteResponse } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Deletes an existing mailbox.
|
|
5
|
+
*
|
|
6
|
+
* @param connection - IMAP connection instance
|
|
7
|
+
* @param path - Mailbox path to delete
|
|
8
|
+
* @returns Object with the deleted path, or undefined if preconditions not met
|
|
9
|
+
* @throws If the DELETE command fails
|
|
10
|
+
*/
|
|
11
|
+
export default function deleteMailbox(connection: ImapFlow, path: string | string[]): Promise<MailboxDeleteResponse | undefined>;
|
|
@@ -1,29 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { encodePath, normalizePath, enhanceCommandError } = require('../tools.js');
|
|
4
|
-
|
|
1
|
+
import { encodePath, normalizePath, enhanceCommandError } from '../tools.js';
|
|
5
2
|
/**
|
|
6
3
|
* Deletes an existing mailbox.
|
|
7
4
|
*
|
|
8
|
-
* @param
|
|
9
|
-
* @param
|
|
10
|
-
* @returns
|
|
11
|
-
* @throws
|
|
5
|
+
* @param connection - IMAP connection instance
|
|
6
|
+
* @param path - Mailbox path to delete
|
|
7
|
+
* @returns Object with the deleted path, or undefined if preconditions not met
|
|
8
|
+
* @throws If the DELETE command fails
|
|
12
9
|
*/
|
|
13
|
-
|
|
10
|
+
export default async function deleteMailbox(connection, path) {
|
|
14
11
|
if (![connection.states.AUTHENTICATED, connection.states.SELECTED].includes(connection.state)) {
|
|
15
12
|
// nothing to do here
|
|
16
13
|
return;
|
|
17
14
|
}
|
|
18
|
-
|
|
19
15
|
path = normalizePath(connection, path);
|
|
20
|
-
|
|
21
16
|
// If the mailbox to delete is currently selected, we must close/deselect it first.
|
|
22
17
|
// IMAP servers reject DELETE on the currently selected mailbox (RFC 3501 6.3.4).
|
|
23
18
|
if (connection.state === connection.states.SELECTED && connection.mailbox.path === path) {
|
|
24
19
|
await connection.run('CLOSE');
|
|
25
20
|
}
|
|
26
|
-
|
|
27
21
|
let response;
|
|
28
22
|
try {
|
|
29
23
|
let map = {
|
|
@@ -32,9 +26,10 @@ module.exports = async (connection, path) => {
|
|
|
32
26
|
response = await connection.exec('DELETE', [{ type: 'ATOM', value: encodePath(connection, path) }]);
|
|
33
27
|
response.next();
|
|
34
28
|
return map;
|
|
35
|
-
}
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
36
31
|
await enhanceCommandError(err);
|
|
37
32
|
connection.log.warn({ err, cid: connection.id });
|
|
38
33
|
throw err;
|
|
39
34
|
}
|
|
40
|
-
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ImapFlow } from '../imap-flow.js';
|
|
2
|
+
/**
|
|
3
|
+
* Enables IMAP extensions on the server.
|
|
4
|
+
*
|
|
5
|
+
* @param connection - IMAP connection instance
|
|
6
|
+
* @param extensionList - List of extension names to enable
|
|
7
|
+
* @returns Set of enabled extensions, false on failure, or undefined if not applicable
|
|
8
|
+
*/
|
|
9
|
+
export default function enable(connection: ImapFlow, extensionList: string[]): Promise<Set<string> | false | undefined>;
|
|
@@ -1,21 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { hasCapability } = require('../tools.js');
|
|
4
|
-
|
|
1
|
+
import { hasCapability } from '../tools.js';
|
|
5
2
|
/**
|
|
6
3
|
* Enables IMAP extensions on the server.
|
|
7
4
|
*
|
|
8
|
-
* @param
|
|
9
|
-
* @param
|
|
10
|
-
* @returns
|
|
5
|
+
* @param connection - IMAP connection instance
|
|
6
|
+
* @param extensionList - List of extension names to enable
|
|
7
|
+
* @returns Set of enabled extensions, false on failure, or undefined if not applicable
|
|
11
8
|
*/
|
|
12
|
-
|
|
9
|
+
export default async function enable(connection, extensionList) {
|
|
13
10
|
// ENABLE is part of base IMAP4rev2, so rev2-only servers may omit the token
|
|
14
11
|
if (!hasCapability(connection, 'ENABLE') || connection.state !== connection.states.AUTHENTICATED) {
|
|
15
12
|
// nothing to do here
|
|
16
13
|
return;
|
|
17
14
|
}
|
|
18
|
-
|
|
19
15
|
// Pre-filter: only request extensions the server actually advertised in its
|
|
20
16
|
// CAPABILITY response. Requesting unsupported extensions would cause an error.
|
|
21
17
|
// Compared case-insensitively - the capability map keeps canonical casing for
|
|
@@ -25,39 +21,36 @@ module.exports = async (connection, extensionList) => {
|
|
|
25
21
|
if (!extensionList.length) {
|
|
26
22
|
return;
|
|
27
23
|
}
|
|
28
|
-
|
|
29
24
|
let response;
|
|
30
25
|
try {
|
|
31
26
|
let enabled = new Set();
|
|
32
|
-
response = await connection.exec(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
ENABLED: async untagged => {
|
|
41
|
-
if (!untagged.attributes || !untagged.attributes.length) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
untagged.attributes.forEach(attr => {
|
|
45
|
-
if (attr.value && typeof attr.value === 'string') {
|
|
46
|
-
enabled.add(attr.value.toUpperCase().trim());
|
|
47
|
-
}
|
|
48
|
-
});
|
|
27
|
+
response = await connection.exec('ENABLE', extensionList.map((extension) => ({ type: 'ATOM', value: extension.toUpperCase() })), {
|
|
28
|
+
untagged: {
|
|
29
|
+
// The untagged ENABLED response is a flat list of extension names
|
|
30
|
+
// (e.g., "* ENABLED CONDSTORE UTF8=ACCEPT"), NOT key-value pairs.
|
|
31
|
+
// Each attribute is a single extension identifier.
|
|
32
|
+
ENABLED: async (untagged) => {
|
|
33
|
+
if (!untagged.attributes || !untagged.attributes.length) {
|
|
34
|
+
return;
|
|
49
35
|
}
|
|
36
|
+
untagged.attributes.forEach(attr => {
|
|
37
|
+
let value = attr.value;
|
|
38
|
+
if (value && typeof value === 'string') {
|
|
39
|
+
enabled.add(value.toUpperCase().trim());
|
|
40
|
+
}
|
|
41
|
+
});
|
|
50
42
|
}
|
|
51
43
|
}
|
|
52
|
-
);
|
|
44
|
+
});
|
|
53
45
|
// Merge instead of replace - the untagged ENABLED response only lists
|
|
54
46
|
// extensions enabled by this command (RFC 5161), so a replace would drop
|
|
55
47
|
// grants from an earlier ENABLE call
|
|
56
48
|
connection.enabled = new Set([...connection.enabled, ...enabled]);
|
|
57
49
|
response.next();
|
|
58
50
|
return connection.enabled;
|
|
59
|
-
}
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
60
53
|
connection.log.warn({ err, cid: connection.id });
|
|
61
54
|
return false;
|
|
62
55
|
}
|
|
63
|
-
}
|
|
56
|
+
}
|
|
@@ -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,88 @@
|
|
|
1
|
+
import { parseBigIntValue, parseUintValue, MAX_UINT32_DIGITS } from '../tools.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parses the key-value attributes from an ESEARCH untagged response.
|
|
4
|
+
*
|
|
5
|
+
* Receives the attribute list AFTER stripping the leading (TAG "X") list
|
|
6
|
+
* and the UID atom, i.e. only the result keyword/value pairs remain.
|
|
7
|
+
*
|
|
8
|
+
* ALL and PARTIAL.messages are kept as compact sequence-set strings.
|
|
9
|
+
* Use expandRange() from tools.ts if you need to expand them.
|
|
10
|
+
* MODSEQ (RFC 7162, sent when the search used a MODSEQ criterion) is
|
|
11
|
+
* returned as a BigInt.
|
|
12
|
+
*
|
|
13
|
+
* @param attrs - Attribute array from the IMAP parser
|
|
14
|
+
* @returns ESearchResult object
|
|
15
|
+
*/
|
|
16
|
+
export function parseEsearchResponse(attrs) {
|
|
17
|
+
const result = {};
|
|
18
|
+
let i = 0;
|
|
19
|
+
while (i < attrs.length) {
|
|
20
|
+
const token = attrs[i];
|
|
21
|
+
if (!token || token.type !== 'ATOM') {
|
|
22
|
+
i++;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const key = token.value.toUpperCase();
|
|
26
|
+
if (i + 1 >= attrs.length) {
|
|
27
|
+
i++;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
switch (key) {
|
|
31
|
+
// COUNT is a plain message count; MIN and MAX are sequence numbers or UIDs. All
|
|
32
|
+
// three are bounded decimal runs - isNaN() would also admit '1e400' (Infinity)
|
|
33
|
+
case 'COUNT': {
|
|
34
|
+
const n = parseUintValue(attrs[++i]?.value, MAX_UINT32_DIGITS);
|
|
35
|
+
if (n !== false)
|
|
36
|
+
result.count = n;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case 'MIN': {
|
|
40
|
+
const n = parseUintValue(attrs[++i]?.value, MAX_UINT32_DIGITS);
|
|
41
|
+
if (n !== false)
|
|
42
|
+
result.min = n;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
case 'MAX': {
|
|
46
|
+
const n = parseUintValue(attrs[++i]?.value, MAX_UINT32_DIGITS);
|
|
47
|
+
if (n !== false)
|
|
48
|
+
result.max = n;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case 'MODSEQ': {
|
|
52
|
+
// RFC 7162 section 3.1.5: present when the SEARCH used a MODSEQ
|
|
53
|
+
// criterion on a CONDSTORE-enabled session. BigInt because
|
|
54
|
+
// mod-sequence values are unsigned 63-bit
|
|
55
|
+
const modseq = parseBigIntValue(attrs[++i]?.value);
|
|
56
|
+
if (modseq !== false)
|
|
57
|
+
result.modseq = modseq;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case 'ALL': {
|
|
61
|
+
const allToken = attrs[++i];
|
|
62
|
+
if (allToken && typeof allToken.value === 'string') {
|
|
63
|
+
result.all = allToken.value;
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
case 'PARTIAL': {
|
|
68
|
+
const listToken = attrs[++i];
|
|
69
|
+
const items = Array.isArray(listToken) ? listToken : null;
|
|
70
|
+
if (!items || items.length < 2)
|
|
71
|
+
break;
|
|
72
|
+
result.partial = {
|
|
73
|
+
range: items[0].value,
|
|
74
|
+
messages: items[1].value
|
|
75
|
+
};
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
default:
|
|
79
|
+
// Skip the value token for unknown keys to keep the stream aligned.
|
|
80
|
+
// The loop's unconditional i++ at the bottom advances past the key;
|
|
81
|
+
// this extra i++ advances past the value token.
|
|
82
|
+
i++;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
i++;
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
@@ -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>;
|
|
@@ -1,60 +1,55 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { enhanceCommandError, hasCapability, parseBigIntValue } = require('../tools.js');
|
|
4
|
-
|
|
1
|
+
import { enhanceCommandError, hasCapability, parseBigIntValue } from '../tools.js';
|
|
5
2
|
/**
|
|
6
3
|
* Deletes specified messages by flagging them as Deleted and expunging.
|
|
7
4
|
*
|
|
8
|
-
* @param
|
|
9
|
-
* @param
|
|
10
|
-
* @param
|
|
11
|
-
* @param
|
|
12
|
-
* @returns
|
|
5
|
+
* @param connection - IMAP connection instance
|
|
6
|
+
* @param range - Message sequence number or UID range
|
|
7
|
+
* @param options - Expunge options
|
|
8
|
+
* @param options.uid - If true, use UID EXPUNGE when UIDPLUS is available
|
|
9
|
+
* @returns True on success, false on failure, or undefined if preconditions not met
|
|
13
10
|
*/
|
|
14
|
-
|
|
11
|
+
export default async function expunge(connection, range, options) {
|
|
15
12
|
if (connection.state !== connection.states.SELECTED || !range) {
|
|
16
13
|
// nothing to do here
|
|
17
14
|
return;
|
|
18
15
|
}
|
|
19
|
-
|
|
20
16
|
options = options || {};
|
|
21
|
-
|
|
22
17
|
// Two-step deletion process per IMAP protocol:
|
|
23
18
|
// Step 1: Mark the target messages with the \Deleted flag.
|
|
24
19
|
await connection.messageFlagsAdd(range, ['\\Deleted'], options);
|
|
25
|
-
|
|
26
20
|
// Step 2: Issue EXPUNGE to permanently remove \Deleted messages.
|
|
27
21
|
// With UIDPLUS (RFC 4315): "UID EXPUNGE <uids>" removes only the specified UIDs,
|
|
28
|
-
// leaving other \Deleted messages untouched
|
|
22
|
+
// leaving other \Deleted messages untouched, important for concurrent access.
|
|
29
23
|
// Without UIDPLUS: plain "EXPUNGE" removes ALL messages flagged \Deleted in the mailbox.
|
|
30
24
|
let byUid = options.uid && hasCapability(connection, 'UIDPLUS');
|
|
31
25
|
let command = byUid ? 'UID EXPUNGE' : 'EXPUNGE';
|
|
32
26
|
let attributes = byUid ? [{ type: 'SEQUENCE', value: range }] : false;
|
|
33
|
-
|
|
34
27
|
let response;
|
|
35
28
|
try {
|
|
36
29
|
response = await connection.exec(command, attributes);
|
|
37
|
-
|
|
38
30
|
// CONDSTORE (RFC 7162): the server may return HIGHESTMODSEQ in the response code
|
|
39
31
|
// (e.g., "A OK [HIGHESTMODSEQ 9122] Expunge completed").
|
|
40
32
|
// Track this so the client can detect concurrent mailbox changes via mod-sequences.
|
|
41
33
|
let section = response.response.attributes && response.response.attributes[0] && response.response.attributes[0].section;
|
|
42
34
|
let responseCode = section && section.length && section[0] && typeof section[0].value === 'string' ? section[0].value : '';
|
|
43
35
|
if (responseCode.toUpperCase() === 'HIGHESTMODSEQ') {
|
|
36
|
+
// A response code always comes with its section, see responseCode above
|
|
37
|
+
let codeSection = section;
|
|
38
|
+
let mailbox = connection.mailbox;
|
|
44
39
|
// Bounded digit runs only: isNaN() also passes '1e5', which BigInt() rejects with
|
|
45
40
|
// a throw that the catch below would swallow, making messageDelete() report false
|
|
46
41
|
// even though the server expunged the messages.
|
|
47
|
-
let highestModseq = parseBigIntValue(
|
|
48
|
-
if (highestModseq && (!
|
|
49
|
-
|
|
42
|
+
let highestModseq = parseBigIntValue(codeSection[1] && codeSection[1].value);
|
|
43
|
+
if (highestModseq && (!mailbox.highestModseq || highestModseq > mailbox.highestModseq)) {
|
|
44
|
+
mailbox.highestModseq = highestModseq;
|
|
50
45
|
}
|
|
51
46
|
}
|
|
52
|
-
|
|
53
47
|
response.next();
|
|
54
48
|
return true;
|
|
55
|
-
}
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
56
51
|
await enhanceCommandError(err);
|
|
57
52
|
connection.log.warn({ err, cid: connection.id });
|
|
58
53
|
return false;
|
|
59
54
|
}
|
|
60
|
-
}
|
|
55
|
+
}
|