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,5 @@
1
+ "use strict";
2
+ // Public data shapes of the ImapFlow API. Every optional property is declared with an
3
+ // explicit `| undefined` so that a consumer compiling with exactOptionalPropertyTypes
4
+ // can still pass a value that may be undefined.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ export declare const resolveCharset: (charset: string) => string | null;
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  // Subset of the IANA Character Sets registry (https://www.iana.org/assignments/character-sets/).
4
2
  // Used to validate and resolve charset names found in MIME Content-Type parameters.
5
3
  // This list covers the most commonly encountered charsets in email messages.
@@ -262,9 +260,7 @@ const CHARACTER_SETS = [
262
260
  'TIS-620',
263
261
  'CP50220'
264
262
  ];
265
-
266
263
  const CHARSET_MAP = new Map();
267
-
268
264
  // Build a lookup map with normalized keys for fuzzy charset resolution.
269
265
  // Normalization strategy:
270
266
  // 1. Strip all underscores, hyphens, and spaces, then lowercase (e.g., "ISO-8859-1" -> "iso88591")
@@ -284,12 +280,11 @@ CHARACTER_SETS.forEach(entry => {
284
280
  CHARSET_MAP.set(modifiedKey, entry);
285
281
  }
286
282
  });
287
-
288
283
  // Resolves a charset name to its canonical IANA form using case-insensitive,
289
284
  // symbol-stripping normalization. For example, "WIN-1252", "windows_1252",
290
285
  // and "WINDOWS-1252" all resolve to "windows-1252".
291
286
  // Returns null if the charset is not recognized.
