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,520 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = list;
4
+ const tools_js_1 = require("../tools.js");
5
+ const status_fields_js_1 = require("./status-fields.js");
6
+ const special_use_js_1 = require("../special-use.js");
7
+ /**
8
+ * Lists mailboxes from the server, including subscription status and special-use flags.
9
+ *
10
+ * @param connection - IMAP connection instance
11
+ * @param reference - Reference name (namespace prefix)
12
+ * @param mailbox - Mailbox name pattern with possible wildcards
13
+ * @param options - List options
14
+ * @param options.listOnly - If true, return entries after LIST without LSUB or status queries
15
+ * @param options.statusQuery - Status data items to query for each listed mailbox
16
+ * @param options.specialUseHints - Hints mapping mailbox paths to special-use types (sent, junk, trash, drafts, archive)
17
+ * @returns Array of mailbox entries sorted by special-use flags and name
18
+ * @throws If the LIST command fails
19
+ */
20
+ async function list(connection, reference, mailbox, options) {
21
+ options = options || {};
22
+ // Special-use flags sorted by display priority (INBOX first, Trash last).
23
+ // Used in the final sort to group special-use mailboxes at the top of the list.
24
+ const FLAG_SORT_ORDER = ['\\Inbox', '\\Flagged', '\\Sent', '\\Drafts', '\\All', '\\Archive', '\\Junk', '\\Trash'];
25
+ // Priority for how a special-use flag was determined: explicit user hint > server
26
+ // extension flag (SPECIAL-USE/XLIST) > known localized name > relaxed name guess.
27
+ // When multiple mailboxes claim the same special-use type, the highest-priority
28
+ // source wins, so an exactly named folder beats one matched by a decorated or
29
+ // morphological variant of that name.
30
+ const SOURCE_SORT_ORDER = ['user', 'extension', 'name', 'name-guess'];
31
+ // "name-guess" is an internal precedence tier only. It is reported as "name" so
32
+ // that specialUseSource keeps its documented set of values for consumers.
33
+ const PUBLIC_SOURCE = { 'name-guess': 'name' };
34
+ const isNameSource = (source) => source === 'name' || source === 'name-guess';
35
+ // Prefer XLIST (legacy Gmail extension) only if SPECIAL-USE (RFC 6154) is unavailable.
36
+ // Both provide special-use flags, but SPECIAL-USE is the standardized approach.
37
+ // SPECIAL-USE is checked with rev2 folding - a rev2 session implies SPECIAL-USE,
38
+ // so LIST is preferred even if a rev2 server also advertised legacy XLIST.
39
+ let listCommand = connection.capabilities.has('XLIST') && !(0, tools_js_1.hasCapability)(connection, 'SPECIAL-USE') ? 'XLIST' : 'LIST';
40
+ try {
41
+ // Accumulators filled by the untagged LIST/STATUS handlers below. statusMap
42
+ // caches STATUS responses received inline via LIST-STATUS extension, keyed by
43
+ // normalized mailbox path (avoids separate STATUS commands per mailbox), and
44
+ // specialUseMatches tracks candidate mailboxes for each special-use type.
45
+ // (Re)initialized at the start of each retry stage of the main listing.
46
+ let entries = [];
47
+ let statusMap = new Map();
48
+ let specialUseMatches = {};
49
+ // STATUS data items to request (MESSAGES, UIDNEXT, etc.)
50
+ let statusQueryAttributes = (0, tools_js_1.buildStatusQueryAttributes)(connection, options.statusQuery);
51
+ // Extended LIST syntax (RETURN options) is understood by servers advertising
52
+ // LIST-EXTENDED (RFC 5258) or IMAP4rev2 (RFC 9051). Deliberately keyed on the
53
+ // advertisement alone (not hasCapability/isRev2Active): the staged retry below
54
+ // handles servers that advertise but reject RETURN options, so the wider gate
55
+ // is safe for anything it covers, while gates without a retry ladder stay
56
+ // conservative. The rev2 advertisement stops counting once the session has
57
+ // been told not to act on it (skipRev2), because the ladder costs one rejected
58
+ // command per stage and a server that has disowned rev2 may not grant that many.
59
+ let supportsExtendedList = connection.capabilities.has('LIST-EXTENDED') || (connection.capabilities.has('IMAP4rev2') && !connection.skipRev2);
60
+ // RETURN options for the LIST command. Servers occasionally advertise the
61
+ // extensions but still reject RETURN options - the staged retry below then
62
+ // re-runs the LIST with fewer options and latches a skip flag for the option
63
+ // group the server proved to reject, keeping later listings efficient.
64
+ // LIST-STATUS (RFC 5819, folded into base IMAP4rev2): request STATUS data
65
+ // inline with LIST, avoiding a separate STATUS command for each mailbox.
66
+ let canRequestStatus = listCommand === 'LIST' && !connection.skipListStatusArgs && (0, tools_js_1.hasCapability)(connection, 'LIST-STATUS') && !!statusQueryAttributes.length;
67
+ // RETURN (SUBSCRIBED): request subscription state inline instead of a separate
68
+ // LSUB command. IMAP4rev2 removed LSUB entirely, and some servers (e.g.
69
+ // Exchange in IMAP4rev2 mode) reject it with BAD even while still advertising
70
+ // IMAP4rev1.
71
+ let canRequestSubscribed = listCommand === 'LIST' && !options.listOnly && !connection.skipListSubscribedArg && supportsExtendedList;
72
+ // Auxiliary RETURN options (SPECIAL-USE/CHILDREN) that ride along with the
73
+ // STATUS/SUBSCRIBED option groups. When RETURN options are present, servers
74
+ // may report only what was explicitly requested (verified against Dovecot
75
+ // 2.4: special-use and child attributes disappear from such responses), so
76
+ // request everything a plain LIST would have provided.
77
+ let auxArgsAvailable = (0, tools_js_1.hasCapability)(connection, 'SPECIAL-USE') || connection.capabilities.has('CHILDREN') || supportsExtendedList;
78
+ let stageHasAuxArgs = (stage) => (stage.status || stage.subscribed) && stage.aux !== false && !connection.skipListAuxArgs && auxArgsAvailable;
79
+ // Builds the RETURN (...) argument list for one retry stage
80
+ let buildListArgs = (stage) => {
81
+ let args = [];
82
+ if (stage.status) {
83
+ args.push({ type: 'ATOM', value: 'STATUS' }, statusQueryAttributes);
84
+ }
85
+ if (stageHasAuxArgs(stage)) {
86
+ if ((0, tools_js_1.hasCapability)(connection, 'SPECIAL-USE')) {
87
+ args.push({ type: 'ATOM', value: 'SPECIAL-USE' });
88
+ }
89
+ if (connection.capabilities.has('CHILDREN') || supportsExtendedList) {
90
+ args.push({ type: 'ATOM', value: 'CHILDREN' });
91
+ }
92
+ }
93
+ if (stage.subscribed) {
94
+ args.push({ type: 'ATOM', value: 'SUBSCRIBED' });
95
+ }
96
+ return args;
97
+ };
98
+ // Multiple mailboxes may claim the same special-use type (e.g., \\Sent) via
99
+ // different sources (user hint, server extension, name match). After listing,
100
+ // the best match wins.
101
+ let addSpecialUseMatch = (entry, type, source) => {
102
+ if (!specialUseMatches[type]) {
103
+ specialUseMatches[type] = [];
104
+ }
105
+ specialUseMatches[type].push({ entry, source });
106
+ };
107
+ // RFC 5258: the \NonExistent attribute implies \Noselect. Some servers only
108
+ // return \NonExistent for phantom folders, so add \Noselect as well to keep
109
+ // the flags consistent for consumers that only check \Noselect.
110
+ // RETURN (SUBSCRIBED) - and some LSUB implementations - report subscription
111
+ // state as a \Subscribed attribute. Move it to the subscribed property so the
112
+ // output shape is the same however the state was delivered.
113
+ let normalizeFlags = (entry) => {
114
+ if (entry.flags.has('\\NonExistent')) {
115
+ entry.flags.add('\\Noselect');
116
+ }
117
+ if (entry.flags.has('\\Subscribed')) {
118
+ entry.flags.delete('\\Subscribed');
119
+ entry.subscribed = true;
120
+ }
121
+ };
122
+ // User-provided hints map mailbox paths to special-use types (e.g., {sent: "Sent Items"}).
123
+ // These override server-reported flags and name-based guesses. Converted to a
124
+ // path-keyed lookup: { "Sent Items" => "\\Sent" }
125
+ // Keyed by server-supplied mailbox paths further down, so a null prototype keeps a
126
+ // path like "constructor" from resolving to an inherited member on lookup
127
+ let specialUseHints = Object.create(null);
128
+ if (options.specialUseHints && typeof options.specialUseHints === 'object') {
129
+ for (let type of Object.keys(options.specialUseHints)) {
130
+ if (['sent', 'junk', 'trash', 'drafts', 'archive'].includes(type) &&
131
+ options.specialUseHints[type] &&
132
+ typeof options.specialUseHints[type] === 'string') {
133
+ // Capitalize first letter: "sent" -> "\\Sent"
134
+ specialUseHints[(0, tools_js_1.normalizePath)(connection, options.specialUseHints[type])] = `\\${type.replace(/^./, c => c.toUpperCase())}`;
135
+ }
136
+ }
137
+ }
138
+ // Executes a LIST (or XLIST) command and collects mailbox entries.
139
+ // Called once for the main listing and optionally again for INBOX if a
140
+ // namespace prefix was used (INBOX may live outside the namespace).
141
+ let runList = async (reference, mailbox, returnArgs) => {
142
+ const cmdArgs = [(0, tools_js_1.encodePath)(connection, reference), (0, tools_js_1.encodePath)(connection, mailbox)];
143
+ if (returnArgs.length) {
144
+ cmdArgs.push({ type: 'ATOM', value: 'RETURN' }, returnArgs);
145
+ }
146
+ let response = await connection.exec(listCommand, cmdArgs, {
147
+ untagged: {
148
+ // Each untagged LIST response: * LIST (<flags>) "<delimiter>" "<mailbox name>"
149
+ // attributes[0] = flags array, attributes[1] = delimiter, attributes[2] = mailbox name
150
+ [listCommand]: async (untagged) => {
151
+ if (!untagged.attributes || !untagged.attributes.length) {
152
+ return;
153
+ }
154
+ let entry = {
155
+ // Decode from modified UTF-7 wire format and normalize the path
156
+ path: (0, tools_js_1.normalizePath)(connection, (0, tools_js_1.decodePath)(connection, ((untagged.attributes[2] && untagged.attributes[2].value) || ''))),
157
+ pathAsListed: ((untagged.attributes[2] && untagged.attributes[2].value) || ''),
158
+ flags: new Set((0, tools_js_1.getStringList)(untagged.attributes[0])),
159
+ delimiter: (untagged.attributes[1] && untagged.attributes[1].value),
160
+ listed: true
161
+ };
162
+ normalizeFlags(entry);
163
+ // Check user-provided hints first (highest priority)
164
+ if (specialUseHints[entry.path]) {
165
+ addSpecialUseMatch(entry, specialUseHints[entry.path], 'user');
166
+ }
167
+ // XLIST marks INBOX with a \\Inbox flag. Remove it from flags
168
+ // (it's not a standard flag) and register as special-use match.
169
+ // XLIST may also use a localised name (e.g., "Posteingang" for German INBOX).
170
+ if (listCommand === 'XLIST' && entry.flags.has('\\Inbox')) {
171
+ entry.flags.delete('\\Inbox');
172
+ if (entry.path !== 'INBOX') {
173
+ addSpecialUseMatch(entry, '\\Inbox', 'extension');
174
+ }
175
+ }
176
+ // Name-based INBOX detection: any mailbox named "INBOX" (case-insensitive)
177
+ // is the inbox per RFC 3501. Phantom \NonExistent entries (subscribed
178
+ // leftovers of deleted mailboxes) must not claim the slot by name.
179
+ if (entry.path.toUpperCase() === 'INBOX' && !entry.flags.has('\\NonExistent')) {
180
+ addSpecialUseMatch(entry, '\\Inbox', 'name');
181
+ }
182
+ // Strip leading delimiter (some servers prepend it to paths)
183
+ if (entry.delimiter && entry.path.charAt(0) === entry.delimiter) {
184
+ entry.path = entry.path.slice(1);
185
+ }
186
+ // Build parent path hierarchy for tree construction and sorting
187
+ entry.parentPath = entry.delimiter && entry.path ? entry.path.substr(0, entry.path.lastIndexOf(entry.delimiter)) : '';
188
+ entry.parent = entry.delimiter ? entry.path.split(entry.delimiter) : [entry.path];
189
+ entry.name = entry.parent.pop();
190
+ // Try to detect special-use from server flags or well-known names
191
+ // (e.g., "Sent", "Drafts", "Junk", "Trash")
192
+ let { flag: specialUseFlag, source: flagSource } = (0, special_use_js_1.specialUse)(connection.capabilities.has('XLIST') || (0, tools_js_1.hasCapability)(connection, 'SPECIAL-USE'), entry);
193
+ // A name-based match for a \NonExistent phantom entry could win the
194
+ // special-use slot over the real folder - only server-provided flags
195
+ // are trusted for nonexistent entries. Covers every name-derived
196
+ // source, exact and relaxed alike.
197
+ if (specialUseFlag && (!isNameSource(flagSource) || !entry.flags.has('\\NonExistent'))) {
198
+ addSpecialUseMatch(entry, specialUseFlag, flagSource);
199
+ }
200
+ entries.push(entry);
201
+ },
202
+ // Inline STATUS response from LIST-STATUS extension (RFC 5819).
203
+ // Parses alternating key-value pairs (i % 2 pattern).
204
+ STATUS: async (untagged) => {
205
+ let statusPath = (0, tools_js_1.normalizePath)(connection, (0, tools_js_1.decodePath)(connection, ((untagged.attributes[0] && untagged.attributes[0].value) || '')));
206
+ let statusList = untagged.attributes && Array.isArray(untagged.attributes[1]) ? untagged.attributes[1] : false;
207
+ if (!statusList || !statusPath) {
208
+ return;
209
+ }
210
+ let map = { path: statusPath };
211
+ (0, status_fields_js_1.parseStatusList)(statusList, (key, value) => {
212
+ map[key] = value;
213
+ });
214
+ statusMap.set(statusPath, map);
215
+ }
216
+ }
217
+ });
218
+ response.next();
219
+ };
220
+ let normalizedReference = (0, tools_js_1.normalizePath)(connection, reference || '');
221
+ let normalizedMailbox = (0, tools_js_1.normalizePath)(connection, mailbox || '', true);
222
+ // Retry stages for the main listing: start with all applicable RETURN options
223
+ // and drop one option group per retry. Consecutive stages differ by exactly one
224
+ // group, so a success right after a rejection identifies the offending group
225
+ // and only that group's skip flag is latched for the rest of the connection.
226
+ // When a stage carrying the auxiliary SPECIAL-USE/CHILDREN options is rejected,
227
+ // a copy of the same stage without them is inserted first (once per listing),
228
+ // so an auxiliary-only rejection does not get a whole option group blamed.
229
+ let stages = [];
230
+ if (canRequestStatus && canRequestSubscribed) {
231
+ stages.push({ status: true, subscribed: true });
232
+ }
233
+ if (canRequestStatus) {
234
+ stages.push({ status: true, subscribed: false });
235
+ }
236
+ else if (canRequestSubscribed) {
237
+ stages.push({ status: false, subscribed: true });
238
+ }
239
+ stages.push({ status: false, subscribed: false });
240
+ // A tagged BAD is how servers reject unrecognized RETURN options (RFC 9051
241
+ // section 6.3.9). A tagged NO is an operational failure, and throttling
242
+ // errors (code ETHROTTLE) also surface with a BAD status - neither says
243
+ // anything about the RETURN options, so they propagate to the caller.
244
+ let isRejectedCommand = (err) => err.responseStatus === 'BAD' && err.code !== 'ETHROTTLE';
245
+ // Stage of the successful attempt - reused by the INBOX fixup and the LSUB
246
+ // decision below
247
+ let successStage = null;
248
+ // Whether any source actually reported subscription state. RETURN (SUBSCRIBED)
249
+ // and LSUB are the only two, and a server can refuse both
250
+ let subscriptionStateKnown = false;
251
+ // A server may also volunteer \Subscribed on a plain LIST, which normalizeFlags
252
+ // folds into the entry - that counts as the state having been reported
253
+ let anyEntrySubscribed = () => entries.some(entry => entry.subscribed);
254
+ let lastRejectedStage = null;
255
+ let auxRetryInserted = false;
256
+ for (let i = 0; i < stages.length; i++) {
257
+ let stage = stages[i];
258
+ let stageArgs = buildListArgs(stage);
259
+ // Discard partial results from a rejected attempt
260
+ entries = [];
261
+ statusMap = new Map();
262
+ specialUseMatches = {};
263
+ try {
264
+ await runList(normalizedReference, normalizedMailbox, stageArgs);
265
+ if (lastRejectedStage) {
266
+ // Latch only the option group that was present in the rejected
267
+ // attempt but missing from this successful one - that group is
268
+ // proven to be what the server rejects. An unproven group (e.g.
269
+ // SUBSCRIBED when both groups were dropped one by one) is decided
270
+ // by the reduced stage list of the next listing.
271
+ if (lastRejectedStage.subscribed && !stage.subscribed) {
272
+ connection.skipListSubscribedArg = true;
273
+ }
274
+ if (lastRejectedStage.status && !stage.status) {
275
+ connection.skipListStatusArgs = true;
276
+ }
277
+ if (stageHasAuxArgs(lastRejectedStage) &&
278
+ stage.aux === false &&
279
+ lastRejectedStage.status === stage.status &&
280
+ lastRejectedStage.subscribed === stage.subscribed) {
281
+ // Same option groups, only the auxiliary args dropped - the
282
+ // auxiliaries are proven to be what the server rejects
283
+ connection.skipListAuxArgs = true;
284
+ }
285
+ }
286
+ successStage = stage;
287
+ subscriptionStateKnown = !!stage.subscribed;
288
+ break;
289
+ }
290
+ catch (err) {
291
+ if (i === stages.length - 1 || !isRejectedCommand(err)) {
292
+ throw err;
293
+ }
294
+ lastRejectedStage = stage;
295
+ if (!auxRetryInserted && stageHasAuxArgs(stage)) {
296
+ // The rejection may be about the auxiliary options rather than the
297
+ // option groups - try the same groups without the auxiliaries before
298
+ // dropping a group
299
+ stages.splice(i + 1, 0, { ...stage, aux: false });
300
+ auxRetryInserted = true;
301
+ }
302
+ connection.log.warn({ msg: 'LIST RETURN options rejected, retrying with reduced options', err, cid: connection.id });
303
+ }
304
+ }
305
+ if (options.listOnly) {
306
+ return entries;
307
+ }
308
+ // When listing with a namespace prefix (e.g., "INBOX."), INBOX itself may
309
+ // not appear in results. Run a separate LIST for INBOX to ensure it's included.
310
+ if (normalizedReference && !specialUseMatches['\\Inbox']) {
311
+ let returnArgs = buildListArgs(successStage);
312
+ // Snapshot the accumulator sizes: a rejected fixup attempt may have
313
+ // streamed partial untagged responses before its tagged BAD, and those
314
+ // must be discarded before the retry or INBOX would be listed twice -
315
+ // while the main run's results must be kept
316
+ let entryCountBefore = entries.length;
317
+ let specialUseCountsBefore = {};
318
+ for (let type of Object.keys(specialUseMatches)) {
319
+ specialUseCountsBefore[type] = specialUseMatches[type].length;
320
+ }
321
+ try {
322
+ await runList('', 'INBOX', returnArgs);
323
+ }
324
+ catch (err) {
325
+ // The main listing just succeeded with the same RETURN options, so a
326
+ // rejection here says nothing about the options themselves - retry
327
+ // this one call plain without latching any skip flags. Accepted edge:
328
+ // if the main run filled statusMap, INBOX ends up without inline
329
+ // status data.
330
+ if (!returnArgs.length || !isRejectedCommand(err)) {
331
+ throw err;
332
+ }
333
+ entries.length = entryCountBefore;
334
+ for (let type of Object.keys(specialUseMatches)) {
335
+ if (!(type in specialUseCountsBefore)) {
336
+ delete specialUseMatches[type];
337
+ }
338
+ else {
339
+ specialUseMatches[type].length = specialUseCountsBefore[type];
340
+ }
341
+ }
342
+ connection.log.warn({ msg: 'INBOX LIST with RETURN options failed, retrying plain', err, cid: connection.id });
343
+ await runList('', 'INBOX', []);
344
+ }
345
+ }
346
+ // Attach STATUS data to each selectable mailbox. If LIST-STATUS was used,
347
+ // data is already in statusMap; otherwise, fall back to individual STATUS commands.
348
+ if (options.statusQuery) {
349
+ // RECENT does not exist in IMAP4rev2, so it is never requested from a rev2
350
+ // session - its defined value there is always 0 (the STATUS command module
351
+ // applies the same rule on the per-mailbox fallback path)
352
+ let syntheticRecent = options.statusQuery.recent && (0, tools_js_1.isRev2Active)(connection);
353
+ for (let entry of entries) {
354
+ // \\Noselect and \\NonExistent mailboxes cannot hold messages
355
+ if (!entry.flags.has('\\Noselect') && !entry.flags.has('\\NonExistent')) {
356
+ if (statusMap.has(entry.path)) {
357
+ entry.status = statusMap.get(entry.path);
358
+ if (syntheticRecent) {
359
+ entry.status.recent = 0;
360
+ }
361
+ }
362
+ else if (!statusMap.size) {
363
+ // Server didn't support LIST-STATUS; fall back to per-mailbox STATUS
364
+ try {
365
+ entry.status = await connection.run('STATUS', entry.path, options.statusQuery);
366
+ }
367
+ catch (err) {
368
+ entry.status = { error: err };
369
+ }
370
+ }
371
+ }
372
+ }
373
+ }
374
+ // LSUB (RFC 3501 6.3.9): queries which mailboxes the user is subscribed to.
375
+ // We merge subscription info into the entries already collected from LIST.
376
+ // Subscribed-only mailboxes that weren't in LIST are intentionally ignored
377
+ // (they may be phantom entries from old subscriptions to deleted mailboxes).
378
+ let runLsub = async () => {
379
+ let response = await connection.exec('LSUB', [(0, tools_js_1.encodePath)(connection, normalizedReference), (0, tools_js_1.encodePath)(connection, normalizedMailbox)], {
380
+ untagged: {
381
+ LSUB: async (untagged) => {
382
+ if (!untagged.attributes || !untagged.attributes.length) {
383
+ return;
384
+ }
385
+ let entry = {
386
+ path: (0, tools_js_1.normalizePath)(connection, (0, tools_js_1.decodePath)(connection, ((untagged.attributes[2] && untagged.attributes[2].value) || ''))),
387
+ pathAsListed: ((untagged.attributes[2] && untagged.attributes[2].value) || ''),
388
+ flags: new Set((0, tools_js_1.getStringList)(untagged.attributes[0])),
389
+ delimiter: (untagged.attributes[1] && untagged.attributes[1].value),
390
+ subscribed: true
391
+ };
392
+ if (entry.path.toUpperCase() === 'INBOX') {
393
+ addSpecialUseMatch(entry, '\\Inbox', 'name');
394
+ }
395
+ if (entry.delimiter && entry.path.charAt(0) === entry.delimiter) {
396
+ entry.path = entry.path.slice(1);
397
+ }
398
+ entry.parentPath = entry.delimiter && entry.path ? entry.path.substr(0, entry.path.lastIndexOf(entry.delimiter)) : '';
399
+ entry.parent = entry.delimiter ? entry.path.split(entry.delimiter) : [entry.path];
400
+ entry.name = entry.parent.pop();
401
+ // Merge LSUB data into existing LIST entry if found
402
+ let existing = entries.find(existing => existing.path === entry.path);
403
+ if (existing) {
404
+ existing.subscribed = true;
405
+ // Merge any additional flags from LSUB into the LIST entry
406
+ entry.flags.forEach(flag => existing.flags.add(flag));
407
+ normalizeFlags(existing);
408
+ }
409
+ // Non-listed subscribed folders are intentionally ignored
410
+ }
411
+ }
412
+ });
413
+ response.next();
414
+ };
415
+ // Never sent on a rev2 session - LSUB is not part of that protocol version, and
416
+ // some servers break the rest of the session over the rejection, so this is
417
+ // decided up front rather than left to the skipLsub latch below. On rev1 it is
418
+ // skipped when RETURN (SUBSCRIBED) already answered. Safety net: if the extended
419
+ // LIST was accepted but not a single mailbox came back subscribed, assume the
420
+ // server silently ignored the option and fall back to LSUB anyway (an account
421
+ // with no subscriptions legitimately looks the same).
422
+ let needsLsub = !(0, tools_js_1.isRev2Active)(connection) && (!successStage.subscribed || !anyEntrySubscribed());
423
+ if (needsLsub) {
424
+ // Reaching here means the listing did not settle the question after all -
425
+ // either no RETURN (SUBSCRIBED) was granted, or one was and the server
426
+ // ignored it. Only LSUB can answer now
427
+ subscriptionStateKnown = false;
428
+ }
429
+ if (needsLsub && !connection.skipLsub) {
430
+ try {
431
+ await runLsub();
432
+ subscriptionStateKnown = true;
433
+ }
434
+ catch (err) {
435
+ if (isRejectedCommand(err)) {
436
+ // Tagged BAD: the server does not implement LSUB despite advertising
437
+ // rev1 - skip it for the rest of this connection
438
+ connection.skipLsub = true;
439
+ }
440
+ else if (err.responseStatus !== 'NO' || err.code === 'ETHROTTLE') {
441
+ // Transport failures and throttling: rethrow, every follow-up
442
+ // command would fail too or the caller needs to back off
443
+ throw err;
444
+ }
445
+ // Subscription state is auxiliary - keep the LIST results usable. A
446
+ // tagged NO is treated as transient, so the next listing tries again.
447
+ connection.log.warn({ msg: 'Failed to request subscription info', err, cid: connection.id });
448
+ }
449
+ }
450
+ // Resolve special-use conflicts: for each type, pick the best candidate
451
+ // based on source priority (user > extension > name), then alphabetically.
452
+ // Only the winning entry gets the specialUse property set.
453
+ for (let type of Object.keys(specialUseMatches)) {
454
+ let sortedEntries = specialUseMatches[type].sort((a, b) => {
455
+ let aSource = SOURCE_SORT_ORDER.indexOf(a.source);
456
+ let bSource = SOURCE_SORT_ORDER.indexOf(b.source);
457
+ if (aSource === bSource) {
458
+ return a.entry.path.localeCompare(b.entry.path);
459
+ }
460
+ return aSource - bSource;
461
+ });
462
+ if (!sortedEntries[0].entry.specialUse) {
463
+ let source = sortedEntries[0].source;
464
+ sortedEntries[0].entry.specialUse = type;
465
+ sortedEntries[0].entry.specialUseSource = PUBLIC_SOURCE[source] || source;
466
+ }
467
+ }
468
+ // No source answered, so "not subscribed" was never actually reported for any of
469
+ // these folders - the state is unknown, not false. Reporting the whole listing as
470
+ // unsubscribed would hide every folder from a client that filters on subscription
471
+ // state, so assume subscribed instead. Phantom entries are excluded, the same way
472
+ // the rest of this file declines to trust them. NB! a transient LSUB NO lands here
473
+ // too, so the assumption can hold for one listing and be replaced by real state on
474
+ // the next
475
+ if (!subscriptionStateKnown && !anyEntrySubscribed()) {
476
+ for (let entry of entries) {
477
+ if (!entry.flags.has('\\NonExistent')) {
478
+ entry.subscribed = true;
479
+ }
480
+ }
481
+ }
482
+ // INBOX should always appear as subscribed regardless of LSUB results
483
+ let inboxEntry = entries.find(entry => entry.specialUse === '\\Inbox');
484
+ if (inboxEntry && !inboxEntry.subscribed) {
485
+ inboxEntry.subscribed = true;
486
+ }
487
+ // Sort: special-use mailboxes first (in FLAG_SORT_ORDER), then alphabetically
488
+ // by path segments for a natural folder hierarchy ordering.
489
+ return entries.sort((a, b) => {
490
+ if (a.specialUse && !b.specialUse) {
491
+ return -1;
492
+ }
493
+ if (!a.specialUse && b.specialUse) {
494
+ return 1;
495
+ }
496
+ if (a.specialUse && b.specialUse) {
497
+ return FLAG_SORT_ORDER.indexOf(a.specialUse) - FLAG_SORT_ORDER.indexOf(b.specialUse);
498
+ }
499
+ let aList = [].concat(a.parent).concat(a.name);
500
+ let bList = [].concat(b.parent).concat(b.name);
501
+ for (let i = 0; i < aList.length; i++) {
502
+ let aPart = aList[i];
503
+ let bPart = bList[i];
504
+ if (aPart !== bPart) {
505
+ return aPart.localeCompare(bPart || '');
506
+ }
507
+ }
508
+ return a.path.localeCompare(b.path);
509
+ });
510
+ }
511
+ catch (err) {
512
+ // Rewrite the parsed err.response into the response text and set
513
+ // serverResponseCode, same as the other command modules
514
+ await (0, tools_js_1.enhanceCommandError)(err);
515
+ connection.log.warn({ msg: 'Failed to list folders', err, cid: connection.id });
516
+ throw err;
517
+ }
518
+ }
519
+ module.exports = exports.default;
520
+ Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
@@ -0,0 +1,11 @@
1
+ import type { ImapFlow } from '../imap-flow.js';
2
+ /**
3
+ * Authenticates user using the IMAP LOGIN command.
4
+ *
5
+ * @param connection - IMAP connection instance
6
+ * @param username - The username to authenticate with
7
+ * @param password - The password to authenticate with
8
+ * @returns The authenticated username, or undefined if already authenticated
9
+ * @throws If authentication fails, with authenticationFailed and serverResponseCode properties set
10
+ */
11
+ export default function login(connection: ImapFlow, username: string, password: string): Promise<string | undefined>;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = login;
4
+ const tools_js_1 = require("../tools.js");
5
+ /**
6
+ * Authenticates user using the IMAP LOGIN command.
7
+ *
8
+ * @param connection - IMAP connection instance
9
+ * @param username - The username to authenticate with
10
+ * @param password - The password to authenticate with
11
+ * @returns The authenticated username, or undefined if already authenticated
12
+ * @throws If authentication fails, with authenticationFailed and serverResponseCode properties set
13
+ */
14
+ async function login(connection, username, password) {
15
+ if (connection.state !== connection.states.NOT_AUTHENTICATED) {
16
+ // nothing to do here
17
+ return;
18
+ }
19
+ try {
20
+ let response = await connection.exec('LOGIN', [
21
+ { type: 'STRING', value: username },
22
+ // sensitive: true prevents the password from appearing in debug logs
23
+ { type: 'STRING', value: password, sensitive: true }
24
+ ]);
25
+ response.next();
26
+ // Record that LOGIN was the method used, so the connection knows which
27
+ // auth mechanism succeeded (used for reconnection and diagnostics).
28
+ connection.authCapabilities.set('LOGIN', true);
29
+ return username;
30
+ }
31
+ catch (err) {
32
+ let errorCode = (0, tools_js_1.getStatusCode)(err.response);
33
+ if (errorCode) {
34
+ err.serverResponseCode = errorCode;
35
+ }
36
+ err.authenticationFailed = true;
37
+ err.response = await (0, tools_js_1.getErrorText)(err.response);
38
+ throw err;
39
+ }
40
+ }
41
+ module.exports = exports.default;
42
+ 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
+ * Logs out the user and closes the connection.
4
+ *
5
+ * @param connection - IMAP connection instance
6
+ * @returns True if logout command succeeded, false otherwise
7
+ */
8
+ export default function logout(connection: ImapFlow): Promise<boolean>;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = logout;
4
+ /**
5
+ * Logs out the user and closes the connection.
6
+ *
7
+ * @param connection - IMAP connection instance
8
+ * @returns True if logout command succeeded, false otherwise
9
+ */
10
+ async function logout(connection) {
11
+ if (connection.state === connection.states.LOGOUT) {
12
+ // nothing to do here
13
+ return false;
14
+ }
15
+ if (connection.state === connection.states.NOT_AUTHENTICATED) {
16
+ // Not yet authenticated, no LOGOUT command needed; just close the socket.
17
+ connection.state = connection.states.LOGOUT;
18
+ connection.close();
19
+ return false;
20
+ }
21
+ let response;
22
+ try {
23
+ response = await connection.exec('LOGOUT');
24
+ return true;
25
+ }
26
+ catch (err) {
27
+ // If the connection is already gone, treat as successful logout
28
+ if (err.code === 'NoConnection') {
29
+ return true;
30
+ }
31
+ connection.log.warn({ err, cid: connection.id });
32
+ return false;
33
+ /* c8 ignore next */ // the catch above is exhaustive (never re-throws), so finally is only ever reached via normal completion
34
+ }
35
+ finally {
36
+ // Set state to LOGOUT before closing to prevent any further commands from
37
+ // being queued. The socket is closed unconditionally in this finally block
38
+ // regardless of whether the LOGOUT command succeeded or failed.
39
+ connection.state = connection.states.LOGOUT;
40
+ if (response && typeof response.next === 'function') {
41
+ response.next();
42
+ }
43
+ connection.close();
44
+ }
45
+ }
46
+ module.exports = exports.default;
47
+ Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
@@ -0,0 +1,13 @@
1
+ import type { ImapFlow } from '../imap-flow.js';
2
+ import type { CopyResponseObject, MessageRangeOptions } from '../types.js';
3
+ /**
4
+ * Moves messages from the current mailbox to another mailbox.
5
+ *
6
+ * @param connection - IMAP connection instance
7
+ * @param range - Message sequence number or UID range
8
+ * @param destination - Destination mailbox path
9
+ * @param options - Move options
10
+ * @param options.uid - If true, use UID MOVE instead of MOVE
11
+ * @returns Move result with UID mapping if available, false on failure, or undefined if preconditions not met
12
+ */
13
+ export default function move(connection: ImapFlow, range: string, destination: string | string[], options?: MessageRangeOptions | undefined): Promise<CopyResponseObject | false | undefined>;