imapflow 1.7.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/CHANGELOG.md +20 -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 +761 -1789
  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,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = enable;
4
+ const tools_js_1 = require("../tools.js");
5
+ /**
6
+ * Enables IMAP extensions on the server.
7
+ *
8
+ * @param connection - IMAP connection instance
9
+ * @param extensionList - List of extension names to enable
10
+ * @returns Set of enabled extensions, false on failure, or undefined if not applicable
11
+ */
12
+ async function enable(connection, extensionList) {
13
+ // ENABLE is part of base IMAP4rev2, so rev2-only servers may omit the token
14
+ if (!(0, tools_js_1.hasCapability)(connection, 'ENABLE') || connection.state !== connection.states.AUTHENTICATED) {
15
+ // nothing to do here
16
+ return;
17
+ }
18
+ // Pre-filter: only request extensions the server actually advertised in its
19
+ // CAPABILITY response. Requesting unsupported extensions would cause an error.
20
+ // Compared case-insensitively - the capability map keeps canonical casing for
21
+ // some keys (e.g. IMAP4rev2).
22
+ let advertised = new Set([...connection.capabilities.keys()].map(capability => capability.toUpperCase()));
23
+ extensionList = extensionList.filter(extension => advertised.has(extension.toUpperCase()));
24
+ if (!extensionList.length) {
25
+ return;
26
+ }
27
+ let response;
28
+ try {
29
+ let enabled = new Set();
30
+ response = await connection.exec('ENABLE', extensionList.map((extension) => ({ type: 'ATOM', value: extension.toUpperCase() })), {
31
+ untagged: {
32
+ // The untagged ENABLED response is a flat list of extension names
33
+ // (e.g., "* ENABLED CONDSTORE UTF8=ACCEPT"), NOT key-value pairs.
34
+ // Each attribute is a single extension identifier.
35
+ ENABLED: async (untagged) => {
36
+ if (!untagged.attributes || !untagged.attributes.length) {
37
+ return;
38
+ }
39
+ untagged.attributes.forEach(attr => {
40
+ let value = attr.value;
41
+ if (value && typeof value === 'string') {
42
+ enabled.add(value.toUpperCase().trim());
43
+ }
44
+ });
45
+ }
46
+ }
47
+ });
48
+ // Merge instead of replace - the untagged ENABLED response only lists
49
+ // extensions enabled by this command (RFC 5161), so a replace would drop
50
+ // grants from an earlier ENABLE call
51
+ connection.enabled = new Set([...connection.enabled, ...enabled]);
52
+ response.next();
53
+ return connection.enabled;
54
+ }
55
+ catch (err) {
56
+ connection.log.warn({ err, cid: connection.id });
57
+ return false;
58
+ }
59
+ }
60
+ module.exports = exports.default;
61
+ Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
@@ -0,0 +1,17 @@
1
+ import type { ImapAttributeList } from '../handler/types.js';
2
+ import type { ESearchResult } from '../types.js';
3
+ /**
4
+ * Parses the key-value attributes from an ESEARCH untagged response.
5
+ *
6
+ * Receives the attribute list AFTER stripping the leading (TAG "X") list
7
+ * and the UID atom, i.e. only the result keyword/value pairs remain.
8
+ *
9
+ * ALL and PARTIAL.messages are kept as compact sequence-set strings.
10
+ * Use expandRange() from tools.ts if you need to expand them.
11
+ * MODSEQ (RFC 7162, sent when the search used a MODSEQ criterion) is
12
+ * returned as a BigInt.
13
+ *
14
+ * @param attrs - Attribute array from the IMAP parser
15
+ * @returns ESearchResult object
16
+ */
17
+ export declare function parseEsearchResponse(attrs: ImapAttributeList): ESearchResult;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseEsearchResponse = parseEsearchResponse;
4
+ const tools_js_1 = require("../tools.js");
5
+ /**
6
+ * Parses the key-value attributes from an ESEARCH untagged response.
7
+ *
8
+ * Receives the attribute list AFTER stripping the leading (TAG "X") list
9
+ * and the UID atom, i.e. only the result keyword/value pairs remain.
10
+ *
11
+ * ALL and PARTIAL.messages are kept as compact sequence-set strings.
12
+ * Use expandRange() from tools.ts if you need to expand them.
13
+ * MODSEQ (RFC 7162, sent when the search used a MODSEQ criterion) is
14
+ * returned as a BigInt.
15
+ *
16
+ * @param attrs - Attribute array from the IMAP parser
17
+ * @returns ESearchResult object
18
+ */
19
+ function parseEsearchResponse(attrs) {
20
+ const result = {};
21
+ let i = 0;
22
+ while (i < attrs.length) {
23
+ const token = attrs[i];
24
+ if (!token || token.type !== 'ATOM') {
25
+ i++;
26
+ continue;
27
+ }
28
+ const key = token.value.toUpperCase();
29
+ if (i + 1 >= attrs.length) {
30
+ i++;
31
+ continue;
32
+ }
33
+ switch (key) {
34
+ // COUNT is a plain message count; MIN and MAX are sequence numbers or UIDs. All
35
+ // three are bounded decimal runs - isNaN() would also admit '1e400' (Infinity)
36
+ case 'COUNT': {
37
+ const n = (0, tools_js_1.parseUintValue)(attrs[++i]?.value, tools_js_1.MAX_UINT32_DIGITS);
38
+ if (n !== false)
39
+ result.count = n;
40
+ break;
41
+ }
42
+ case 'MIN': {
43
+ const n = (0, tools_js_1.parseUintValue)(attrs[++i]?.value, tools_js_1.MAX_UINT32_DIGITS);
44
+ if (n !== false)
45
+ result.min = n;
46
+ break;
47
+ }
48
+ case 'MAX': {
49
+ const n = (0, tools_js_1.parseUintValue)(attrs[++i]?.value, tools_js_1.MAX_UINT32_DIGITS);
50
+ if (n !== false)
51
+ result.max = n;
52
+ break;
53
+ }
54
+ case 'MODSEQ': {
55
+ // RFC 7162 section 3.1.5: present when the SEARCH used a MODSEQ
56
+ // criterion on a CONDSTORE-enabled session. BigInt because
57
+ // mod-sequence values are unsigned 63-bit
58
+ const modseq = (0, tools_js_1.parseBigIntValue)(attrs[++i]?.value);
59
+ if (modseq !== false)
60
+ result.modseq = modseq;
61
+ break;
62
+ }
63
+ case 'ALL': {
64
+ const allToken = attrs[++i];
65
+ if (allToken && typeof allToken.value === 'string') {
66
+ result.all = allToken.value;
67
+ }
68
+ break;
69
+ }
70
+ case 'PARTIAL': {
71
+ const listToken = attrs[++i];
72
+ const items = Array.isArray(listToken) ? listToken : null;
73
+ if (!items || items.length < 2)
74
+ break;
75
+ result.partial = {
76
+ range: items[0].value,
77
+ messages: items[1].value
78
+ };
79
+ break;
80
+ }
81
+ default:
82
+ // Skip the value token for unknown keys to keep the stream aligned.
83
+ // The loop's unconditional i++ at the bottom advances past the key;
84
+ // this extra i++ advances past the value token.
85
+ i++;
86
+ break;
87
+ }
88
+ i++;
89
+ }
90
+ return result;
91
+ }
@@ -0,0 +1,12 @@
1
+ import type { ImapFlow } from '../imap-flow.js';
2
+ import type { MessageRangeOptions } from '../types.js';
3
+ /**
4
+ * Deletes specified messages by flagging them as Deleted and expunging.
5
+ *
6
+ * @param connection - IMAP connection instance
7
+ * @param range - Message sequence number or UID range
8
+ * @param options - Expunge options
9
+ * @param options.uid - If true, use UID EXPUNGE when UIDPLUS is available
10
+ * @returns True on success, false on failure, or undefined if preconditions not met
11
+ */
12
+ export default function expunge(connection: ImapFlow, range: string, options?: MessageRangeOptions | undefined): Promise<boolean | undefined>;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = expunge;
4
+ const tools_js_1 = require("../tools.js");
5
+ /**
6
+ * Deletes specified messages by flagging them as Deleted and expunging.
7
+ *
8
+ * @param connection - IMAP connection instance
9
+ * @param range - Message sequence number or UID range
10
+ * @param options - Expunge options
11
+ * @param options.uid - If true, use UID EXPUNGE when UIDPLUS is available
12
+ * @returns True on success, false on failure, or undefined if preconditions not met
13
+ */
14
+ async function expunge(connection, range, options) {
15
+ if (connection.state !== connection.states.SELECTED || !range) {
16
+ // nothing to do here
17
+ return;
18
+ }
19
+ options = options || {};
20
+ // Two-step deletion process per IMAP protocol:
21
+ // Step 1: Mark the target messages with the \Deleted flag.
22
+ await connection.messageFlagsAdd(range, ['\\Deleted'], options);
23
+ // Step 2: Issue EXPUNGE to permanently remove \Deleted messages.
24
+ // With UIDPLUS (RFC 4315): "UID EXPUNGE <uids>" removes only the specified UIDs,
25
+ // leaving other \Deleted messages untouched, important for concurrent access.
26
+ // Without UIDPLUS: plain "EXPUNGE" removes ALL messages flagged \Deleted in the mailbox.
27
+ let byUid = options.uid && (0, tools_js_1.hasCapability)(connection, 'UIDPLUS');
28
+ let command = byUid ? 'UID EXPUNGE' : 'EXPUNGE';
29
+ let attributes = byUid ? [{ type: 'SEQUENCE', value: range }] : false;
30
+ let response;
31
+ try {
32
+ response = await connection.exec(command, attributes);
33
+ // CONDSTORE (RFC 7162): the server may return HIGHESTMODSEQ in the response code
34
+ // (e.g., "A OK [HIGHESTMODSEQ 9122] Expunge completed").
35
+ // Track this so the client can detect concurrent mailbox changes via mod-sequences.
36
+ let section = response.response.attributes && response.response.attributes[0] && response.response.attributes[0].section;
37
+ let responseCode = section && section.length && section[0] && typeof section[0].value === 'string' ? section[0].value : '';
38
+ if (responseCode.toUpperCase() === 'HIGHESTMODSEQ') {
39
+ // A response code always comes with its section, see responseCode above
40
+ let codeSection = section;
41
+ let mailbox = connection.mailbox;
42
+ // Bounded digit runs only: isNaN() also passes '1e5', which BigInt() rejects with
43
+ // a throw that the catch below would swallow, making messageDelete() report false
44
+ // even though the server expunged the messages.
45
+ let highestModseq = (0, tools_js_1.parseBigIntValue)(codeSection[1] && codeSection[1].value);
46
+ if (highestModseq && (!mailbox.highestModseq || highestModseq > mailbox.highestModseq)) {
47
+ mailbox.highestModseq = highestModseq;
48
+ }
49
+ }
50
+ response.next();
51
+ return true;
52
+ }
53
+ catch (err) {
54
+ await (0, tools_js_1.enhanceCommandError)(err);
55
+ connection.log.warn({ err, cid: connection.id });
56
+ return false;
57
+ }
58
+ }
59
+ module.exports = exports.default;
60
+ Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
@@ -0,0 +1,30 @@
1
+ import type { ImapFlow } from '../imap-flow.js';
2
+ import type { FetchMessageObject, FetchOptions, FetchQueryObject } from '../types.js';
3
+ /**
4
+ * Options for the FETCH command
5
+ */
6
+ export interface FetchCommandOptions extends Omit<FetchOptions, 'changedSince'> {
7
+ /** Only fetch messages changed since this modseq value */
8
+ changedSince?: bigint | number | string | undefined;
9
+ /** Callback for processing each fetched message individually. Call `next()` to release the next message */
10
+ onUntaggedFetch?: ((message: FetchMessageObject, next: (err?: Error | null | undefined) => void) => void) | undefined;
11
+ }
12
+ /**
13
+ * Result of the FETCH command
14
+ */
15
+ export interface FetchCommandResult {
16
+ /** Number of untagged FETCH responses received */
17
+ count: number;
18
+ /** Formatted messages, empty when `onUntaggedFetch` consumed them */
19
+ list: FetchMessageObject[];
20
+ }
21
+ /**
22
+ * Fetches emails from the server.
23
+ *
24
+ * @param connection - IMAP connection instance
25
+ * @param range - Message sequence number or UID range
26
+ * @param query - Fetch query specifying which data to retrieve (e.g., flags, envelope, bodyStructure, headers, source, bodyParts)
27
+ * @param options - Fetch options
28
+ * @returns Object with message count and list, or undefined if not in SELECTED state
29
+ */
30
+ export default function fetch(connection: ImapFlow, range: string, query: FetchQueryObject, options?: FetchCommandOptions | undefined): Promise<FetchCommandResult | undefined>;
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = fetch;
4
+ const tools_js_1 = require("../tools.js");
5
+ /**
6
+ * Fetches emails from the server.
7
+ *
8
+ * @param connection - IMAP connection instance
9
+ * @param range - Message sequence number or UID range
10
+ * @param query - Fetch query specifying which data to retrieve (e.g., flags, envelope, bodyStructure, headers, source, bodyParts)
11
+ * @param options - Fetch options
12
+ * @returns Object with message count and list, or undefined if not in SELECTED state
13
+ */
14
+ async function fetch(connection, range, query, options) {
15
+ if (connection.state !== connection.states.SELECTED || !range) {
16
+ // nothing to do here
17
+ return;
18
+ }
19
+ options = options || {};
20
+ let mailbox = connection.mailbox;
21
+ // Use BINARY extension for fetching if supported and requested, otherwise fall back to BODY.
22
+ // RFC 9051 folds the FETCH side of the BINARY extension into base IMAP4rev2, so an active
23
+ // rev2 session can use it even without the BINARY capability token (the APPEND side is NOT
24
+ // folded in and stays gated on the token in append.ts)
25
+ const canUseBinary = connection.capabilities.has('BINARY') || (0, tools_js_1.isRev2Active)(connection);
26
+ const commandKey = canUseBinary && options.binary && !connection.disableBinary ? 'BINARY' : 'BODY';
27
+ // Retry logic for ETHROTTLE errors (server rate limiting) with exponential backoff
28
+ let retryCount = 0;
29
+ const maxRetries = 4;
30
+ const baseDelay = 1000; // Start with 1 second delay
31
+ while (retryCount < maxRetries) {
32
+ let messages = {
33
+ count: 0,
34
+ list: []
35
+ };
36
+ let response;
37
+ try {
38
+ /* c8 ignore next */ // range is guaranteed truthy by the early-return guard above, so the '*' fallback is unreachable
39
+ let attributes = [{ type: 'SEQUENCE', value: (range || '*').toString() }];
40
+ let queryStructure = [];
41
+ // Helper to build BODY.PEEK[section]<partial> or BINARY.PEEK[section]<partial> atoms.
42
+ // PEEK avoids marking messages as \Seen. Section identifies what to fetch (HEADER, specific part, etc.)
43
+ // Partial is an optional byte range [start, maxLength].
44
+ let setBodyPeek = (attributes, partial) => {
45
+ let section = [].concat(attributes || []);
46
+ // BINARY may only address the empty section or a numeric part specifier
47
+ // (RFC 3516 / RFC 9051 section-binary) - HEADER, HEADER.FIELDS, TEXT and
48
+ // n.MIME are invalid after BINARY and must stay BODY fetches
49
+ let first = section[0];
50
+ let binaryAddressable = !section.length || (section.length === 1 && typeof first.value === 'string' && /^\d+(\.\d+)*$/.test(first.value));
51
+ let bodyPeek = {
52
+ type: 'ATOM',
53
+ value: `${binaryAddressable ? commandKey : 'BODY'}.PEEK`,
54
+ section: section,
55
+ partial
56
+ };
57
+ queryStructure.push(bodyPeek);
58
+ };
59
+ // IMAP fetch macros (ALL, FAST, FULL) and standard data items map directly to IMAP atoms
60
+ ['all', 'fast', 'full', 'uid', 'flags', 'bodyStructure', 'envelope', 'internalDate'].forEach(key => {
61
+ if (query[key]) {
62
+ queryStructure.push({ type: 'ATOM', value: key.toUpperCase() });
63
+ }
64
+ });
65
+ if (query.size) {
66
+ queryStructure.push({ type: 'ATOM', value: 'RFC822.SIZE' });
67
+ }
68
+ // Fetch full message source, optionally with byte range (start/maxLength)
69
+ if (query.source) {
70
+ let partial;
71
+ if (typeof query.source === 'object' && (query.source.start || query.source.maxLength)) {
72
+ partial = [Number(query.source.start) || 0];
73
+ if (query.source.maxLength && !isNaN(query.source.maxLength)) {
74
+ partial.push(Number(query.source.maxLength));
75
+ }
76
+ }
77
+ setBodyPeek(null, partial);
78
+ }
79
+ // Always request a unique email ID for message deduplication.
80
+ // Prefer OBJECTID (RFC 8474) over Gmail's X-GM-MSGID extension.
81
+ if (connection.capabilities.has('OBJECTID')) {
82
+ queryStructure.push({ type: 'ATOM', value: 'EMAILID' });
83
+ }
84
+ else if (connection.capabilities.has('X-GM-EXT-1')) {
85
+ queryStructure.push({ type: 'ATOM', value: 'X-GM-MSGID' });
86
+ }
87
+ // Thread ID: OBJECTID's THREADID or Gmail's X-GM-THRID
88
+ if (query.threadId) {
89
+ if (connection.capabilities.has('OBJECTID')) {
90
+ queryStructure.push({ type: 'ATOM', value: 'THREADID' });
91
+ }
92
+ else if (connection.capabilities.has('X-GM-EXT-1')) {
93
+ queryStructure.push({ type: 'ATOM', value: 'X-GM-THRID' });
94
+ }
95
+ }
96
+ // Gmail labels are only available with X-GM-EXT-1 extension
97
+ if (query.labels) {
98
+ if (connection.capabilities.has('X-GM-EXT-1')) {
99
+ queryStructure.push({ type: 'ATOM', value: 'X-GM-LABELS' });
100
+ }
101
+ }
102
+ // always ask for modseq if possible
103
+ if (connection.enabled.has('CONDSTORE') && !mailbox.noModseq) {
104
+ queryStructure.push({ type: 'ATOM', value: 'MODSEQ' });
105
+ }
106
+ // Always include UID in the response even if not explicitly requested,
107
+ // since we use it internally for message identification and tracking
108
+ if (!query.uid) {
109
+ queryStructure.push({ type: 'ATOM', value: 'UID' });
110
+ }
111
+ // Headers: fetch all headers or only specific ones via HEADER.FIELDS
112
+ if (query.headers) {
113
+ if (Array.isArray(query.headers)) {
114
+ setBodyPeek([{ type: 'ATOM', value: 'HEADER.FIELDS' }, query.headers.map(header => ({ type: 'ATOM', value: header }))]);
115
+ }
116
+ else {
117
+ setBodyPeek({ type: 'ATOM', value: 'HEADER' });
118
+ }
119
+ }
120
+ // Fetch specific body parts by MIME part number (e.g., "1", "1.2", "2.MIME")
121
+ // Each part can optionally include a byte range (start/maxLength)
122
+ if (query.bodyParts && query.bodyParts.length) {
123
+ query.bodyParts.forEach(part => {
124
+ if (!part) {
125
+ return;
126
+ }
127
+ let key;
128
+ let partial;
129
+ if (typeof part === 'object') {
130
+ if (!part.key || typeof part.key !== 'string') {
131
+ return;
132
+ }
133
+ key = part.key.toUpperCase();
134
+ if (part.start || part.maxLength) {
135
+ partial = [Number(part.start) || 0];
136
+ if (part.maxLength && !isNaN(part.maxLength)) {
137
+ partial.push(Number(part.maxLength));
138
+ }
139
+ }
140
+ }
141
+ else if (typeof part === 'string') {
142
+ key = part.toUpperCase();
143
+ }
144
+ else {
145
+ return;
146
+ }
147
+ setBodyPeek({ type: 'ATOM', value: key }, partial);
148
+ });
149
+ }
150
+ // IMAP requires a single item to not be wrapped in parentheses, but
151
+ // multiple items must be in a list. If only one item, unwrap the array.
152
+ let queryAttribute = queryStructure;
153
+ if (queryStructure.length === 1) {
154
+ queryAttribute = queryStructure.pop();
155
+ }
156
+ attributes.push(queryAttribute);
157
+ // CONDSTORE extension: only fetch messages with modseq higher than the given value.
158
+ // QRESYNC adds VANISHED to also get expunged UIDs since last sync.
159
+ if (options.changedSince && connection.enabled.has('CONDSTORE') && !mailbox.noModseq) {
160
+ let changedSinceArgs = [
161
+ {
162
+ type: 'ATOM',
163
+ value: 'CHANGEDSINCE'
164
+ },
165
+ {
166
+ type: 'ATOM',
167
+ value: options.changedSince.toString()
168
+ }
169
+ ];
170
+ if (options.uid && connection.enabled.has('QRESYNC')) {
171
+ changedSinceArgs.push({
172
+ type: 'ATOM',
173
+ value: 'VANISHED'
174
+ });
175
+ }
176
+ attributes.push(changedSinceArgs);
177
+ }
178
+ response = await connection.exec(options.uid ? 'UID FETCH' : 'FETCH', attributes, {
179
+ untagged: {
180
+ // Each matching message triggers an untagged FETCH response.
181
+ // If onUntaggedFetch callback is provided, stream messages to it one by one
182
+ // (useful for large result sets). Otherwise, collect all into messages.list.
183
+ FETCH: async (untagged) => {
184
+ messages.count++;
185
+ let formatted = await (0, tools_js_1.formatMessageResponse)(untagged, mailbox);
186
+ if (typeof options.onUntaggedFetch === 'function') {
187
+ await new Promise((resolve, reject) => {
188
+ options.onUntaggedFetch(formatted, err => {
189
+ if (err) {
190
+ reject(err);
191
+ }
192
+ else {
193
+ resolve();
194
+ }
195
+ });
196
+ });
197
+ }
198
+ else {
199
+ messages.list.push(formatted);
200
+ }
201
+ }
202
+ }
203
+ });
204
+ response.next();
205
+ return messages;
206
+ }
207
+ catch (err) {
208
+ if (err.code === 'ETHROTTLE') {
209
+ // Server returned a throttle error (rate limiting). Retry with exponential backoff.
210
+ // Delay doubles each retry: 1s, 2s, 4s, 8s (capped at 30s).
211
+ // If server provides a throttleReset hint, use that if longer.
212
+ const backoffDelay = Math.min(baseDelay * Math.pow(2, retryCount), 30000); // Cap at 30 seconds
213
+ // Use throttle reset time if provided and longer than backoff. The hint is
214
+ // server-controlled, so the wait goes through connection.throttleWait(), which caps
215
+ // it and keeps the timer tracked and abortable.
216
+ const delay = err.throttleReset && err.throttleReset > backoffDelay ? err.throttleReset : backoffDelay;
217
+ connection.log.warn({
218
+ msg: 'Retrying throttled request with exponential backoff',
219
+ cid: connection.id,
220
+ code: err.code,
221
+ response: err.responseText,
222
+ throttleReset: err.throttleReset,
223
+ retryCount,
224
+ delayMs: delay
225
+ });
226
+ // An aborted wait means the client was closed, so give up rather than reissuing
227
+ // the FETCH on a connection that is already gone.
228
+ let aborted = await connection.throttleWait(delay);
229
+ if (aborted) {
230
+ throw connection.createNoConnectionError(connection.byeReason, { rejectedFrom: 'throttleAbort', command: 'FETCH' });
231
+ }
232
+ retryCount++;
233
+ continue;
234
+ }
235
+ connection.log.warn({ err, cid: connection.id });
236
+ throw err;
237
+ }
238
+ }
239
+ }
240
+ module.exports = exports.default;
241
+ Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
@@ -0,0 +1,10 @@
1
+ import type { ImapFlow } from '../imap-flow.js';
2
+ import type { IdInfoObject } from '../types.js';
3
+ /**
4
+ * Sends ID info to the server and updates server info data based on the response.
5
+ *
6
+ * @param connection - IMAP connection instance
7
+ * @param clientInfo - Client identification key-value pairs to send to the server
8
+ * @returns Server information map, false on failure, or undefined if ID not supported
9
+ */
10
+ export default function id(connection: ImapFlow, clientInfo?: IdInfoObject | null | undefined): Promise<IdInfoObject | false | undefined>;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = id;
4
+ const tools_js_1 = require("../tools.js");
5
+ /**
6
+ * Sends ID info to the server and updates server info data based on the response.
7
+ *
8
+ * @param connection - IMAP connection instance
9
+ * @param clientInfo - Client identification key-value pairs to send to the server
10
+ * @returns Server information map, false on failure, or undefined if ID not supported
11
+ */
12
+ // RFC 2971: The ID command exchanges client/server implementation info
13
+ // (name, version, vendor, etc.) for diagnostic and compatibility purposes.
14
+ async function id(connection, clientInfo) {
15
+ if (!connection.capabilities.has('ID')) {
16
+ // nothing to do here
17
+ return;
18
+ }
19
+ let response;
20
+ try {
21
+ let map = {};
22
+ // Convert the clientInfo object into a flat array of alternating key-value strings
23
+ // for the IMAP wire format: ("key1" "value1" "key2" "value2" ...)
24
+ let formattedClientInfo = !clientInfo
25
+ ? null
26
+ : Object.keys(clientInfo)
27
+ .map(key => [key, formatValue(key, clientInfo[key])])
28
+ .filter(entry => entry[1])
29
+ .flatMap(entry => entry);
30
+ if (formattedClientInfo && !formattedClientInfo.length) {
31
+ // value array has no elements
32
+ formattedClientInfo = null;
33
+ }
34
+ response = await connection.exec('ID', [formattedClientInfo], {
35
+ untagged: {
36
+ // Parse the server's ID response: a flat list of alternating key-value atoms.
37
+ // Even indices (i % 2 === 0) are keys, odd indices are the corresponding values.
38
+ ID: async (untagged) => {
39
+ let params = untagged.attributes && untagged.attributes[0];
40
+ let key;
41
+ (Array.isArray(params) ? params : [].concat(params || [])).forEach((val, i) => {
42
+ if (i % 2 === 0) {
43
+ key = val.value;
44
+ }
45
+ else if (typeof key === 'string' && typeof val.value === 'string') {
46
+ map[key.toLowerCase().trim()] = val.value;
47
+ }
48
+ });
49
+ }
50
+ }
51
+ });
52
+ connection.serverInfo = map;
53
+ response.next();
54
+ return map;
55
+ }
56
+ catch (err) {
57
+ connection.log.warn({ err, cid: connection.id });
58
+ return false;
59
+ }
60
+ }
61
+ /**
62
+ * Formats a client info value for the ID command.
63
+ *
64
+ * @param key - The info key name
65
+ * @param value - The value to format
66
+ * @returns Formatted value string
67
+ */
68
+ function formatValue(key, value) {
69
+ switch (key.toLowerCase()) {
70
+ case 'date':
71
+ // RFC 2971 requires the "date" field to use IMAP date-time format
72
+ // (e.g., "06-Feb-2026 12:00:00 +0000"), not ISO 8601 or other formats.
73
+ return (0, tools_js_1.formatDateTime)(value);
74
+ default:
75
+ // Other values are strings without newlines
76
+ return (value || '').toString().replace(/\s+/g, ' ');
77
+ }
78
+ }
79
+ module.exports = exports.default;
80
+ Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
@@ -0,0 +1,9 @@
1
+ import type { ImapFlow } from '../imap-flow.js';
2
+ /**
3
+ * Listens for changes in the selected mailbox using IDLE or NOOP polling fallback.
4
+ *
5
+ * @param connection - IMAP connection instance
6
+ * @param maxIdleTime - Maximum time in milliseconds to stay in IDLE before restarting
7
+ * @returns Void on success, false on failure, or undefined if not in SELECTED state
8
+ */
9
+ export default function idle(connection: ImapFlow, maxIdleTime?: number | false | undefined): Promise<void | false | undefined>;