imapflow 1.7.7 → 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.
Files changed (296) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +8 -2
  3. package/dist/cjs/charsets.d.ts +1 -0
  4. package/dist/cjs/charsets.js +294 -0
  5. package/dist/cjs/commands/append.d.ts +22 -0
  6. package/dist/cjs/commands/append.js +151 -0
  7. package/dist/cjs/commands/authenticate.d.ts +24 -0
  8. package/dist/cjs/commands/authenticate.js +223 -0
  9. package/dist/cjs/commands/capability.d.ts +8 -0
  10. package/dist/cjs/commands/capability.js +32 -0
  11. package/dist/cjs/commands/close.d.ts +8 -0
  12. package/dist/cjs/commands/close.js +39 -0
  13. package/dist/cjs/commands/compress.d.ts +8 -0
  14. package/dist/cjs/commands/compress.js +56 -0
  15. package/dist/cjs/commands/copy.d.ts +13 -0
  16. package/dist/cjs/commands/copy.js +44 -0
  17. package/dist/cjs/commands/copyuid-parser.d.ts +11 -0
  18. package/dist/cjs/commands/copyuid-parser.js +32 -0
  19. package/dist/cjs/commands/create.d.ts +11 -0
  20. package/dist/cjs/commands/create.js +80 -0
  21. package/dist/cjs/commands/delete.d.ts +11 -0
  22. package/dist/cjs/commands/delete.js +40 -0
  23. package/dist/cjs/commands/enable.d.ts +9 -0
  24. package/dist/cjs/commands/enable.js +61 -0
  25. package/dist/cjs/commands/esearch-parser.d.ts +17 -0
  26. package/dist/cjs/commands/esearch-parser.js +91 -0
  27. package/dist/cjs/commands/expunge.d.ts +12 -0
  28. package/dist/cjs/commands/expunge.js +60 -0
  29. package/dist/cjs/commands/fetch.d.ts +30 -0
  30. package/dist/cjs/commands/fetch.js +241 -0
  31. package/dist/cjs/commands/id.d.ts +10 -0
  32. package/dist/cjs/commands/id.js +80 -0
  33. package/dist/cjs/commands/idle.d.ts +9 -0
  34. package/dist/cjs/commands/idle.js +347 -0
  35. package/dist/cjs/commands/list.d.ts +16 -0
  36. package/dist/cjs/commands/list.js +518 -0
  37. package/dist/cjs/commands/login.d.ts +11 -0
  38. package/dist/cjs/commands/login.js +42 -0
  39. package/dist/cjs/commands/logout.d.ts +8 -0
  40. package/dist/cjs/commands/logout.js +47 -0
  41. package/dist/cjs/commands/move.d.ts +13 -0
  42. package/dist/cjs/commands/move.js +57 -0
  43. package/dist/cjs/commands/namespace.d.ts +25 -0
  44. package/dist/cjs/commands/namespace.js +139 -0
  45. package/dist/cjs/commands/noop.d.ts +8 -0
  46. package/dist/cjs/commands/noop.js +22 -0
  47. package/dist/cjs/commands/quota.d.ts +10 -0
  48. package/dist/cjs/commands/quota.js +119 -0
  49. package/dist/cjs/commands/rename.d.ts +12 -0
  50. package/dist/cjs/commands/rename.js +48 -0
  51. package/dist/cjs/commands/search.d.ts +15 -0
  52. package/dist/cjs/commands/search.js +228 -0
  53. package/dist/cjs/commands/select.d.ts +25 -0
  54. package/dist/cjs/commands/select.js +250 -0
  55. package/dist/cjs/commands/starttls.d.ts +8 -0
  56. package/dist/cjs/commands/starttls.js +30 -0
  57. package/dist/cjs/commands/status-fields.d.ts +14 -0
  58. package/dist/cjs/commands/status-fields.js +61 -0
  59. package/dist/cjs/commands/status.d.ts +12 -0
  60. package/dist/cjs/commands/status.js +108 -0
  61. package/dist/cjs/commands/store.d.ts +19 -0
  62. package/dist/cjs/commands/store.js +93 -0
  63. package/dist/cjs/commands/subscribe.d.ts +9 -0
  64. package/dist/cjs/commands/subscribe.js +31 -0
  65. package/dist/cjs/commands/unsubscribe.d.ts +9 -0
  66. package/dist/cjs/commands/unsubscribe.js +31 -0
  67. package/dist/cjs/connection-deadline.d.ts +49 -0
  68. package/dist/cjs/connection-deadline.js +91 -0
  69. package/dist/cjs/errors.d.ts +83 -0
  70. package/dist/cjs/errors.js +13 -0
  71. package/dist/cjs/handler/imap-compiler.d.ts +24 -0
  72. package/dist/cjs/handler/imap-compiler.js +285 -0
  73. package/dist/cjs/handler/imap-formal-syntax.d.ts +28 -0
  74. package/dist/cjs/handler/imap-formal-syntax.js +121 -0
  75. package/dist/cjs/handler/imap-handler.d.ts +9 -0
  76. package/dist/cjs/handler/imap-handler.js +10 -0
  77. package/dist/cjs/handler/imap-parser.d.ts +16 -0
  78. package/dist/cjs/handler/imap-parser.js +90 -0
  79. package/dist/cjs/handler/imap-stream.d.ts +181 -0
  80. package/dist/cjs/handler/imap-stream.js +446 -0
  81. package/dist/cjs/handler/limits.d.ts +25 -0
  82. package/dist/cjs/handler/limits.js +51 -0
  83. package/dist/cjs/handler/parser-instance.d.ts +68 -0
  84. package/dist/cjs/handler/parser-instance.js +223 -0
  85. package/dist/cjs/handler/token-parser.d.ts +91 -0
  86. package/dist/cjs/handler/token-parser.js +673 -0
  87. package/dist/cjs/handler/types.d.ts +91 -0
  88. package/dist/cjs/handler/types.js +4 -0
  89. package/dist/cjs/imap-commands.d.ts +16 -0
  90. package/dist/cjs/imap-commands.js +74 -0
  91. package/dist/cjs/imap-flow.d.ts +676 -0
  92. package/dist/cjs/imap-flow.js +3949 -0
  93. package/dist/cjs/jp-decoder.d.ts +12 -0
  94. package/dist/cjs/jp-decoder.js +79 -0
  95. package/dist/cjs/limited-passthrough.d.ts +25 -0
  96. package/dist/cjs/limited-passthrough.js +54 -0
  97. package/dist/cjs/logger.d.ts +3 -0
  98. package/dist/cjs/logger.js +11 -0
  99. package/dist/cjs/package-info.d.ts +3 -0
  100. package/dist/cjs/package-info.js +7 -0
  101. package/dist/cjs/package.json +3 -0
  102. package/dist/cjs/proxy-connection.d.ts +33 -0
  103. package/dist/cjs/proxy-connection.js +392 -0
  104. package/dist/cjs/search-compiler.d.ts +34 -0
  105. package/dist/cjs/search-compiler.js +476 -0
  106. package/dist/cjs/special-use.d.ts +22 -0
  107. package/dist/cjs/special-use.js +911 -0
  108. package/dist/cjs/tools.d.ts +427 -0
  109. package/dist/cjs/tools.js +1496 -0
  110. package/{lib/imap-flow.d.ts → dist/cjs/types.d.ts} +386 -516
  111. package/dist/cjs/types.js +5 -0
  112. package/dist/esm/charsets.d.ts +1 -0
  113. package/{lib → dist/esm}/charsets.js +1 -6
  114. package/dist/esm/commands/append.d.ts +22 -0
  115. package/{lib → dist/esm}/commands/append.js +22 -52
  116. package/dist/esm/commands/authenticate.d.ts +24 -0
  117. package/{lib → dist/esm}/commands/authenticate.js +62 -87
  118. package/dist/esm/commands/capability.d.ts +8 -0
  119. package/{lib → dist/esm}/commands/capability.js +6 -9
  120. package/dist/esm/commands/close.d.ts +8 -0
  121. package/{lib → dist/esm}/commands/close.js +6 -10
  122. package/dist/esm/commands/compress.d.ts +8 -0
  123. package/{lib → dist/esm}/commands/compress.js +7 -11
  124. package/dist/esm/commands/copy.d.ts +13 -0
  125. package/{lib → dist/esm}/commands/copy.js +12 -20
  126. package/dist/esm/commands/copyuid-parser.d.ts +11 -0
  127. package/{lib → dist/esm}/commands/copyuid-parser.js +9 -15
  128. package/dist/esm/commands/create.d.ts +11 -0
  129. package/{lib → dist/esm}/commands/create.js +13 -27
  130. package/dist/esm/commands/delete.d.ts +11 -0
  131. package/{lib → dist/esm}/commands/delete.js +9 -14
  132. package/dist/esm/commands/enable.d.ts +9 -0
  133. package/{lib → dist/esm}/commands/enable.js +23 -30
  134. package/dist/esm/commands/esearch-parser.d.ts +17 -0
  135. package/dist/esm/commands/esearch-parser.js +88 -0
  136. package/dist/esm/commands/expunge.d.ts +12 -0
  137. package/{lib → dist/esm}/commands/expunge.js +17 -22
  138. package/dist/esm/commands/fetch.d.ts +30 -0
  139. package/{lib → dist/esm}/commands/fetch.js +32 -64
  140. package/dist/esm/commands/id.d.ts +10 -0
  141. package/{lib → dist/esm}/commands/id.js +17 -23
  142. package/dist/esm/commands/idle.d.ts +9 -0
  143. package/{lib → dist/esm}/commands/idle.js +47 -81
  144. package/dist/esm/commands/list.d.ts +16 -0
  145. package/{lib → dist/esm}/commands/list.js +56 -121
  146. package/dist/esm/commands/login.d.ts +11 -0
  147. package/{lib → dist/esm}/commands/login.js +10 -15
  148. package/dist/esm/commands/logout.d.ts +8 -0
  149. package/{lib → dist/esm}/commands/logout.js +9 -11
  150. package/dist/esm/commands/move.d.ts +13 -0
  151. package/{lib → dist/esm}/commands/move.js +13 -21
  152. package/dist/esm/commands/namespace.d.ts +25 -0
  153. package/{lib → dist/esm}/commands/namespace.js +34 -44
  154. package/dist/esm/commands/noop.d.ts +8 -0
  155. package/{lib → dist/esm}/commands/noop.js +6 -7
  156. package/dist/esm/commands/quota.d.ts +10 -0
  157. package/{lib → dist/esm}/commands/quota.js +18 -36
  158. package/dist/esm/commands/rename.d.ts +12 -0
  159. package/{lib → dist/esm}/commands/rename.js +10 -15
  160. package/dist/esm/commands/search.d.ts +15 -0
  161. package/{lib → dist/esm}/commands/search.js +36 -135
  162. package/dist/esm/commands/select.d.ts +25 -0
  163. package/{lib → dist/esm}/commands/select.js +33 -64
  164. package/dist/esm/commands/starttls.d.ts +8 -0
  165. package/{lib → dist/esm}/commands/starttls.js +6 -8
  166. package/dist/esm/commands/status-fields.d.ts +14 -0
  167. package/{lib → dist/esm}/commands/status-fields.js +5 -16
  168. package/dist/esm/commands/status.d.ts +12 -0
  169. package/{lib → dist/esm}/commands/status.js +18 -29
  170. package/dist/esm/commands/store.d.ts +19 -0
  171. package/{lib → dist/esm}/commands/store.js +24 -37
  172. package/dist/esm/commands/subscribe.d.ts +9 -0
  173. package/{lib → dist/esm}/commands/subscribe.js +8 -12
  174. package/dist/esm/commands/unsubscribe.d.ts +9 -0
  175. package/{lib → dist/esm}/commands/unsubscribe.js +8 -12
  176. package/dist/esm/connection-deadline.d.ts +49 -0
  177. package/{lib → dist/esm}/connection-deadline.js +14 -25
  178. package/dist/esm/errors.d.ts +83 -0
  179. package/dist/esm/errors.js +9 -0
  180. package/dist/esm/handler/imap-compiler.d.ts +24 -0
  181. package/{lib → dist/esm}/handler/imap-compiler.js +22 -80
  182. package/dist/esm/handler/imap-formal-syntax.d.ts +28 -0
  183. package/dist/esm/handler/imap-formal-syntax.js +117 -0
  184. package/dist/esm/handler/imap-handler.d.ts +9 -0
  185. package/dist/esm/handler/imap-handler.js +9 -0
  186. package/dist/esm/handler/imap-parser.d.ts +16 -0
  187. package/{lib → dist/esm}/handler/imap-parser.js +31 -44
  188. package/dist/esm/handler/imap-stream.d.ts +181 -0
  189. package/{lib → dist/esm}/handler/imap-stream.js +29 -121
  190. package/dist/esm/handler/limits.d.ts +25 -0
  191. package/{lib → dist/esm}/handler/limits.js +13 -22
  192. package/dist/esm/handler/parser-instance.d.ts +68 -0
  193. package/{lib → dist/esm}/handler/parser-instance.js +19 -47
  194. package/dist/esm/handler/token-parser.d.ts +91 -0
  195. package/{lib → dist/esm}/handler/token-parser.js +71 -155
  196. package/dist/esm/handler/types.d.ts +91 -0
  197. package/dist/esm/handler/types.js +3 -0
  198. package/dist/esm/imap-commands.d.ts +16 -0
  199. package/dist/esm/imap-commands.js +67 -0
  200. package/dist/esm/imap-flow.d.ts +676 -0
  201. package/{lib → dist/esm}/imap-flow.js +785 -1802
  202. package/dist/esm/jp-decoder.d.ts +12 -0
  203. package/{lib → dist/esm}/jp-decoder.js +6 -21
  204. package/dist/esm/limited-passthrough.d.ts +25 -0
  205. package/{lib → dist/esm}/limited-passthrough.js +7 -20
  206. package/dist/esm/logger.d.ts +3 -0
  207. package/dist/esm/logger.js +4 -0
  208. package/dist/esm/package-info.d.ts +3 -0
  209. package/dist/esm/package-info.js +4 -0
  210. package/dist/esm/package.json +3 -0
  211. package/dist/esm/proxy-connection.d.ts +33 -0
  212. package/{lib → dist/esm}/proxy-connection.js +56 -127
  213. package/dist/esm/search-compiler.d.ts +34 -0
  214. package/{lib → dist/esm}/search-compiler.js +54 -110
  215. package/dist/esm/special-use.d.ts +22 -0
  216. package/dist/esm/special-use.js +907 -0
  217. package/dist/esm/tools.d.ts +427 -0
  218. package/dist/esm/tools.js +1446 -0
  219. package/dist/esm/types.d.ts +828 -0
  220. package/dist/esm/types.js +4 -0
  221. package/package.json +60 -20
  222. package/.gitattributes +0 -1
  223. package/.github/CODE_OF_CONDUCT.md +0 -76
  224. package/.github/FUNDING.yml +0 -4
  225. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
  226. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  227. package/.github/contributing.md +0 -17
  228. package/.github/workflows/release.yaml +0 -36
  229. package/.github/workflows/stale.yml +0 -29
  230. package/.github/workflows/test.yml +0 -51
  231. package/.ncurc.js +0 -4
  232. package/.prettierignore +0 -4
  233. package/.prettierrc.js +0 -8
  234. package/.release-please-manifest.json +0 -3
  235. package/CLAUDE.md +0 -104
  236. package/Gruntfile.js +0 -23
  237. package/eslint.config.js +0 -45
  238. package/lib/handler/imap-formal-syntax.js +0 -189
  239. package/lib/handler/imap-handler.js +0 -17
  240. package/lib/imap-commands.js +0 -45
  241. package/lib/logger.js +0 -5
  242. package/lib/special-use.js +0 -923
  243. package/lib/tools.js +0 -1612
  244. package/release-please-config.json +0 -10
  245. package/test/authentication-test.js +0 -101
  246. package/test/auto-idle-test.js +0 -470
  247. package/test/bodystructure-test.js +0 -899
  248. package/test/charsets-test.js +0 -161
  249. package/test/commands-branches-test.js +0 -1095
  250. package/test/commands-integration-test.js +0 -11124
  251. package/test/commands-test.js +0 -73
  252. package/test/connection-edge-cases-test.js +0 -1828
  253. package/test/connection-test.js +0 -162
  254. package/test/copyuid-parser-test.js +0 -173
  255. package/test/fetch-generator-test.js +0 -218
  256. package/test/fixtures/fake-timers.js +0 -115
  257. package/test/fixtures/serialized-mimetorture.js +0 -2738
  258. package/test/fixtures/test-client.js +0 -57
  259. package/test/fixtures/test-tls.js +0 -8
  260. package/test/handler-branches-test.js +0 -310
  261. package/test/idle-polling-test.js +0 -518
  262. package/test/imap-compiler-test.js +0 -809
  263. package/test/imap-flow-compress-test.js +0 -166
  264. package/test/imap-flow-coverage-test.js +0 -612
  265. package/test/imap-flow-fetch-download-test.js +0 -873
  266. package/test/imap-flow-internals-test.js +0 -725
  267. package/test/imap-flow-methods-test.js +0 -889
  268. package/test/imap-flow-proxy-paths-test.js +0 -366
  269. package/test/imap-flow-secure-test.js +0 -573
  270. package/test/imap-flow-server-test.js +0 -1474
  271. package/test/imap-formal-syntax-test.js +0 -293
  272. package/test/imap-parser-test.js +0 -1474
  273. package/test/imap-stream-edge-cases-test.js +0 -666
  274. package/test/imap-stream-test.js +0 -177
  275. package/test/imapflow-test.js +0 -258
  276. package/test/integration/README.md +0 -52
  277. package/test/integration/dovecot-test.conf +0 -27
  278. package/test/integration/rev2-live-test.js +0 -431
  279. package/test/integration/run-rev2-tests.sh +0 -75
  280. package/test/integration-test.js +0 -83
  281. package/test/jp-decoder-test.js +0 -304
  282. package/test/limited-passthrough-test.js +0 -299
  283. package/test/memory-cleanup-test.js +0 -144
  284. package/test/memory-leak-test.js +0 -667
  285. package/test/parser-limits-test.js +0 -292
  286. package/test/proxy-connection-test.js +0 -738
  287. package/test/reliability-improvements-test.js +0 -548
  288. package/test/search-compiler-test.js +0 -1300
  289. package/test/search-test.js +0 -329
  290. package/test/special-use-test.js +0 -418
  291. package/test/starttls-injection-test.js +0 -181
  292. package/test/tag-correlation-test.js +0 -333
  293. package/test/timer-policy-test.js +0 -227
  294. package/test/token-parser-test.js +0 -456
  295. package/test/tools-test.js +0 -2013
  296. package/test/unhandled-rejection-test.js +0 -593
@@ -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;