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,476 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint no-control-regex:0 */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.searchCompiler = void 0;
|
|
5
|
+
const tools_js_1 = require("./tools.js");
|
|
6
|
+
/**
|
|
7
|
+
* Sets a boolean flag in the IMAP search attributes.
|
|
8
|
+
* Automatically handles UN- prefixing for falsy values.
|
|
9
|
+
*
|
|
10
|
+
* @param attributes - Array to append the attribute to
|
|
11
|
+
* @param term - The flag name (e.g., 'SEEN', 'DELETED')
|
|
12
|
+
* @param value - Whether to set or unset the flag
|
|
13
|
+
* @example
|
|
14
|
+
* setBoolOpt(attributes, 'SEEN', false) // Adds 'UNSEEN'
|
|
15
|
+
* setBoolOpt(attributes, 'UNSEEN', false) // Adds 'SEEN' (removes UN prefix)
|
|
16
|
+
*/
|
|
17
|
+
let setBoolOpt = (attributes, term, value) => {
|
|
18
|
+
if (!value) {
|
|
19
|
+
// For falsy values, toggle the UN- prefix
|
|
20
|
+
if (/^un/i.test(term)) {
|
|
21
|
+
// Remove existing UN prefix
|
|
22
|
+
term = term.slice(2);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
// Add UN prefix
|
|
26
|
+
term = 'UN' + term;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
attributes.push({ type: 'ATOM', value: term.toUpperCase() });
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Normalizes a user-supplied sequence set (string, number, bigint, or an array of
|
|
33
|
+
* them) into the single string value of a SEQUENCE token. An array is one
|
|
34
|
+
* comma-joined set: separate tokens would be parsed by the server as extra
|
|
35
|
+
* sequence-number search keys ANDed to the query, not as part of the set.
|
|
36
|
+
*
|
|
37
|
+
* @param value - The sequence set value(s)
|
|
38
|
+
* @returns The joined sequence set string
|
|
39
|
+
*/
|
|
40
|
+
let toSequenceValue = (value) => [].concat(value).join(',');
|
|
41
|
+
/**
|
|
42
|
+
* Adds a search option with its value(s) to the attributes array.
|
|
43
|
+
* Handles NOT operations and array values.
|
|
44
|
+
*
|
|
45
|
+
* @param attributes - Array to append the attribute to
|
|
46
|
+
* @param term - The search term (e.g., 'FROM', 'SUBJECT')
|
|
47
|
+
* @param value - The value for the search term (string, array, or falsy for NOT)
|
|
48
|
+
*/
|
|
49
|
+
let setOpt = (attributes, term, value) => {
|
|
50
|
+
// Handle NOT operations for false or null values
|
|
51
|
+
if (value === false || value === null) {
|
|
52
|
+
attributes.push({ type: 'ATOM', value: 'NOT' });
|
|
53
|
+
}
|
|
54
|
+
attributes.push({ type: 'ATOM', value: term.toUpperCase() });
|
|
55
|
+
// Handle array values (e.g. HEADER name/value pairs)
|
|
56
|
+
if (Array.isArray(value)) {
|
|
57
|
+
value.forEach(entry => attributes.push({ type: 'ATOM', value: (entry || '').toString() }));
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
attributes.push({ type: 'ATOM', value: value.toString() });
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Processes date fields for IMAP search.
|
|
65
|
+
* Converts JavaScript dates to IMAP date format.
|
|
66
|
+
*
|
|
67
|
+
* @param attributes - Array to append the attribute to
|
|
68
|
+
* @param term - The date search term (e.g., 'BEFORE', 'SINCE')
|
|
69
|
+
* @param value - Date value to format
|
|
70
|
+
*/
|
|
71
|
+
let processDateField = (attributes, term, value) => {
|
|
72
|
+
// Normalize first. A Date brand check is not enough on its own: an invalid
|
|
73
|
+
// Date is still a Date and toISOString() throws on it. Normalizing here also
|
|
74
|
+
// means a date string behaves exactly like the equivalent Date object.
|
|
75
|
+
let date = (0, tools_js_1.toValidDate)(value);
|
|
76
|
+
if (!date) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (['BEFORE', 'SENTBEFORE'].includes(term.toUpperCase()) && date.toISOString().substring(11) !== '00:00:00.000Z') {
|
|
80
|
+
// Set to next day to include current day as well, othwerise BEFORE+AFTER
|
|
81
|
+
// searches for the same day but different time values do not match anything
|
|
82
|
+
date = new Date(date.getTime() + 24 * 3600 * 1000);
|
|
83
|
+
}
|
|
84
|
+
// Still reachable after the guard above: the +24h shift can push a near-max
|
|
85
|
+
// Date past the representable range
|
|
86
|
+
let formatted = (0, tools_js_1.formatDate)(date);
|
|
87
|
+
if (!formatted) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
setOpt(attributes, term, formatted);
|
|
91
|
+
};
|
|
92
|
+
// Pre-compiled regex for better performance
|
|
93
|
+
const UNICODE_PATTERN = /[^\x00-\x7F]/;
|
|
94
|
+
/**
|
|
95
|
+
* Checks if a string contains Unicode characters.
|
|
96
|
+
* Used to determine if CHARSET UTF-8 needs to be specified.
|
|
97
|
+
*
|
|
98
|
+
* @param str - String to check
|
|
99
|
+
* @returns True if string contains non-ASCII characters
|
|
100
|
+
*/
|
|
101
|
+
let isUnicodeString = (str) => {
|
|
102
|
+
if (!str || typeof str !== 'string') {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
// Regex test is ~3-5x faster than Buffer.byteLength
|
|
106
|
+
// Matches any character outside ASCII range (0x00-0x7F)
|
|
107
|
+
return UNICODE_PATTERN.test(str);
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Compiles a JavaScript object query into IMAP search command attributes.
|
|
111
|
+
* Supports standard IMAP search criteria and extensions like OBJECTID and Gmail extensions.
|
|
112
|
+
*
|
|
113
|
+
* @param connection - IMAP connection object (capabilities, enabled extensions and the current mailbox are read)
|
|
114
|
+
* @param query - Search query object
|
|
115
|
+
* @returns Array of IMAP search attributes
|
|
116
|
+
* @throws {Error} When required server extensions are not available
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* // Simple search for unseen messages from a sender
|
|
120
|
+
* searchCompiler(connection, {
|
|
121
|
+
* unseen: true,
|
|
122
|
+
* from: 'sender@example.com'
|
|
123
|
+
* });
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* // Complex OR search with date range
|
|
127
|
+
* searchCompiler(connection, {
|
|
128
|
+
* or: [
|
|
129
|
+
* { from: 'alice@example.com' },
|
|
130
|
+
* { from: 'bob@example.com' }
|
|
131
|
+
* ],
|
|
132
|
+
* since: new Date('2024-01-01')
|
|
133
|
+
* });
|
|
134
|
+
*/
|
|
135
|
+
const searchCompiler = (connection, query) => {
|
|
136
|
+
const attributes = [];
|
|
137
|
+
// Track if we need to specify UTF-8 charset
|
|
138
|
+
let hasUnicode = false;
|
|
139
|
+
const mailbox = connection.mailbox;
|
|
140
|
+
/**
|
|
141
|
+
* Recursively walks through the query object and builds IMAP attributes.
|
|
142
|
+
* @param params - Query parameters to process
|
|
143
|
+
*/
|
|
144
|
+
const walk = (params) => {
|
|
145
|
+
// Walks a query object and wraps the resulting attributes in a
|
|
146
|
+
// sub-array so the IMAP compiler emits parentheses around them.
|
|
147
|
+
// Used when a single search-key is required (NOT, OR operands)
|
|
148
|
+
// but the condition has multiple keys (RFC 3501 Section 6.4.4).
|
|
149
|
+
let walkGrouped = (obj) => {
|
|
150
|
+
let startIdx = attributes.length;
|
|
151
|
+
walk(obj);
|
|
152
|
+
let subAttrs = attributes.splice(startIdx);
|
|
153
|
+
attributes.push(subAttrs);
|
|
154
|
+
};
|
|
155
|
+
Object.keys(params || {}).forEach(term => {
|
|
156
|
+
switch (term.toUpperCase()) {
|
|
157
|
+
// Custom sequence range support (non-standard)
|
|
158
|
+
case 'SEQ':
|
|
159
|
+
{
|
|
160
|
+
// Passed through as a SEQUENCE token: the compiler validates the
|
|
161
|
+
// set grammar and throws a coded error. An invalid value used to
|
|
162
|
+
// be dropped silently here, which turned a bad filter into an
|
|
163
|
+
// unrestricted search that matched every message.
|
|
164
|
+
let value = params[term] || params[term] === 0 ? toSequenceValue(params[term]) : '';
|
|
165
|
+
if (value) {
|
|
166
|
+
attributes.push({ type: 'SEQUENCE', value });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
// Boolean flags that support UN- prefixing
|
|
171
|
+
case 'ANSWERED':
|
|
172
|
+
case 'DELETED':
|
|
173
|
+
case 'DRAFT':
|
|
174
|
+
case 'FLAGGED':
|
|
175
|
+
case 'SEEN':
|
|
176
|
+
case 'UNANSWERED':
|
|
177
|
+
case 'UNDELETED':
|
|
178
|
+
case 'UNDRAFT':
|
|
179
|
+
case 'UNFLAGGED':
|
|
180
|
+
case 'UNSEEN':
|
|
181
|
+
// toggles UN-prefix for falsy values
|
|
182
|
+
setBoolOpt(attributes, term, !!params[term]);
|
|
183
|
+
break;
|
|
184
|
+
// Simple boolean flags without UN- support
|
|
185
|
+
case 'ALL':
|
|
186
|
+
if (params[term]) {
|
|
187
|
+
setBoolOpt(attributes, term, true);
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
190
|
+
case 'NEW':
|
|
191
|
+
case 'OLD':
|
|
192
|
+
case 'RECENT':
|
|
193
|
+
if (params[term]) {
|
|
194
|
+
// The \Recent flag and the NEW/OLD/RECENT search keys were
|
|
195
|
+
// removed in IMAP4rev2 (RFC 9051) - a rev2 session would
|
|
196
|
+
// reject the whole search with a tagged BAD, so fail with a
|
|
197
|
+
// descriptive error instead
|
|
198
|
+
if ((0, tools_js_1.isRev2Active)(connection)) {
|
|
199
|
+
let error = new Error(`The "${term.toLowerCase()}" search key does not exist in IMAP4rev2`);
|
|
200
|
+
error.code = 'MissingServerExtension';
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
setBoolOpt(attributes, term, true);
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
// Numeric comparisons
|
|
207
|
+
case 'LARGER':
|
|
208
|
+
case 'SMALLER':
|
|
209
|
+
case 'MODSEQ':
|
|
210
|
+
if (params[term]) {
|
|
211
|
+
setOpt(attributes, term, params[term]);
|
|
212
|
+
}
|
|
213
|
+
break;
|
|
214
|
+
// Text search fields - check for Unicode
|
|
215
|
+
case 'BCC':
|
|
216
|
+
case 'BODY':
|
|
217
|
+
case 'CC':
|
|
218
|
+
case 'FROM':
|
|
219
|
+
case 'SUBJECT':
|
|
220
|
+
case 'TEXT':
|
|
221
|
+
case 'TO':
|
|
222
|
+
if (isUnicodeString(params[term])) {
|
|
223
|
+
hasUnicode = true;
|
|
224
|
+
}
|
|
225
|
+
if (params[term]) {
|
|
226
|
+
setOpt(attributes, term, params[term]);
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
// UID sequences. The key stays an ATOM and only the value is a
|
|
230
|
+
// SEQUENCE token, so the compiler validates the sequence set
|
|
231
|
+
// itself rather than the "UID" keyword in front of it.
|
|
232
|
+
case 'UID':
|
|
233
|
+
if (params[term]) {
|
|
234
|
+
attributes.push({ type: 'ATOM', value: 'UID' });
|
|
235
|
+
attributes.push({ type: 'SEQUENCE', value: toSequenceValue(params[term]) });
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
// Email ID support (OBJECTID or Gmail extension)
|
|
239
|
+
case 'EMAILID':
|
|
240
|
+
if (connection.capabilities.has('OBJECTID')) {
|
|
241
|
+
setOpt(attributes, 'EMAILID', params[term]);
|
|
242
|
+
}
|
|
243
|
+
else if (connection.capabilities.has('X-GM-EXT-1')) {
|
|
244
|
+
// Fallback to Gmail message ID
|
|
245
|
+
setOpt(attributes, 'X-GM-MSGID', params[term]);
|
|
246
|
+
}
|
|
247
|
+
break;
|
|
248
|
+
// Thread ID support (OBJECTID or Gmail extension)
|
|
249
|
+
case 'THREADID':
|
|
250
|
+
if (connection.capabilities.has('OBJECTID')) {
|
|
251
|
+
setOpt(attributes, 'THREADID', params[term]);
|
|
252
|
+
}
|
|
253
|
+
else if (connection.capabilities.has('X-GM-EXT-1')) {
|
|
254
|
+
// Fallback to Gmail thread ID
|
|
255
|
+
setOpt(attributes, 'X-GM-THRID', params[term]);
|
|
256
|
+
}
|
|
257
|
+
break;
|
|
258
|
+
// Gmail raw search
|
|
259
|
+
case 'GMRAW':
|
|
260
|
+
case 'GMAILRAW': // alias for GMRAW
|
|
261
|
+
if (connection.capabilities.has('X-GM-EXT-1')) {
|
|
262
|
+
if (isUnicodeString(params[term])) {
|
|
263
|
+
hasUnicode = true;
|
|
264
|
+
}
|
|
265
|
+
setOpt(attributes, 'X-GM-RAW', params[term]);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
let error = new Error('Server does not support X-GM-EXT-1 extension required for X-GM-RAW');
|
|
269
|
+
error.code = 'MissingServerExtension';
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
break;
|
|
273
|
+
// Gmail label search. Compiles { has, not } into an X-GM-RAW "label:"/"-label:" query
|
|
274
|
+
// since Gmail labels are not a native IMAP SEARCH key. Gmail-only (X-GM-EXT-1).
|
|
275
|
+
case 'LABELS': {
|
|
276
|
+
let labelQuery = params[term];
|
|
277
|
+
if (!labelQuery || typeof labelQuery !== 'object') {
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
// Collapse whitespace/quotes and quote multi-word names so they survive as a single token
|
|
281
|
+
let formatLabel = (name) => {
|
|
282
|
+
let label = (name || '')
|
|
283
|
+
.toString()
|
|
284
|
+
.replace(/[\s"]+/g, ' ')
|
|
285
|
+
.trim();
|
|
286
|
+
return label.indexOf(' ') >= 0 ? `"${label}"` : label;
|
|
287
|
+
};
|
|
288
|
+
let rawParts = [];
|
|
289
|
+
for (let name of [].concat(labelQuery.has || [])) {
|
|
290
|
+
if (name) {
|
|
291
|
+
rawParts.push(`label:${formatLabel(name)}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
for (let name of [].concat(labelQuery.not || [])) {
|
|
295
|
+
if (name) {
|
|
296
|
+
rawParts.push(`-label:${formatLabel(name)}`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// Empty filter is a no-op on any server (do not require the extension)
|
|
300
|
+
if (!rawParts.length) {
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
if (!connection.capabilities.has('X-GM-EXT-1')) {
|
|
304
|
+
let error = new Error('Server does not support X-GM-EXT-1 extension required for label search');
|
|
305
|
+
error.code = 'MissingServerExtension';
|
|
306
|
+
throw error;
|
|
307
|
+
}
|
|
308
|
+
let rawQuery = rawParts.join(' ');
|
|
309
|
+
if (isUnicodeString(rawQuery)) {
|
|
310
|
+
hasUnicode = true;
|
|
311
|
+
}
|
|
312
|
+
setOpt(attributes, 'X-GM-RAW', rawQuery);
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
// Date searches with WITHIN extension support
|
|
316
|
+
case 'BEFORE':
|
|
317
|
+
case 'SINCE':
|
|
318
|
+
{
|
|
319
|
+
// Normalize above the capability check so the WITHIN shortcut
|
|
320
|
+
// and the standard path agree on what counts as a usable date
|
|
321
|
+
let value = (0, tools_js_1.toValidDate)(params[term]);
|
|
322
|
+
if (!value) {
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
// Use WITHIN extension for better timezone handling if available
|
|
326
|
+
if (connection.capabilities.has('WITHIN')) {
|
|
327
|
+
// Convert to seconds ago from now
|
|
328
|
+
const now = Date.now();
|
|
329
|
+
const withinSeconds = Math.round(Math.max(0, now - value.getTime()) / 1000);
|
|
330
|
+
const withinKeyword = term.toUpperCase() === 'BEFORE' ? 'OLDER' : 'YOUNGER';
|
|
331
|
+
setOpt(attributes, withinKeyword, withinSeconds.toString());
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
// Fallback to standard date search
|
|
335
|
+
processDateField(attributes, term, value);
|
|
336
|
+
}
|
|
337
|
+
break;
|
|
338
|
+
// Standard date searches
|
|
339
|
+
case 'ON':
|
|
340
|
+
case 'SENTBEFORE':
|
|
341
|
+
case 'SENTON':
|
|
342
|
+
case 'SENTSINCE':
|
|
343
|
+
processDateField(attributes, term, params[term]);
|
|
344
|
+
break;
|
|
345
|
+
// Keyword/flag searches
|
|
346
|
+
case 'KEYWORD':
|
|
347
|
+
case 'UNKEYWORD':
|
|
348
|
+
{
|
|
349
|
+
let flag = (0, tools_js_1.formatFlag)(params[term]);
|
|
350
|
+
// Only add if flag is supported or already exists in mailbox
|
|
351
|
+
if ((0, tools_js_1.canUseFlag)(mailbox, flag) || mailbox.flags.has(flag)) {
|
|
352
|
+
setOpt(attributes, term, flag);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
break;
|
|
356
|
+
// Header field searches
|
|
357
|
+
case 'HEADER':
|
|
358
|
+
if (params[term] && typeof params[term] === 'object') {
|
|
359
|
+
Object.keys(params[term]).forEach(header => {
|
|
360
|
+
let value = params[term][header];
|
|
361
|
+
// Allow boolean true to search for header existence
|
|
362
|
+
if (value === true) {
|
|
363
|
+
value = '';
|
|
364
|
+
}
|
|
365
|
+
// Skip non-string values (after true->'' conversion)
|
|
366
|
+
if (typeof value !== 'string') {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (isUnicodeString(value)) {
|
|
370
|
+
hasUnicode = true;
|
|
371
|
+
}
|
|
372
|
+
setOpt(attributes, term, [header.toUpperCase().trim(), value]);
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
break;
|
|
376
|
+
// NOT operator
|
|
377
|
+
case 'NOT':
|
|
378
|
+
if (params[term] && typeof params[term] === 'object') {
|
|
379
|
+
attributes.push({ type: 'ATOM', value: 'NOT' });
|
|
380
|
+
if (Object.keys(params[term]).length > 1) {
|
|
381
|
+
walkGrouped(params[term]);
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
walk(params[term]);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
break;
|
|
388
|
+
// OR operator - complex logic for building OR trees
|
|
389
|
+
case 'OR':
|
|
390
|
+
{
|
|
391
|
+
if (!params[term] || !Array.isArray(params[term]) || !params[term].length) {
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
// Single element - just process it directly
|
|
395
|
+
if (params[term].length === 1) {
|
|
396
|
+
if (typeof params[term][0] === 'object' && params[term][0]) {
|
|
397
|
+
walk(params[term][0]);
|
|
398
|
+
}
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Generates a binary tree structure for OR operations.
|
|
403
|
+
* IMAP OR takes exactly 2 operands, so we need to nest them.
|
|
404
|
+
*
|
|
405
|
+
* @param list - List of conditions to OR together
|
|
406
|
+
* @returns Binary tree structure
|
|
407
|
+
*/
|
|
408
|
+
let genOrTree = (list) => {
|
|
409
|
+
let group = false;
|
|
410
|
+
let groups = [];
|
|
411
|
+
// Group items in pairs
|
|
412
|
+
list.forEach((entry, i) => {
|
|
413
|
+
if (i % 2 === 0) {
|
|
414
|
+
group = [entry];
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
group.push(entry);
|
|
418
|
+
groups.push(group);
|
|
419
|
+
group = false;
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
// Handle odd number of items
|
|
423
|
+
if (group && group.length) {
|
|
424
|
+
while (group.length === 1 && Array.isArray(group[0])) {
|
|
425
|
+
group = group[0];
|
|
426
|
+
}
|
|
427
|
+
groups.push(group);
|
|
428
|
+
}
|
|
429
|
+
// Recursively group until we have a binary tree
|
|
430
|
+
while (groups.length > 2) {
|
|
431
|
+
groups = genOrTree(groups);
|
|
432
|
+
}
|
|
433
|
+
// Flatten single-element arrays
|
|
434
|
+
while (groups.length === 1 && Array.isArray(groups[0])) {
|
|
435
|
+
groups = groups[0];
|
|
436
|
+
}
|
|
437
|
+
return groups;
|
|
438
|
+
};
|
|
439
|
+
/**
|
|
440
|
+
* Walks the OR tree and generates IMAP commands.
|
|
441
|
+
* @param entry - Tree node to process
|
|
442
|
+
*/
|
|
443
|
+
let walkOrTree = (entry) => {
|
|
444
|
+
if (Array.isArray(entry)) {
|
|
445
|
+
if (entry.length > 1) {
|
|
446
|
+
attributes.push({ type: 'ATOM', value: 'OR' });
|
|
447
|
+
}
|
|
448
|
+
entry.forEach(walkOrTree);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (entry && typeof entry === 'object') {
|
|
452
|
+
if (Object.keys(entry).length > 1) {
|
|
453
|
+
walkGrouped(entry);
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
walk(entry);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
walkOrTree(genOrTree(params[term]));
|
|
461
|
+
}
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
};
|
|
466
|
+
// Process the query
|
|
467
|
+
walk(query);
|
|
468
|
+
// If we encountered Unicode strings and UTF-8 is not already accepted,
|
|
469
|
+
// prepend CHARSET UTF-8 to the search command
|
|
470
|
+
if (hasUnicode && !connection.enabled.has('UTF8=ACCEPT')) {
|
|
471
|
+
attributes.unshift({ type: 'ATOM', value: 'UTF-8' });
|
|
472
|
+
attributes.unshift({ type: 'ATOM', value: 'CHARSET' });
|
|
473
|
+
}
|
|
474
|
+
return attributes;
|
|
475
|
+
};
|
|
476
|
+
exports.searchCompiler = searchCompiler;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const flags: string[];
|
|
2
|
+
export declare const names: {
|
|
3
|
+
[flag: string]: string[];
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* The parts of a listed folder that special-use detection reads
|
|
7
|
+
*/
|
|
8
|
+
export interface SpecialUseFolder {
|
|
9
|
+
/** Flags reported for the folder by LIST */
|
|
10
|
+
flags: Set<string>;
|
|
11
|
+
/** Folder name, the last path component */
|
|
12
|
+
name: string;
|
|
13
|
+
}
|
|
14
|
+
/** How a special-use flag was determined */
|
|
15
|
+
export type SpecialUseSource = 'extension' | 'name' | 'name-guess';
|
|
16
|
+
export interface SpecialUseResult {
|
|
17
|
+
/** The special-use flag, or null when the folder is not a special-use folder */
|
|
18
|
+
flag: string | null;
|
|
19
|
+
/** Set when a flag was found */
|
|
20
|
+
source?: SpecialUseSource | undefined;
|
|
21
|
+
}
|
|
22
|
+
export declare const specialUse: (hasSpecialUseExtension: boolean, folder: SpecialUseFolder) => SpecialUseResult;
|