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.
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 +520 -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 +3956 -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} +387 -517
  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 +60 -123
  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 +769 -1790
  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 -101
  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 -909
  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 -661
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = search;
4
+ const tools_js_1 = require("../tools.js");
5
+ const search_compiler_js_1 = require("../search-compiler.js");
6
+ const esearch_parser_js_1 = require("./esearch-parser.js");
7
+ /**
8
+ * Strips the leading (TAG "X") correlator list and the optional UID atom from an
9
+ * ESEARCH untagged response, leaving only the result keyword/value pairs.
10
+ * The IMAP parser represents parenthesized groups as plain Arrays, not objects
11
+ * with type: 'LIST'.
12
+ *
13
+ * @param attrs - Raw attribute array from the IMAP parser
14
+ * @returns Attribute array starting at the first result keyword
15
+ */
16
+ const stripEsearchPrefix = (attrs) => {
17
+ let start = 0;
18
+ if (attrs[start] && Array.isArray(attrs[start]))
19
+ start++;
20
+ if (attrs[start] && typeof attrs[start].value === 'string' && attrs[start].value.toUpperCase() === 'UID')
21
+ start++;
22
+ return attrs.slice(start);
23
+ };
24
+ /**
25
+ * Searches for messages matching the specified criteria.
26
+ *
27
+ * @param connection - IMAP connection instance
28
+ * @param query - Search query object, or true/empty object to match all messages
29
+ * @param options - Search options
30
+ * @param options.uid - If true, use UID SEARCH instead of SEARCH
31
+ * @param options.returnOptions - ESEARCH RETURN options. When present AND the
32
+ * server advertises ESEARCH capability, triggers ESEARCH and returns an ESearchResult.
33
+ * Items are strings ('MIN','MAX','COUNT','ALL') or objects ({ partial: '1:100' }).
34
+ * When server lacks ESEARCH, falls back to plain SEARCH and returns number[].
35
+ */
36
+ async function search(connection, query, options) {
37
+ if (connection.state !== connection.states.SELECTED) {
38
+ // nothing to do here
39
+ return false;
40
+ }
41
+ options = options || {};
42
+ let attributes;
43
+ // Three query branches:
44
+ // 1. Empty/truthy/all-only query -> use IMAP "SEARCH ALL" to match every message
45
+ // 2. Non-empty object -> compile into IMAP SEARCH criteria via searchCompiler
46
+ // 3. Anything else (unexpected type) -> bail out with false
47
+ if (!query || query === true || (typeof query === 'object' && (!Object.keys(query).length || (Object.keys(query).length === 1 && query.all)))) {
48
+ // search for all messages
49
+ attributes = [{ type: 'ATOM', value: 'ALL' }];
50
+ }
51
+ else if (query && typeof query === 'object') {
52
+ // normal query
53
+ attributes = (0, search_compiler_js_1.searchCompiler)(connection, query);
54
+ }
55
+ else {
56
+ return false;
57
+ }
58
+ // ESEARCH is part of base IMAP4rev2
59
+ const useEsearch = options.returnOptions && options.returnOptions.length > 0 && (0, tools_js_1.hasCapability)(connection, 'ESEARCH');
60
+ if (useEsearch) {
61
+ // Build RETURN (...) item list
62
+ const returnItems = [];
63
+ for (const opt of options.returnOptions) {
64
+ if (typeof opt === 'string') {
65
+ returnItems.push({ type: 'ATOM', value: opt.toUpperCase() });
66
+ }
67
+ else if (opt && typeof opt.partial === 'string') {
68
+ // RFC 9394: PARTIAL is an atom followed by the range atom, both inside RETURN (...)
69
+ returnItems.push({ type: 'ATOM', value: 'PARTIAL' });
70
+ returnItems.push({ type: 'ATOM', value: opt.partial });
71
+ }
72
+ }
73
+ // If all returnOptions entries were invalid (e.g. objects lacking a string
74
+ // `partial` field), returnItems would be empty. Emitting "RETURN ()" is
75
+ // technically valid per RFC 4731 but returns nothing useful. Fall through
76
+ // to the legacy SEARCH path instead so the caller gets a usable result.
77
+ if (returnItems.length > 0) {
78
+ const returnClause = [{ type: 'ATOM', value: 'RETURN' }, returnItems];
79
+ let esearchResult = {};
80
+ let response;
81
+ try {
82
+ response = await connection.exec(options.uid ? 'UID SEARCH' : 'SEARCH', [...returnClause, ...attributes], {
83
+ untagged: {
84
+ ESEARCH: async (untagged) => {
85
+ if (!untagged || !untagged.attributes)
86
+ return;
87
+ esearchResult = (0, esearch_parser_js_1.parseEsearchResponse)(stripEsearchPrefix(untagged.attributes));
88
+ }
89
+ }
90
+ });
91
+ response.next();
92
+ return esearchResult;
93
+ }
94
+ catch (err) {
95
+ await (0, tools_js_1.enhanceCommandError)(err);
96
+ connection.log.warn({ err, cid: connection.id });
97
+ return false;
98
+ }
99
+ }
100
+ // returnItems was empty - fall through to legacy SEARCH path below
101
+ }
102
+ // Legacy SEARCH path (no returnOptions, or server lacks ESEARCH)
103
+ // Use a Set to deduplicate sequence numbers/UIDs - servers may return
104
+ // duplicates across multiple untagged SEARCH responses.
105
+ let results = new Set();
106
+ let response;
107
+ try {
108
+ response = await connection.exec(options.uid ? 'UID SEARCH' : 'SEARCH', attributes, {
109
+ untagged: {
110
+ SEARCH: async (untagged) => {
111
+ if (untagged && untagged.attributes && untagged.attributes.length) {
112
+ let truncated = false;
113
+ let discarded = false;
114
+ for (let attribute of untagged.attributes) {
115
+ // The result set is server-controlled and accumulated across
116
+ // responses, so stop at the same absolute ceiling expandRange()
117
+ // uses - a server streaming SEARCH responses could otherwise
118
+ // grow the set until the process runs out of memory
119
+ /* c8 ignore next 4 */ // reaching the ceiling needs 2^24 accumulated results, which no unit test can produce in reasonable time
120
+ if (results.size >= tools_js_1.EXPANDED_RANGE_LIMIT) {
121
+ truncated = true;
122
+ break;
123
+ }
124
+ // Same nz-number check the ESEARCH branch below applies. isNaN()
125
+ // is not enough: it passes '1e400' (Infinity), '-3' and '2.5', and
126
+ // a single one of those makes the sequence set compiled from this
127
+ // result set invalid, failing the caller's whole follow-up command
128
+ let value = attribute && typeof attribute.value === 'string' ? Number(attribute.value) : NaN;
129
+ if (!(0, tools_js_1.isValidSequenceValue)(value)) {
130
+ discarded = true;
131
+ continue;
132
+ }
133
+ results.add(value);
134
+ }
135
+ if (truncated || discarded) {
136
+ connection.log.warn({
137
+ msg: 'Invalid entries in the SEARCH result',
138
+ truncated,
139
+ discarded,
140
+ cid: connection.id
141
+ });
142
+ }
143
+ }
144
+ },
145
+ // IMAP4rev2 servers answer even a plain SEARCH with an untagged
146
+ // ESEARCH response (RFC 9051 deprecated the SEARCH response), so
147
+ // both forms are collected into the same result set
148
+ ESEARCH: async (untagged) => {
149
+ if (!untagged || !untagged.attributes) {
150
+ return;
151
+ }
152
+ let parsed = (0, esearch_parser_js_1.parseEsearchResponse)(stripEsearchPrefix(untagged.attributes));
153
+ if (parsed.all) {
154
+ // Walk the compact sequence-set directly into the Set - the ALL
155
+ // result may cover the entire mailbox, so expanding it into an
156
+ // intermediate array first would double the peak memory use.
157
+ // The set comes from an untrusted server: endpoints must be
158
+ // valid nz-numbers ('Infinity' would otherwise loop forever)
159
+ // and the expansion stops at the mailbox EXISTS count - a
160
+ // conforming server cannot match more messages than exist, so
161
+ // a hostile range like 1:4294967295 cannot exhaust memory.
162
+ // A '*' means "largest number in use": that is exactly EXISTS
163
+ // for message sequence numbers, while server-sent UID sets may
164
+ // not contain '*' at all (RFC 9051 section 4.1.1), so UID
165
+ // parts with '*' are dropped
166
+ let existsCount = () => (connection.mailbox && connection.mailbox.exists) || 0;
167
+ // The mailbox EXISTS count is itself server-supplied and can be
168
+ // absurdly large, so the budget is additionally capped at the same
169
+ // absolute ceiling expandRange() uses - a hostile server cannot
170
+ // bypass it by inflating EXISTS first
171
+ let overBudget = () => results.size >= existsCount() || results.size >= tools_js_1.EXPANDED_RANGE_LIMIT;
172
+ let resolveId = (part) => (part === '*' ? (options.uid ? 0 : existsCount()) : Number(part));
173
+ let truncated = false;
174
+ let discarded = false;
175
+ sequenceSetLoop: for (let part of parsed.all.split(',')) {
176
+ part = part.trim();
177
+ let colon = part.indexOf(':');
178
+ if (colon < 0) {
179
+ let value = resolveId(part);
180
+ if (!(0, tools_js_1.isValidSequenceValue)(value)) {
181
+ discarded = true;
182
+ continue;
183
+ }
184
+ if (overBudget()) {
185
+ truncated = true;
186
+ break;
187
+ }
188
+ results.add(value);
189
+ continue;
190
+ }
191
+ let first = resolveId(part.substr(0, colon));
192
+ let second = resolveId(part.substr(colon + 1));
193
+ if (!(0, tools_js_1.isValidSequenceValue)(first) || !(0, tools_js_1.isValidSequenceValue)(second)) {
194
+ discarded = true;
195
+ continue;
196
+ }
197
+ for (let id = Math.min(first, second); id <= Math.max(first, second); id++) {
198
+ if (overBudget()) {
199
+ truncated = true;
200
+ break sequenceSetLoop;
201
+ }
202
+ results.add(id);
203
+ }
204
+ }
205
+ if (truncated || discarded) {
206
+ connection.log.warn({
207
+ msg: 'Invalid entries in the ESEARCH ALL result',
208
+ truncated,
209
+ discarded,
210
+ cid: connection.id
211
+ });
212
+ }
213
+ }
214
+ }
215
+ }
216
+ });
217
+ response.next();
218
+ // Sort numerically for consistent, predictable output order
219
+ return Array.from(results).sort((a, b) => a - b);
220
+ }
221
+ catch (err) {
222
+ await (0, tools_js_1.enhanceCommandError)(err);
223
+ connection.log.warn({ err, cid: connection.id });
224
+ return false;
225
+ }
226
+ }
227
+ module.exports = exports.default;
228
+ Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
@@ -0,0 +1,25 @@
1
+ import type { ImapFlow } from '../imap-flow.js';
2
+ import type { MailboxObject, MailboxOpenOptions } from '../types.js';
3
+ /**
4
+ * Options for SELECT/EXAMINE: the public open options plus the QRESYNC resynchronization
5
+ * parameters, which are only honored when the QRESYNC extension has been enabled
6
+ */
7
+ export interface SelectOptions extends MailboxOpenOptions {
8
+ /** QRESYNC modseq value to fetch changes since */
9
+ changedSince?: bigint | number | string | undefined;
10
+ /** QRESYNC UID validity value */
11
+ uidValidity?: bigint | number | string | undefined;
12
+ }
13
+ /**
14
+ * Selects or examines a mailbox, making it the current mailbox for subsequent operations.
15
+ *
16
+ * @param connection - IMAP connection instance
17
+ * @param path - Mailbox path to select
18
+ * @param options - Select options
19
+ * @param options.readOnly - If true, use EXAMINE instead of SELECT (read-only access)
20
+ * @param options.changedSince - QRESYNC modseq value to fetch changes since
21
+ * @param options.uidValidity - QRESYNC UID validity value
22
+ * @returns Mailbox info object with path, flags, exists, uidNext, uidValidity, highestModseq, etc., or undefined if preconditions not met
23
+ * @throws If the SELECT/EXAMINE command fails
24
+ */
25
+ export default function select(connection: ImapFlow, pathInput: string | string[], options?: SelectOptions | undefined): Promise<MailboxObject | undefined>;
@@ -0,0 +1,250 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = select;
4
+ const tools_js_1 = require("../tools.js");
5
+ // Response codes carrying a value that SELECT/EXAMINE may write to the mailbox object, keyed by
6
+ // the lowercased code, mapped to the fixed public property name and the parser for the value.
7
+ // Every parser returns false for a value it cannot use, and the field is then left unset.
8
+ //
9
+ // This is an allowlist on purpose. The mailbox object is API surface: without one, an arbitrary
10
+ // server-sent [KEY value] code could overwrite `path` (defeating the DELETE/RENAME guards that
11
+ // compare paths) or `flags`, and a parenthesized value under "__proto__" would replace the
12
+ // object's prototype. The lookup itself is on a null-prototype object for the same reason - the
13
+ // key is lowercased, so "constructor" would otherwise resolve to an inherited member.
14
+ const VALUED_RESPONSE_CODES = Object.assign(Object.create(null), {
15
+ // CONDSTORE (RFC 7162): highest mod-sequence value for the mailbox, used for incremental
16
+ // sync. Stored as a BigInt since modseq values can exceed Number.MAX_SAFE_INTEGER.
17
+ //
18
+ // A value that is not a bounded digit run is dropped rather than stored raw: every consumer
19
+ // compares highestModseq relationally, and a relational compare against a non-numeric string
20
+ // is false in both directions, so the value could never advance and delta sync would stop.
21
+ highestmodseq: { key: 'highestModseq', parse: (value) => (0, tools_js_1.parseBigIntValue)(value) },
22
+ // Unique identifier validity. If this changes between sessions, all previously cached UIDs
23
+ // are invalid and the client must re-sync from scratch. Nominally 32-bit, but stored as a
24
+ // BigInt precisely so a server that exceeds that still round-trips, hence the wider bound.
25
+ uidvalidity: { key: 'uidValidity', parse: (value) => (0, tools_js_1.parseBigIntValue)(value) },
26
+ // The next UID to be assigned in this mailbox, useful for detecting new arrivals. A huge
27
+ // digit run would coerce to Infinity and corrupt every later UID range computation.
28
+ uidnext: { key: 'uidNext', parse: (value) => (0, tools_js_1.parseUintValue)(value, tools_js_1.MAX_UINT32_DIGITS) },
29
+ // Sequence number of the first unseen message (RFC 3501 section 7.1). Not a count of unseen
30
+ // messages - use mailboxStatus() with {unseen: true} for that.
31
+ unseen: { key: 'unseen', parse: (value) => (0, tools_js_1.parseUintValue)(value, tools_js_1.MAX_UINT32_DIGITS) },
32
+ // APPENDLIMIT (RFC 7889): largest message size in octets the server accepts for APPEND into
33
+ // this mailbox. Spelled all lowercase, unlike the camelCase fields around it, because that is
34
+ // the name this object has always exposed.
35
+ appendlimit: { key: 'appendlimit', parse: (value) => (0, tools_js_1.parseUintValue)(value) },
36
+ // OBJECTID (RFC 8474): server-assigned mailbox identifier that survives renames. Sent as a
37
+ // parenthesized list, but servers in the wild send it bare too.
38
+ mailboxid: {
39
+ key: 'mailboxId',
40
+ parse: (value) => (Array.isArray(value) ? value.length > 0 && value[0] : typeof value === 'string' && value)
41
+ },
42
+ // Flags the client may change permanently on messages in this mailbox, including \* if the
43
+ // server allows custom flags. Only the parenthesized form carries flags, and a malformed
44
+ // value must leave permanentFlags unset rather than set an empty Set: canUseFlag() reads
45
+ // unset as permissive and empty as deny-all, so an empty Set would turn every later flag
46
+ // update into a silent no-op for the rest of the session.
47
+ permanentflags: { key: 'permanentFlags', parse: (value) => Array.isArray(value) && new Set(value) }
48
+ });
49
+ /**
50
+ * Selects or examines a mailbox, making it the current mailbox for subsequent operations.
51
+ *
52
+ * @param connection - IMAP connection instance
53
+ * @param path - Mailbox path to select
54
+ * @param options - Select options
55
+ * @param options.readOnly - If true, use EXAMINE instead of SELECT (read-only access)
56
+ * @param options.changedSince - QRESYNC modseq value to fetch changes since
57
+ * @param options.uidValidity - QRESYNC UID validity value
58
+ * @returns Mailbox info object with path, flags, exists, uidNext, uidValidity, highestModseq, etc., or undefined if preconditions not met
59
+ * @throws If the SELECT/EXAMINE command fails
60
+ */
61
+ async function select(connection, pathInput, options) {
62
+ if (![connection.states.AUTHENTICATED, connection.states.SELECTED].includes(connection.state)) {
63
+ // nothing to do here
64
+ return;
65
+ }
66
+ options = options || {};
67
+ let path = (0, tools_js_1.normalizePath)(connection, pathInput);
68
+ // Ensure we have folder metadata (delimiter, flags, specialUse) by running LIST if needed.
69
+ // This is cached in connection.folders to avoid repeated LIST calls.
70
+ // Note: this uses run() rather than runInternal(), so it terminates a running IDLE first,
71
+ // which is what a caller-issued mailboxOpen() needs. Fallback polling reaches SELECT through
72
+ // runInternal(), and on a cache miss this LIST awaits the polling session's own preCheck and
73
+ // so cancels that session. Not a deadlock, and only reachable when the folder is uncached,
74
+ // but a polled SELECT of an unlisted folder ends the poll early.
75
+ if (!connection.folders.has(path)) {
76
+ let folders = await connection.run('LIST', '', path);
77
+ if (!folders) {
78
+ throw new Error('Failed to fetch folders');
79
+ }
80
+ folders.forEach(folder => {
81
+ connection.folders.set(folder.path, folder);
82
+ });
83
+ }
84
+ let folderListData = connection.folders.has(path) ? connection.folders.get(path) : false;
85
+ let response;
86
+ try {
87
+ let map = { path };
88
+ if (folderListData) {
89
+ ['delimiter', 'specialUse', 'subscribed', 'listed'].forEach(key => {
90
+ if (folderListData[key]) {
91
+ map[key] = folderListData[key];
92
+ }
93
+ });
94
+ }
95
+ // QRESYNC (RFC 7162): allows efficient mailbox resynchronization by sending
96
+ // the last known UIDVALIDITY and HIGHESTMODSEQ. Server responds with only
97
+ // the changes (new flags, expunged UIDs) since that point.
98
+ let extraArgs = [];
99
+ if (connection.enabled.has('QRESYNC') && options.changedSince && options.uidValidity) {
100
+ extraArgs.push([
101
+ { type: 'ATOM', value: 'QRESYNC' },
102
+ [
103
+ { type: 'ATOM', value: options.uidValidity?.toString() },
104
+ { type: 'ATOM', value: options.changedSince.toString() }
105
+ ]
106
+ ]);
107
+ map.qresync = true;
108
+ }
109
+ let encodedPath = (0, tools_js_1.encodePath)(connection, path);
110
+ // SELECT opens the mailbox read-write; EXAMINE opens it read-only.
111
+ // Path encoding: if the encoded path contains '&' (UTF-7 encoding marker),
112
+ // send as quoted STRING to avoid parser issues with the ampersand.
113
+ let selectCommand = {
114
+ command: !options.readOnly ? 'SELECT' : 'EXAMINE',
115
+ /* c8 ignore next */ // extraArgs is always initialised to an array, so the [] fallback is unreachable
116
+ arguments: [{ type: encodedPath.indexOf('&') >= 0 ? 'STRING' : 'ATOM', value: encodedPath }].concat(extraArgs || [])
117
+ };
118
+ response = await connection.exec(selectCommand.command, selectCommand.arguments, {
119
+ untagged: {
120
+ // Untagged OK responses carry response codes in brackets, e.g.:
121
+ // * OK [UIDVALIDITY 1234] UIDs valid
122
+ // * OK [PERMANENTFLAGS (\Seen \Answered \*)] Flags permitted
123
+ // The section array holds the parsed bracket contents: section[0] is the
124
+ // key (e.g., "UIDVALIDITY"), section[1] is the value or list.
125
+ OK: async (untagged) => {
126
+ if (!untagged.attributes || !untagged.attributes.length) {
127
+ return;
128
+ }
129
+ let section = !untagged.attributes[0].value && untagged.attributes[0].section;
130
+ // Handle response codes with a key-value pair (section has 2+ elements)
131
+ if (section && section.length > 1 && section[0] && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
132
+ let key = section[0].value.toLowerCase();
133
+ let value;
134
+ // Value can be a single string or a list of strings (e.g., PERMANENTFLAGS).
135
+ // section[1] can be a parsed NIL (null), and so can any element inside a
136
+ // parenthesized list, so both levels need the guard
137
+ if (section[1] && typeof section[1].value === 'string') {
138
+ value = section[1].value;
139
+ }
140
+ else if (Array.isArray(section[1])) {
141
+ value = (0, tools_js_1.getStringList)(section[1]);
142
+ }
143
+ let field = VALUED_RESPONSE_CODES[key];
144
+ if (field) {
145
+ let parsed = field.parse(value);
146
+ if (parsed !== false) {
147
+ map[field.key] = parsed;
148
+ }
149
+ }
150
+ }
151
+ // Handle response codes with only a keyword (no value), e.g., [NOMODSEQ].
152
+ // NOMODSEQ means the mailbox does not support mod-sequences, so the
153
+ // CONDSTORE/QRESYNC features are unavailable for it.
154
+ if (section &&
155
+ section.length === 1 &&
156
+ section[0] &&
157
+ section[0].type === 'ATOM' &&
158
+ section[0].value?.toUpperCase() === 'NOMODSEQ') {
159
+ map.noModseq = true;
160
+ }
161
+ },
162
+ // Untagged FLAGS response lists all flags defined for this mailbox
163
+ // (both system flags and custom flags). Example: * FLAGS (\Seen \Answered \Flagged)
164
+ FLAGS: async (untagged) => {
165
+ if (!untagged.attributes || !untagged.attributes.length || !Array.isArray(untagged.attributes[0])) {
166
+ return;
167
+ }
168
+ map.flags = new Set((0, tools_js_1.getStringList)(untagged.attributes[0]));
169
+ },
170
+ // Untagged EXISTS response: "* <count> EXISTS" tells us the total number
171
+ // of messages in the mailbox. The count is in the command field (numeric prefix).
172
+ EXISTS: async (untagged) => {
173
+ // Not a usable count: anything but a bounded digit run. A long digit run
174
+ // coerces to Infinity, which would corrupt every later range computation
175
+ let num = (0, tools_js_1.parseUintValue)(untagged.command, tools_js_1.MAX_UINT32_DIGITS);
176
+ if (num === false) {
177
+ return false;
178
+ }
179
+ map.exists = num;
180
+ },
181
+ // VANISHED responses (QRESYNC): server reports UIDs that have been expunged
182
+ // since the client's last known state. Only received when QRESYNC was requested.
183
+ // A dummy mailbox object is passed because the mailbox isn't officially open yet.
184
+ VANISHED: async (untagged) => {
185
+ await connection.untaggedVanished(untagged, { path, uidNext: false, uidValidity: false });
186
+ },
187
+ // Untagged FETCH during SELECT/EXAMINE: only occurs with QRESYNC, delivering
188
+ // updated flags for messages that changed since the client's last modseq.
189
+ FETCH: async (untagged) => {
190
+ await connection.untaggedFetch(untagged, { path, uidNext: false, uidValidity: false });
191
+ }
192
+ }
193
+ });
194
+ // The tagged OK response to SELECT/EXAMINE includes [READ-ONLY] or [READ-WRITE]
195
+ // in its response code, indicating the access mode the server granted. A tagged OK
196
+ // with no resp-text has no `attributes` property at all, and unlike the untagged
197
+ // handlers above this runs in the command body, where a throw would tear down the
198
+ // mailbox state the server has actually selected.
199
+ let okAttributes = (response.response && response.response.attributes) || [];
200
+ let section = okAttributes[0] && !okAttributes[0].value && okAttributes[0].section;
201
+ if (section && section.length && section[0] && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
202
+ map.readOnly = section[0].value.toUpperCase() === 'READ-ONLY';
203
+ }
204
+ // Validate QRESYNC preconditions (RFC 7162 Section 3.2.5):
205
+ // QRESYNC results are only valid if UIDVALIDITY matches, HIGHESTMODSEQ is
206
+ // present, and the mailbox supports mod-sequences. If any condition fails,
207
+ // the client cannot trust the incremental updates and must do a full resync.
208
+ if (map.qresync && (options.uidValidity !== map.uidValidity || !map.highestModseq || map.noModseq)) {
209
+ map.qresync = false;
210
+ }
211
+ // Transition mailbox state: save previous mailbox reference, temporarily
212
+ // clear it, then emit events and set the new mailbox.
213
+ let currentMailbox = connection.mailbox;
214
+ connection.mailbox = false;
215
+ // Emit mailboxClose if we're switching from a different mailbox.
216
+ // Re-selecting the same mailbox (e.g., for resync) does not trigger close/open.
217
+ if (currentMailbox && currentMailbox.path !== path) {
218
+ connection.emit('mailboxClose', currentMailbox);
219
+ }
220
+ connection.mailbox = map;
221
+ // Save the SELECT command for potential re-use (e.g., NOOP fallback polling
222
+ // re-issues the SELECT to detect changes on servers without IDLE support).
223
+ connection.currentSelectCommand = selectCommand;
224
+ connection.state = connection.states.SELECTED;
225
+ if (!currentMailbox || currentMailbox.path !== path) {
226
+ connection.emit('mailboxOpen', connection.mailbox);
227
+ }
228
+ response.next();
229
+ return map;
230
+ }
231
+ catch (err) {
232
+ await (0, tools_js_1.enhanceCommandError)(err);
233
+ // If SELECT/EXAMINE fails while a mailbox was already selected, we must
234
+ // reset to AUTHENTICATED state since the server has implicitly deselected
235
+ // the previous mailbox on failure (RFC 3501 Section 6.3.1).
236
+ if (connection.state === connection.states.SELECTED) {
237
+ let currentMailbox = connection.mailbox;
238
+ connection.mailbox = false;
239
+ connection.currentSelectCommand = false;
240
+ connection.state = connection.states.AUTHENTICATED;
241
+ if (currentMailbox) {
242
+ connection.emit('mailboxClose', currentMailbox);
243
+ }
244
+ }
245
+ connection.log.warn({ err, cid: connection.id });
246
+ throw err;
247
+ }
248
+ }
249
+ module.exports = exports.default;
250
+ Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
@@ -0,0 +1,8 @@
1
+ import type { ImapFlow } from '../imap-flow.js';
2
+ /**
3
+ * Initiates STARTTLS connection upgrade.
4
+ *
5
+ * @param connection - IMAP connection instance
6
+ * @returns True if STARTTLS was initiated, false if not supported or already secure
7
+ */
8
+ export default function starttls(connection: ImapFlow): Promise<boolean>;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = starttls;
4
+ /**
5
+ * Initiates STARTTLS connection upgrade.
6
+ *
7
+ * @param connection - IMAP connection instance
8
+ * @returns True if STARTTLS was initiated, false if not supported or already secure
9
+ */
10
+ async function starttls(connection) {
11
+ if (!connection.capabilities.has('STARTTLS') || connection.secureConnection) {
12
+ // nothing to do here
13
+ return false;
14
+ }
15
+ let response;
16
+ try {
17
+ response = await connection.exec('STARTTLS');
18
+ // Whether the server sent anything after the STARTTLS OK and before the TLS
19
+ // handshake. upgradeToSTARTTLS() uses this to reject a plaintext injection.
20
+ connection._starttlsHadTrailingData = !!(response && response.hasTrailingData);
21
+ response.next();
22
+ return true;
23
+ }
24
+ catch (err) {
25
+ connection.log.warn({ err, cid: connection.id });
26
+ return false;
27
+ }
28
+ }
29
+ module.exports = exports.default;
30
+ Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
@@ -0,0 +1,14 @@
1
+ import type { ImapAttributeList } from '../handler/types.js';
2
+ /**
3
+ * A parsed STATUS value: counts and sizes as numbers, UIDVALIDITY and HIGHESTMODSEQ as BigInts
4
+ */
5
+ export type StatusFieldValue = number | bigint;
6
+ /**
7
+ * Walks a STATUS data-item list - alternating item-name and item-value tokens - and reports
8
+ * every recognized field that parsed successfully. Unknown item names and unusable values are
9
+ * skipped, so one bad field never costs the rest of the response.
10
+ *
11
+ * @param list - Parsed attribute list from the untagged STATUS response.
12
+ * @param onField - Called as (key, value) for each usable field.
13
+ */
14
+ export declare const parseStatusList: (list: ImapAttributeList, onField: (key: string, value: StatusFieldValue) => void) => void;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseStatusList = void 0;
4
+ const tools_js_1 = require("../tools.js");
5
+ // STATUS data items (RFC 3501 section 6.3.10, RFC 7162 for HIGHESTMODSEQ, RFC 9051 for SIZE
6
+ // and DELETED) mapped to the property name each one is exposed under, together with the
7
+ // parser that turns the raw response token into a usable value. Shared by the STATUS command
8
+ // and by the inline STATUS responses of LIST-STATUS (RFC 5819) so the two cannot drift apart.
9
+ //
10
+ // Every parser rejects anything that is not a bounded decimal digit run, returning false.
11
+ // These values are server-controlled and several of them are written straight into the live
12
+ // mailbox state, where a NaN or a value coerced to Infinity corrupts every later range
13
+ // computation. A plain isNaN() test is not enough: it passes '1e5', ' 12 ' and 'Infinity',
14
+ // and BigInt() throws on all three, aborting the walk over the remaining fields.
15
+ const uint32 = (value) => (0, tools_js_1.parseUintValue)(value, tools_js_1.MAX_UINT32_DIGITS);
16
+ const STATUS_FIELDS = {
17
+ MESSAGES: { key: 'messages', parser: uint32 },
18
+ RECENT: { key: 'recent', parser: uint32 },
19
+ UIDNEXT: { key: 'uidNext', parser: uint32 },
20
+ // Nominally 32-bit, but stored as a BigInt precisely so a server that exceeds that still
21
+ // round-trips, so the wider bound applies
22
+ UIDVALIDITY: { key: 'uidValidity', parser: value => (0, tools_js_1.parseBigIntValue)(value) },
23
+ UNSEEN: { key: 'unseen', parser: uint32 },
24
+ HIGHESTMODSEQ: { key: 'highestModseq', parser: value => (0, tools_js_1.parseBigIntValue)(value) },
25
+ // IMAP4rev2 additions (RFC 9051): total mailbox size in octets (number64, exact as a JS
26
+ // number up to 2^53-1) and count of messages carrying the \Deleted flag
27
+ SIZE: { key: 'size', parser: value => (0, tools_js_1.parseUintValue)(value) },
28
+ DELETED: { key: 'deleted', parser: uint32 }
29
+ };
30
+ /**
31
+ * Walks a STATUS data-item list - alternating item-name and item-value tokens - and reports
32
+ * every recognized field that parsed successfully. Unknown item names and unusable values are
33
+ * skipped, so one bad field never costs the rest of the response.
34
+ *
35
+ * @param list - Parsed attribute list from the untagged STATUS response.
36
+ * @param onField - Called as (key, value) for each usable field.
37
+ */
38
+ const parseStatusList = (list, onField) => {
39
+ let name;
40
+ list.forEach((entry, i) => {
41
+ if (i % 2 === 0) {
42
+ name = entry && typeof entry.value === 'string' ? entry.value : false;
43
+ return;
44
+ }
45
+ if (!name || !entry) {
46
+ return;
47
+ }
48
+ // The item name is server-controlled, but uppercasing it before the lookup means no
49
+ // Object.prototype member can be reached: every builtin name has a lowercase letter.
50
+ const field = STATUS_FIELDS[name.toUpperCase()];
51
+ if (!field) {
52
+ return;
53
+ }
54
+ const value = field.parser(entry.value);
55
+ if (value === false) {
56
+ return;
57
+ }
58
+ onField(field.key, value);
59
+ });
60
+ };
61
+ exports.parseStatusList = parseStatusList;
@@ -0,0 +1,12 @@
1
+ import type { ImapFlow } from '../imap-flow.js';
2
+ import type { StatusObject, StatusQuery } from '../types.js';
3
+ /**
4
+ * Requests status information about a mailbox.
5
+ *
6
+ * @param connection - IMAP connection instance
7
+ * @param path - Mailbox path to query
8
+ * @param query - Status data items to request (e.g., {messages: true, uidNext: true, unseen: true})
9
+ * @returns Status information object, or false if preconditions not met or on failure
10
+ * @throws {Error} If the mailbox does not exist
11
+ */
12
+ export default function status(connection: ImapFlow, path: string | string[], query: StatusQuery | undefined): Promise<StatusObject | false>;