imapflow 1.7.7 → 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 +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 +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 +785 -1802
  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 -57
  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 -873
  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 -593
@@ -0,0 +1,83 @@
1
+ import type { ImapResponse } from './handler/types.js';
2
+ /**
3
+ * An Error raised by ImapFlow, with the extra properties the library attaches to describe
4
+ * the failure. Every property is optional: which ones are present depends on where the
5
+ * error came from.
6
+ */
7
+ export interface ImapFlowError extends Error {
8
+ /** Error code, e.g. 'NoConnection', 'ETIMEOUT', 'LockTimeout' or a parser error code */
9
+ code?: string | undefined;
10
+ /** Connection id the error belongs to */
11
+ cid?: string | undefined;
12
+ /** Connection id, stamped by emitError() */
13
+ _connId?: string | undefined;
14
+ /** Which internal site rejected with this error */
15
+ rejectedFrom?: string | undefined;
16
+ /** The command that was affected */
17
+ command?: string | undefined;
18
+ /** The mailbox path that was affected */
19
+ path?: string | undefined;
20
+ /** Status of the tagged response that failed the command: 'NO' or 'BAD' */
21
+ responseStatus?: string | undefined;
22
+ /** Human readable text of the failed tagged response */
23
+ responseText?: string | undefined;
24
+ /** The server response: the parsed response, or its text once enhanceCommandError() ran */
25
+ response?: ImapResponse | string | false | undefined;
26
+ /** Response code of the failed tagged response, e.g. 'AUTHENTICATIONFAILED' */
27
+ serverResponseCode?: string | undefined;
28
+ /** The command as it was sent, for logging */
29
+ executedCommand?: string | undefined;
30
+ /** Set when authentication failed */
31
+ authenticationFailed?: boolean | undefined;
32
+ /** Set when a TLS or STARTTLS upgrade failed */
33
+ tlsFailed?: boolean | undefined;
34
+ /** Server suggested back-off in milliseconds for an ETHROTTLE error */
35
+ throttleReset?: number | undefined;
36
+ /** Additional details, e.g. the timeouts that applied */
37
+ details?: {
38
+ [key: string]: any;
39
+ } | undefined;
40
+ /** The underlying error */
41
+ _err?: Error | undefined;
42
+ /** Server BYE reason */
43
+ reason?: string | undefined;
44
+ /** Set when a mailbox could not be selected because it does not exist */
45
+ mailboxMissing?: boolean | undefined;
46
+ /** Id of the mailbox lock that timed out */
47
+ lockId?: number | undefined;
48
+ /** The parser error that failed a command completion */
49
+ parserError?: ImapFlowError | undefined;
50
+ /** Parser diagnostics */
51
+ parserContext?: {
52
+ [key: string]: any;
53
+ } | undefined;
54
+ /** The tag the parser had already read before it failed */
55
+ parsedTag?: string | undefined;
56
+ /** The declared size of a rejected literal */
57
+ literalSize?: number | undefined;
58
+ /** The length of a rejected line */
59
+ lineLength?: number | undefined;
60
+ /** The size of a rejected response */
61
+ responseSize?: number | undefined;
62
+ /** The bound that was exceeded */
63
+ maxSize?: number | undefined;
64
+ /** OAuth error details from the server, for XOAUTH2 authentication failures */
65
+ oauthError?: any;
66
+ /** The IMAP string that could not be parsed */
67
+ _imapStr?: string | undefined;
68
+ }
69
+ /**
70
+ * The fields a connection error is stamped with to say where it was rejected, see
71
+ * buildConnectionError() in tools.ts
72
+ */
73
+ export type ConnectionErrorSite = Pick<ImapFlowError, 'rejectedFrom' | 'command' | 'path'>;
74
+ /**
75
+ * Error subclass thrown when IMAP authentication fails.
76
+ */
77
+ export declare class AuthenticationFailure extends Error implements ImapFlowError {
78
+ authenticationFailed: true;
79
+ serverResponseCode?: string | undefined;
80
+ /** Text of the server's error response */
81
+ response?: string | undefined;
82
+ oauthError?: any;
83
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Error subclass thrown when IMAP authentication fails.
3
+ */
4
+ export class AuthenticationFailure extends Error {
5
+ constructor() {
6
+ super(...arguments);
7
+ this.authenticationFailed = true;
8
+ }
9
+ }
@@ -0,0 +1,24 @@
1
+ import type { CompilerOptions, ImapCompileInput } from './types.js';
2
+ /**
3
+ * Compiles an input object into a sequence of Buffers representing an IMAP protocol response string.
4
+ * Handles various node types including literals, strings, atoms, sections, sequences, and nested lists.
5
+ *
6
+ * @param response - The response object to compile.
7
+ * @param response.tag - The IMAP command tag (e.g., "*" or a sequence number).
8
+ * @param response.command - The IMAP command name.
9
+ * @param response.attributes - The response attributes to compile into IMAP format.
10
+ * @param options - Compilation options.
11
+ * @param options.asArray - If true, returns an array of Buffers (one per literal segment); otherwise returns a single concatenated Buffer.
12
+ * @param options.isLogging - If true, redacts sensitive values and truncates long strings/literals for logging purposes.
13
+ * @param options.literalPlus - If true, uses the LITERAL+ extension (appends "+" to literal length markers).
14
+ * @param options.literalMinus - If true, uses the LITERAL- extension for literals up to 4096 bytes.
15
+ * @returns A promise that resolves to an array of Buffers (if asArray is true) or a single concatenated Buffer.
16
+ */
17
+ declare function compiler(response: ImapCompileInput, options: CompilerOptions & {
18
+ asArray: true;
19
+ }): Promise<Buffer[]>;
20
+ declare function compiler(response: ImapCompileInput, options?: (CompilerOptions & {
21
+ asArray?: false | undefined;
22
+ }) | undefined): Promise<Buffer>;
23
+ declare function compiler(response: ImapCompileInput, options?: CompilerOptions | undefined): Promise<Buffer | Buffer[]>;
24
+ export default compiler;
@@ -1,50 +1,41 @@
1
1
  /* eslint no-console: 0, new-cap: 0 */
2
-
3
- 'use strict';
4
-
5
- const imapFormalSyntax = require('./imap-formal-syntax');
6
-
2
+ import imapFormalSyntax from './imap-formal-syntax.js';
7
3
  // A single element of a sequence-set as defined by the RFC 9051 grammar: a number
8
4
  // or a range, where "*" stands for the largest number in use. Digit strings are not
9
5
  // range-checked here (a server rejects "0" or an overlong number on its own); the
10
6
  // point of the check is that nothing outside this alphabet can reach the wire.
11
7
  const SEQ_RANGE = /^(\d+|\*)(:(\d+|\*))?$/;
12
-
13
8
  // Validates a full sequence-set: comma-separated SEQ_RANGE elements, or "$"
14
9
  // (RFC 5182 SEARCHRES), which references the previous SEARCH result and is only
15
10
  // valid as the entire set. Split into per-element tests on purpose - a whole-set
16
11
  // regex with an unbounded repeat group overflows the regex engine's backtrack
17
12
  // stack with an uncoded RangeError on valid sets in the million-element range,
18
13
  // while the per-element regex is bounded.
19
- const isValidSequenceSet = value => value === '$' || value.split(',').every(part => SEQ_RANGE.test(part));
20
-
14
+ const isValidSequenceSet = (value) => value === '$' || value.split(',').every(part => SEQ_RANGE.test(part));
21
15
  // Numeric tokens may only put the digit alphabet on the wire. Anything that does
22
16
  // not round to a bounded non-negative integer (NaN, Infinity, negatives, unsafe
23
17
  // magnitudes) degrades to 0 - the fallback the NaN coercion has always used.
24
- const safeNumber = value => {
18
+ const safeNumber = (value) => {
25
19
  let num = Math.round(Number(value));
26
20
  return Number.isSafeInteger(num) && num >= 0 ? num : 0;
27
21
  };
28
-
29
22
  // Characters that cannot appear in an IMAP quoted string: CR and LF terminate a
30
23
  // command line, and NUL is outside the CHAR production entirely. A value carrying
31
24
  // any of them has to be sent as a literal, so quoting it is never correct.
32
25
  const NOT_QUOTABLE = /[\r\n\0]/;
33
-
34
26
  // A line terminator ends an IMAP command, so no token may carry one to the wire.
35
27
  const CRLF = /[\r\n]/;
36
-
37
28
  /**
38
29
  * Quotes a value as an IMAP quoted string. Only DQUOTE and backslash are escaped -
39
30
  * the IMAP grammar defines no other escape sequence, so JSON-style escaping (which
40
31
  * turns a tab into a literal backslash-t and a control character into \\uXXXX) would
41
32
  * silently change the value the server receives.
42
33
  *
43
- * @param {string} value - The value to quote.
44
- * @returns {string} The quoted string, ready to be written to the wire.
34
+ * @param value - The value to quote.
35
+ * @returns The quoted string, ready to be written to the wire.
45
36
  * @throws {Error} If the value contains CR, LF or NUL, which a quoted string cannot carry.
46
37
  */
47
- const quoteString = value => {
38
+ const quoteString = (value) => {
48
39
  if (NOT_QUOTABLE.test(value)) {
49
40
  let error = new Error('Unquotable character in IMAP string value');
50
41
  error.code = 'InvalidStringValue';
@@ -52,26 +43,9 @@ const quoteString = value => {
52
43
  }
53
44
  return '"' + value.replace(/["\\]/g, char => '\\' + char) + '"';
54
45
  };
55
-
56
- /**
57
- * Compiles an input object into a sequence of Buffers representing an IMAP protocol response string.
58
- * Handles various node types including literals, strings, atoms, sections, sequences, and nested lists.
59
- *
60
- * @param {Object} response - The response object to compile.
61
- * @param {string} [response.tag] - The IMAP command tag (e.g., "*" or a sequence number).
62
- * @param {string} [response.command] - The IMAP command name.
63
- * @param {Array|Object} [response.attributes] - The response attributes to compile into IMAP format.
64
- * @param {Object} [options] - Compilation options.
65
- * @param {boolean} [options.asArray] - If true, returns an array of Buffers (one per literal segment); otherwise returns a single concatenated Buffer.
66
- * @param {boolean} [options.isLogging] - If true, redacts sensitive values and truncates long strings/literals for logging purposes.
67
- * @param {boolean} [options.literalPlus] - If true, uses the LITERAL+ extension (appends "+" to literal length markers).
68
- * @param {boolean} [options.literalMinus] - If true, uses the LITERAL- extension for literals up to 4096 bytes.
69
- * @returns {Promise<Buffer[]|Buffer>} A promise that resolves to an array of Buffers (if asArray is true) or a single concatenated Buffer.
70
- */
71
- module.exports = async (response, options) => {
46
+ async function compiler(response, options) {
72
47
  let { asArray, isLogging, literalPlus, literalMinus } = options || {};
73
48
  const respParts = [];
74
-
75
49
  // Formats an entry (string, number or Buffer) into the Buffer that is written to
76
50
  // the wire, and is the choke point every emission passes through: a line
77
51
  // terminator ends an IMAP command, so no token - the tag and command name
@@ -88,33 +62,27 @@ module.exports = async (response, options) => {
88
62
  error.code = 'InvalidTokenValue';
89
63
  throw error;
90
64
  }
91
-
92
65
  if (typeof entry === 'string') {
93
66
  return Buffer.from(entry);
94
67
  }
95
-
96
68
  if (typeof entry === 'number') {
97
69
  return Buffer.from(entry.toString());
98
70
  }
99
-
100
71
  if (Buffer.isBuffer(entry)) {
101
72
  return entry;
102
73
  }
103
-
104
74
  if (returnEmpty) {
105
75
  return null;
106
76
  }
107
-
108
77
  return Buffer.alloc(0);
109
78
  };
110
-
111
- let resp = [].concat(emitEntry(response.tag, { returnEmpty: true }) || []).concat(response.command ? emitEntry(' ' + response.command) : []);
79
+ let resp = []
80
+ .concat(emitEntry(response.tag, { returnEmpty: true }) || [])
81
+ .concat(response.command ? emitEntry(' ' + response.command) : []);
112
82
  let val;
113
83
  let lastType;
114
-
115
84
  let walk = async (node, options) => {
116
85
  options = options || {};
117
-
118
86
  // Determine whether a space separator is needed before this node.
119
87
  // Inspect the last byte written to decide context.
120
88
  let lastRespEntry = resp.length && resp[resp.length - 1];
@@ -122,7 +90,6 @@ module.exports = async (response, options) => {
122
90
  if (typeof lastRespByte === 'number') {
123
91
  lastRespByte = String.fromCharCode(lastRespByte);
124
92
  }
125
-
126
93
  // Add a space separator when:
127
94
  // - The previous token was a LITERAL. Literal data ends exactly at its declared length, so
128
95
  // a following token always needs an explicit separator, even though the last written byte
@@ -136,65 +103,55 @@ module.exports = async (response, options) => {
136
103
  resp.push(emitEntry(' '));
137
104
  }
138
105
  }
139
-
140
106
  if (node && node.buffer && !Buffer.isBuffer(node)) {
141
107
  // mongodb binary
142
108
  node = node.buffer;
143
109
  }
144
-
145
110
  if (Array.isArray(node)) {
146
111
  lastType = 'LIST';
147
112
  resp.push(emitEntry('('));
148
-
149
113
  // check if we need to skip separator WS between two arrays
150
114
  let subArray = node.length > 1 && Array.isArray(node[0]);
151
-
152
115
  for (let child of node) {
153
116
  if (subArray && !Array.isArray(child)) {
154
117
  subArray = false;
155
118
  }
156
119
  await walk(child, { subArray });
157
120
  }
158
-
159
121
  resp.push(emitEntry(')'));
160
122
  return;
161
123
  }
162
-
163
124
  if (!node && typeof node !== 'string' && typeof node !== 'number' && !Buffer.isBuffer(node)) {
164
125
  resp.push(emitEntry('NIL'));
165
126
  return;
166
127
  }
167
-
168
128
  if (typeof node === 'string' || Buffer.isBuffer(node)) {
169
129
  if (isLogging && node.length > 100) {
170
130
  resp.push(emitEntry('"(* ' + node.length + 'B string *)"'));
171
- } else {
131
+ }
132
+ else {
172
133
  resp.push(emitEntry(isLogging ? JSON.stringify(node.toString()) : quoteString(node.toString())));
173
134
  }
174
135
  return;
175
136
  }
176
-
177
137
  if (typeof node === 'number') {
178
138
  resp.push(emitEntry(safeNumber(node))); // Only bounded non-negative integers allowed
179
139
  return;
180
140
  }
181
-
182
141
  lastType = node.type;
183
-
184
142
  if (isLogging && node.sensitive) {
185
143
  resp.push(emitEntry('"(* value hidden *)"'));
186
144
  return;
187
145
  }
188
-
189
146
  switch (node.type.toUpperCase()) {
190
147
  case 'LITERAL':
191
148
  if (isLogging) {
192
149
  resp.push(emitEntry('"(* ' + node.value.length + 'B literal *)"'));
193
- } else {
150
+ }
151
+ else {
194
152
  // The literal size marker counts octets - string values are written as
195
153
  // UTF-8, so their UTF-16 .length would undercount multi-byte characters
196
154
  let literalLength = !node.value ? 0 : Buffer.isBuffer(node.value) ? node.value.length : Buffer.byteLength(node.value.toString());
197
-
198
155
  // Append '+' to the size marker only when the extension actually permits a
199
156
  // non-synchronizing literal of this size (RFC 7888): LITERAL+ always,
200
157
  // LITERAL- only up to 4096 bytes
@@ -203,16 +160,15 @@ module.exports = async (response, options) => {
203
160
  // non-synchronizing literals always, and everything in single-buffer mode
204
161
  // (asArray false), which has no continuation flow
205
162
  let canAppend = !asArray || usePlus;
206
-
207
163
  // Emit the literal header: optional '~' prefix for literal8, then {size[+]}\r\n
208
164
  resp.push(emitEntry(`${node.isLiteral8 ? '~' : ''}{${literalLength}${usePlus ? '+' : ''}}\r\n`, { raw: true }));
209
-
210
165
  if (canAppend) {
211
166
  // Literal data follows immediately in the same buffer segment
212
167
  if (node.value && node.value.length) {
213
168
  resp.push(emitEntry(node.value, { raw: true }));
214
169
  }
215
- } else {
170
+ }
171
+ else {
216
172
  // For synchronizing literals in asArray mode, split output into separate
217
173
  // parts. The caller must send each part and wait for a continuation
218
174
  // response from the server before sending the next.
@@ -221,16 +177,15 @@ module.exports = async (response, options) => {
221
177
  }
222
178
  }
223
179
  break;
224
-
225
180
  case 'STRING':
226
181
  if (isLogging && node.value.length > 100) {
227
182
  resp.push(emitEntry('"(* ' + node.value.length + 'B string *)"'));
228
- } else {
183
+ }
184
+ else {
229
185
  val = (node.value || '').toString();
230
186
  resp.push(emitEntry(isLogging ? JSON.stringify(val) : quoteString(val)));
231
187
  }
232
188
  break;
233
-
234
189
  case 'SEQUENCE':
235
190
  // Sequence sets are written verbatim - they are the one token type with
236
191
  // no quoting to fall back on. Callers build them from user-supplied
@@ -255,7 +210,6 @@ module.exports = async (response, options) => {
255
210
  resp.push(emitEntry(node.value, { raw: true }));
256
211
  }
257
212
  break;
258
-
259
213
  case 'TEXT':
260
214
  // Response text is written verbatim. Only the parser produces it today, for
261
215
  // incoming lines, so this is a re-encoding path rather than a command-building
@@ -270,18 +224,15 @@ module.exports = async (response, options) => {
270
224
  resp.push(emitEntry(node.value));
271
225
  }
272
226
  break;
273
-
274
227
  case 'NUMBER':
275
228
  // Coerced rather than written through: formatRespEntry passes a string or
276
229
  // Buffer straight to the wire, so a numeric token carrying a string value
277
230
  // would be another verbatim channel
278
231
  resp.push(emitEntry(safeNumber(node.value)));
279
232
  break;
280
-
281
233
  case 'ATOM':
282
234
  case 'SECTION':
283
235
  val = (node.value || '').toString();
284
-
285
236
  if (!node.section || val) {
286
237
  // Verify the value contains only valid ATOM-CHAR characters.
287
238
  // Strip a leading backslash before checking (system flags like \Seen start with '\').
@@ -290,19 +241,15 @@ module.exports = async (response, options) => {
290
241
  if (node.value === '' || imapFormalSyntax.verify(val.charAt(0) === '\\' ? val.substr(1) : val, imapFormalSyntax['ATOM-CHAR']()) >= 0) {
291
242
  val = isLogging ? JSON.stringify(val) : quoteString(val);
292
243
  }
293
-
294
244
  resp.push(emitEntry(val));
295
245
  }
296
-
297
246
  // Section bracket handling: emit [section-contents] after the ATOM value
298
247
  // e.g., BODY[HEADER.FIELDS (Subject)] or BODY[1.MIME]
299
248
  if (node.section) {
300
249
  resp.push(emitEntry('['));
301
-
302
250
  for (let child of node.section) {
303
251
  await walk(child);
304
252
  }
305
-
306
253
  resp.push(emitEntry(']'));
307
254
  }
308
255
  // Partial range: emit <origin.length> after the section brackets. Coerced
@@ -316,21 +263,16 @@ module.exports = async (response, options) => {
316
263
  break;
317
264
  }
318
265
  };
319
-
320
266
  if (response.attributes) {
321
267
  let attributes = Array.isArray(response.attributes) ? response.attributes : [].concat(response.attributes);
322
268
  for (let child of attributes) {
323
269
  await walk(child);
324
270
  }
325
271
  }
326
-
327
272
  if (resp.length) {
328
273
  respParts.push(resp);
329
274
  }
330
-
331
- for (let i = 0; i < respParts.length; i++) {
332
- respParts[i] = Buffer.concat(respParts[i]);
333
- }
334
-
335
- return asArray ? respParts : respParts.flatMap(entry => entry);
336
- };
275
+ const compiled = respParts.map(part => Buffer.concat(part));
276
+ return asArray ? compiled : compiled.flatMap(entry => entry);
277
+ }
278
+ export default compiler;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Verifies that every character in the given string is within the set of allowed characters.
3
+ *
4
+ * @param str - The string to validate.
5
+ * @param allowedChars - A string containing all allowed characters.
6
+ * @returns The index of the first disallowed character, or -1 if all characters are valid.
7
+ */
8
+ declare function verify(str: string, allowedChars: string): number;
9
+ declare const imapFormalSyntax: {
10
+ CHAR: () => string;
11
+ CHAR8: () => string;
12
+ SP: () => string;
13
+ CTL: () => string;
14
+ DQUOTE: () => string;
15
+ ALPHA: () => string;
16
+ DIGIT: () => string;
17
+ 'ATOM-CHAR': () => string;
18
+ 'ASTRING-CHAR': () => string;
19
+ 'TEXT-CHAR': () => string;
20
+ 'atom-specials': () => string;
21
+ 'list-wildcards': () => string;
22
+ 'quoted-specials': () => string;
23
+ 'resp-specials': () => string;
24
+ tag: () => string;
25
+ command: () => string;
26
+ verify: typeof verify;
27
+ };
28
+ export default imapFormalSyntax;
@@ -0,0 +1,117 @@
1
+ /* eslint new-cap: 0, no-useless-concat: 0 */
2
+ /**
3
+ * Defines the IMAP formal syntax character classes and validation rules as specified
4
+ * in RFC 3501 Section 9 (http://tools.ietf.org/html/rfc3501#section-9).
5
+ *
6
+ * Each exported method returns a string of allowed characters for a given IMAP grammar
7
+ * production rule (e.g., ATOM-CHAR, ASTRING-CHAR, TEXT-CHAR). Results are computed once,
8
+ * on first use, and cached at module level.
9
+ *
10
+ * Also exports a `verify` function for validating strings against a set of allowed characters.
11
+ */
12
+ /**
13
+ * Generates a string containing all characters in the given Unicode code point range (inclusive).
14
+ *
15
+ * @param start - The starting character code point.
16
+ * @param end - The ending character code point.
17
+ * @returns A string containing all characters from start to end.
18
+ */
19
+ function expandRange(start, end) {
20
+ let chars = [];
21
+ for (let i = start; i <= end; i++) {
22
+ chars.push(i);
23
+ }
24
+ return String.fromCharCode(...chars);
25
+ }
26
+ /**
27
+ * Returns a new string with all characters from the exclude string removed from the source string.
28
+ *
29
+ * @param source - The source string to filter.
30
+ * @param exclude - A string of characters to exclude from the source.
31
+ * @returns The source string with excluded characters removed.
32
+ */
33
+ function excludeChars(source, exclude) {
34
+ return Array.prototype.filter.call(source, (ch) => exclude.indexOf(ch) < 0).join('');
35
+ }
36
+ /**
37
+ * Wraps a computation so that it runs once, on first call, and the result is reused afterwards.
38
+ *
39
+ * @param compute - Produces the character set.
40
+ * @returns A zero-argument function returning the cached character set.
41
+ */
42
+ function memo(compute) {
43
+ let value = null;
44
+ return () => {
45
+ if (value === null) {
46
+ value = compute();
47
+ }
48
+ return value;
49
+ };
50
+ }
51
+ /** All 7-bit US-ASCII characters excluding NUL (0x01-0x7F). */
52
+ const CHAR = memo(() => expandRange(0x01, 0x7f));
53
+ /** All 8-bit characters excluding NUL (0x01-0xFF). */
54
+ const CHAR8 = memo(() => expandRange(0x01, 0xff));
55
+ /** The space character (0x20). */
56
+ const SP = () => ' ';
57
+ /** All control characters (0x00-0x1F and 0x7F). */
58
+ const CTL = memo(() => expandRange(0x00, 0x1f) + '\x7F');
59
+ /** The double-quote character. */
60
+ const DQUOTE = () => '"';
61
+ /** All uppercase and lowercase ASCII alphabetic characters (A-Z, a-z). */
62
+ const ALPHA = memo(() => expandRange(0x41, 0x5a) + expandRange(0x61, 0x7a));
63
+ /** All ASCII digit characters (0-9). */
64
+ const DIGIT = memo(() => expandRange(0x30, 0x39));
65
+ /** The LIST wildcard characters ("%" and "*"). */
66
+ const listWildcards = () => '%' + '*';
67
+ /** Characters that are special inside quoted strings (DQUOTE and backslash). */
68
+ const quotedSpecials = memo(() => DQUOTE() + '\\');
69
+ /** The response-special character ("]"). */
70
+ const respSpecials = () => ']';
71
+ /** Characters that are special in ATOMs and must be excluded: "(", ")", "{", SP, CTL, list-wildcards, quoted-specials, resp-specials. */
72
+ const atomSpecials = memo(() => '(' + ')' + '{' + SP() + CTL() + listWildcards() + quotedSpecials() + respSpecials());
73
+ /** Characters allowed in an IMAP ATOM (CHAR minus atom-specials). */
74
+ const atomChar = memo(() => excludeChars(CHAR(), atomSpecials()));
75
+ /** Characters allowed in an IMAP ASTRING (ATOM-CHAR plus resp-specials). */
76
+ const astringChar = memo(() => atomChar() + respSpecials());
77
+ /** Characters allowed in IMAP text (CHAR minus CR and LF). */
78
+ const textChar = memo(() => excludeChars(CHAR(), '\r\n'));
79
+ /** Characters allowed in an IMAP tag (ASTRING-CHAR minus "+"). */
80
+ const tag = memo(() => excludeChars(astringChar(), '+'));
81
+ /** Characters allowed in an IMAP command name (ALPHA, DIGIT, and hyphen). */
82
+ const command = memo(() => ALPHA() + DIGIT() + '-');
83
+ /**
84
+ * Verifies that every character in the given string is within the set of allowed characters.
85
+ *
86
+ * @param str - The string to validate.
87
+ * @param allowedChars - A string containing all allowed characters.
88
+ * @returns The index of the first disallowed character, or -1 if all characters are valid.
89
+ */
90
+ function verify(str, allowedChars) {
91
+ for (let i = 0, len = str.length; i < len; i++) {
92
+ if (allowedChars.indexOf(str.charAt(i)) < 0) {
93
+ return i;
94
+ }
95
+ }
96
+ return -1;
97
+ }
98
+ const imapFormalSyntax = {
99
+ CHAR,
100
+ CHAR8,
101
+ SP,
102
+ CTL,
103
+ DQUOTE,
104
+ ALPHA,
105
+ DIGIT,
106
+ 'ATOM-CHAR': atomChar,
107
+ 'ASTRING-CHAR': astringChar,
108
+ 'TEXT-CHAR': textChar,
109
+ 'atom-specials': atomSpecials,
110
+ 'list-wildcards': listWildcards,
111
+ 'quoted-specials': quotedSpecials,
112
+ 'resp-specials': respSpecials,
113
+ tag,
114
+ command,
115
+ verify
116
+ };
117
+ export default imapFormalSyntax;
@@ -0,0 +1,9 @@
1
+ import parser from './imap-parser.js';
2
+ import compiler from './imap-compiler.js';
3
+ /**
4
+ * Re-exports the IMAP protocol parser and compiler as a single module.
5
+ *
6
+ * - `parser` parses raw IMAP command/response buffers into structured objects.
7
+ * - `compiler` compiles structured response objects into IMAP protocol Buffers.
8
+ */
9
+ export { parser, compiler };
@@ -0,0 +1,9 @@
1
+ import parser from './imap-parser.js';
2
+ import compiler from './imap-compiler.js';
3
+ /**
4
+ * Re-exports the IMAP protocol parser and compiler as a single module.
5
+ *
6
+ * - `parser` parses raw IMAP command/response buffers into structured objects.
7
+ * - `compiler` compiles structured response objects into IMAP protocol Buffers.
8
+ */
9
+ export { parser, compiler };
@@ -0,0 +1,16 @@
1
+ import type { ImapResponse, ParserOptions } from './types.js';
2
+ /**
3
+ * Parses a raw IMAP command or response buffer into a structured object.
4
+ * Handles edge cases such as null-byte-padded responses from buggy servers and
5
+ * multi-word commands like UID and AUTHENTICATE.
6
+ *
7
+ * @param command - The raw IMAP command or response data to parse.
8
+ * @param options - Parser options passed through to the underlying ParserInstance and TokenParser.
9
+ * @param options.literalPlus - Whether the LITERAL+ extension is in use.
10
+ * @param options.literals - Pre-parsed literal values extracted from the input stream.
11
+ * @returns A promise that resolves to a parsed response object with `tag` (the IMAP tag, e.g. "*",
12
+ * "+", or a command tag like "A1"), `command` (the IMAP command or response name, e.g. "OK",
13
+ * "FETCH"), `attributes` (parsed attributes of the response) and `nullBytesRemoved` (number of
14
+ * leading null bytes removed, if any).
15
+ */
16
+ export default function parser(command: Buffer | string, options?: ParserOptions | undefined): Promise<ImapResponse>;