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,138 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
enhanceCommandError,
|
|
5
|
-
hasCapability,
|
|
6
|
-
isValidSequenceValue,
|
|
7
|
-
parseBigIntValue,
|
|
8
|
-
parseUintValue,
|
|
9
|
-
MAX_UINT32_DIGITS,
|
|
10
|
-
EXPANDED_RANGE_LIMIT
|
|
11
|
-
} = require('../tools.js');
|
|
12
|
-
const { searchCompiler } = require('../search-compiler.js');
|
|
13
|
-
|
|
1
|
+
import { enhanceCommandError, hasCapability, isValidSequenceValue, EXPANDED_RANGE_LIMIT } from '../tools.js';
|
|
2
|
+
import { searchCompiler } from '../search-compiler.js';
|
|
3
|
+
import { parseEsearchResponse } from './esearch-parser.js';
|
|
14
4
|
/**
|
|
15
5
|
* Strips the leading (TAG "X") correlator list and the optional UID atom from an
|
|
16
6
|
* ESEARCH untagged response, leaving only the result keyword/value pairs.
|
|
17
7
|
* The IMAP parser represents parenthesized groups as plain Arrays, not objects
|
|
18
8
|
* with type: 'LIST'.
|
|
19
9
|
*
|
|
20
|
-
* @param
|
|
21
|
-
* @returns
|
|
10
|
+
* @param attrs - Raw attribute array from the IMAP parser
|
|
11
|
+
* @returns Attribute array starting at the first result keyword
|
|
22
12
|
*/
|
|
23
|
-
const stripEsearchPrefix = attrs => {
|
|
13
|
+
const stripEsearchPrefix = (attrs) => {
|
|
24
14
|
let start = 0;
|
|
25
|
-
if (attrs[start] && Array.isArray(attrs[start]))
|
|
26
|
-
|
|
15
|
+
if (attrs[start] && Array.isArray(attrs[start]))
|
|
16
|
+
start++;
|
|
17
|
+
if (attrs[start] && typeof attrs[start].value === 'string' && attrs[start].value.toUpperCase() === 'UID')
|
|
18
|
+
start++;
|
|
27
19
|
return attrs.slice(start);
|
|
28
20
|
};
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Parses the key-value attributes from an ESEARCH untagged response.
|
|
32
|
-
*
|
|
33
|
-
* Receives the attribute list AFTER stripping the leading (TAG "X") list
|
|
34
|
-
* and the UID atom — i.e. only the result keyword/value pairs remain.
|
|
35
|
-
*
|
|
36
|
-
* ALL and PARTIAL.messages are kept as compact sequence-set strings.
|
|
37
|
-
* Use expandRange() from tools.js if you need to expand them.
|
|
38
|
-
* MODSEQ (RFC 7162, sent when the search used a MODSEQ criterion) is
|
|
39
|
-
* returned as a BigInt.
|
|
40
|
-
*
|
|
41
|
-
* @param {Array} attrs - Attribute array from the IMAP parser
|
|
42
|
-
* @returns {Object} ESearchResult object
|
|
43
|
-
*/
|
|
44
|
-
function parseEsearchResponse(attrs) {
|
|
45
|
-
const result = {};
|
|
46
|
-
let i = 0;
|
|
47
|
-
while (i < attrs.length) {
|
|
48
|
-
const token = attrs[i];
|
|
49
|
-
if (!token || token.type !== 'ATOM') {
|
|
50
|
-
i++;
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
const key = token.value.toUpperCase();
|
|
54
|
-
if (i + 1 >= attrs.length) {
|
|
55
|
-
i++;
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
switch (key) {
|
|
59
|
-
// COUNT is a plain message count; MIN and MAX are sequence numbers or UIDs. All
|
|
60
|
-
// three are bounded decimal runs - isNaN() would also admit '1e400' (Infinity)
|
|
61
|
-
case 'COUNT': {
|
|
62
|
-
const n = parseUintValue(attrs[++i]?.value, MAX_UINT32_DIGITS);
|
|
63
|
-
if (n !== false) result.count = n;
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
case 'MIN': {
|
|
67
|
-
const n = parseUintValue(attrs[++i]?.value, MAX_UINT32_DIGITS);
|
|
68
|
-
if (n !== false) result.min = n;
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
case 'MAX': {
|
|
72
|
-
const n = parseUintValue(attrs[++i]?.value, MAX_UINT32_DIGITS);
|
|
73
|
-
if (n !== false) result.max = n;
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
case 'MODSEQ': {
|
|
77
|
-
// RFC 7162 section 3.1.5: present when the SEARCH used a MODSEQ
|
|
78
|
-
// criterion on a CONDSTORE-enabled session. BigInt because
|
|
79
|
-
// mod-sequence values are unsigned 63-bit
|
|
80
|
-
const modseq = parseBigIntValue(attrs[++i]?.value);
|
|
81
|
-
if (modseq !== false) result.modseq = modseq;
|
|
82
|
-
break;
|
|
83
|
-
}
|
|
84
|
-
case 'ALL': {
|
|
85
|
-
const allToken = attrs[++i];
|
|
86
|
-
if (allToken && typeof allToken.value === 'string') {
|
|
87
|
-
result.all = allToken.value;
|
|
88
|
-
}
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
case 'PARTIAL': {
|
|
92
|
-
const listToken = attrs[++i];
|
|
93
|
-
const items = Array.isArray(listToken) ? listToken : null;
|
|
94
|
-
if (!items || items.length < 2) break;
|
|
95
|
-
result.partial = {
|
|
96
|
-
range: items[0].value,
|
|
97
|
-
messages: items[1].value
|
|
98
|
-
};
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
default:
|
|
102
|
-
// Skip the value token for unknown keys to keep the stream aligned.
|
|
103
|
-
// The loop's unconditional i++ at the bottom advances past the key;
|
|
104
|
-
// this extra i++ advances past the value token.
|
|
105
|
-
i++;
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
i++;
|
|
109
|
-
}
|
|
110
|
-
return result;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
21
|
/**
|
|
114
22
|
* Searches for messages matching the specified criteria.
|
|
115
23
|
*
|
|
116
|
-
* @param
|
|
117
|
-
* @param
|
|
118
|
-
* @param
|
|
119
|
-
* @param
|
|
120
|
-
* @param
|
|
24
|
+
* @param connection - IMAP connection instance
|
|
25
|
+
* @param query - Search query object, or true/empty object to match all messages
|
|
26
|
+
* @param options - Search options
|
|
27
|
+
* @param options.uid - If true, use UID SEARCH instead of SEARCH
|
|
28
|
+
* @param options.returnOptions - ESEARCH RETURN options. When present AND the
|
|
121
29
|
* server advertises ESEARCH capability, triggers ESEARCH and returns an ESearchResult.
|
|
122
30
|
* Items are strings ('MIN','MAX','COUNT','ALL') or objects ({ partial: '1:100' }).
|
|
123
31
|
* When server lacks ESEARCH, falls back to plain SEARCH and returns number[].
|
|
124
|
-
* @returns {Promise<number[]|Object|boolean>}
|
|
125
32
|
*/
|
|
126
|
-
|
|
33
|
+
export default async function search(connection, query, options) {
|
|
127
34
|
if (connection.state !== connection.states.SELECTED) {
|
|
128
35
|
// nothing to do here
|
|
129
36
|
return false;
|
|
130
37
|
}
|
|
131
|
-
|
|
132
38
|
options = options || {};
|
|
133
|
-
|
|
134
39
|
let attributes;
|
|
135
|
-
|
|
136
40
|
// Three query branches:
|
|
137
41
|
// 1. Empty/truthy/all-only query -> use IMAP "SEARCH ALL" to match every message
|
|
138
42
|
// 2. Non-empty object -> compile into IMAP SEARCH criteria via searchCompiler
|
|
@@ -140,67 +44,67 @@ module.exports = async (connection, query, options) => {
|
|
|
140
44
|
if (!query || query === true || (typeof query === 'object' && (!Object.keys(query).length || (Object.keys(query).length === 1 && query.all)))) {
|
|
141
45
|
// search for all messages
|
|
142
46
|
attributes = [{ type: 'ATOM', value: 'ALL' }];
|
|
143
|
-
}
|
|
47
|
+
}
|
|
48
|
+
else if (query && typeof query === 'object') {
|
|
144
49
|
// normal query
|
|
145
50
|
attributes = searchCompiler(connection, query);
|
|
146
|
-
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
147
53
|
return false;
|
|
148
54
|
}
|
|
149
|
-
|
|
150
55
|
// ESEARCH is part of base IMAP4rev2
|
|
151
56
|
const useEsearch = options.returnOptions && options.returnOptions.length > 0 && hasCapability(connection, 'ESEARCH');
|
|
152
|
-
|
|
153
57
|
if (useEsearch) {
|
|
154
58
|
// Build RETURN (...) item list
|
|
155
59
|
const returnItems = [];
|
|
156
60
|
for (const opt of options.returnOptions) {
|
|
157
61
|
if (typeof opt === 'string') {
|
|
158
62
|
returnItems.push({ type: 'ATOM', value: opt.toUpperCase() });
|
|
159
|
-
}
|
|
63
|
+
}
|
|
64
|
+
else if (opt && typeof opt.partial === 'string') {
|
|
160
65
|
// RFC 9394: PARTIAL is an atom followed by the range atom, both inside RETURN (...)
|
|
161
66
|
returnItems.push({ type: 'ATOM', value: 'PARTIAL' });
|
|
162
67
|
returnItems.push({ type: 'ATOM', value: opt.partial });
|
|
163
68
|
}
|
|
164
69
|
}
|
|
165
|
-
|
|
166
70
|
// If all returnOptions entries were invalid (e.g. objects lacking a string
|
|
167
71
|
// `partial` field), returnItems would be empty. Emitting "RETURN ()" is
|
|
168
72
|
// technically valid per RFC 4731 but returns nothing useful. Fall through
|
|
169
73
|
// to the legacy SEARCH path instead so the caller gets a usable result.
|
|
170
74
|
if (returnItems.length > 0) {
|
|
171
75
|
const returnClause = [{ type: 'ATOM', value: 'RETURN' }, returnItems];
|
|
172
|
-
|
|
173
76
|
let esearchResult = {};
|
|
174
77
|
let response;
|
|
175
78
|
try {
|
|
176
79
|
response = await connection.exec(options.uid ? 'UID SEARCH' : 'SEARCH', [...returnClause, ...attributes], {
|
|
177
80
|
untagged: {
|
|
178
|
-
ESEARCH: async untagged => {
|
|
179
|
-
if (!untagged || !untagged.attributes)
|
|
81
|
+
ESEARCH: async (untagged) => {
|
|
82
|
+
if (!untagged || !untagged.attributes)
|
|
83
|
+
return;
|
|
180
84
|
esearchResult = parseEsearchResponse(stripEsearchPrefix(untagged.attributes));
|
|
181
85
|
}
|
|
182
86
|
}
|
|
183
87
|
});
|
|
184
88
|
response.next();
|
|
185
89
|
return esearchResult;
|
|
186
|
-
}
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
187
92
|
await enhanceCommandError(err);
|
|
188
93
|
connection.log.warn({ err, cid: connection.id });
|
|
189
94
|
return false;
|
|
190
95
|
}
|
|
191
96
|
}
|
|
192
|
-
// returnItems was empty
|
|
97
|
+
// returnItems was empty - fall through to legacy SEARCH path below
|
|
193
98
|
}
|
|
194
|
-
|
|
195
|
-
//
|
|
196
|
-
// Use a Set to deduplicate sequence numbers/UIDs -- servers may return
|
|
99
|
+
// Legacy SEARCH path (no returnOptions, or server lacks ESEARCH)
|
|
100
|
+
// Use a Set to deduplicate sequence numbers/UIDs - servers may return
|
|
197
101
|
// duplicates across multiple untagged SEARCH responses.
|
|
198
102
|
let results = new Set();
|
|
199
103
|
let response;
|
|
200
104
|
try {
|
|
201
105
|
response = await connection.exec(options.uid ? 'UID SEARCH' : 'SEARCH', attributes, {
|
|
202
106
|
untagged: {
|
|
203
|
-
SEARCH: async untagged => {
|
|
107
|
+
SEARCH: async (untagged) => {
|
|
204
108
|
if (untagged && untagged.attributes && untagged.attributes.length) {
|
|
205
109
|
let truncated = false;
|
|
206
110
|
let discarded = false;
|
|
@@ -235,11 +139,10 @@ module.exports = async (connection, query, options) => {
|
|
|
235
139
|
}
|
|
236
140
|
}
|
|
237
141
|
},
|
|
238
|
-
|
|
239
142
|
// IMAP4rev2 servers answer even a plain SEARCH with an untagged
|
|
240
143
|
// ESEARCH response (RFC 9051 deprecated the SEARCH response), so
|
|
241
144
|
// both forms are collected into the same result set
|
|
242
|
-
ESEARCH: async untagged => {
|
|
145
|
+
ESEARCH: async (untagged) => {
|
|
243
146
|
if (!untagged || !untagged.attributes) {
|
|
244
147
|
return;
|
|
245
148
|
}
|
|
@@ -263,7 +166,7 @@ module.exports = async (connection, query, options) => {
|
|
|
263
166
|
// absolute ceiling expandRange() uses - a hostile server cannot
|
|
264
167
|
// bypass it by inflating EXISTS first
|
|
265
168
|
let overBudget = () => results.size >= existsCount() || results.size >= EXPANDED_RANGE_LIMIT;
|
|
266
|
-
let resolveId = part => (part === '*' ? (options.uid ? 0 : existsCount()) : Number(part));
|
|
169
|
+
let resolveId = (part) => (part === '*' ? (options.uid ? 0 : existsCount()) : Number(part));
|
|
267
170
|
let truncated = false;
|
|
268
171
|
let discarded = false;
|
|
269
172
|
sequenceSetLoop: for (let part of parsed.all.split(',')) {
|
|
@@ -311,12 +214,10 @@ module.exports = async (connection, query, options) => {
|
|
|
311
214
|
response.next();
|
|
312
215
|
// Sort numerically for consistent, predictable output order
|
|
313
216
|
return Array.from(results).sort((a, b) => a - b);
|
|
314
|
-
}
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
315
219
|
await enhanceCommandError(err);
|
|
316
220
|
connection.log.warn({ err, cid: connection.id });
|
|
317
221
|
return false;
|
|
318
222
|
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Exported for unit testing — not intended as public library API
|
|
322
|
-
module.exports.parseEsearchResponse = parseEsearchResponse;
|
|
223
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ImapFlow } from '../imap-flow.js';
|
|
2
|
+
import type { MailboxObject, MailboxOpenOptions } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Options for SELECT/EXAMINE: the public open options plus the QRESYNC resynchronization
|
|
5
|
+
* parameters, which are only honored when the QRESYNC extension has been enabled
|
|
6
|
+
*/
|
|
7
|
+
export interface SelectOptions extends MailboxOpenOptions {
|
|
8
|
+
/** QRESYNC modseq value to fetch changes since */
|
|
9
|
+
changedSince?: bigint | number | string | undefined;
|
|
10
|
+
/** QRESYNC UID validity value */
|
|
11
|
+
uidValidity?: bigint | number | string | undefined;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Selects or examines a mailbox, making it the current mailbox for subsequent operations.
|
|
15
|
+
*
|
|
16
|
+
* @param connection - IMAP connection instance
|
|
17
|
+
* @param path - Mailbox path to select
|
|
18
|
+
* @param options - Select options
|
|
19
|
+
* @param options.readOnly - If true, use EXAMINE instead of SELECT (read-only access)
|
|
20
|
+
* @param options.changedSince - QRESYNC modseq value to fetch changes since
|
|
21
|
+
* @param options.uidValidity - QRESYNC UID validity value
|
|
22
|
+
* @returns Mailbox info object with path, flags, exists, uidNext, uidValidity, highestModseq, etc., or undefined if preconditions not met
|
|
23
|
+
* @throws If the SELECT/EXAMINE command fails
|
|
24
|
+
*/
|
|
25
|
+
export default function select(connection: ImapFlow, pathInput: string | string[], options?: SelectOptions | undefined): Promise<MailboxObject | undefined>;
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { encodePath, normalizePath, enhanceCommandError, parseBigIntValue, parseUintValue, getStringList, MAX_UINT32_DIGITS } = require('../tools.js');
|
|
4
|
-
|
|
1
|
+
import { encodePath, normalizePath, enhanceCommandError, parseBigIntValue, parseUintValue, getStringList, MAX_UINT32_DIGITS } from '../tools.js';
|
|
5
2
|
// Response codes carrying a value that SELECT/EXAMINE may write to the mailbox object, keyed by
|
|
6
3
|
// the lowercased code, mapped to the fixed public property name and the parser for the value.
|
|
7
4
|
// Every parser returns false for a value it cannot use, and the field is then left unset.
|
|
@@ -18,62 +15,53 @@ const VALUED_RESPONSE_CODES = Object.assign(Object.create(null), {
|
|
|
18
15
|
// A value that is not a bounded digit run is dropped rather than stored raw: every consumer
|
|
19
16
|
// compares highestModseq relationally, and a relational compare against a non-numeric string
|
|
20
17
|
// is false in both directions, so the value could never advance and delta sync would stop.
|
|
21
|
-
highestmodseq: { key: 'highestModseq', parse: value => parseBigIntValue(value) },
|
|
22
|
-
|
|
18
|
+
highestmodseq: { key: 'highestModseq', parse: (value) => parseBigIntValue(value) },
|
|
23
19
|
// Unique identifier validity. If this changes between sessions, all previously cached UIDs
|
|
24
20
|
// are invalid and the client must re-sync from scratch. Nominally 32-bit, but stored as a
|
|
25
21
|
// BigInt precisely so a server that exceeds that still round-trips, hence the wider bound.
|
|
26
|
-
uidvalidity: { key: 'uidValidity', parse: value => parseBigIntValue(value) },
|
|
27
|
-
|
|
22
|
+
uidvalidity: { key: 'uidValidity', parse: (value) => parseBigIntValue(value) },
|
|
28
23
|
// The next UID to be assigned in this mailbox, useful for detecting new arrivals. A huge
|
|
29
24
|
// digit run would coerce to Infinity and corrupt every later UID range computation.
|
|
30
|
-
uidnext: { key: 'uidNext', parse: value => parseUintValue(value, MAX_UINT32_DIGITS) },
|
|
31
|
-
|
|
25
|
+
uidnext: { key: 'uidNext', parse: (value) => parseUintValue(value, MAX_UINT32_DIGITS) },
|
|
32
26
|
// Sequence number of the first unseen message (RFC 3501 section 7.1). Not a count of unseen
|
|
33
27
|
// messages - use mailboxStatus() with {unseen: true} for that.
|
|
34
|
-
unseen: { key: 'unseen', parse: value => parseUintValue(value, MAX_UINT32_DIGITS) },
|
|
35
|
-
|
|
28
|
+
unseen: { key: 'unseen', parse: (value) => parseUintValue(value, MAX_UINT32_DIGITS) },
|
|
36
29
|
// APPENDLIMIT (RFC 7889): largest message size in octets the server accepts for APPEND into
|
|
37
30
|
// this mailbox. Spelled all lowercase, unlike the camelCase fields around it, because that is
|
|
38
31
|
// the name this object has always exposed.
|
|
39
|
-
appendlimit: { key: 'appendlimit', parse: value => parseUintValue(value) },
|
|
40
|
-
|
|
32
|
+
appendlimit: { key: 'appendlimit', parse: (value) => parseUintValue(value) },
|
|
41
33
|
// OBJECTID (RFC 8474): server-assigned mailbox identifier that survives renames. Sent as a
|
|
42
34
|
// parenthesized list, but servers in the wild send it bare too.
|
|
43
35
|
mailboxid: {
|
|
44
36
|
key: 'mailboxId',
|
|
45
|
-
parse: value => (Array.isArray(value) ? value.length > 0 && value[0] : typeof value === 'string' && value)
|
|
37
|
+
parse: (value) => (Array.isArray(value) ? value.length > 0 && value[0] : typeof value === 'string' && value)
|
|
46
38
|
},
|
|
47
|
-
|
|
48
39
|
// Flags the client may change permanently on messages in this mailbox, including \* if the
|
|
49
40
|
// server allows custom flags. Only the parenthesized form carries flags, and a malformed
|
|
50
41
|
// value must leave permanentFlags unset rather than set an empty Set: canUseFlag() reads
|
|
51
42
|
// unset as permissive and empty as deny-all, so an empty Set would turn every later flag
|
|
52
43
|
// update into a silent no-op for the rest of the session.
|
|
53
|
-
permanentflags: { key: 'permanentFlags', parse: value => Array.isArray(value) && new Set(value) }
|
|
44
|
+
permanentflags: { key: 'permanentFlags', parse: (value) => Array.isArray(value) && new Set(value) }
|
|
54
45
|
});
|
|
55
|
-
|
|
56
46
|
/**
|
|
57
47
|
* Selects or examines a mailbox, making it the current mailbox for subsequent operations.
|
|
58
48
|
*
|
|
59
|
-
* @param
|
|
60
|
-
* @param
|
|
61
|
-
* @param
|
|
62
|
-
* @param
|
|
63
|
-
* @param
|
|
64
|
-
* @param
|
|
65
|
-
* @returns
|
|
66
|
-
* @throws
|
|
49
|
+
* @param connection - IMAP connection instance
|
|
50
|
+
* @param path - Mailbox path to select
|
|
51
|
+
* @param options - Select options
|
|
52
|
+
* @param options.readOnly - If true, use EXAMINE instead of SELECT (read-only access)
|
|
53
|
+
* @param options.changedSince - QRESYNC modseq value to fetch changes since
|
|
54
|
+
* @param options.uidValidity - QRESYNC UID validity value
|
|
55
|
+
* @returns Mailbox info object with path, flags, exists, uidNext, uidValidity, highestModseq, etc., or undefined if preconditions not met
|
|
56
|
+
* @throws If the SELECT/EXAMINE command fails
|
|
67
57
|
*/
|
|
68
|
-
|
|
58
|
+
export default async function select(connection, pathInput, options) {
|
|
69
59
|
if (![connection.states.AUTHENTICATED, connection.states.SELECTED].includes(connection.state)) {
|
|
70
60
|
// nothing to do here
|
|
71
61
|
return;
|
|
72
62
|
}
|
|
73
63
|
options = options || {};
|
|
74
|
-
|
|
75
|
-
path = normalizePath(connection, path);
|
|
76
|
-
|
|
64
|
+
let path = normalizePath(connection, pathInput);
|
|
77
65
|
// Ensure we have folder metadata (delimiter, flags, specialUse) by running LIST if needed.
|
|
78
66
|
// This is cached in connection.folders to avoid repeated LIST calls.
|
|
79
67
|
// Note: this uses run() rather than runInternal(), so it terminates a running IDLE first,
|
|
@@ -90,9 +78,7 @@ module.exports = async (connection, path, options) => {
|
|
|
90
78
|
connection.folders.set(folder.path, folder);
|
|
91
79
|
});
|
|
92
80
|
}
|
|
93
|
-
|
|
94
81
|
let folderListData = connection.folders.has(path) ? connection.folders.get(path) : false;
|
|
95
|
-
|
|
96
82
|
let response;
|
|
97
83
|
try {
|
|
98
84
|
let map = { path };
|
|
@@ -103,7 +89,6 @@ module.exports = async (connection, path, options) => {
|
|
|
103
89
|
}
|
|
104
90
|
});
|
|
105
91
|
}
|
|
106
|
-
|
|
107
92
|
// QRESYNC (RFC 7162): allows efficient mailbox resynchronization by sending
|
|
108
93
|
// the last known UIDVALIDITY and HIGHESTMODSEQ. Server responds with only
|
|
109
94
|
// the changes (new flags, expunged UIDs) since that point.
|
|
@@ -118,9 +103,7 @@ module.exports = async (connection, path, options) => {
|
|
|
118
103
|
]);
|
|
119
104
|
map.qresync = true;
|
|
120
105
|
}
|
|
121
|
-
|
|
122
106
|
let encodedPath = encodePath(connection, path);
|
|
123
|
-
|
|
124
107
|
// SELECT opens the mailbox read-write; EXAMINE opens it read-only.
|
|
125
108
|
// Path encoding: if the encoded path contains '&' (UTF-7 encoding marker),
|
|
126
109
|
// send as quoted STRING to avoid parser issues with the ampersand.
|
|
@@ -129,7 +112,6 @@ module.exports = async (connection, path, options) => {
|
|
|
129
112
|
/* c8 ignore next */ // extraArgs is always initialised to an array, so the [] fallback is unreachable
|
|
130
113
|
arguments: [{ type: encodedPath.indexOf('&') >= 0 ? 'STRING' : 'ATOM', value: encodedPath }].concat(extraArgs || [])
|
|
131
114
|
};
|
|
132
|
-
|
|
133
115
|
response = await connection.exec(selectCommand.command, selectCommand.arguments, {
|
|
134
116
|
untagged: {
|
|
135
117
|
// Untagged OK responses carry response codes in brackets, e.g.:
|
|
@@ -137,7 +119,7 @@ module.exports = async (connection, path, options) => {
|
|
|
137
119
|
// * OK [PERMANENTFLAGS (\Seen \Answered \*)] Flags permitted
|
|
138
120
|
// The section array holds the parsed bracket contents: section[0] is the
|
|
139
121
|
// key (e.g., "UIDVALIDITY"), section[1] is the value or list.
|
|
140
|
-
OK: async untagged => {
|
|
122
|
+
OK: async (untagged) => {
|
|
141
123
|
if (!untagged.attributes || !untagged.attributes.length) {
|
|
142
124
|
return;
|
|
143
125
|
}
|
|
@@ -146,16 +128,15 @@ module.exports = async (connection, path, options) => {
|
|
|
146
128
|
if (section && section.length > 1 && section[0] && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
|
|
147
129
|
let key = section[0].value.toLowerCase();
|
|
148
130
|
let value;
|
|
149
|
-
|
|
150
131
|
// Value can be a single string or a list of strings (e.g., PERMANENTFLAGS).
|
|
151
132
|
// section[1] can be a parsed NIL (null), and so can any element inside a
|
|
152
133
|
// parenthesized list, so both levels need the guard
|
|
153
134
|
if (section[1] && typeof section[1].value === 'string') {
|
|
154
135
|
value = section[1].value;
|
|
155
|
-
}
|
|
136
|
+
}
|
|
137
|
+
else if (Array.isArray(section[1])) {
|
|
156
138
|
value = getStringList(section[1]);
|
|
157
139
|
}
|
|
158
|
-
|
|
159
140
|
let field = VALUED_RESPONSE_CODES[key];
|
|
160
141
|
if (field) {
|
|
161
142
|
let parsed = field.parse(value);
|
|
@@ -164,52 +145,49 @@ module.exports = async (connection, path, options) => {
|
|
|
164
145
|
}
|
|
165
146
|
}
|
|
166
147
|
}
|
|
167
|
-
|
|
168
148
|
// Handle response codes with only a keyword (no value), e.g., [NOMODSEQ].
|
|
169
149
|
// NOMODSEQ means the mailbox does not support mod-sequences, so the
|
|
170
150
|
// CONDSTORE/QRESYNC features are unavailable for it.
|
|
171
|
-
if (section &&
|
|
151
|
+
if (section &&
|
|
152
|
+
section.length === 1 &&
|
|
153
|
+
section[0] &&
|
|
154
|
+
section[0].type === 'ATOM' &&
|
|
155
|
+
section[0].value?.toUpperCase() === 'NOMODSEQ') {
|
|
172
156
|
map.noModseq = true;
|
|
173
157
|
}
|
|
174
158
|
},
|
|
175
|
-
|
|
176
159
|
// Untagged FLAGS response lists all flags defined for this mailbox
|
|
177
160
|
// (both system flags and custom flags). Example: * FLAGS (\Seen \Answered \Flagged)
|
|
178
|
-
FLAGS: async untagged => {
|
|
161
|
+
FLAGS: async (untagged) => {
|
|
179
162
|
if (!untagged.attributes || !untagged.attributes.length || !Array.isArray(untagged.attributes[0])) {
|
|
180
163
|
return;
|
|
181
164
|
}
|
|
182
165
|
map.flags = new Set(getStringList(untagged.attributes[0]));
|
|
183
166
|
},
|
|
184
|
-
|
|
185
167
|
// Untagged EXISTS response: "* <count> EXISTS" tells us the total number
|
|
186
168
|
// of messages in the mailbox. The count is in the command field (numeric prefix).
|
|
187
|
-
EXISTS: async untagged => {
|
|
169
|
+
EXISTS: async (untagged) => {
|
|
188
170
|
// Not a usable count: anything but a bounded digit run. A long digit run
|
|
189
171
|
// coerces to Infinity, which would corrupt every later range computation
|
|
190
172
|
let num = parseUintValue(untagged.command, MAX_UINT32_DIGITS);
|
|
191
173
|
if (num === false) {
|
|
192
174
|
return false;
|
|
193
175
|
}
|
|
194
|
-
|
|
195
176
|
map.exists = num;
|
|
196
177
|
},
|
|
197
|
-
|
|
198
178
|
// VANISHED responses (QRESYNC): server reports UIDs that have been expunged
|
|
199
179
|
// since the client's last known state. Only received when QRESYNC was requested.
|
|
200
180
|
// A dummy mailbox object is passed because the mailbox isn't officially open yet.
|
|
201
|
-
VANISHED: async untagged => {
|
|
181
|
+
VANISHED: async (untagged) => {
|
|
202
182
|
await connection.untaggedVanished(untagged, { path, uidNext: false, uidValidity: false });
|
|
203
183
|
},
|
|
204
|
-
|
|
205
184
|
// Untagged FETCH during SELECT/EXAMINE: only occurs with QRESYNC, delivering
|
|
206
185
|
// updated flags for messages that changed since the client's last modseq.
|
|
207
|
-
FETCH: async untagged => {
|
|
186
|
+
FETCH: async (untagged) => {
|
|
208
187
|
await connection.untaggedFetch(untagged, { path, uidNext: false, uidValidity: false });
|
|
209
188
|
}
|
|
210
189
|
}
|
|
211
190
|
});
|
|
212
|
-
|
|
213
191
|
// The tagged OK response to SELECT/EXAMINE includes [READ-ONLY] or [READ-WRITE]
|
|
214
192
|
// in its response code, indicating the access mode the server granted. A tagged OK
|
|
215
193
|
// with no resp-text has no `attributes` property at all, and unlike the untagged
|
|
@@ -220,7 +198,6 @@ module.exports = async (connection, path, options) => {
|
|
|
220
198
|
if (section && section.length && section[0] && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
|
|
221
199
|
map.readOnly = section[0].value.toUpperCase() === 'READ-ONLY';
|
|
222
200
|
}
|
|
223
|
-
|
|
224
201
|
// Validate QRESYNC preconditions (RFC 7162 Section 3.2.5):
|
|
225
202
|
// QRESYNC results are only valid if UIDVALIDITY matches, HIGHESTMODSEQ is
|
|
226
203
|
// present, and the mailbox supports mod-sequences. If any condition fails,
|
|
@@ -228,49 +205,41 @@ module.exports = async (connection, path, options) => {
|
|
|
228
205
|
if (map.qresync && (options.uidValidity !== map.uidValidity || !map.highestModseq || map.noModseq)) {
|
|
229
206
|
map.qresync = false;
|
|
230
207
|
}
|
|
231
|
-
|
|
232
208
|
// Transition mailbox state: save previous mailbox reference, temporarily
|
|
233
209
|
// clear it, then emit events and set the new mailbox.
|
|
234
210
|
let currentMailbox = connection.mailbox;
|
|
235
211
|
connection.mailbox = false;
|
|
236
|
-
|
|
237
212
|
// Emit mailboxClose if we're switching from a different mailbox.
|
|
238
213
|
// Re-selecting the same mailbox (e.g., for resync) does not trigger close/open.
|
|
239
214
|
if (currentMailbox && currentMailbox.path !== path) {
|
|
240
215
|
connection.emit('mailboxClose', currentMailbox);
|
|
241
216
|
}
|
|
242
|
-
|
|
243
217
|
connection.mailbox = map;
|
|
244
218
|
// Save the SELECT command for potential re-use (e.g., NOOP fallback polling
|
|
245
219
|
// re-issues the SELECT to detect changes on servers without IDLE support).
|
|
246
220
|
connection.currentSelectCommand = selectCommand;
|
|
247
221
|
connection.state = connection.states.SELECTED;
|
|
248
|
-
|
|
249
222
|
if (!currentMailbox || currentMailbox.path !== path) {
|
|
250
223
|
connection.emit('mailboxOpen', connection.mailbox);
|
|
251
224
|
}
|
|
252
|
-
|
|
253
225
|
response.next();
|
|
254
226
|
return map;
|
|
255
|
-
}
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
256
229
|
await enhanceCommandError(err);
|
|
257
|
-
|
|
258
230
|
// If SELECT/EXAMINE fails while a mailbox was already selected, we must
|
|
259
231
|
// reset to AUTHENTICATED state since the server has implicitly deselected
|
|
260
232
|
// the previous mailbox on failure (RFC 3501 Section 6.3.1).
|
|
261
233
|
if (connection.state === connection.states.SELECTED) {
|
|
262
234
|
let currentMailbox = connection.mailbox;
|
|
263
|
-
|
|
264
235
|
connection.mailbox = false;
|
|
265
236
|
connection.currentSelectCommand = false;
|
|
266
237
|
connection.state = connection.states.AUTHENTICATED;
|
|
267
|
-
|
|
268
238
|
if (currentMailbox) {
|
|
269
239
|
connection.emit('mailboxClose', currentMailbox);
|
|
270
240
|
}
|
|
271
241
|
}
|
|
272
|
-
|
|
273
242
|
connection.log.warn({ err, cid: connection.id });
|
|
274
243
|
throw err;
|
|
275
244
|
}
|
|
276
|
-
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ImapFlow } from '../imap-flow.js';
|
|
2
|
+
/**
|
|
3
|
+
* Initiates STARTTLS connection upgrade.
|
|
4
|
+
*
|
|
5
|
+
* @param connection - IMAP connection instance
|
|
6
|
+
* @returns True if STARTTLS was initiated, false if not supported or already secure
|
|
7
|
+
*/
|
|
8
|
+
export default function starttls(connection: ImapFlow): Promise<boolean>;
|
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Initiates STARTTLS connection upgrade.
|
|
5
3
|
*
|
|
6
|
-
* @param
|
|
7
|
-
* @returns
|
|
4
|
+
* @param connection - IMAP connection instance
|
|
5
|
+
* @returns True if STARTTLS was initiated, false if not supported or already secure
|
|
8
6
|
*/
|
|
9
|
-
|
|
7
|
+
export default async function starttls(connection) {
|
|
10
8
|
if (!connection.capabilities.has('STARTTLS') || connection.secureConnection) {
|
|
11
9
|
// nothing to do here
|
|
12
10
|
return false;
|
|
13
11
|
}
|
|
14
|
-
|
|
15
12
|
let response;
|
|
16
13
|
try {
|
|
17
14
|
response = await connection.exec('STARTTLS');
|
|
@@ -20,8 +17,9 @@ module.exports = async connection => {
|
|
|
20
17
|
connection._starttlsHadTrailingData = !!(response && response.hasTrailingData);
|
|
21
18
|
response.next();
|
|
22
19
|
return true;
|
|
23
|
-
}
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
24
22
|
connection.log.warn({ err, cid: connection.id });
|
|
25
23
|
return false;
|
|
26
24
|
}
|
|
27
|
-
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ImapAttributeList } from '../handler/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* A parsed STATUS value: counts and sizes as numbers, UIDVALIDITY and HIGHESTMODSEQ as BigInts
|
|
4
|
+
*/
|
|
5
|
+
export type StatusFieldValue = number | bigint;
|
|
6
|
+
/**
|
|
7
|
+
* Walks a STATUS data-item list - alternating item-name and item-value tokens - and reports
|
|
8
|
+
* every recognized field that parsed successfully. Unknown item names and unusable values are
|
|
9
|
+
* skipped, so one bad field never costs the rest of the response.
|
|
10
|
+
*
|
|
11
|
+
* @param list - Parsed attribute list from the untagged STATUS response.
|
|
12
|
+
* @param onField - Called as (key, value) for each usable field.
|
|
13
|
+
*/
|
|
14
|
+
export declare const parseStatusList: (list: ImapAttributeList, onField: (key: string, value: StatusFieldValue) => void) => void;
|