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,223 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = authenticate;
4
+ const tools_js_1 = require("../tools.js");
5
+ /**
6
+ * Handles authentication errors by enriching the error object with server response details.
7
+ *
8
+ * @param err - The original authentication error
9
+ * @param errorResponse - Optional OAuth error response from the server
10
+ * @returns The enriched error; the caller is expected to throw it
11
+ */
12
+ async function handleAuthError(err, errorResponse) {
13
+ let errorCode = (0, tools_js_1.getStatusCode)(err.response);
14
+ if (errorCode) {
15
+ err.serverResponseCode = errorCode;
16
+ }
17
+ err.authenticationFailed = true;
18
+ err.response = await (0, tools_js_1.getErrorText)(err.response);
19
+ if (errorResponse) {
20
+ err.oauthError = errorResponse;
21
+ }
22
+ return err;
23
+ }
24
+ /**
25
+ * Authenticates using OAuth (OAUTHBEARER or XOAUTH2).
26
+ *
27
+ * @param connection - IMAP connection instance
28
+ * @param username - The username to authenticate with
29
+ * @param accessToken - The OAuth2 access token
30
+ * @returns The authenticated username
31
+ * @throws {Error} If authentication fails
32
+ */
33
+ async function authOauth(connection, username, accessToken) {
34
+ let oauthbearer;
35
+ let command;
36
+ let breaker;
37
+ if (connection.capabilities.has('AUTH=OAUTHBEARER')) {
38
+ // OAUTHBEARER payload per RFC 7628: fields separated by \x01 (SASL GS2 framing).
39
+ // Format: "n,a=<user>," \x01 "host=..." \x01 "port=..." \x01 "auth=Bearer <token>" \x01 \x01
40
+ // The trailing empty strings produce the required double-\x01 terminator.
41
+ // Both fields must describe the connection actually in use. The port was hardcoded to 993,
42
+ // so an OAuth2 server reached over 143/STARTTLS advertised a payload that did not match, and
43
+ // `servername` is set to false for a bare-IP host, which rendered as a literal "host=false".
44
+ // A server validating either field rejects with status `invalid_request` - a permanent
45
+ // failure that refreshing the access token can never clear.
46
+ oauthbearer = [
47
+ `n,a=${username},`,
48
+ `host=${connection.servername || connection.host}`,
49
+ `port=${connection.port}`,
50
+ `auth=Bearer ${accessToken}`,
51
+ '',
52
+ ''
53
+ ].join('\x01');
54
+ command = 'OAUTHBEARER';
55
+ // "AQ==" is base64 for \x01, sent as the error continuation to abort the SASL exchange
56
+ breaker = 'AQ==';
57
+ }
58
+ else if (connection.capabilities.has('AUTH=XOAUTH') || connection.capabilities.has('AUTH=XOAUTH2')) {
59
+ // XOAUTH2 payload (Google-specific): simpler format, also \x01-delimited.
60
+ // Format: "user=<user>" \x01 "auth=Bearer <token>" \x01 \x01
61
+ oauthbearer = [`user=${username}`, `auth=Bearer ${accessToken}`, '', ''].join('\x01');
62
+ command = 'XOAUTH2';
63
+ // Empty breaker: XOAUTH2 expects an empty response to abort the SASL exchange
64
+ breaker = '';
65
+ }
66
+ let errorResponse = false;
67
+ try {
68
+ let response = await connection.exec('AUTHENTICATE', [
69
+ { type: 'ATOM', value: command },
70
+ { type: 'ATOM', value: Buffer.from(oauthbearer).toString('base64'), sensitive: true }
71
+ ], {
72
+ // Server sends a "+" continuation if auth fails, with a base64 JSON error payload.
73
+ // We decode it for diagnostics, then send the breaker to terminate the exchange.
74
+ onPlusTag: async (resp) => {
75
+ if (resp.attributes && resp.attributes[0] && resp.attributes[0].type === 'TEXT') {
76
+ try {
77
+ errorResponse = JSON.parse(Buffer.from(resp.attributes[0].value, 'base64').toString());
78
+ }
79
+ catch (err) {
80
+ connection.log.debug({
81
+ msg: 'Failed to parse OAuth error response',
82
+ errorResponse: resp.attributes[0].value,
83
+ err,
84
+ cid: connection.id
85
+ });
86
+ }
87
+ }
88
+ connection.log.debug({ src: 'c', msg: breaker, comment: `Error response for ${command}`, cid: connection.id });
89
+ connection.write(breaker);
90
+ }
91
+ });
92
+ response.next();
93
+ connection.authCapabilities.set(`AUTH=${command}`, true);
94
+ return username;
95
+ }
96
+ catch (err) {
97
+ throw await handleAuthError(err, errorResponse);
98
+ }
99
+ }
100
+ /**
101
+ * Authenticates using the SASL LOGIN mechanism.
102
+ *
103
+ * @param connection - IMAP connection instance
104
+ * @param username - The username to authenticate with
105
+ * @param password - The password to authenticate with
106
+ * @returns The authenticated username
107
+ * @throws {Error} If authentication fails
108
+ */
109
+ async function authLogin(connection, username, password) {
110
+ let errorResponse = false;
111
+ try {
112
+ // SASL LOGIN is a challenge-response mechanism: the server sends base64-encoded
113
+ // prompts ("Username:" and "Password:") and the client responds with base64-encoded values.
114
+ let response = await connection.exec('AUTHENTICATE', [{ type: 'ATOM', value: 'LOGIN' }], {
115
+ onPlusTag: async (resp) => {
116
+ if (resp.attributes && resp.attributes[0] && resp.attributes[0].type === 'TEXT') {
117
+ // Decode the server's base64 challenge to determine what it's asking for.
118
+ // Strip trailing colons and null bytes (\x00) that some servers append to the prompt.
119
+ let question = Buffer.from(resp.attributes[0].value, 'base64')
120
+ .toString()
121
+ .toLowerCase()
122
+ .replace(/[:\x00]*$/, '');
123
+ if (question === 'username' || question === 'user name') {
124
+ let encodedUsername = Buffer.from(username).toString('base64');
125
+ connection.log.debug({ src: 'c', msg: encodedUsername, comment: `Encoded username for AUTH=LOGIN`, cid: connection.id });
126
+ connection.write(encodedUsername);
127
+ }
128
+ else if (question === 'password') {
129
+ connection.log.debug({ src: 'c', msg: '(* value hidden *)', comment: `Encoded password for AUTH=LOGIN`, cid: connection.id });
130
+ connection.write(Buffer.from(password).toString('base64'));
131
+ }
132
+ else {
133
+ throw new Error(`Unknown LOGIN question "${question}"`);
134
+ }
135
+ }
136
+ }
137
+ });
138
+ response.next();
139
+ connection.authCapabilities.set(`AUTH=LOGIN`, true);
140
+ return username;
141
+ }
142
+ catch (err) {
143
+ throw await handleAuthError(err, errorResponse);
144
+ }
145
+ }
146
+ /**
147
+ * Authenticates using the SASL PLAIN mechanism.
148
+ *
149
+ * @param connection - IMAP connection instance
150
+ * @param username - The authentication identity (authcid)
151
+ * @param password - The password to authenticate with
152
+ * @param authzid - Optional authorization identity to impersonate
153
+ * @returns The authorized identity (authzid if provided, otherwise username)
154
+ * @throws {Error} If authentication fails
155
+ */
156
+ async function authPlain(connection, username, password, authzid) {
157
+ let errorResponse = false;
158
+ try {
159
+ let response = await connection.exec('AUTHENTICATE', [{ type: 'ATOM', value: 'PLAIN' }], {
160
+ onPlusTag: async () => {
161
+ // SASL PLAIN format: [authzid]\x00authcid\x00password
162
+ // authzid: authorization identity (who to impersonate)
163
+ // authcid: authentication identity (who is authenticating)
164
+ let authzidValue = authzid || '';
165
+ let encodedResponse = Buffer.from([authzidValue, username, password].join('\x00')).toString('base64');
166
+ let loggedResponse = Buffer.from([authzidValue, username, '(* value hidden *)'].join('\x00')).toString('base64');
167
+ connection.log.debug({
168
+ src: 'c',
169
+ msg: loggedResponse,
170
+ comment: `Encoded response for AUTH=PLAIN${authzid ? ' with authzid' : ''}`,
171
+ cid: connection.id
172
+ });
173
+ connection.write(encodedResponse);
174
+ }
175
+ });
176
+ response.next();
177
+ connection.authCapabilities.set(`AUTH=PLAIN`, true);
178
+ // Return the identity we're authorized as (authzid if provided, otherwise username)
179
+ return authzid || username;
180
+ }
181
+ catch (err) {
182
+ throw await handleAuthError(err, errorResponse);
183
+ }
184
+ }
185
+ /**
186
+ * Authenticates user using the best available method.
187
+ *
188
+ * @param connection - IMAP connection instance
189
+ * @param username - The username to authenticate with
190
+ * @param credentials - Authentication credentials
191
+ * @returns The authenticated username, or undefined if already authenticated
192
+ * @throws {Error} If no supported authentication mechanism is available or if authentication fails
193
+ */
194
+ async function authenticate(connection, username, { accessToken, password, loginMethod, authzid }) {
195
+ if (connection.state !== connection.states.NOT_AUTHENTICATED) {
196
+ // nothing to do here
197
+ return;
198
+ }
199
+ // Authentication method selection order:
200
+ // 1. OAuth (OAUTHBEARER > XOAUTH2), preferred when an accessToken is provided,
201
+ // as it avoids transmitting passwords entirely.
202
+ // 2. SASL PLAIN, preferred over LOGIN because it supports authzid (impersonation)
203
+ // and sends credentials in a single round trip.
204
+ // 3. SASL LOGIN, the fallback; an older challenge-response mechanism (two round trips).
205
+ // If loginMethod is explicitly set, it overrides the automatic capability-based selection.
206
+ if (accessToken) {
207
+ // AUTH=OAUTHBEARER and AUTH=XOAUTH in the context of OAuth2 or very similar so we can handle these together
208
+ if (connection.capabilities.has('AUTH=OAUTHBEARER') || connection.capabilities.has('AUTH=XOAUTH') || connection.capabilities.has('AUTH=XOAUTH2')) {
209
+ return await authOauth(connection, username, accessToken);
210
+ }
211
+ }
212
+ if (password) {
213
+ if ((!loginMethod && connection.capabilities.has('AUTH=PLAIN')) || loginMethod === 'AUTH=PLAIN') {
214
+ return await authPlain(connection, username, password, authzid);
215
+ }
216
+ if ((!loginMethod && connection.capabilities.has('AUTH=LOGIN')) || loginMethod === 'AUTH=LOGIN') {
217
+ return await authLogin(connection, username, password);
218
+ }
219
+ }
220
+ throw new Error('Unsupported authentication mechanism');
221
+ }
222
+ module.exports = exports.default;
223
+ 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
+ * Refreshes capabilities from server.
4
+ *
5
+ * @param connection - IMAP connection instance
6
+ * @returns Server capabilities map, or false on failure
7
+ */
8
+ export default function capability(connection: ImapFlow): Promise<Map<string, boolean | number> | false>;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = capability;
4
+ /**
5
+ * Refreshes capabilities from server.
6
+ *
7
+ * @param connection - IMAP connection instance
8
+ * @returns Server capabilities map, or false on failure
9
+ */
10
+ // Capabilities are normally received and updated by the global response handler
11
+ // (e.g., from the server greeting or after authentication). This explicit CAPABILITY
12
+ // command is only needed when capabilities must be refreshed on demand, such as
13
+ // after STARTTLS or when the server signals a capability change.
14
+ async function capability(connection) {
15
+ if (connection.capabilities.size && !connection.expectCapabilityUpdate) {
16
+ return connection.capabilities;
17
+ }
18
+ let response;
19
+ try {
20
+ // The actual parsing of the untagged CAPABILITY response is handled by the
21
+ // global handler, not here. We just trigger the server to send it.
22
+ response = await connection.exec('CAPABILITY');
23
+ response.next();
24
+ return connection.capabilities;
25
+ }
26
+ catch (err) {
27
+ connection.log.warn({ err, cid: connection.id });
28
+ return false;
29
+ }
30
+ }
31
+ module.exports = exports.default;
32
+ 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
+ * Closes the currently selected mailbox.
4
+ *
5
+ * @param connection - IMAP connection instance
6
+ * @returns True on success, false on failure, or undefined if not in SELECTED state
7
+ */
8
+ export default function close(connection: ImapFlow): Promise<boolean | undefined>;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = close;
4
+ /**
5
+ * Closes the currently selected mailbox.
6
+ *
7
+ * @param connection - IMAP connection instance
8
+ * @returns True on success, false on failure, or undefined if not in SELECTED state
9
+ */
10
+ async function close(connection) {
11
+ if (connection.state !== connection.states.SELECTED) {
12
+ // nothing to do here
13
+ return;
14
+ }
15
+ let response;
16
+ try {
17
+ // IMAP CLOSE (RFC 3501 6.4.2): permanently removes all messages flagged \Deleted
18
+ // from the currently selected mailbox (implicit expunge) and deselects it.
19
+ // Unlike EXPUNGE, CLOSE does not send individual untagged EXPUNGE responses.
20
+ response = await connection.exec('CLOSE');
21
+ response.next();
22
+ // Transition from SELECTED back to AUTHENTICATED state.
23
+ // Clear mailbox metadata so subsequent operations know no mailbox is selected.
24
+ let currentMailbox = connection.mailbox;
25
+ connection.mailbox = false;
26
+ connection.currentSelectCommand = false;
27
+ connection.state = connection.states.AUTHENTICATED;
28
+ if (currentMailbox) {
29
+ connection.emit('mailboxClose', currentMailbox);
30
+ }
31
+ return true;
32
+ }
33
+ catch (err) {
34
+ connection.log.warn({ err, cid: connection.id });
35
+ return false;
36
+ }
37
+ }
38
+ module.exports = exports.default;
39
+ 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
+ * Requests DEFLATE compression from the server.
4
+ *
5
+ * @param connection - IMAP connection instance
6
+ * @returns True if compression was enabled, false otherwise
7
+ */
8
+ export default function compress(connection: ImapFlow): Promise<boolean>;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = compress;
4
+ /**
5
+ * Requests DEFLATE compression from the server.
6
+ *
7
+ * @param connection - IMAP connection instance
8
+ * @returns True if compression was enabled, false otherwise
9
+ */
10
+ // COMPRESS=DEFLATE (RFC 4978): enables zlib compression on the IMAP connection
11
+ // to reduce bandwidth. Once enabled, all subsequent data in both directions is compressed.
12
+ async function compress(connection) {
13
+ // Skip if the server doesn't support COMPRESS=DEFLATE, or if compression
14
+ // is already active (connection._inflate exists) to avoid double-compression.
15
+ if (!connection.capabilities.has('COMPRESS=DEFLATE') || connection._inflate) {
16
+ // nothing to do here
17
+ return false;
18
+ }
19
+ let response;
20
+ try {
21
+ response = await connection.exec('COMPRESS', [{ type: 'ATOM', value: 'DEFLATE' }]);
22
+ }
23
+ catch (err) {
24
+ // The server declined (NO/BAD): nothing switched, staying uncompressed is safe.
25
+ connection.log.warn({ err, cid: connection.id });
26
+ return false;
27
+ }
28
+ // Everything after the tagged OK is already deflate-framed (RFC 4978 section 4) -
29
+ // the server switches at the OK, so declining the upgrade at this point is not a
30
+ // protocol option. The socket stays piped into the plaintext parser until the
31
+ // transport swaps in the inflater, so bytes that arrived in the same chunk as the
32
+ // OK have been consumed as cleartext and are missing from the head of the deflate
33
+ // stream: the session is unrecoverable in both directions. Fail it immediately
34
+ // (the same way STARTTLS treats post-OK trailing data) instead of letting it die
35
+ // slowly on garbage. Closing this window without failing needs the stream to hand
36
+ // back its unconsumed tail on unpipe so the transport can feed it into the
37
+ // inflater - not something a command module can reach from here.
38
+ if (response.hasTrailingData) {
39
+ let error = new Error('Server sent data between the COMPRESS response and the compression layer switch');
40
+ error.code = 'COMPRESS_TRAILING_DATA';
41
+ connection.log.error({ err: error, cid: connection.id });
42
+ // Schedule the close before releasing parser backpressure, so the buffered
43
+ // deflate-framed bytes cannot settle anything before teardown begins. This is
44
+ // why the decision lives here rather than at the connection layer the way the
45
+ // STARTTLS guard does (starttls.ts records a flag, upgradeToSTARTTLS decides):
46
+ // only the command module holds the response before its backpressure release,
47
+ // so only it can order teardown ahead of that release.
48
+ connection.closeAfter();
49
+ response.next();
50
+ throw error;
51
+ }
52
+ response.next();
53
+ return true;
54
+ }
55
+ module.exports = exports.default;
56
+ 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
+ * Copies 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 - Copy options
10
+ * @param options.uid - If true, use UID COPY instead of COPY
11
+ * @returns Copy result with UID mapping if available, false on failure, or undefined if preconditions not met
12
+ */
13
+ export default function copy(connection: ImapFlow, range: string, destination: string | string[], options?: MessageRangeOptions | undefined): Promise<CopyResponseObject | false | undefined>;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = copy;
4
+ const tools_js_1 = require("../tools.js");
5
+ const copyuid_parser_js_1 = require("./copyuid-parser.js");
6
+ /**
7
+ * Copies messages from the current mailbox to another mailbox.
8
+ *
9
+ * @param connection - IMAP connection instance
10
+ * @param range - Message sequence number or UID range
11
+ * @param destination - Destination mailbox path
12
+ * @param options - Copy options
13
+ * @param options.uid - If true, use UID COPY instead of COPY
14
+ * @returns Copy result with UID mapping if available, false on failure, or undefined if preconditions not met
15
+ */
16
+ async function copy(connection, range, destination, options) {
17
+ if (connection.state !== connection.states.SELECTED || !range || !destination) {
18
+ // nothing to do here
19
+ return;
20
+ }
21
+ options = options || {};
22
+ destination = (0, tools_js_1.normalizePath)(connection, destination);
23
+ let attributes = [
24
+ { type: 'SEQUENCE', value: range },
25
+ { type: 'ATOM', value: (0, tools_js_1.encodePath)(connection, destination) }
26
+ ];
27
+ let response;
28
+ try {
29
+ response = await connection.exec(options.uid ? 'UID COPY' : 'COPY', attributes);
30
+ response.next();
31
+ let map = { path: connection.mailbox.path, destination };
32
+ // UIDPLUS (RFC 4315): the server may include a COPYUID response code in the
33
+ // tagged OK response, providing a mapping from source UIDs to destination UIDs.
34
+ (0, copyuid_parser_js_1.parseCopyUid)(response.response, map);
35
+ return map;
36
+ }
37
+ catch (err) {
38
+ await (0, tools_js_1.enhanceCommandError)(err);
39
+ connection.log.warn({ err, cid: connection.id });
40
+ return false;
41
+ }
42
+ }
43
+ module.exports = exports.default;
44
+ Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
@@ -0,0 +1,11 @@
1
+ import type { ImapResponse } from '../handler/types.js';
2
+ import type { CopyResponseObject } from '../types.js';
3
+ /**
4
+ * Parses COPYUID response code from an IMAP response (RFC 4315).
5
+ * Used by both COPY and MOVE commands to extract the UID mapping
6
+ * from source mailbox to destination mailbox.
7
+ *
8
+ * @param response - IMAP response object with attributes
9
+ * @param map - Result map to populate with uidValidity and uidMap
10
+ */
11
+ export declare function parseCopyUid(response: ImapResponse, map: CopyResponseObject): void;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseCopyUid = parseCopyUid;
4
+ const tools_js_1 = require("../tools.js");
5
+ /**
6
+ * Parses COPYUID response code from an IMAP response (RFC 4315).
7
+ * Used by both COPY and MOVE commands to extract the UID mapping
8
+ * from source mailbox to destination mailbox.
9
+ *
10
+ * @param response - IMAP response object with attributes
11
+ * @param map - Result map to populate with uidValidity and uidMap
12
+ */
13
+ function parseCopyUid(response, map) {
14
+ let section = response.attributes && response.attributes[0] && response.attributes[0].section;
15
+ let responseCode = section && section.length && section[0] && typeof section[0].value === 'string' ? section[0].value : '';
16
+ if (responseCode !== 'COPYUID') {
17
+ return;
18
+ }
19
+ // A COPYUID code always comes with its section, see responseCode above
20
+ let codeSection = section;
21
+ // Only a bounded pure digit string is accepted: isNaN() also passes values like "1e5" or
22
+ // "Infinity", which BigInt() then rejects with a throw that loses the uidMap.
23
+ let uidValidity = (0, tools_js_1.parseBigIntValue)(codeSection[1] && codeSection[1].value);
24
+ if (uidValidity !== false) {
25
+ map.uidValidity = uidValidity;
26
+ }
27
+ const sourceUids = codeSection[2] && typeof codeSection[2].value === 'string' ? (0, tools_js_1.expandRange)(codeSection[2].value) : false;
28
+ const destinationUids = codeSection[3] && typeof codeSection[3].value === 'string' ? (0, tools_js_1.expandRange)(codeSection[3].value) : false;
29
+ if (sourceUids && destinationUids && sourceUids.length === destinationUids.length) {
30
+ map.uidMap = new Map(sourceUids.map((uid, i) => [uid, destinationUids[i]]));
31
+ }
32
+ }
@@ -0,0 +1,11 @@
1
+ import type { ImapFlow } from '../imap-flow.js';
2
+ import type { MailboxCreateResponse } from '../types.js';
3
+ /**
4
+ * Creates a new mailbox and subscribes to it.
5
+ *
6
+ * @param connection - IMAP connection instance
7
+ * @param path - Mailbox path to create
8
+ * @returns Object with path and creation status, or undefined if preconditions not met
9
+ * @throws If the CREATE command fails (except when mailbox already exists)
10
+ */
11
+ export default function create(connection: ImapFlow, path: string | string[]): Promise<MailboxCreateResponse | undefined>;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = create;
4
+ const tools_js_1 = require("../tools.js");
5
+ /**
6
+ * Creates a new mailbox and subscribes to it.
7
+ *
8
+ * @param connection - IMAP connection instance
9
+ * @param path - Mailbox path to create
10
+ * @returns Object with path and creation status, or undefined if preconditions not met
11
+ * @throws If the CREATE command fails (except when mailbox already exists)
12
+ */
13
+ async function create(connection, path) {
14
+ if (![connection.states.AUTHENTICATED, connection.states.SELECTED].includes(connection.state)) {
15
+ // nothing to do here
16
+ return;
17
+ }
18
+ path = (0, tools_js_1.normalizePath)(connection, path);
19
+ let response;
20
+ try {
21
+ let map = {
22
+ path
23
+ };
24
+ response = await connection.exec('CREATE', [{ type: 'ATOM', value: (0, tools_js_1.encodePath)(connection, path) }]);
25
+ // Parse the response code section (e.g., [MAILBOXID (<id>)]) from the tagged OK response.
26
+ // IMAP response code attributes are structured as alternating key-value pairs.
27
+ let section = response.response.attributes &&
28
+ response.response.attributes[0] &&
29
+ response.response.attributes[0].section &&
30
+ response.response.attributes[0].section.length
31
+ ? response.response.attributes[0].section
32
+ : false;
33
+ if (section) {
34
+ let key;
35
+ section.forEach((attribute, i) => {
36
+ // IMAP key-value pairs: even indices (i % 2 === 0) are keys, odd indices are values
37
+ if (i % 2 === 0) {
38
+ key = attribute && typeof attribute.value === 'string' ? attribute.value : false;
39
+ return;
40
+ }
41
+ if (!key) {
42
+ return;
43
+ }
44
+ let value;
45
+ switch (key.toLowerCase()) {
46
+ case 'mailboxid':
47
+ key = 'mailboxId';
48
+ value = Array.isArray(attribute) && attribute[0] && typeof attribute[0].value === 'string' ? attribute[0].value : false;
49
+ break;
50
+ }
51
+ if (key && value) {
52
+ map[key] = value;
53
+ }
54
+ });
55
+ }
56
+ map.created = true;
57
+ response.next();
58
+ // Auto-subscribe after creation so the new mailbox appears in LSUB listings
59
+ // and is visible to clients that only show subscribed folders.
60
+ await connection.run('SUBSCRIBE', path);
61
+ return map;
62
+ }
63
+ catch (err) {
64
+ let errorCode = (0, tools_js_1.getStatusCode)(err.response);
65
+ // ALREADYEXISTS (RFC 5530) means the mailbox already exists on the server.
66
+ // This is not a true error, we return created:false to indicate nothing was created.
67
+ if (errorCode === 'ALREADYEXISTS') {
68
+ // no need to do anything, mailbox already exists
69
+ return {
70
+ path,
71
+ created: false
72
+ };
73
+ }
74
+ await (0, tools_js_1.enhanceCommandError)(err);
75
+ connection.log.warn({ err, cid: connection.id });
76
+ throw err;
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,11 @@
1
+ import type { ImapFlow } from '../imap-flow.js';
2
+ import type { MailboxDeleteResponse } from '../types.js';
3
+ /**
4
+ * Deletes an existing mailbox.
5
+ *
6
+ * @param connection - IMAP connection instance
7
+ * @param path - Mailbox path to delete
8
+ * @returns Object with the deleted path, or undefined if preconditions not met
9
+ * @throws If the DELETE command fails
10
+ */
11
+ export default function deleteMailbox(connection: ImapFlow, path: string | string[]): Promise<MailboxDeleteResponse | undefined>;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = deleteMailbox;
4
+ const tools_js_1 = require("../tools.js");
5
+ /**
6
+ * Deletes an existing mailbox.
7
+ *
8
+ * @param connection - IMAP connection instance
9
+ * @param path - Mailbox path to delete
10
+ * @returns Object with the deleted path, or undefined if preconditions not met
11
+ * @throws If the DELETE command fails
12
+ */
13
+ async function deleteMailbox(connection, path) {
14
+ if (![connection.states.AUTHENTICATED, connection.states.SELECTED].includes(connection.state)) {
15
+ // nothing to do here
16
+ return;
17
+ }
18
+ path = (0, tools_js_1.normalizePath)(connection, path);
19
+ // If the mailbox to delete is currently selected, we must close/deselect it first.
20
+ // IMAP servers reject DELETE on the currently selected mailbox (RFC 3501 6.3.4).
21
+ if (connection.state === connection.states.SELECTED && connection.mailbox.path === path) {
22
+ await connection.run('CLOSE');
23
+ }
24
+ let response;
25
+ try {
26
+ let map = {
27
+ path
28
+ };
29
+ response = await connection.exec('DELETE', [{ type: 'ATOM', value: (0, tools_js_1.encodePath)(connection, path) }]);
30
+ response.next();
31
+ return map;
32
+ }
33
+ catch (err) {
34
+ await (0, tools_js_1.enhanceCommandError)(err);
35
+ connection.log.warn({ err, cid: connection.id });
36
+ throw err;
37
+ }
38
+ }
39
+ module.exports = exports.default;
40
+ 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
+ * Enables IMAP extensions on the server.
4
+ *
5
+ * @param connection - IMAP connection instance
6
+ * @param extensionList - List of extension names to enable
7
+ * @returns Set of enabled extensions, false on failure, or undefined if not applicable
8
+ */
9
+ export default function enable(connection: ImapFlow, extensionList: string[]): Promise<Set<string> | false | undefined>;