imapflow 1.7.8 → 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 +20 -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 +761 -1789
- 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
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = authenticate;
|
|
4
|
+
const tools_js_1 = require("../tools.js");
|
|
5
|
+
/**
|
|
6
|
+
* Handles authentication errors by enriching the error object with server response details.
|
|
7
|
+
*
|
|
8
|
+
* @param err - The original authentication error
|
|
9
|
+
* @param errorResponse - Optional OAuth error response from the server
|
|
10
|
+
* @returns The enriched error; the caller is expected to throw it
|
|
11
|
+
*/
|
|
12
|
+
async function handleAuthError(err, errorResponse) {
|
|
13
|
+
let errorCode = (0, tools_js_1.getStatusCode)(err.response);
|
|
14
|
+
if (errorCode) {
|
|
15
|
+
err.serverResponseCode = errorCode;
|
|
16
|
+
}
|
|
17
|
+
err.authenticationFailed = true;
|
|
18
|
+
err.response = await (0, tools_js_1.getErrorText)(err.response);
|
|
19
|
+
if (errorResponse) {
|
|
20
|
+
err.oauthError = errorResponse;
|
|
21
|
+
}
|
|
22
|
+
return err;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Authenticates using OAuth (OAUTHBEARER or XOAUTH2).
|
|
26
|
+
*
|
|
27
|
+
* @param connection - IMAP connection instance
|
|
28
|
+
* @param username - The username to authenticate with
|
|
29
|
+
* @param accessToken - The OAuth2 access token
|
|
30
|
+
* @returns The authenticated username
|
|
31
|
+
* @throws {Error} If authentication fails
|
|
32
|
+
*/
|
|
33
|
+
async function authOauth(connection, username, accessToken) {
|
|
34
|
+
let oauthbearer;
|
|
35
|
+
let command;
|
|
36
|
+
let breaker;
|
|
37
|
+
if (connection.capabilities.has('AUTH=OAUTHBEARER')) {
|
|
38
|
+
// OAUTHBEARER payload per RFC 7628: fields separated by \x01 (SASL GS2 framing).
|
|
39
|
+
// Format: "n,a=<user>," \x01 "host=..." \x01 "port=..." \x01 "auth=Bearer <token>" \x01 \x01
|
|
40
|
+
// The trailing empty strings produce the required double-\x01 terminator.
|
|
41
|
+
// Both fields must describe the connection actually in use. The port was hardcoded to 993,
|
|
42
|
+
// so an OAuth2 server reached over 143/STARTTLS advertised a payload that did not match, and
|
|
43
|
+
// `servername` is set to false for a bare-IP host, which rendered as a literal "host=false".
|
|
44
|
+
// A server validating either field rejects with status `invalid_request` - a permanent
|
|
45
|
+
// failure that refreshing the access token can never clear.
|
|
46
|
+
oauthbearer = [
|
|
47
|
+
`n,a=${username},`,
|
|
48
|
+
`host=${connection.servername || connection.host}`,
|
|
49
|
+
`port=${connection.port}`,
|
|
50
|
+
`auth=Bearer ${accessToken}`,
|
|
51
|
+
'',
|
|
52
|
+
''
|
|
53
|
+
].join('\x01');
|
|
54
|
+
command = 'OAUTHBEARER';
|
|
55
|
+
// "AQ==" is base64 for \x01, sent as the error continuation to abort the SASL exchange
|
|
56
|
+
breaker = 'AQ==';
|
|
57
|
+
}
|
|
58
|
+
else if (connection.capabilities.has('AUTH=XOAUTH') || connection.capabilities.has('AUTH=XOAUTH2')) {
|
|
59
|
+
// XOAUTH2 payload (Google-specific): simpler format, also \x01-delimited.
|
|
60
|
+
// Format: "user=<user>" \x01 "auth=Bearer <token>" \x01 \x01
|
|
61
|
+
oauthbearer = [`user=${username}`, `auth=Bearer ${accessToken}`, '', ''].join('\x01');
|
|
62
|
+
command = 'XOAUTH2';
|
|
63
|
+
// Empty breaker: XOAUTH2 expects an empty response to abort the SASL exchange
|
|
64
|
+
breaker = '';
|
|
65
|
+
}
|
|
66
|
+
let errorResponse = false;
|
|
67
|
+
try {
|
|
68
|
+
let response = await connection.exec('AUTHENTICATE', [
|
|
69
|
+
{ type: 'ATOM', value: command },
|
|
70
|
+
{ type: 'ATOM', value: Buffer.from(oauthbearer).toString('base64'), sensitive: true }
|
|
71
|
+
], {
|
|
72
|
+
// Server sends a "+" continuation if auth fails, with a base64 JSON error payload.
|
|
73
|
+
// We decode it for diagnostics, then send the breaker to terminate the exchange.
|
|
74
|
+
onPlusTag: async (resp) => {
|
|
75
|
+
if (resp.attributes && resp.attributes[0] && resp.attributes[0].type === 'TEXT') {
|
|
76
|
+
try {
|
|
77
|
+
errorResponse = JSON.parse(Buffer.from(resp.attributes[0].value, 'base64').toString());
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
connection.log.debug({
|
|
81
|
+
msg: 'Failed to parse OAuth error response',
|
|
82
|
+
errorResponse: resp.attributes[0].value,
|
|
83
|
+
err,
|
|
84
|
+
cid: connection.id
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
connection.log.debug({ src: 'c', msg: breaker, comment: `Error response for ${command}`, cid: connection.id });
|
|
89
|
+
connection.write(breaker);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
response.next();
|
|
93
|
+
connection.authCapabilities.set(`AUTH=${command}`, true);
|
|
94
|
+
return username;
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
throw await handleAuthError(err, errorResponse);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Authenticates using the SASL LOGIN mechanism.
|
|
102
|
+
*
|
|
103
|
+
* @param connection - IMAP connection instance
|
|
104
|
+
* @param username - The username to authenticate with
|
|
105
|
+
* @param password - The password to authenticate with
|
|
106
|
+
* @returns The authenticated username
|
|
107
|
+
* @throws {Error} If authentication fails
|
|
108
|
+
*/
|
|
109
|
+
async function authLogin(connection, username, password) {
|
|
110
|
+
let errorResponse = false;
|
|
111
|
+
try {
|
|
112
|
+
// SASL LOGIN is a challenge-response mechanism: the server sends base64-encoded
|
|
113
|
+
// prompts ("Username:" and "Password:") and the client responds with base64-encoded values.
|
|
114
|
+
let response = await connection.exec('AUTHENTICATE', [{ type: 'ATOM', value: 'LOGIN' }], {
|
|
115
|
+
onPlusTag: async (resp) => {
|
|
116
|
+
if (resp.attributes && resp.attributes[0] && resp.attributes[0].type === 'TEXT') {
|
|
117
|
+
// Decode the server's base64 challenge to determine what it's asking for.
|
|
118
|
+
// Strip trailing colons and null bytes (\x00) that some servers append to the prompt.
|
|
119
|
+
let question = Buffer.from(resp.attributes[0].value, 'base64')
|
|
120
|
+
.toString()
|
|
121
|
+
.toLowerCase()
|
|
122
|
+
.replace(/[:\x00]*$/, '');
|
|
123
|
+
if (question === 'username' || question === 'user name') {
|
|
124
|
+
let encodedUsername = Buffer.from(username).toString('base64');
|
|
125
|
+
connection.log.debug({ src: 'c', msg: encodedUsername, comment: `Encoded username for AUTH=LOGIN`, cid: connection.id });
|
|
126
|
+
connection.write(encodedUsername);
|
|
127
|
+
}
|
|
128
|
+
else if (question === 'password') {
|
|
129
|
+
connection.log.debug({ src: 'c', msg: '(* value hidden *)', comment: `Encoded password for AUTH=LOGIN`, cid: connection.id });
|
|
130
|
+
connection.write(Buffer.from(password).toString('base64'));
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
throw new Error(`Unknown LOGIN question "${question}"`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
response.next();
|
|
139
|
+
connection.authCapabilities.set(`AUTH=LOGIN`, true);
|
|
140
|
+
return username;
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
throw await handleAuthError(err, errorResponse);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Authenticates using the SASL PLAIN mechanism.
|
|
148
|
+
*
|
|
149
|
+
* @param connection - IMAP connection instance
|
|
150
|
+
* @param username - The authentication identity (authcid)
|
|
151
|
+
* @param password - The password to authenticate with
|
|
152
|
+
* @param authzid - Optional authorization identity to impersonate
|
|
153
|
+
* @returns The authorized identity (authzid if provided, otherwise username)
|
|
154
|
+
* @throws {Error} If authentication fails
|
|
155
|
+
*/
|
|
156
|
+
async function authPlain(connection, username, password, authzid) {
|
|
157
|
+
let errorResponse = false;
|
|
158
|
+
try {
|
|
159
|
+
let response = await connection.exec('AUTHENTICATE', [{ type: 'ATOM', value: 'PLAIN' }], {
|
|
160
|
+
onPlusTag: async () => {
|
|
161
|
+
// SASL PLAIN format: [authzid]\x00authcid\x00password
|
|
162
|
+
// authzid: authorization identity (who to impersonate)
|
|
163
|
+
// authcid: authentication identity (who is authenticating)
|
|
164
|
+
let authzidValue = authzid || '';
|
|
165
|
+
let encodedResponse = Buffer.from([authzidValue, username, password].join('\x00')).toString('base64');
|
|
166
|
+
let loggedResponse = Buffer.from([authzidValue, username, '(* value hidden *)'].join('\x00')).toString('base64');
|
|
167
|
+
connection.log.debug({
|
|
168
|
+
src: 'c',
|
|
169
|
+
msg: loggedResponse,
|
|
170
|
+
comment: `Encoded response for AUTH=PLAIN${authzid ? ' with authzid' : ''}`,
|
|
171
|
+
cid: connection.id
|
|
172
|
+
});
|
|
173
|
+
connection.write(encodedResponse);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
response.next();
|
|
177
|
+
connection.authCapabilities.set(`AUTH=PLAIN`, true);
|
|
178
|
+
// Return the identity we're authorized as (authzid if provided, otherwise username)
|
|
179
|
+
return authzid || username;
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
throw await handleAuthError(err, errorResponse);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Authenticates user using the best available method.
|
|
187
|
+
*
|
|
188
|
+
* @param connection - IMAP connection instance
|
|
189
|
+
* @param username - The username to authenticate with
|
|
190
|
+
* @param credentials - Authentication credentials
|
|
191
|
+
* @returns The authenticated username, or undefined if already authenticated
|
|
192
|
+
* @throws {Error} If no supported authentication mechanism is available or if authentication fails
|
|
193
|
+
*/
|
|
194
|
+
async function authenticate(connection, username, { accessToken, password, loginMethod, authzid }) {
|
|
195
|
+
if (connection.state !== connection.states.NOT_AUTHENTICATED) {
|
|
196
|
+
// nothing to do here
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
// Authentication method selection order:
|
|
200
|
+
// 1. OAuth (OAUTHBEARER > XOAUTH2), preferred when an accessToken is provided,
|
|
201
|
+
// as it avoids transmitting passwords entirely.
|
|
202
|
+
// 2. SASL PLAIN, preferred over LOGIN because it supports authzid (impersonation)
|
|
203
|
+
// and sends credentials in a single round trip.
|
|
204
|
+
// 3. SASL LOGIN, the fallback; an older challenge-response mechanism (two round trips).
|
|
205
|
+
// If loginMethod is explicitly set, it overrides the automatic capability-based selection.
|
|
206
|
+
if (accessToken) {
|
|
207
|
+
// AUTH=OAUTHBEARER and AUTH=XOAUTH in the context of OAuth2 or very similar so we can handle these together
|
|
208
|
+
if (connection.capabilities.has('AUTH=OAUTHBEARER') || connection.capabilities.has('AUTH=XOAUTH') || connection.capabilities.has('AUTH=XOAUTH2')) {
|
|
209
|
+
return await authOauth(connection, username, accessToken);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (password) {
|
|
213
|
+
if ((!loginMethod && connection.capabilities.has('AUTH=PLAIN')) || loginMethod === 'AUTH=PLAIN') {
|
|
214
|
+
return await authPlain(connection, username, password, authzid);
|
|
215
|
+
}
|
|
216
|
+
if ((!loginMethod && connection.capabilities.has('AUTH=LOGIN')) || loginMethod === 'AUTH=LOGIN') {
|
|
217
|
+
return await authLogin(connection, username, password);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
throw new Error('Unsupported authentication mechanism');
|
|
221
|
+
}
|
|
222
|
+
module.exports = exports.default;
|
|
223
|
+
Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
|
|
@@ -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>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = capability;
|
|
4
|
+
/**
|
|
5
|
+
* Refreshes capabilities from server.
|
|
6
|
+
*
|
|
7
|
+
* @param connection - IMAP connection instance
|
|
8
|
+
* @returns Server capabilities map, or false on failure
|
|
9
|
+
*/
|
|
10
|
+
// Capabilities are normally received and updated by the global response handler
|
|
11
|
+
// (e.g., from the server greeting or after authentication). This explicit CAPABILITY
|
|
12
|
+
// command is only needed when capabilities must be refreshed on demand, such as
|
|
13
|
+
// after STARTTLS or when the server signals a capability change.
|
|
14
|
+
async function capability(connection) {
|
|
15
|
+
if (connection.capabilities.size && !connection.expectCapabilityUpdate) {
|
|
16
|
+
return connection.capabilities;
|
|
17
|
+
}
|
|
18
|
+
let response;
|
|
19
|
+
try {
|
|
20
|
+
// The actual parsing of the untagged CAPABILITY response is handled by the
|
|
21
|
+
// global handler, not here. We just trigger the server to send it.
|
|
22
|
+
response = await connection.exec('CAPABILITY');
|
|
23
|
+
response.next();
|
|
24
|
+
return connection.capabilities;
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
connection.log.warn({ err, cid: connection.id });
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
module.exports = exports.default;
|
|
32
|
+
Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
|
|
@@ -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>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = close;
|
|
4
|
+
/**
|
|
5
|
+
* Closes the currently selected mailbox.
|
|
6
|
+
*
|
|
7
|
+
* @param connection - IMAP connection instance
|
|
8
|
+
* @returns True on success, false on failure, or undefined if not in SELECTED state
|
|
9
|
+
*/
|
|
10
|
+
async function close(connection) {
|
|
11
|
+
if (connection.state !== connection.states.SELECTED) {
|
|
12
|
+
// nothing to do here
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
let response;
|
|
16
|
+
try {
|
|
17
|
+
// IMAP CLOSE (RFC 3501 6.4.2): permanently removes all messages flagged \Deleted
|
|
18
|
+
// from the currently selected mailbox (implicit expunge) and deselects it.
|
|
19
|
+
// Unlike EXPUNGE, CLOSE does not send individual untagged EXPUNGE responses.
|
|
20
|
+
response = await connection.exec('CLOSE');
|
|
21
|
+
response.next();
|
|
22
|
+
// Transition from SELECTED back to AUTHENTICATED state.
|
|
23
|
+
// Clear mailbox metadata so subsequent operations know no mailbox is selected.
|
|
24
|
+
let currentMailbox = connection.mailbox;
|
|
25
|
+
connection.mailbox = false;
|
|
26
|
+
connection.currentSelectCommand = false;
|
|
27
|
+
connection.state = connection.states.AUTHENTICATED;
|
|
28
|
+
if (currentMailbox) {
|
|
29
|
+
connection.emit('mailboxClose', currentMailbox);
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
connection.log.warn({ err, cid: connection.id });
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
module.exports = exports.default;
|
|
39
|
+
Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
|
|
@@ -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>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = compress;
|
|
4
|
+
/**
|
|
5
|
+
* Requests DEFLATE compression from the server.
|
|
6
|
+
*
|
|
7
|
+
* @param connection - IMAP connection instance
|
|
8
|
+
* @returns True if compression was enabled, false otherwise
|
|
9
|
+
*/
|
|
10
|
+
// COMPRESS=DEFLATE (RFC 4978): enables zlib compression on the IMAP connection
|
|
11
|
+
// to reduce bandwidth. Once enabled, all subsequent data in both directions is compressed.
|
|
12
|
+
async function compress(connection) {
|
|
13
|
+
// Skip if the server doesn't support COMPRESS=DEFLATE, or if compression
|
|
14
|
+
// is already active (connection._inflate exists) to avoid double-compression.
|
|
15
|
+
if (!connection.capabilities.has('COMPRESS=DEFLATE') || connection._inflate) {
|
|
16
|
+
// nothing to do here
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
let response;
|
|
20
|
+
try {
|
|
21
|
+
response = await connection.exec('COMPRESS', [{ type: 'ATOM', value: 'DEFLATE' }]);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
// The server declined (NO/BAD): nothing switched, staying uncompressed is safe.
|
|
25
|
+
connection.log.warn({ err, cid: connection.id });
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
// Everything after the tagged OK is already deflate-framed (RFC 4978 section 4) -
|
|
29
|
+
// the server switches at the OK, so declining the upgrade at this point is not a
|
|
30
|
+
// protocol option. The socket stays piped into the plaintext parser until the
|
|
31
|
+
// transport swaps in the inflater, so bytes that arrived in the same chunk as the
|
|
32
|
+
// OK have been consumed as cleartext and are missing from the head of the deflate
|
|
33
|
+
// stream: the session is unrecoverable in both directions. Fail it immediately
|
|
34
|
+
// (the same way STARTTLS treats post-OK trailing data) instead of letting it die
|
|
35
|
+
// slowly on garbage. Closing this window without failing needs the stream to hand
|
|
36
|
+
// back its unconsumed tail on unpipe so the transport can feed it into the
|
|
37
|
+
// inflater - not something a command module can reach from here.
|
|
38
|
+
if (response.hasTrailingData) {
|
|
39
|
+
let error = new Error('Server sent data between the COMPRESS response and the compression layer switch');
|
|
40
|
+
error.code = 'COMPRESS_TRAILING_DATA';
|
|
41
|
+
connection.log.error({ err: error, cid: connection.id });
|
|
42
|
+
// Schedule the close before releasing parser backpressure, so the buffered
|
|
43
|
+
// deflate-framed bytes cannot settle anything before teardown begins. This is
|
|
44
|
+
// why the decision lives here rather than at the connection layer the way the
|
|
45
|
+
// STARTTLS guard does (starttls.ts records a flag, upgradeToSTARTTLS decides):
|
|
46
|
+
// only the command module holds the response before its backpressure release,
|
|
47
|
+
// so only it can order teardown ahead of that release.
|
|
48
|
+
connection.closeAfter();
|
|
49
|
+
response.next();
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
response.next();
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
module.exports = exports.default;
|
|
56
|
+
Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
|
|
@@ -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>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = copy;
|
|
4
|
+
const tools_js_1 = require("../tools.js");
|
|
5
|
+
const copyuid_parser_js_1 = require("./copyuid-parser.js");
|
|
6
|
+
/**
|
|
7
|
+
* Copies messages from the current mailbox to another mailbox.
|
|
8
|
+
*
|
|
9
|
+
* @param connection - IMAP connection instance
|
|
10
|
+
* @param range - Message sequence number or UID range
|
|
11
|
+
* @param destination - Destination mailbox path
|
|
12
|
+
* @param options - Copy options
|
|
13
|
+
* @param options.uid - If true, use UID COPY instead of COPY
|
|
14
|
+
* @returns Copy result with UID mapping if available, false on failure, or undefined if preconditions not met
|
|
15
|
+
*/
|
|
16
|
+
async function copy(connection, range, destination, options) {
|
|
17
|
+
if (connection.state !== connection.states.SELECTED || !range || !destination) {
|
|
18
|
+
// nothing to do here
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
options = options || {};
|
|
22
|
+
destination = (0, tools_js_1.normalizePath)(connection, destination);
|
|
23
|
+
let attributes = [
|
|
24
|
+
{ type: 'SEQUENCE', value: range },
|
|
25
|
+
{ type: 'ATOM', value: (0, tools_js_1.encodePath)(connection, destination) }
|
|
26
|
+
];
|
|
27
|
+
let response;
|
|
28
|
+
try {
|
|
29
|
+
response = await connection.exec(options.uid ? 'UID COPY' : 'COPY', attributes);
|
|
30
|
+
response.next();
|
|
31
|
+
let map = { path: connection.mailbox.path, destination };
|
|
32
|
+
// UIDPLUS (RFC 4315): the server may include a COPYUID response code in the
|
|
33
|
+
// tagged OK response, providing a mapping from source UIDs to destination UIDs.
|
|
34
|
+
(0, copyuid_parser_js_1.parseCopyUid)(response.response, map);
|
|
35
|
+
return map;
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
await (0, tools_js_1.enhanceCommandError)(err);
|
|
39
|
+
connection.log.warn({ err, cid: connection.id });
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
module.exports = exports.default;
|
|
44
|
+
Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
|
|
@@ -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;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseCopyUid = parseCopyUid;
|
|
4
|
+
const tools_js_1 = require("../tools.js");
|
|
5
|
+
/**
|
|
6
|
+
* Parses COPYUID response code from an IMAP response (RFC 4315).
|
|
7
|
+
* Used by both COPY and MOVE commands to extract the UID mapping
|
|
8
|
+
* from source mailbox to destination mailbox.
|
|
9
|
+
*
|
|
10
|
+
* @param response - IMAP response object with attributes
|
|
11
|
+
* @param map - Result map to populate with uidValidity and uidMap
|
|
12
|
+
*/
|
|
13
|
+
function parseCopyUid(response, map) {
|
|
14
|
+
let section = response.attributes && response.attributes[0] && response.attributes[0].section;
|
|
15
|
+
let responseCode = section && section.length && section[0] && typeof section[0].value === 'string' ? section[0].value : '';
|
|
16
|
+
if (responseCode !== 'COPYUID') {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
// A COPYUID code always comes with its section, see responseCode above
|
|
20
|
+
let codeSection = section;
|
|
21
|
+
// Only a bounded pure digit string is accepted: isNaN() also passes values like "1e5" or
|
|
22
|
+
// "Infinity", which BigInt() then rejects with a throw that loses the uidMap.
|
|
23
|
+
let uidValidity = (0, tools_js_1.parseBigIntValue)(codeSection[1] && codeSection[1].value);
|
|
24
|
+
if (uidValidity !== false) {
|
|
25
|
+
map.uidValidity = uidValidity;
|
|
26
|
+
}
|
|
27
|
+
const sourceUids = codeSection[2] && typeof codeSection[2].value === 'string' ? (0, tools_js_1.expandRange)(codeSection[2].value) : false;
|
|
28
|
+
const destinationUids = codeSection[3] && typeof codeSection[3].value === 'string' ? (0, tools_js_1.expandRange)(codeSection[3].value) : false;
|
|
29
|
+
if (sourceUids && destinationUids && sourceUids.length === destinationUids.length) {
|
|
30
|
+
map.uidMap = new Map(sourceUids.map((uid, i) => [uid, destinationUids[i]]));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -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>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = create;
|
|
4
|
+
const tools_js_1 = require("../tools.js");
|
|
5
|
+
/**
|
|
6
|
+
* Creates a new mailbox and subscribes to it.
|
|
7
|
+
*
|
|
8
|
+
* @param connection - IMAP connection instance
|
|
9
|
+
* @param path - Mailbox path to create
|
|
10
|
+
* @returns Object with path and creation status, or undefined if preconditions not met
|
|
11
|
+
* @throws If the CREATE command fails (except when mailbox already exists)
|
|
12
|
+
*/
|
|
13
|
+
async function create(connection, path) {
|
|
14
|
+
if (![connection.states.AUTHENTICATED, connection.states.SELECTED].includes(connection.state)) {
|
|
15
|
+
// nothing to do here
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
path = (0, tools_js_1.normalizePath)(connection, path);
|
|
19
|
+
let response;
|
|
20
|
+
try {
|
|
21
|
+
let map = {
|
|
22
|
+
path
|
|
23
|
+
};
|
|
24
|
+
response = await connection.exec('CREATE', [{ type: 'ATOM', value: (0, tools_js_1.encodePath)(connection, path) }]);
|
|
25
|
+
// Parse the response code section (e.g., [MAILBOXID (<id>)]) from the tagged OK response.
|
|
26
|
+
// IMAP response code attributes are structured as alternating key-value pairs.
|
|
27
|
+
let section = response.response.attributes &&
|
|
28
|
+
response.response.attributes[0] &&
|
|
29
|
+
response.response.attributes[0].section &&
|
|
30
|
+
response.response.attributes[0].section.length
|
|
31
|
+
? response.response.attributes[0].section
|
|
32
|
+
: false;
|
|
33
|
+
if (section) {
|
|
34
|
+
let key;
|
|
35
|
+
section.forEach((attribute, i) => {
|
|
36
|
+
// IMAP key-value pairs: even indices (i % 2 === 0) are keys, odd indices are values
|
|
37
|
+
if (i % 2 === 0) {
|
|
38
|
+
key = attribute && typeof attribute.value === 'string' ? attribute.value : false;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (!key) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let value;
|
|
45
|
+
switch (key.toLowerCase()) {
|
|
46
|
+
case 'mailboxid':
|
|
47
|
+
key = 'mailboxId';
|
|
48
|
+
value = Array.isArray(attribute) && attribute[0] && typeof attribute[0].value === 'string' ? attribute[0].value : false;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
if (key && value) {
|
|
52
|
+
map[key] = value;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
map.created = true;
|
|
57
|
+
response.next();
|
|
58
|
+
// Auto-subscribe after creation so the new mailbox appears in LSUB listings
|
|
59
|
+
// and is visible to clients that only show subscribed folders.
|
|
60
|
+
await connection.run('SUBSCRIBE', path);
|
|
61
|
+
return map;
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
let errorCode = (0, tools_js_1.getStatusCode)(err.response);
|
|
65
|
+
// ALREADYEXISTS (RFC 5530) means the mailbox already exists on the server.
|
|
66
|
+
// This is not a true error, we return created:false to indicate nothing was created.
|
|
67
|
+
if (errorCode === 'ALREADYEXISTS') {
|
|
68
|
+
// no need to do anything, mailbox already exists
|
|
69
|
+
return {
|
|
70
|
+
path,
|
|
71
|
+
created: false
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
await (0, tools_js_1.enhanceCommandError)(err);
|
|
75
|
+
connection.log.warn({ err, cid: connection.id });
|
|
76
|
+
throw err;
|
|
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,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>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = deleteMailbox;
|
|
4
|
+
const tools_js_1 = require("../tools.js");
|
|
5
|
+
/**
|
|
6
|
+
* Deletes an existing mailbox.
|
|
7
|
+
*
|
|
8
|
+
* @param connection - IMAP connection instance
|
|
9
|
+
* @param path - Mailbox path to delete
|
|
10
|
+
* @returns Object with the deleted path, or undefined if preconditions not met
|
|
11
|
+
* @throws If the DELETE command fails
|
|
12
|
+
*/
|
|
13
|
+
async function deleteMailbox(connection, path) {
|
|
14
|
+
if (![connection.states.AUTHENTICATED, connection.states.SELECTED].includes(connection.state)) {
|
|
15
|
+
// nothing to do here
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
path = (0, tools_js_1.normalizePath)(connection, path);
|
|
19
|
+
// If the mailbox to delete is currently selected, we must close/deselect it first.
|
|
20
|
+
// IMAP servers reject DELETE on the currently selected mailbox (RFC 3501 6.3.4).
|
|
21
|
+
if (connection.state === connection.states.SELECTED && connection.mailbox.path === path) {
|
|
22
|
+
await connection.run('CLOSE');
|
|
23
|
+
}
|
|
24
|
+
let response;
|
|
25
|
+
try {
|
|
26
|
+
let map = {
|
|
27
|
+
path
|
|
28
|
+
};
|
|
29
|
+
response = await connection.exec('DELETE', [{ type: 'ATOM', value: (0, tools_js_1.encodePath)(connection, path) }]);
|
|
30
|
+
response.next();
|
|
31
|
+
return map;
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
await (0, tools_js_1.enhanceCommandError)(err);
|
|
35
|
+
connection.log.warn({ err, cid: connection.id });
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
module.exports = exports.default;
|
|
40
|
+
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
|
+
* 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>;
|