292
- module.exports.resolveCharset = charset => {
287
+ export const resolveCharset = (charset) => {
293
288
  let key = charset.replace(/[_-\s]/g, '').toLowerCase();
294
289
  return CHARSET_MAP.get(key) ?? null;
295
290
  };
@@ -0,0 +1,22 @@
1
+ import type { ImapFlow } from '../imap-flow.js';
2
+ import type { AppendResponseObject } from '../types.js';
3
+ /**
4
+ * Result of an APPEND: the destination, the selected mailbox path at the time of the append,
5
+ * and the UID information the server reported
6
+ */
7
+ export interface AppendResult extends AppendResponseObject {
8
+ /** Path of the currently selected mailbox, if any */
9
+ path?: string | undefined;
10
+ }
11
+ /**
12
+ * Appends a message to a mailbox.
13
+ *
14
+ * @param connection - IMAP connection instance
15
+ * @param destination - Destination mailbox path
16
+ * @param content - Message content (RFC 822 format)
17
+ * @param flags - Message flags to set on the appended message
18
+ * @param idate - Internal date to set for the message
19
+ * @returns Append result with UID info if available, or undefined if preconditions not met
20
+ * @throws {Error} If the APPEND command fails or message exceeds APPENDLIMIT
21
+ */
22
+ export default function append(connection: ImapFlow, destination: string | string[], content: Buffer | string, flags?: string | string[] | undefined, idate?: Date | string | false | undefined): Promise<AppendResult | undefined>;
@@ -1,39 +1,23 @@
1
- 'use strict';
2
-
3
- const {
4
- formatFlag,
5
- canUseFlag,
6
- formatDateTime,
7
- normalizePath,
8
- encodePath,
9
- comparePaths,
10
- enhanceCommandError,
11
- parseBigIntValue,
12
- parseUintValue,
13
- MAX_UINT32_DIGITS
14
- } = require('../tools.js');
15
-
1
+ import { formatFlag, canUseFlag, formatDateTime, normalizePath, encodePath, comparePaths, enhanceCommandError, parseBigIntValue, parseUintValue, MAX_UINT32_DIGITS } from '../tools.js';
16
2
  /**
17
3
  * Appends a message to a mailbox.
18
4
  *
19
- * @param {Object} connection - IMAP connection instance
20
- * @param {string} destination - Destination mailbox path
21
- * @param {Buffer|string} content - Message content (RFC 822 format)
22
- * @param {string|string[]} [flags] - Message flags to set on the appended message
23
- * @param {Date|string} [idate] - Internal date to set for the message
24
- * @returns {Promise<{destination: string, path?: string, uid?: number, uidValidity?: BigInt, seq?: number}|undefined>} Append result with UID info if available, or undefined if preconditions not met
5
+ * @param connection - IMAP connection instance
6
+ * @param destination - Destination mailbox path
7
+ * @param content - Message content (RFC 822 format)
8
+ * @param flags - Message flags to set on the appended message
9
+ * @param idate - Internal date to set for the message
10
+ * @returns Append result with UID info if available, or undefined if preconditions not met
25
11
  * @throws {Error} If the APPEND command fails or message exceeds APPENDLIMIT
26
12
  */
27
- module.exports = async (connection, destination, content, flags, idate) => {
13
+ export default async function append(connection, destination, content, flags, idate) {
28
14
  if (![connection.states.AUTHENTICATED, connection.states.SELECTED].includes(connection.state) || !destination) {
29
15
  // nothing to do here
30
16
  return;
31
17
  }
32
-
33
18
  if (typeof content === 'string') {
34
19
  content = Buffer.from(content);
35
20
  }
36
-
37
21
  // APPENDLIMIT capability (RFC 7889): server may advertise the maximum message
38
22
  // size it accepts. Check before sending to avoid a wasted round-trip.
39
23
  if (connection.capabilities.has('APPENDLIMIT')) {
@@ -44,34 +28,26 @@ module.exports = async (connection, destination, content, flags, idate) => {
44
28
  throw err;
45
29
  }
46
30
  }
47
-
48
31
  destination = normalizePath(connection, destination);
49
-
50
32
  // If appending to the currently selected mailbox, we can listen for the
51
33
  // untagged EXISTS response to capture the new message's sequence number.
52
34
  let expectExists = comparePaths(connection, connection.mailbox.path, destination);
53
-
54
35
  // Validate and format flags. Only flags allowed by the mailbox's permanentFlags are included.
55
36
  flags = (Array.isArray(flags) ? flags : [].concat(flags || []))
56
37
  .map(flag => flag && formatFlag(flag.toString()))
57
- .filter(flag => flag && canUseFlag(connection.mailbox, flag));
58
-
38
+ .filter((flag) => !!flag && canUseFlag(connection.mailbox, flag));
59
39
  // APPEND command format: APPEND <mailbox> [<flags>] [<date-time>] <literal>
60
40
  let attributes = [{ type: 'ATOM', value: encodePath(connection, destination) }];
61
-
62
41
  // Internal date: the date the server should record for this message.
63
42
  // Must be quoted (STRING type) per the IMAP date-time grammar.
64
43
  idate = idate ? formatDateTime(idate) : false;
65
-
66
44
  // Flags and date are optional; flags must come before date if both are present
67
45
  if (flags.length || idate) {
68
46
  attributes.push(flags.map(flag => ({ type: 'ATOM', value: flag })));
69
47
  }
70
-
71
48
  if (idate) {
72
49
  attributes.push({ type: 'STRING', value: idate });
73
50
  }
74
-
75
51
  // BINARY extension (RFC 3516): if the message content contains NUL bytes,
76
52
  // use literal8 syntax (~{size}\r\n) instead of regular literal ({size}\r\n).
77
53
  // Regular literals cannot contain NUL bytes per the IMAP grammar.
@@ -79,17 +55,14 @@ module.exports = async (connection, destination, content, flags, idate) => {
79
55
  if (connection.capabilities.has('BINARY') && !connection.disableBinary) {
80
56
  isLiteral8 = content.indexOf(Buffer.from([0])) >= 0;
81
57
  }
82
-
83
58
  attributes.push({ type: 'LITERAL', value: content, isLiteral8 });
84
-
85
59
  let map = { destination };
86
60
  if (connection.mailbox && connection.mailbox.path) {
87
61
  map.path = connection.mailbox.path;
88
62
  }
89
-
90
63
  // Handler for untagged EXISTS: captures the new message count which gives
91
64
  // us the sequence number of the appended message (it's the latest message).
92
- const handleExistsUpdate = untagged => {
65
+ const handleExistsUpdate = (untagged) => {
93
66
  // The count is written into the live mailbox state below, so it has to clear the
94
67
  // same bar untaggedExists() applies: a digit run long enough to coerce to Infinity
95
68
  // would make resolveRange('*') compile to the literal string "Infinity" and break
@@ -99,34 +72,33 @@ module.exports = async (connection, destination, content, flags, idate) => {
99
72
  return;
100
73
  }
101
74
  map.seq = seq;
102
-
103
75
  // Update the connection's mailbox state and emit 'exists' event if the
104
76
  // count changed (notifies listeners about the new message).
105
77
  if (expectExists) {
106
- let prevCount = connection.mailbox.exists;
78
+ let mailbox = connection.mailbox;
79
+ let prevCount = mailbox.exists;
107
80
  if (map.seq !== prevCount) {
108
- connection.mailbox.exists = map.seq;
81
+ mailbox.exists = map.seq;
109
82
  connection.emit('exists', {
110
- path: connection.mailbox.path,
83
+ path: mailbox.path,
111
84
  count: map.seq,
112
85
  prevCount
113
86
  });
114
87
  }
115
88
  }
116
89
  };
117
-
118
90
  let response;
119
91
  try {
120
92
  response = await connection.exec('APPEND', attributes, {
121
93
  // Only listen for EXISTS if we're appending to the currently selected mailbox
122
94
  untagged: expectExists ? { EXISTS: handleExistsUpdate } : false
123
95
  });
124
-
125
96
  // UIDPLUS (RFC 4315): the server may include APPENDUID response code in
126
97
  // the tagged OK. Format: [APPENDUID <uidValidity> <uid>]
127
98
  let section = response.response.attributes && response.response.attributes[0] && response.response.attributes[0].section;
128
99
  if (section && section.length) {
129
- let responseCode = section[0] && typeof section[0].value === 'string' ? section[0].value : '';
100
+ let first = section[0];
101
+ let responseCode = first && typeof first.value === 'string' ? first.value : '';
130
102
  if (responseCode.toUpperCase() === 'APPENDUID') {
131
103
  // Bounded digit runs only: isNaN() also passes '1e5', which BigInt() rejects
132
104
  // with a throw - and this catch rethrows, so the append would reject after the
@@ -141,9 +113,7 @@ module.exports = async (connection, destination, content, flags, idate) => {
141
113
  }
142
114
  }
143
115
  }
144
-
145
116
  response.next();
146
-
147
117
  // If we didn't get an EXISTS during APPEND (some servers don't send it
148
118
  // until the next command), issue a NOOP to flush pending notifications.
149
119
  if (expectExists && !map.seq) {
@@ -153,24 +123,24 @@ module.exports = async (connection, destination, content, flags, idate) => {
153
123
  comment: 'Sequence not found from APPEND output'
154
124
  });
155
125
  response.next();
156
- } catch (err) {
126
+ }
127
+ catch (err) {
157
128
  connection.log.warn({ err, cid: connection.id });
158
129
  }
159
130
  }
160
-
161
131
  // If we have a sequence number but no UID (server doesn't support UIDPLUS),
162
132
  // look up the UID via SEARCH to provide a consistent result to the caller.
163
133
  if (map.seq && !map.uid) {
164
134
  let list = await connection.search({ seq: map.seq }, { uid: true });
165
- if (list && list.length) {
135
+ if (Array.isArray(list) && list.length) {
166
136
  map.uid = list[0];
167
137
  }
168
138
  }
169
-
170
139
  return map;
171
- } catch (err) {
140
+ }
141
+ catch (err) {
172
142
  await enhanceCommandError(err);
173
143
  connection.log.warn({ err, cid: connection.id });
174
144
  throw err;
175
145
  }
176
- };
146
+ }
@@ -0,0 +1,24 @@
1
+ import type { ImapFlow } from '../imap-flow.js';
2
+ /**
3
+ * Credentials for the AUTHENTICATE command
4
+ */
5
+ export interface AuthenticateCredentials {
6
+ /** OAuth2 access token for OAUTHBEARER/XOAUTH2 authentication */
7
+ accessToken?: string | undefined;
8
+ /** Password for PLAIN or LOGIN authentication */
9
+ password?: string | undefined;
10
+ /** Force a specific login method (e.g., 'AUTH=PLAIN', 'AUTH=LOGIN') */
11
+ loginMethod?: string | undefined;
12
+ /** Authorization identity for PLAIN authentication */
13
+ authzid?: string | undefined;
14
+ }
15
+ /**
16
+ * Authenticates user using the best available method.
17
+ *
18
+ * @param connection - IMAP connection instance
19
+ * @param username - The username to authenticate with
20
+ * @param credentials - Authentication credentials
21
+ * @returns The authenticated username, or undefined if already authenticated
22
+ * @throws {Error} If no supported authentication mechanism is available or if authentication fails
23
+ */
24
+ export default function authenticate(connection: ImapFlow, username: string, { accessToken, password, loginMethod, authzid }: AuthenticateCredentials): Promise<string | undefined>;
@@ -1,13 +1,10 @@
1
- 'use strict';
2
-
3
- const { getStatusCode, getErrorText } = require('../tools.js');
4
-
1
+ import { getStatusCode, getErrorText } from '../tools.js';
5
2
  /**
6
3
  * Handles authentication errors by enriching the error object with server response details.
7
4
  *
8
- * @param {Error} err - The original authentication error
9
- * @param {Object} [errorResponse] - Optional OAuth error response from the server
10
- * @returns {Error} The enriched error; the caller is expected to throw it
5
+ * @param err - The original authentication error
6
+ * @param errorResponse - Optional OAuth error response from the server
7
+ * @returns The enriched error; the caller is expected to throw it
11
8
  */
12
9
  async function handleAuthError(err, errorResponse) {
13
10
  let errorCode = getStatusCode(err.response);
@@ -21,21 +18,19 @@ async function handleAuthError(err, errorResponse) {
21
18
  }
22
19
  return err;
23
20
  }
24
-
25
21
  /**
26
22
  * Authenticates using OAuth (OAUTHBEARER or XOAUTH2).
27
23
  *
28
- * @param {Object} connection - IMAP connection instance
29
- * @param {string} username - The username to authenticate with
30
- * @param {string} accessToken - The OAuth2 access token
31
- * @returns {Promise<string>} The authenticated username
24
+ * @param connection - IMAP connection instance
25
+ * @param username - The username to authenticate with
26
+ * @param accessToken - The OAuth2 access token
27
+ * @returns The authenticated username
32
28
  * @throws {Error} If authentication fails
33
29
  */
34
30
  async function authOauth(connection, username, accessToken) {
35
31
  let oauthbearer;
36
32
  let command;
37
33
  let breaker;
38
-
39
34
  if (connection.capabilities.has('AUTH=OAUTHBEARER')) {
40
35
  // OAUTHBEARER payload per RFC 7628: fields separated by \x01 (SASL GS2 framing).
41
36
  // Format: "n,a=<user>," \x01 "host=..." \x01 "port=..." \x01 "auth=Bearer <token>" \x01 \x01
@@ -54,9 +49,10 @@ async function authOauth(connection, username, accessToken) {
54
49
  ''
55
50
  ].join('\x01');
56
51
  command = 'OAUTHBEARER';
57
- // "AQ==" is base64 for \x01 -- sent as the error continuation to abort the SASL exchange
52
+ // "AQ==" is base64 for \x01, sent as the error continuation to abort the SASL exchange
58
53
  breaker = 'AQ==';
59
- } else if (connection.capabilities.has('AUTH=XOAUTH') || connection.capabilities.has('AUTH=XOAUTH2')) {
54
+ }
55
+ else if (connection.capabilities.has('AUTH=XOAUTH') || connection.capabilities.has('AUTH=XOAUTH2')) {
60
56
  // XOAUTH2 payload (Google-specific): simpler format, also \x01-delimited.
61
57
  // Format: "user=<user>" \x01 "auth=Bearer <token>" \x01 \x01
62
58
  oauthbearer = [`user=${username}`, `auth=Bearer ${accessToken}`, '', ''].join('\x01');
@@ -64,54 +60,47 @@ async function authOauth(connection, username, accessToken) {
64
60
  // Empty breaker: XOAUTH2 expects an empty response to abort the SASL exchange
65
61
  breaker = '';
66
62
  }
67
-
68
63
  let errorResponse = false;
69
64
  try {
70
- let response = await connection.exec(
71
- 'AUTHENTICATE',
72
- [
73
- { type: 'ATOM', value: command },
74
- { type: 'ATOM', value: Buffer.from(oauthbearer).toString('base64'), sensitive: true }
75
- ],
76
- {
77
- // Server sends a "+" continuation if auth fails, with a base64 JSON error payload.
78
- // We decode it for diagnostics, then send the breaker to terminate the exchange.
79
- onPlusTag: async resp => {
80
- if (resp.attributes && resp.attributes[0] && resp.attributes[0].type === 'TEXT') {
81
- try {
82
- errorResponse = JSON.parse(Buffer.from(resp.attributes[0].value, 'base64').toString());
83
- } catch (err) {
84
- connection.log.debug({
85
- msg: 'Failed to parse OAuth error response',
86
- errorResponse: resp.attributes[0].value,
87
- err,
88
- cid: connection.id
89
- });
90
- }
65
+ let response = await connection.exec('AUTHENTICATE', [
66
+ { type: 'ATOM', value: command },
67
+ { type: 'ATOM', value: Buffer.from(oauthbearer).toString('base64'), sensitive: true }
68
+ ], {
69
+ // Server sends a "+" continuation if auth fails, with a base64 JSON error payload.
70
+ // We decode it for diagnostics, then send the breaker to terminate the exchange.
71
+ onPlusTag: async (resp) => {
72
+ if (resp.attributes && resp.attributes[0] && resp.attributes[0].type === 'TEXT') {
73
+ try {
74
+ errorResponse = JSON.parse(Buffer.from(resp.attributes[0].value, 'base64').toString());
75
+ }
76
+ catch (err) {
77
+ connection.log.debug({
78
+ msg: 'Failed to parse OAuth error response',
79
+ errorResponse: resp.attributes[0].value,
80
+ err,
81
+ cid: connection.id
82
+ });
91
83
  }
92
-
93
- connection.log.debug({ src: 'c', msg: breaker, comment: `Error response for ${command}`, cid: connection.id });
94
- connection.write(breaker);
95
84
  }
85
+ connection.log.debug({ src: 'c', msg: breaker, comment: `Error response for ${command}`, cid: connection.id });
86
+ connection.write(breaker);
96
87
  }
97
- );
88
+ });
98
89
  response.next();
99
-
100
90
  connection.authCapabilities.set(`AUTH=${command}`, true);
101
-
102
91
  return username;
103
- } catch (err) {
92
+ }
93
+ catch (err) {
104
94
  throw await handleAuthError(err, errorResponse);
105
95
  }
106
96
  }
107
-
108
97
  /**
109
98
  * Authenticates using the SASL LOGIN mechanism.
110
99
  *
111
- * @param {Object} connection - IMAP connection instance
112
- * @param {string} username - The username to authenticate with
113
- * @param {string} password - The password to authenticate with
114
- * @returns {Promise<string>} The authenticated username
100
+ * @param connection - IMAP connection instance
101
+ * @param username - The username to authenticate with
102
+ * @param password - The password to authenticate with
103
+ * @returns The authenticated username
115
104
  * @throws {Error} If authentication fails
116
105
  */
117
106
  async function authLogin(connection, username, password) {
@@ -120,47 +109,45 @@ async function authLogin(connection, username, password) {
120
109
  // SASL LOGIN is a challenge-response mechanism: the server sends base64-encoded
121
110
  // prompts ("Username:" and "Password:") and the client responds with base64-encoded values.
122
111
  let response = await connection.exec('AUTHENTICATE', [{ type: 'ATOM', value: 'LOGIN' }], {
123
- onPlusTag: async resp => {
112
+ onPlusTag: async (resp) => {
124
113
  if (resp.attributes && resp.attributes[0] && resp.attributes[0].type === 'TEXT') {
125
114
  // Decode the server's base64 challenge to determine what it's asking for.
126
115
  // Strip trailing colons and null bytes (\x00) that some servers append to the prompt.
127
116
  let question = Buffer.from(resp.attributes[0].value, 'base64')
128
117
  .toString()
129
118
  .toLowerCase()
130
- .replace(/[:\x00]*$/, ''); // eslint-disable-line no-control-regex
131
-
119
+ .replace(/[:\x00]*$/, '');
132
120
  if (question === 'username' || question === 'user name') {
133
121
  let encodedUsername = Buffer.from(username).toString('base64');
134
122
  connection.log.debug({ src: 'c', msg: encodedUsername, comment: `Encoded username for AUTH=LOGIN`, cid: connection.id });
135
123
  connection.write(encodedUsername);
136
- } else if (question === 'password') {
124
+ }
125
+ else if (question === 'password') {
137
126
  connection.log.debug({ src: 'c', msg: '(* value hidden *)', comment: `Encoded password for AUTH=LOGIN`, cid: connection.id });
138
127
  connection.write(Buffer.from(password).toString('base64'));
139
- } else {
128
+ }
129
+ else {
140
130
  throw new Error(`Unknown LOGIN question "${question}"`);
141
131
  }
142
132
  }
143
133
  }
144
134
  });
145
-
146
135
  response.next();
147
-
148
136
  connection.authCapabilities.set(`AUTH=LOGIN`, true);
149
-
150
137
  return username;
151
- } catch (err) {
138
+ }
139
+ catch (err) {
152
140
  throw await handleAuthError(err, errorResponse);
153
141
  }
154
142
  }
155
-
156
143
  /**
157
144
  * Authenticates using the SASL PLAIN mechanism.
158
145
  *
159
- * @param {Object} connection - IMAP connection instance
160
- * @param {string} username - The authentication identity (authcid)
161
- * @param {string} password - The password to authenticate with
162
- * @param {string} [authzid] - Optional authorization identity to impersonate
163
- * @returns {Promise<string>} The authorized identity (authzid if provided, otherwise username)
146
+ * @param connection - IMAP connection instance
147
+ * @param username - The authentication identity (authcid)
148
+ * @param password - The password to authenticate with
149
+ * @param authzid - Optional authorization identity to impersonate
150
+ * @returns The authorized identity (authzid if provided, otherwise username)
164
151
  * @throws {Error} If authentication fails
165
152
  */
166
153
  async function authPlain(connection, username, password, authzid) {
@@ -183,61 +170,49 @@ async function authPlain(connection, username, password, authzid) {
183
170
  connection.write(encodedResponse);
184
171
  }
185
172
  });
186
-
187
173
  response.next();
188
-
189
174
  connection.authCapabilities.set(`AUTH=PLAIN`, true);
190
-
191
175
  // Return the identity we're authorized as (authzid if provided, otherwise username)
192
176
  return authzid || username;
193
- } catch (err) {
177
+ }
178
+ catch (err) {
194
179
  throw await handleAuthError(err, errorResponse);
195
180
  }
196
181
  }
197
-
198
182
  /**
199
183
  * Authenticates user using the best available method.
200
184
  *
201
- * @param {Object} connection - IMAP connection instance
202
- * @param {string} username - The username to authenticate with
203
- * @param {Object} credentials - Authentication credentials
204
- * @param {string} [credentials.accessToken] - OAuth2 access token for OAUTHBEARER/XOAUTH2 authentication
205
- * @param {string} [credentials.password] - Password for PLAIN or LOGIN authentication
206
- * @param {string} [credentials.loginMethod] - Force a specific login method (e.g., 'AUTH=PLAIN', 'AUTH=LOGIN')
207
- * @param {string} [credentials.authzid] - Authorization identity for PLAIN authentication
208
- * @returns {Promise<string|undefined>} The authenticated username, or undefined if already authenticated
185
+ * @param connection - IMAP connection instance
186
+ * @param username - The username to authenticate with
187
+ * @param credentials - Authentication credentials
188
+ * @returns The authenticated username, or undefined if already authenticated
209
189
  * @throws {Error} If no supported authentication mechanism is available or if authentication fails
210
190
  */
211
- module.exports = async (connection, username, { accessToken, password, loginMethod, authzid }) => {
191
+ export default async function authenticate(connection, username, { accessToken, password, loginMethod, authzid }) {
212
192
  if (connection.state !== connection.states.NOT_AUTHENTICATED) {
213
193
  // nothing to do here
214
194
  return;
215
195
  }
216
-
217
196
  // Authentication method selection order:
218
- // 1. OAuth (OAUTHBEARER > XOAUTH2) -- preferred when an accessToken is provided,
197
+ // 1. OAuth (OAUTHBEARER > XOAUTH2), preferred when an accessToken is provided,
219
198
  // as it avoids transmitting passwords entirely.
220
- // 2. SASL PLAIN -- preferred over LOGIN because it supports authzid (impersonation)
199
+ // 2. SASL PLAIN, preferred over LOGIN because it supports authzid (impersonation)
221
200
  // and sends credentials in a single round trip.
222
- // 3. SASL LOGIN -- fallback; an older challenge-response mechanism (two round trips).
201
+ // 3. SASL LOGIN, the fallback; an older challenge-response mechanism (two round trips).
223
202
  // If loginMethod is explicitly set, it overrides the automatic capability-based selection.
224
-
225
203
  if (accessToken) {
226
204
  // AUTH=OAUTHBEARER and AUTH=XOAUTH in the context of OAuth2 or very similar so we can handle these together
227
205
  if (connection.capabilities.has('AUTH=OAUTHBEARER') || connection.capabilities.has('AUTH=XOAUTH') || connection.capabilities.has('AUTH=XOAUTH2')) {
228
206
  return await authOauth(connection, username, accessToken);
229
207
  }
230
208
  }
231
-
232
209
  if (password) {
233
210
  if ((!loginMethod && connection.capabilities.has('AUTH=PLAIN')) || loginMethod === 'AUTH=PLAIN') {
234
211
  return await authPlain(connection, username, password, authzid);
235
212
  }
236
-
237
213
  if ((!loginMethod && connection.capabilities.has('AUTH=LOGIN')) || loginMethod === 'AUTH=LOGIN') {
238
214
  return await authLogin(connection, username, password);
239
215
  }
240
216
  }
241
-
242
217
  throw new Error('Unsupported authentication mechanism');
243
- };
218
+ }
@@ -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>;
@@ -1,30 +1,27 @@
1
- 'use strict';
2
-
3
1
  /**
4
2
  * Refreshes capabilities from server.
5
3
  *
6
- * @param {Object} connection - IMAP connection instance
7
- * @returns {Promise<Map|boolean>} Server capabilities map, or false on failure
4
+ * @param connection - IMAP connection instance
5
+ * @returns Server capabilities map, or false on failure
8
6
  */
9
7
  // Capabilities are normally received and updated by the global response handler
10
8
  // (e.g., from the server greeting or after authentication). This explicit CAPABILITY
11
9
  // command is only needed when capabilities must be refreshed on demand, such as
12
10
  // after STARTTLS or when the server signals a capability change.
13
- module.exports = async connection => {
11
+ export default async function capability(connection) {
14
12
  if (connection.capabilities.size && !connection.expectCapabilityUpdate) {
15
13
  return connection.capabilities;
16
14
  }
17
-
18
15
  let response;
19
16
  try {
20
17
  // The actual parsing of the untagged CAPABILITY response is handled by the
21
18
  // global handler, not here. We just trigger the server to send it.
22
19
  response = await connection.exec('CAPABILITY');
23
-
24
20
  response.next();
25
21
  return connection.capabilities;
26
- } catch (err) {
22
+ }
23
+ catch (err) {
27
24
  connection.log.warn({ err, cid: connection.id });
28
25
  return false;
29
26
  }
30
- };
27
+ }
@@ -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>;
@@ -1,17 +1,14 @@
1
- 'use strict';
2
-
3
1
  /**
4
2
  * Closes the currently selected mailbox.
5
3
  *
6
- * @param {Object} connection - IMAP connection instance
7
- * @returns {Promise<boolean|undefined>} True on success, false on failure, or undefined if not in SELECTED state
4
+ * @param connection - IMAP connection instance
5
+ * @returns True on success, false on failure, or undefined if not in SELECTED state
8
6
  */
9
- module.exports = async connection => {
7
+ export default async function close(connection) {
10
8
  if (connection.state !== connection.states.SELECTED) {
11
9
  // nothing to do here
12
10
  return;
13
11
  }
14
-
15
12
  let response;
16
13
  try {
17
14
  // IMAP CLOSE (RFC 3501 6.4.2): permanently removes all messages flagged \Deleted
@@ -19,20 +16,19 @@ module.exports = async connection => {
19
16
  // Unlike EXPUNGE, CLOSE does not send individual untagged EXPUNGE responses.
20
17
  response = await connection.exec('CLOSE');
21
18
  response.next();
22
-
23
19
  // Transition from SELECTED back to AUTHENTICATED state.
24
20
  // Clear mailbox metadata so subsequent operations know no mailbox is selected.
25
21
  let currentMailbox = connection.mailbox;
26
22
  connection.mailbox = false;
27
23
  connection.currentSelectCommand = false;
28
24
  connection.state = connection.states.AUTHENTICATED;
29
-
30
25
  if (currentMailbox) {
31
26
  connection.emit('mailboxClose', currentMailbox);
32
27
  }
33
28
  return true;
34
- } catch (err) {
29
+ }
30
+ catch (err) {
35
31
  connection.log.warn({ err, cid: connection.id });
36
32
  return false;
37
33
  }
38
- };
34
+ }
@@ -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>;