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