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,285 @@
1
+ "use strict";
2
+ /* eslint no-console: 0, new-cap: 0 */
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const imap_formal_syntax_js_1 = __importDefault(require("./imap-formal-syntax.js"));
8
+ // A single element of a sequence-set as defined by the RFC 9051 grammar: a number
9
+ // or a range, where "*" stands for the largest number in use. Digit strings are not
10
+ // range-checked here (a server rejects "0" or an overlong number on its own); the
11
+ // point of the check is that nothing outside this alphabet can reach the wire.
12
+ const SEQ_RANGE = /^(\d+|\*)(:(\d+|\*))?$/;
13
+ // Validates a full sequence-set: comma-separated SEQ_RANGE elements, or "$"
14
+ // (RFC 5182 SEARCHRES), which references the previous SEARCH result and is only
15
+ // valid as the entire set. Split into per-element tests on purpose - a whole-set
16
+ // regex with an unbounded repeat group overflows the regex engine's backtrack
17
+ // stack with an uncoded RangeError on valid sets in the million-element range,
18
+ // while the per-element regex is bounded.
19
+ const isValidSequenceSet = (value) => value === '$' || value.split(',').every(part => SEQ_RANGE.test(part));
20
+ // Numeric tokens may only put the digit alphabet on the wire. Anything that does
21
+ // not round to a bounded non-negative integer (NaN, Infinity, negatives, unsafe
22
+ // magnitudes) degrades to 0 - the fallback the NaN coercion has always used.
23
+ const safeNumber = (value) => {
24
+ let num = Math.round(Number(value));
25
+ return Number.isSafeInteger(num) && num >= 0 ? num : 0;
26
+ };
27
+ // Characters that cannot appear in an IMAP quoted string: CR and LF terminate a
28
+ // command line, and NUL is outside the CHAR production entirely. A value carrying
29
+ // any of them has to be sent as a literal, so quoting it is never correct.
30
+ const NOT_QUOTABLE = /[\r\n\0]/;
31
+ // A line terminator ends an IMAP command, so no token may carry one to the wire.
32
+ const CRLF = /[\r\n]/;
33
+ /**
34
+ * Quotes a value as an IMAP quoted string. Only DQUOTE and backslash are escaped -
35
+ * the IMAP grammar defines no other escape sequence, so JSON-style escaping (which
36
+ * turns a tab into a literal backslash-t and a control character into \\uXXXX) would
37
+ * silently change the value the server receives.
38
+ *
39
+ * @param value - The value to quote.
40
+ * @returns The quoted string, ready to be written to the wire.
41
+ * @throws {Error} If the value contains CR, LF or NUL, which a quoted string cannot carry.
42
+ */
43
+ const quoteString = (value) => {
44
+ if (NOT_QUOTABLE.test(value)) {
45
+ let error = new Error('Unquotable character in IMAP string value');
46
+ error.code = 'InvalidStringValue';
47
+ throw error;
48
+ }
49
+ return '"' + value.replace(/["\\]/g, char => '\\' + char) + '"';
50
+ };
51
+ async function compiler(response, options) {
52
+ let { asArray, isLogging, literalPlus, literalMinus } = options || {};
53
+ const respParts = [];
54
+ // Formats an entry (string, number or Buffer) into the Buffer that is written to
55
+ // the wire, and is the choke point every emission passes through: a line
56
+ // terminator ends an IMAP command, so no token - the tag and command name
57
+ // included - may put one on the wire. The literal size marker and literal data
58
+ // are the only emissions where CRLF is legitimate; those call sites opt out with
59
+ // `raw`. Never enforced when logging: re-encoding an incoming server response for
60
+ // the log or for error text must not throw, whatever the server sent. With
61
+ // `returnEmpty`, an unrecognized entry type yields null instead of an empty
62
+ // Buffer.
63
+ const emitEntry = (entry, opts) => {
64
+ let { returnEmpty, raw } = opts || {};
65
+ if (!raw && !isLogging && (typeof entry === 'string' || Buffer.isBuffer(entry)) && CRLF.test(entry.toString('latin1'))) {
66
+ let error = new Error('Line terminator in IMAP token');
67
+ error.code = 'InvalidTokenValue';
68
+ throw error;
69
+ }
70
+ if (typeof entry === 'string') {
71
+ return Buffer.from(entry);
72
+ }
73
+ if (typeof entry === 'number') {
74
+ return Buffer.from(entry.toString());
75
+ }
76
+ if (Buffer.isBuffer(entry)) {
77
+ return entry;
78
+ }
79
+ if (returnEmpty) {
80
+ return null;
81
+ }
82
+ return Buffer.alloc(0);
83
+ };
84
+ let resp = []
85
+ .concat(emitEntry(response.tag, { returnEmpty: true }) || [])
86
+ .concat(response.command ? emitEntry(' ' + response.command) : []);
87
+ let val;
88
+ let lastType;
89
+ let walk = async (node, options) => {
90
+ options = options || {};
91
+ // Determine whether a space separator is needed before this node.
92
+ // Inspect the last byte written to decide context.
93
+ let lastRespEntry = resp.length && resp[resp.length - 1];
94
+ let lastRespByte = (lastRespEntry && lastRespEntry.length && lastRespEntry[lastRespEntry.length - 1]) || '';
95
+ if (typeof lastRespByte === 'number') {
96
+ lastRespByte = String.fromCharCode(lastRespByte);
97
+ }
98
+ // Add a space separator when:
99
+ // - The previous token was a LITERAL. Literal data ends exactly at its declared length, so
100
+ // a following token always needs an explicit separator, even though the last written byte
101
+ // is arbitrary literal content.
102
+ // - Otherwise: there is something written already (resp is not empty) and the last byte is
103
+ // not an opening delimiter ('(', '<' or '['), which suppresses the space.
104
+ // A sub-array element in a consecutive-list context never gets one (no space between
105
+ // adjacent lists).
106
+ if (lastType === 'LITERAL' || (!['(', '<', '['].includes(lastRespByte) && resp.length)) {
107
+ if (!options.subArray) {
108
+ resp.push(emitEntry(' '));
109
+ }
110
+ }
111
+ if (node && node.buffer && !Buffer.isBuffer(node)) {
112
+ // mongodb binary
113
+ node = node.buffer;
114
+ }
115
+ if (Array.isArray(node)) {
116
+ lastType = 'LIST';
117
+ resp.push(emitEntry('('));
118
+ // check if we need to skip separator WS between two arrays
119
+ let subArray = node.length > 1 && Array.isArray(node[0]);
120
+ for (let child of node) {
121
+ if (subArray && !Array.isArray(child)) {
122
+ subArray = false;
123
+ }
124
+ await walk(child, { subArray });
125
+ }
126
+ resp.push(emitEntry(')'));
127
+ return;
128
+ }
129
+ if (!node && typeof node !== 'string' && typeof node !== 'number' && !Buffer.isBuffer(node)) {
130
+ resp.push(emitEntry('NIL'));
131
+ return;
132
+ }
133
+ if (typeof node === 'string' || Buffer.isBuffer(node)) {
134
+ if (isLogging && node.length > 100) {
135
+ resp.push(emitEntry('"(* ' + node.length + 'B string *)"'));
136
+ }
137
+ else {
138
+ resp.push(emitEntry(isLogging ? JSON.stringify(node.toString()) : quoteString(node.toString())));
139
+ }
140
+ return;
141
+ }
142
+ if (typeof node === 'number') {
143
+ resp.push(emitEntry(safeNumber(node))); // Only bounded non-negative integers allowed
144
+ return;
145
+ }
146
+ lastType = node.type;
147
+ if (isLogging && node.sensitive) {
148
+ resp.push(emitEntry('"(* value hidden *)"'));
149
+ return;
150
+ }
151
+ switch (node.type.toUpperCase()) {
152
+ case 'LITERAL':
153
+ if (isLogging) {
154
+ resp.push(emitEntry('"(* ' + node.value.length + 'B literal *)"'));
155
+ }
156
+ else {
157
+ // The literal size marker counts octets - string values are written as
158
+ // UTF-8, so their UTF-16 .length would undercount multi-byte characters
159
+ let literalLength = !node.value ? 0 : Buffer.isBuffer(node.value) ? node.value.length : Buffer.byteLength(node.value.toString());
160
+ // Append '+' to the size marker only when the extension actually permits a
161
+ // non-synchronizing literal of this size (RFC 7888): LITERAL+ always,
162
+ // LITERAL- only up to 4096 bytes
163
+ let usePlus = literalPlus || (literalMinus && literalLength <= 4096);
164
+ // canAppend: whether the literal data can be sent in the same buffer segment -
165
+ // non-synchronizing literals always, and everything in single-buffer mode
166
+ // (asArray false), which has no continuation flow
167
+ let canAppend = !asArray || usePlus;
168
+ // Emit the literal header: optional '~' prefix for literal8, then {size[+]}\r\n
169
+ resp.push(emitEntry(`${node.isLiteral8 ? '~' : ''}{${literalLength}${usePlus ? '+' : ''}}\r\n`, { raw: true }));
170
+ if (canAppend) {
171
+ // Literal data follows immediately in the same buffer segment
172
+ if (node.value && node.value.length) {
173
+ resp.push(emitEntry(node.value, { raw: true }));
174
+ }
175
+ }
176
+ else {
177
+ // For synchronizing literals in asArray mode, split output into separate
178
+ // parts. The caller must send each part and wait for a continuation
179
+ // response from the server before sending the next.
180
+ respParts.push(resp);
181
+ resp = [].concat(emitEntry(node.value, { returnEmpty: true, raw: true }) || []);
182
+ }
183
+ }
184
+ break;
185
+ case 'STRING':
186
+ if (isLogging && node.value.length > 100) {
187
+ resp.push(emitEntry('"(* ' + node.value.length + 'B string *)"'));
188
+ }
189
+ else {
190
+ val = (node.value || '').toString();
191
+ resp.push(emitEntry(isLogging ? JSON.stringify(val) : quoteString(val)));
192
+ }
193
+ break;
194
+ case 'SEQUENCE':
195
+ // Sequence sets are written verbatim - they are the one token type with
196
+ // no quoting to fall back on. Callers build them from user-supplied
197
+ // ranges, so validate here, at the single point every outgoing sequence
198
+ // set passes through, rather than trusting each command module. Skipped
199
+ // when logging: the incoming token parser accepts sequence-shaped tokens
200
+ // this strict grammar rejects (an ESEARCH set like "1:2:3", a folder
201
+ // name like "12:30:00"), and re-compiling a server response for the log
202
+ // or for error text must never throw.
203
+ if (!isLogging && (typeof node.value === 'string' || typeof node.value === 'number' || Buffer.isBuffer(node.value))) {
204
+ val = node.value.toString();
205
+ if (val && !isValidSequenceSet(val)) {
206
+ let error = new Error('Invalid sequence set value');
207
+ error.code = 'InvalidSequenceSet';
208
+ throw error;
209
+ }
210
+ }
211
+ if (node.value) {
212
+ // raw: the validated alphabet cannot contain a line terminator, and
213
+ // re-scanning a potentially multi-megabyte set in the choke point
214
+ // would double the cost of exactly the sets this branch exists for
215
+ resp.push(emitEntry(node.value, { raw: true }));
216
+ }
217
+ break;
218
+ case 'TEXT':
219
+ // Response text is written verbatim. Only the parser produces it today, for
220
+ // incoming lines, so this is a re-encoding path rather than a command-building
221
+ // one. The emitEntry choke point would refuse a line terminator here too;
222
+ // this check runs first only to raise the more specific InvalidTextValue code.
223
+ if (node.value) {
224
+ if (!isLogging && CRLF.test(node.value.toString())) {
225
+ let error = new Error('Line terminator in IMAP text value');
226
+ error.code = 'InvalidTextValue';
227
+ throw error;
228
+ }
229
+ resp.push(emitEntry(node.value));
230
+ }
231
+ break;
232
+ case 'NUMBER':
233
+ // Coerced rather than written through: formatRespEntry passes a string or
234
+ // Buffer straight to the wire, so a numeric token carrying a string value
235
+ // would be another verbatim channel
236
+ resp.push(emitEntry(safeNumber(node.value)));
237
+ break;
238
+ case 'ATOM':
239
+ case 'SECTION':
240
+ val = (node.value || '').toString();
241
+ if (!node.section || val) {
242
+ // Verify the value contains only valid ATOM-CHAR characters.
243
+ // Strip a leading backslash before checking (system flags like \Seen start with '\').
244
+ // If any character fails verification, fall back to an IMAP quoted string
245
+ // (JSON.stringify is used only for log output, where values are display-escaped).
246
+ if (node.value === '' || imap_formal_syntax_js_1.default.verify(val.charAt(0) === '\\' ? val.substr(1) : val, imap_formal_syntax_js_1.default['ATOM-CHAR']()) >= 0) {
247
+ val = isLogging ? JSON.stringify(val) : quoteString(val);
248
+ }
249
+ resp.push(emitEntry(val));
250
+ }
251
+ // Section bracket handling: emit [section-contents] after the ATOM value
252
+ // e.g., BODY[HEADER.FIELDS (Subject)] or BODY[1.MIME]
253
+ if (node.section) {
254
+ resp.push(emitEntry('['));
255
+ for (let child of node.section) {
256
+ await walk(child);
257
+ }
258
+ resp.push(emitEntry(']'));
259
+ }
260
+ // Partial range: emit <origin.length> after the section brackets. Coerced
261
+ // rather than joined as-is: this is the last token component written
262
+ // verbatim, and the choke point is only worth relying on if it holds for
263
+ // all of them. Every producer already passes numbers, so nothing changes
264
+ // for them.
265
+ if (node.partial) {
266
+ resp.push(emitEntry(`<${node.partial.map(safeNumber).join('.')}>`));
267
+ }
268
+ break;
269
+ }
270
+ };
271
+ if (response.attributes) {
272
+ let attributes = Array.isArray(response.attributes) ? response.attributes : [].concat(response.attributes);
273
+ for (let child of attributes) {
274
+ await walk(child);
275
+ }
276
+ }
277
+ if (resp.length) {
278
+ respParts.push(resp);
279
+ }
280
+ const compiled = respParts.map(part => Buffer.concat(part));
281
+ return asArray ? compiled : compiled.flatMap(entry => entry);
282
+ }
283
+ exports.default = compiler;
284
+ module.exports = exports.default;
285
+ Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
@@ -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,121 @@
1
+ "use strict";
2
+ /* eslint new-cap: 0, no-useless-concat: 0 */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ /**
5
+ * Defines the IMAP formal syntax character classes and validation rules as specified
6
+ * in RFC 3501 Section 9 (http://tools.ietf.org/html/rfc3501#section-9).
7
+ *
8
+ * Each exported method returns a string of allowed characters for a given IMAP grammar
9
+ * production rule (e.g., ATOM-CHAR, ASTRING-CHAR, TEXT-CHAR). Results are computed once,
10
+ * on first use, and cached at module level.
11
+ *
12
+ * Also exports a `verify` function for validating strings against a set of allowed characters.
13
+ */
14
+ /**
15
+ * Generates a string containing all characters in the given Unicode code point range (inclusive).
16
+ *
17
+ * @param start - The starting character code point.
18
+ * @param end - The ending character code point.
19
+ * @returns A string containing all characters from start to end.
20
+ */
21
+ function expandRange(start, end) {
22
+ let chars = [];
23
+ for (let i = start; i <= end; i++) {
24
+ chars.push(i);
25
+ }
26
+ return String.fromCharCode(...chars);
27
+ }
28
+ /**
29
+ * Returns a new string with all characters from the exclude string removed from the source string.
30
+ *
31
+ * @param source - The source string to filter.
32
+ * @param exclude - A string of characters to exclude from the source.
33
+ * @returns The source string with excluded characters removed.
34
+ */
35
+ function excludeChars(source, exclude) {
36
+ return Array.prototype.filter.call(source, (ch) => exclude.indexOf(ch) < 0).join('');
37
+ }
38
+ /**
39
+ * Wraps a computation so that it runs once, on first call, and the result is reused afterwards.
40
+ *
41
+ * @param compute - Produces the character set.
42
+ * @returns A zero-argument function returning the cached character set.
43
+ */
44
+ function memo(compute) {
45
+ let value = null;
46
+ return () => {
47
+ if (value === null) {
48
+ value = compute();
49
+ }
50
+ return value;
51
+ };
52
+ }
53
+ /** All 7-bit US-ASCII characters excluding NUL (0x01-0x7F). */
54
+ const CHAR = memo(() => expandRange(0x01, 0x7f));
55
+ /** All 8-bit characters excluding NUL (0x01-0xFF). */
56
+ const CHAR8 = memo(() => expandRange(0x01, 0xff));
57
+ /** The space character (0x20). */
58
+ const SP = () => ' ';
59
+ /** All control characters (0x00-0x1F and 0x7F). */
60
+ const CTL = memo(() => expandRange(0x00, 0x1f) + '\x7F');
61
+ /** The double-quote character. */
62
+ const DQUOTE = () => '"';
63
+ /** All uppercase and lowercase ASCII alphabetic characters (A-Z, a-z). */
64
+ const ALPHA = memo(() => expandRange(0x41, 0x5a) + expandRange(0x61, 0x7a));
65
+ /** All ASCII digit characters (0-9). */
66
+ const DIGIT = memo(() => expandRange(0x30, 0x39));
67
+ /** The LIST wildcard characters ("%" and "*"). */
68
+ const listWildcards = () => '%' + '*';
69
+ /** Characters that are special inside quoted strings (DQUOTE and backslash). */
70
+ const quotedSpecials = memo(() => DQUOTE() + '\\');
71
+ /** The response-special character ("]"). */
72
+ const respSpecials = () => ']';
73
+ /** Characters that are special in ATOMs and must be excluded: "(", ")", "{", SP, CTL, list-wildcards, quoted-specials, resp-specials. */
74
+ const atomSpecials = memo(() => '(' + ')' + '{' + SP() + CTL() + listWildcards() + quotedSpecials() + respSpecials());
75
+ /** Characters allowed in an IMAP ATOM (CHAR minus atom-specials). */
76
+ const atomChar = memo(() => excludeChars(CHAR(), atomSpecials()));
77
+ /** Characters allowed in an IMAP ASTRING (ATOM-CHAR plus resp-specials). */
78
+ const astringChar = memo(() => atomChar() + respSpecials());
79
+ /** Characters allowed in IMAP text (CHAR minus CR and LF). */
80
+ const textChar = memo(() => excludeChars(CHAR(), '\r\n'));
81
+ /** Characters allowed in an IMAP tag (ASTRING-CHAR minus "+"). */
82
+ const tag = memo(() => excludeChars(astringChar(), '+'));
83
+ /** Characters allowed in an IMAP command name (ALPHA, DIGIT, and hyphen). */
84
+ const command = memo(() => ALPHA() + DIGIT() + '-');
85
+ /**
86
+ * Verifies that every character in the given string is within the set of allowed characters.
87
+ *
88
+ * @param str - The string to validate.
89
+ * @param allowedChars - A string containing all allowed characters.
90
+ * @returns The index of the first disallowed character, or -1 if all characters are valid.
91
+ */
92
+ function verify(str, allowedChars) {
93
+ for (let i = 0, len = str.length; i < len; i++) {
94
+ if (allowedChars.indexOf(str.charAt(i)) < 0) {
95
+ return i;
96
+ }
97
+ }
98
+ return -1;
99
+ }
100
+ const imapFormalSyntax = {
101
+ CHAR,
102
+ CHAR8,
103
+ SP,
104
+ CTL,
105
+ DQUOTE,
106
+ ALPHA,
107
+ DIGIT,
108
+ 'ATOM-CHAR': atomChar,
109
+ 'ASTRING-CHAR': astringChar,
110
+ 'TEXT-CHAR': textChar,
111
+ 'atom-specials': atomSpecials,
112
+ 'list-wildcards': listWildcards,
113
+ 'quoted-specials': quotedSpecials,
114
+ 'resp-specials': respSpecials,
115
+ tag,
116
+ command,
117
+ verify
118
+ };
119
+ exports.default = imapFormalSyntax;
120
+ module.exports = exports.default;
121
+ Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
@@ -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,10 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.compiler = exports.parser = void 0;
7
+ const imap_parser_js_1 = __importDefault(require("./imap-parser.js"));
8
+ exports.parser = imap_parser_js_1.default;
9
+ const imap_compiler_js_1 = __importDefault(require("./imap-compiler.js"));
10
+ exports.compiler = imap_compiler_js_1.default;
@@ -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>;
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.default = parser;
7
+ const imap_formal_syntax_js_1 = __importDefault(require("./imap-formal-syntax.js"));
8
+ const parser_instance_js_1 = require("./parser-instance.js");
9
+ /**
10
+ * Parses a raw IMAP command or response buffer into a structured object.
11
+ * Handles edge cases such as null-byte-padded responses from buggy servers and
12
+ * multi-word commands like UID and AUTHENTICATE.
13
+ *
14
+ * @param command - The raw IMAP command or response data to parse.
15
+ * @param options - Parser options passed through to the underlying ParserInstance and TokenParser.
16
+ * @param options.literalPlus - Whether the LITERAL+ extension is in use.
17
+ * @param options.literals - Pre-parsed literal values extracted from the input stream.
18
+ * @returns A promise that resolves to a parsed response object with `tag` (the IMAP tag, e.g. "*",
19
+ * "+", or a command tag like "A1"), `command` (the IMAP command or response name, e.g. "OK",
20
+ * "FETCH"), `attributes` (parsed attributes of the response) and `nullBytesRemoved` (number of
21
+ * leading null bytes removed, if any).
22
+ */
23
+ async function parser(command, options) {
24
+ options = options || {};
25
+ let nullBytesRemoved = 0;
26
+ // Workaround for buggy IMAP servers that pad responses with leading NUL (\x00) bytes.
27
+ // Some servers (observed in the wild) prepend null bytes to their output, which would
28
+ // cause parsing to fail. We strip them and note how many were removed for diagnostics.
29
+ if (command[0] === 0) {
30
+ // find the first non null byte and trim
31
+ let firstNonNull = -1;
32
+ for (let i = 0; i < command.length; i++) {
33
+ if (command[i] !== 0) {
34
+ firstNonNull = i;
35
+ break;
36
+ }
37
+ }
38
+ if (firstNonNull === -1) {
39
+ // All bytes are null, treat as a BAD response
40
+ return { tag: '*', command: 'BAD', attributes: [] };
41
+ }
42
+ command = command.slice(firstNonNull);
43
+ nullBytesRemoved = firstNonNull;
44
+ }
45
+ const parserInstance = new parser_instance_js_1.ParserInstance(command, options);
46
+ const response = {};
47
+ try {
48
+ response.tag = await parserInstance.getTag();
49
+ await parserInstance.getSpace();
50
+ response.command = await parserInstance.getCommand();
51
+ if (nullBytesRemoved) {
52
+ response.nullBytesRemoved = nullBytesRemoved;
53
+ }
54
+ // Some IMAP commands are multi-word: "UID FETCH", "UID STORE", "UID COPY",
55
+ // "UID MOVE", "UID SEARCH", "UID EXPUNGE", and "AUTHENTICATE PLAIN", etc.
56
+ // For these, the first word is consumed as the command, then we read the
57
+ // subcommand and concatenate them (e.g., "UID" + " " + "FETCH" -> "UID FETCH").
58
+ if (['UID', 'AUTHENTICATE'].includes((response.command || '').toUpperCase())) {
59
+ await parserInstance.getSpace();
60
+ response.command += ' ' + (await parserInstance.getElement(imap_formal_syntax_js_1.default.command()));
61
+ }
62
+ if (parserInstance.remainder.trim().length) {
63
+ await parserInstance.getSpace();
64
+ response.attributes = await parserInstance.getAttributes();
65
+ }
66
+ if (parserInstance.humanReadable) {
67
+ response.attributes = (response.attributes || []).concat({
68
+ type: 'TEXT',
69
+ value: parserInstance.humanReadable
70
+ });
71
+ }
72
+ }
73
+ catch (err) {
74
+ let error = err;
75
+ if (error.code === 'ParserErrorExchange' && error.parserContext && error.parserContext.value) {
76
+ return error.parserContext.value;
77
+ }
78
+ if (response.tag) {
79
+ // The tag had already been parsed when the rest of the line failed. Expose it
80
+ // so the connection can settle the command this line was addressed to - unlike
81
+ // re-deriving the tag from the raw bytes, this inherits the leading-NUL
82
+ // workaround above.
83
+ error.parsedTag = response.tag;
84
+ }
85
+ throw error;
86
+ }
87
+ return response;
88
+ }
89
+ module.exports = exports.default;
90
+ Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });