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
@@ -1,69 +1,59 @@
1
- 'use strict';
2
-
3
- const { SocksClient } = require('socks');
4
- const dns = require('dns').promises;
5
- const net = require('net');
6
- const tls = require('tls');
7
-
8
- const { ConnectionDeadline } = require('./connection-deadline');
9
-
1
+ import { SocksClient } from 'socks';
2
+ import dns from 'node:dns';
3
+ import net from 'node:net';
4
+ import tls from 'node:tls';
5
+ import { ConnectionDeadline } from './connection-deadline.js';
6
+ import { clearTimer } from './tools.js';
10
7
  // Cap the CONNECT response buffered before the header terminator, so a proxy that never sends
11
8
  // \r\n\r\n cannot grow memory without bound.
12
9
  const MAX_RESPONSE_HEADER_BYTES = 64 * 1024;
13
-
14
10
  const DEFAULT_SOCKS_PORT = 1080;
15
-
16
11
  // URL hostnames keep the brackets around an IPv6 literal ("[2001:db8::1]"), which is neither a
17
12
  // valid input for net.isIP() nor an address net/tls/socks can connect to. Strip them for socket
18
13
  // options; the parsed URL itself stays intact for logging and credentials.
19
- const unbracketAddress = host => (typeof host === 'string' && host.startsWith('[') && host.endsWith(']') ? host.slice(1, -1) : host);
20
-
14
+ const unbracketAddress = (host) => (typeof host === 'string' && host.startsWith('[') && host.endsWith(']') ? host.slice(1, -1) : host);
21
15
  // CONNECT request lines and Host headers need an IPv6 destination wrapped in brackets. Hostnames
22
16
  // and IPv4 literals are used as-is, and an already bracketed literal is not bracketed twice.
23
17
  const formatAuthority = (host, port) => {
24
18
  let address = unbracketAddress(host);
25
19
  return net.isIPv6(address) ? `[${address}]:${port}` : `${address}:${port}`;
26
20
  };
27
-
28
21
  // Password-free rendering of the proxy URL, used in every log path. The caller's URL object is
29
22
  // left untouched so credentials stay available for authentication.
30
- const redactUrl = proxyUrl => {
23
+ const redactUrl = (proxyUrl) => {
31
24
  let redacted = new URL(proxyUrl.href);
32
25
  if (redacted.password) {
33
26
  redacted.password = '(hidden)';
34
27
  }
35
28
  return redacted.href;
36
29
  };
37
-
38
30
  const proxyError = (message, code) => {
39
31
  let err = new Error(message);
40
32
  err.code = code || 'ProxyError';
41
33
  return err;
42
34
  };
43
-
44
35
  // URL userinfo is percent-encoded, so it has to be decoded before it can be used as credentials.
45
36
  // A password containing a bare '%' is not valid percent-encoding and makes decodeURIComponent
46
37
  // throw, so such values are used as they came in rather than failing the connection.
47
- const decodeUserInfo = value => {
38
+ const decodeUserInfo = (value) => {
48
39
  try {
49
40
  return decodeURIComponent(value);
50
- } catch {
41
+ }
42
+ catch {
51
43
  return value;
52
44
  }
53
45
  };
54
-
55
46
  // The socks client attaches its full options object - proxy password included - to the errors it
56
47
  // throws, and Node's URL errors carry the rejected string in `input`. Any logger that serializes
57
48
  // error properties would then write that password out in clear text, so the credentials are
58
49
  // dropped before the error is logged or handed to the caller.
59
- const stripProxyCredentials = err => {
50
+ const stripProxyCredentials = (err) => {
60
51
  if (err && typeof err === 'object') {
61
52
  delete err.options;
62
53
  delete err.input;
63
54
  }
64
55
  return err;
65
56
  };
66
-
67
57
  // Attaches a benign 'error' listener as soon as the proxied socket exists, so an early
68
58
  // socket error (before ImapFlow installs its own handlers) cannot surface as an unhandled
69
59
  // 'error' event and crash the process. The handler is stored on the socket so the caller
@@ -72,21 +62,19 @@ const attachEarlyErrorHandler = (logger, socket) => {
72
62
  if (!socket || typeof socket.on !== 'function') {
73
63
  return;
74
64
  }
75
- socket._earlyErrorHandler = err => {
65
+ socket._earlyErrorHandler = (err) => {
76
66
  logger.error({ msg: 'Proxy socket error before connection setup', err });
77
67
  };
78
68
  socket.on('error', socket._earlyErrorHandler);
79
69
  };
80
-
81
70
  // Removes the handler installed by attachEarlyErrorHandler once the caller takes ownership
82
71
  // of the socket. Keeps the internal `_earlyErrorHandler` contract inside this module.
83
- const detachEarlyErrorHandler = socket => {
72
+ const detachEarlyErrorHandler = (socket) => {
84
73
  if (socket && socket._earlyErrorHandler) {
85
74
  socket.removeListener('error', socket._earlyErrorHandler);
86
75
  socket._earlyErrorHandler = null;
87
76
  }
88
77
  };
89
-
90
78
  /**
91
79
  * Establishes a tunnel through an HTTP or HTTPS proxy with a CONNECT request.
92
80
  *
@@ -97,16 +85,7 @@ const detachEarlyErrorHandler = socket => {
97
85
  * The destination hostname is passed through unresolved - resolving it is the proxy's job, which
98
86
  * is also what keeps DNS traffic off the client for HTTP proxies.
99
87
  *
100
- * @param {Object} params
101
- * @param {Object} params.logger Logger instance.
102
- * @param {URL} params.proxyUrl Parsed proxy URL (credentials intact).
103
- * @param {Boolean} params.secureProxy Whether the proxy endpoint itself speaks TLS.
104
- * @param {String} params.proxyHost Proxy endpoint host, IPv6 literals unbracketed.
105
- * @param {Number} params.proxyPort Proxy endpoint port.
106
- * @param {String} params.host Destination host (hostname or IP literal).
107
- * @param {Number} params.port Destination port.
108
- * @param {ConnectionDeadline} params.deadline Shared connection deadline.
109
- * @returns {Promise<Object>} The established socket, tunnelled to the destination.
88
+ * @returns The established socket, tunnelled to the destination.
110
89
  */
111
90
  const httpConnect = async ({ logger, proxyUrl, secureProxy, proxyHost, proxyPort, host, port, deadline }) => {
112
91
  // Reject CRLF in the destination before it reaches the CONNECT request line and Host header.
@@ -115,27 +94,21 @@ const httpConnect = async ({ logger, proxyUrl, secureProxy, proxyHost, proxyPort
115
94
  if (!destinationPort || /[\r\n]/.test(host)) {
116
95
  throw proxyError('Invalid proxy destination', 'EPROXY');
117
96
  }
118
-
119
97
  let authority = formatAuthority(host, destinationPort);
120
-
121
98
  let remaining = deadline.remaining();
122
99
  if (!remaining) {
123
100
  throw deadline.error();
124
101
  }
125
-
126
102
  let socket = null;
127
-
128
103
  return await new Promise((resolve, reject) => {
129
104
  let settled = false;
130
105
  let timer = null;
131
106
  let headers = '';
132
-
133
- const onSocketData = chunk => {
107
+ const onSocketData = (chunk) => {
134
108
  // Scan only the newly arrived bytes (plus the 3 that a terminator could straddle),
135
109
  // so a proxy that dribbles its headers cannot turn this into a quadratic rescan.
136
110
  let searchFrom = Math.max(0, headers.length - 3);
137
111
  headers += chunk.toString('binary');
138
-
139
112
  let terminator = headers.indexOf('\r\n\r\n', searchFrom);
140
113
  if (terminator < 0) {
141
114
  if (headers.length > MAX_RESPONSE_HEADER_BYTES) {
@@ -143,14 +116,12 @@ const httpConnect = async ({ logger, proxyUrl, secureProxy, proxyHost, proxyPort
143
116
  }
144
117
  return;
145
118
  }
146
-
147
119
  // The header block is complete, so this listener must stop consuming before anything
148
120
  // is put back: unshifting while still subscribed re-emits the data straight back into
149
121
  // this handler, which would swallow it. Pausing hands the socket over cleanly - the
150
122
  // next owner resumes it (ImapFlow pipes it into the parser).
151
123
  socket.removeListener('data', onSocketData);
152
124
  socket.pause();
153
-
154
125
  // Anything after the header terminator already belongs to the tunnelled stream (a
155
126
  // server greeting that the proxy coalesced with its own response) and has to be
156
127
  // preserved for the next consumer. It is put back as the original bytes, taken from
@@ -161,19 +132,16 @@ const httpConnect = async ({ logger, proxyUrl, secureProxy, proxyHost, proxyPort
161
132
  socket.unshift(chunk.subarray(consumedFromChunk));
162
133
  }
163
134
  headers = headers.slice(0, terminator);
164
-
165
135
  let status = headers.match(/^HTTP\/\d+\.\d+ (\d+)/i);
166
136
  if (!status || (status[1] || '').charAt(0) !== '2') {
167
137
  return fail(proxyError(`Invalid response from proxy${status ? `: ${status[1]}` : ''}`, 'EPROXY'));
168
138
  }
169
-
170
139
  succeed();
171
140
  };
172
-
173
141
  // Single settlement path: temporary listeners and the deadline timer are dropped exactly
174
142
  // once, so a late socket event cannot settle the promise twice or leave a timer armed.
175
143
  const cleanup = () => {
176
- clearTimeout(timer);
144
+ clearTimer(timer);
177
145
  timer = null;
178
146
  if (socket) {
179
147
  // Every temporary listener goes, the connect callback included: after settlement
@@ -184,7 +152,6 @@ const httpConnect = async ({ logger, proxyUrl, secureProxy, proxyHost, proxyPort
184
152
  socket.removeListener('close', onEarlyClose);
185
153
  }
186
154
  };
187
-
188
155
  function fail(err) {
189
156
  if (settled) {
190
157
  return;
@@ -196,19 +163,15 @@ const httpConnect = async ({ logger, proxyUrl, secureProxy, proxyHost, proxyPort
196
163
  }
197
164
  reject(err);
198
165
  }
199
-
200
166
  function succeed() {
201
167
  settled = true;
202
168
  cleanup();
203
169
  resolve(socket);
204
170
  }
205
-
206
171
  function onEarlyClose() {
207
172
  fail(proxyError('Proxy closed the connection before the tunnel was established', 'EPROXY'));
208
173
  }
209
-
210
174
  timer = setTimeout(() => fail(deadline.error()), remaining);
211
-
212
175
  let connectOptions = { host: proxyHost, port: proxyPort };
213
176
  if (secureProxy) {
214
177
  // Verify the proxy's certificate (Node default) and target SNI plus hostname
@@ -218,7 +181,6 @@ const httpConnect = async ({ logger, proxyUrl, secureProxy, proxyHost, proxyPort
218
181
  connectOptions.servername = proxyHost;
219
182
  }
220
183
  }
221
-
222
184
  // Declared as a function so cleanup() above can detach it (the connect callback is
223
185
  // registered as a one-shot 'connect' listener by net/tls).
224
186
  function onConnected() {
@@ -226,66 +188,58 @@ const httpConnect = async ({ logger, proxyUrl, secureProxy, proxyHost, proxyPort
226
188
  Host: authority,
227
189
  Connection: 'close'
228
190
  };
229
-
230
191
  if (proxyUrl.username || proxyUrl.password) {
231
192
  let credentials = `${decodeUserInfo(proxyUrl.username)}:${decodeUserInfo(proxyUrl.password)}`;
232
193
  requestHeaders['Proxy-Authorization'] = `Basic ${Buffer.from(credentials).toString('base64')}`;
233
194
  }
234
-
235
- socket.write(
236
- `CONNECT ${authority} HTTP/1.1\r\n` +
237
- Object.keys(requestHeaders)
238
- .map(key => `${key}: ${requestHeaders[key]}`)
239
- .join('\r\n') +
240
- '\r\n\r\n'
241
- );
242
-
195
+ socket.write(`CONNECT ${authority} HTTP/1.1\r\n` +
196
+ Object.keys(requestHeaders)
197
+ .map(key => `${key}: ${requestHeaders[key]}`)
198
+ .join('\r\n') +
199
+ '\r\n\r\n');
243
200
  socket.on('data', onSocketData);
244
201
  }
245
-
246
202
  // The socket is retained as soon as it is created, so an expiry can destroy it at once.
247
203
  socket = secureProxy ? tls.connect(connectOptions, onConnected) : net.connect(connectOptions, onConnected);
248
204
  socket.once('error', fail);
249
205
  socket.once('close', onEarlyClose);
250
206
  })
251
207
  .then(established => {
252
- logger.info({
253
- msg: `Established a socket via HTTP proxy`,
254
- proxyUrl: redactUrl(proxyUrl),
255
- port,
256
- host
257
- });
258
- attachEarlyErrorHandler(logger, established);
259
- return established;
260
- })
208
+ logger.info({
209
+ msg: `Established a socket via HTTP proxy`,
210
+ proxyUrl: redactUrl(proxyUrl),
211
+ port,
212
+ host
213
+ });
214
+ attachEarlyErrorHandler(logger, established);
215
+ return established;
216
+ })
261
217
  .catch(err => {
262
- logger.error({
263
- msg: 'Failed to establish a socket via HTTP proxy',
264
- proxyUrl: redactUrl(proxyUrl),
265
- port,
266
- host,
267
- err
268
- });
269
- throw err;
218
+ logger.error({
219
+ msg: 'Failed to establish a socket via HTTP proxy',
220
+ proxyUrl: redactUrl(proxyUrl),
221
+ port,
222
+ host,
223
+ err
270
224
  });
225
+ throw err;
226
+ });
271
227
  };
272
-
273
228
  /**
274
229
  * Resolves a destination hostname to an IPv4 address. Only used for SOCKS4, which has no IPv6
275
230
  * destination address type and no hostname form of its own.
276
231
  *
277
- * @param {String} hostname Destination hostname.
278
- * @param {ConnectionDeadline} deadline Shared connection deadline.
279
- * @returns {Promise<String>} An IPv4 address.
232
+ * @param hostname Destination hostname.
233
+ * @param deadline Shared connection deadline.
234
+ * @returns An IPv4 address.
280
235
  */
281
236
  const resolveIPv4 = async (hostname, deadline) => {
282
- let addresses = await deadline.race(dns.resolve4(hostname));
237
+ let addresses = await deadline.race(dns.promises.resolve4(hostname));
283
238
  if (!addresses || !addresses.length) {
284
239
  throw proxyError(`Could not resolve an IPv4 address for ${hostname}`, 'EPROXY');
285
240
  }
286
241
  return addresses[0];
287
242
  };
288
-
289
243
  /**
290
244
  * Establishes a tunnel through a SOCKS proxy.
291
245
  *
@@ -298,32 +252,20 @@ const resolveIPv4 = async (hostname, deadline) => {
298
252
  * IPv6 destination literals are rejected for both SOCKS4 and SOCKS4a: neither can carry them, and
299
253
  * the dependency would write the literal into the SOCKS4a hostname field instead.
300
254
  *
301
- * @param {Object} params
302
- * @param {Object} params.logger Logger instance.
303
- * @param {URL} params.proxyUrl Parsed proxy URL (credentials intact).
304
- * @param {String} params.protocol Configured proxy protocol (socks, socks4, socks4a, socks5).
305
- * @param {String} params.proxyHost Proxy endpoint host, IPv6 literals unbracketed.
306
- * @param {Number} params.proxyPort Proxy endpoint port.
307
- * @param {String} params.host Destination host (hostname or IP literal).
308
- * @param {Number} params.port Destination port.
309
- * @param {ConnectionDeadline} params.deadline Shared connection deadline.
310
- * @returns {Promise<Object>} The established socket, tunnelled to the destination.
255
+ * @returns The established socket, tunnelled to the destination.
311
256
  */
312
257
  const socksConnect = async ({ logger, proxyUrl, protocol, proxyHost, proxyPort, host, port, deadline }) => {
313
258
  let proxyType = protocol === 'socks4' || protocol === 'socks4a' ? 4 : 5;
314
259
  let destinationHost = unbracketAddress(host);
315
-
316
260
  try {
317
261
  if (proxyType === 4) {
318
262
  if (net.isIPv6(destinationHost)) {
319
263
  throw proxyError(`SOCKS4 and SOCKS4a cannot address IPv6 destinations (${destinationHost})`, 'UnsupportedProxyAddress');
320
264
  }
321
-
322
265
  if (protocol === 'socks4' && !net.isIP(destinationHost)) {
323
266
  destinationHost = await resolveIPv4(destinationHost, deadline);
324
267
  }
325
268
  }
326
-
327
269
  let connectionOpts = {
328
270
  proxy: {
329
271
  // The endpoint is handed to net.Socket.connect() by the dependency, so a hostname
@@ -339,12 +281,10 @@ const socksConnect = async ({ logger, proxyUrl, protocol, proxyHost, proxyPort,
339
281
  command: 'connect',
340
282
  set_tcp_nodelay: true
341
283
  };
342
-
343
284
  if (proxyUrl.username || proxyUrl.password) {
344
285
  connectionOpts.proxy.userId = proxyUrl.username;
345
286
  connectionOpts.proxy.password = proxyUrl.password;
346
287
  }
347
-
348
288
  // The dependency treats a zero timeout as its own 30 second default, so only a strictly
349
289
  // positive remaining budget may be passed.
350
290
  let remaining = deadline.remaining();
@@ -352,12 +292,10 @@ const socksConnect = async ({ logger, proxyUrl, protocol, proxyHost, proxyPort,
352
292
  throw deadline.error();
353
293
  }
354
294
  connectionOpts.timeout = remaining;
355
-
356
295
  const info = await deadline.race(SocksClient.createConnection(connectionOpts));
357
296
  if (!info || !info.socket) {
358
297
  throw proxyError('SOCKS proxy did not return a socket', 'EPROXY');
359
298
  }
360
-
361
299
  logger.info({
362
300
  msg: 'Established a socket via SOCKS proxy',
363
301
  proxyUrl: redactUrl(proxyUrl),
@@ -365,14 +303,13 @@ const socksConnect = async ({ logger, proxyUrl, protocol, proxyHost, proxyPort,
365
303
  host
366
304
  });
367
305
  attachEarlyErrorHandler(logger, info.socket);
368
-
369
306
  return info.socket;
370
- } catch (caught) {
307
+ }
308
+ catch (caught) {
371
309
  // A dependency expiry and the shared deadline are reported with the same
372
310
  // CONNECT_TIMEOUT shape, so a caller does not need to know which noticed first.
373
311
  let err = deadline.normalize(stripProxyCredentials(caught));
374
312
  stripProxyCredentials(err._err);
375
-
376
313
  logger.error({
377
314
  msg: 'Failed to establish a socket via SOCKS proxy',
378
315
  proxyUrl: redactUrl(proxyUrl),
@@ -383,43 +320,36 @@ const socksConnect = async ({ logger, proxyUrl, protocol, proxyHost, proxyPort,
383
320
  throw err;
384
321
  }
385
322
  };
386
-
387
323
  /**
388
324
  * Opens a socket to `host`:`port` through the configured proxy.
389
325
  *
390
- * @param {Object} logger Logger instance.
391
- * @param {String} connectionUrl Proxy URL (http, https, socks, socks4, socks4a, socks5).
392
- * @param {String} host Destination host, passed through unresolved wherever the proxy protocol
326
+ * @param logger Logger instance.
327
+ * @param connectionUrl Proxy URL (http, https, socks, socks4, socks4a, socks5).
328
+ * @param host Destination host, passed through unresolved wherever the proxy protocol
393
329
  * can resolve it itself.
394
- * @param {Number} port Destination port.
395
- * @param {Object} [options]
396
- * @param {ConnectionDeadline} [options.deadline] Shared connection deadline. Proxy DNS and
397
- * negotiation run inside it, so a stalled proxy cannot exceed the configured connectionTimeout.
398
- * @param {Number} [options.connectionTimeout] Used to build a deadline when none was passed.
399
- * @returns {Promise<Object|undefined>} The tunnelled socket, or undefined for an unknown protocol.
330
+ * @param port Destination port.
331
+ * @param options Deadline options, see ProxyConnectionOptions.
332
+ * @returns The tunnelled socket, or undefined for an unknown protocol.
400
333
  */
401
334
  const proxyConnection = async (logger, connectionUrl, host, port, options) => {
402
335
  options = options || {};
403
-
404
336
  let deadline = options.deadline || new ConnectionDeadline(options.connectionTimeout);
405
337
  deadline.check();
406
-
407
338
  let proxyUrl;
408
339
  try {
409
340
  proxyUrl = new URL(connectionUrl);
410
- } catch (err) {
341
+ }
342
+ catch (err) {
411
343
  // new URL() attaches the string it rejected to err.input, which here is the full proxy
412
344
  // endpoint including its password. Any logger that serializes error properties would
413
345
  // write that out in clear text, so the cause is reported without carrying the value.
414
346
  throw proxyError('Invalid proxy URL', err.code || 'ERR_INVALID_URL');
415
347
  }
416
348
  let protocol = proxyUrl.protocol.replace(/:$/, '').toLowerCase();
417
-
418
349
  // ImapFlow performs no DNS lookup of its own for the proxy endpoint: net, tls and the SOCKS
419
350
  // client all resolve a hostname endpoint themselves, which keeps Node's normal connection
420
351
  // behavior (including address-family selection) instead of pinning one address.
421
352
  let proxyHost = unbracketAddress(proxyUrl.hostname);
422
-
423
353
  switch (protocol) {
424
354
  // Connect using a HTTP CONNECT method
425
355
  case 'http':
@@ -434,7 +364,6 @@ const proxyConnection = async (logger, connectionUrl, host, port, options) => {
434
364
  port,
435
365
  deadline
436
366
  });
437
-
438
367
  // SOCKS proxy
439
368
  case 'socks':
440
369
  case 'socks5':
@@ -451,6 +380,6 @@ const proxyConnection = async (logger, connectionUrl, host, port, options) => {
451
380
  deadline
452
381
  });
453
382
  }
383
+ return undefined;
454
384
  };
455
-
456
- module.exports = { proxyConnection, detachEarlyErrorHandler };
385
+ export { proxyConnection, detachEarlyErrorHandler };
@@ -0,0 +1,34 @@
1
+ import type { ImapFlow } from './imap-flow.js';
2
+ import type { ImapAttributeNode } from './handler/types.js';
3
+ import type { SearchObject } from './types.js';
4
+ /**
5
+ * A compiled search attribute: a token, or a parenthesized group of tokens
6
+ */
7
+ export type SearchAttribute = ImapAttributeNode | SearchAttribute[];
8
+ /**
9
+ * Compiles a JavaScript object query into IMAP search command attributes.
10
+ * Supports standard IMAP search criteria and extensions like OBJECTID and Gmail extensions.
11
+ *
12
+ * @param connection - IMAP connection object (capabilities, enabled extensions and the current mailbox are read)
13
+ * @param query - Search query object
14
+ * @returns Array of IMAP search attributes
15
+ * @throws {Error} When required server extensions are not available
16
+ *
17
+ * @example
18
+ * // Simple search for unseen messages from a sender
19
+ * searchCompiler(connection, {
20
+ * unseen: true,
21
+ * from: 'sender@example.com'
22
+ * });
23
+ *
24
+ * @example
25
+ * // Complex OR search with date range
26
+ * searchCompiler(connection, {
27
+ * or: [
28
+ * { from: 'alice@example.com' },
29
+ * { from: 'bob@example.com' }
30
+ * ],
31
+ * since: new Date('2024-01-01')
32
+ * });
33
+ */
34
+ export declare const searchCompiler: (connection: ImapFlow, query: SearchObject) => SearchAttribute[];