@withbuddi/buddi 0.1.0-pre.52 → 0.1.0-pre.53

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 (164) hide show
  1. package/extension/manifest.json +2 -2
  2. package/node_modules/@buddi/install/dist/.tsbuildinfo +1 -1
  3. package/node_modules/@buddi/install/dist/app-layout.d.ts +24 -0
  4. package/node_modules/@buddi/install/dist/app-layout.d.ts.map +1 -1
  5. package/node_modules/@buddi/install/dist/app-layout.js +64 -2
  6. package/node_modules/@buddi/install/dist/app-layout.js.map +1 -1
  7. package/node_modules/@buddi/install/dist/launcher.d.ts.map +1 -1
  8. package/node_modules/@buddi/install/dist/launcher.js +89 -10
  9. package/node_modules/@buddi/install/dist/launcher.js.map +1 -1
  10. package/node_modules/@buddi/install/dist/linux-install.d.ts +41 -0
  11. package/node_modules/@buddi/install/dist/linux-install.d.ts.map +1 -0
  12. package/node_modules/@buddi/install/dist/linux-install.js +86 -0
  13. package/node_modules/@buddi/install/dist/linux-install.js.map +1 -0
  14. package/node_modules/@buddi/install/dist/runtime-info.d.ts +23 -0
  15. package/node_modules/@buddi/install/dist/runtime-info.d.ts.map +1 -0
  16. package/node_modules/@buddi/install/dist/runtime-info.js +69 -0
  17. package/node_modules/@buddi/install/dist/runtime-info.js.map +1 -0
  18. package/node_modules/@buddi/install/dist/supervisor.d.ts.map +1 -1
  19. package/node_modules/@buddi/install/dist/supervisor.js +8 -6
  20. package/node_modules/@buddi/install/dist/supervisor.js.map +1 -1
  21. package/node_modules/@buddi/install/dist/uninstall.d.ts +20 -0
  22. package/node_modules/@buddi/install/dist/uninstall.d.ts.map +1 -1
  23. package/node_modules/@buddi/install/dist/uninstall.js +50 -1
  24. package/node_modules/@buddi/install/dist/uninstall.js.map +1 -1
  25. package/node_modules/@buddi/install/dist/upgrade.d.ts +4 -1
  26. package/node_modules/@buddi/install/dist/upgrade.d.ts.map +1 -1
  27. package/node_modules/@buddi/install/dist/upgrade.js +77 -51
  28. package/node_modules/@buddi/install/dist/upgrade.js.map +1 -1
  29. package/node_modules/@hono/node-server/package.json +2 -2
  30. package/node_modules/@types/node/README.md +1 -1
  31. package/node_modules/@types/node/package.json +2 -2
  32. package/node_modules/@types/node/util.d.ts +6 -3
  33. package/node_modules/content-disposition/README.md +105 -35
  34. package/node_modules/content-disposition/dist/index.d.ts +72 -0
  35. package/node_modules/content-disposition/dist/index.js +388 -0
  36. package/node_modules/content-disposition/dist/index.js.map +1 -0
  37. package/node_modules/content-disposition/package.json +31 -19
  38. package/node_modules/docx/dist/index.cjs +182 -80
  39. package/node_modules/docx/dist/index.d.cts +63 -6
  40. package/node_modules/docx/dist/index.d.ts +63 -6
  41. package/node_modules/docx/dist/index.iife.js +182 -80
  42. package/node_modules/docx/dist/index.mjs +182 -80
  43. package/node_modules/docx/dist/index.umd.cjs +182 -80
  44. package/node_modules/docx/dist/layout.cjs +1 -6
  45. package/node_modules/docx/dist/layout.iife.js +1 -6
  46. package/node_modules/docx/dist/layout.mjs +1 -6
  47. package/node_modules/docx/dist/layout.umd.cjs +1 -6
  48. package/node_modules/docx/package.json +4 -3
  49. package/node_modules/express/Readme.md +15 -9
  50. package/node_modules/express/lib/application.js +2 -2
  51. package/node_modules/express/lib/request.js +32 -19
  52. package/node_modules/express/lib/response.js +27 -30
  53. package/node_modules/express/node_modules/content-type/LICENSE +22 -0
  54. package/node_modules/express/node_modules/content-type/README.md +71 -0
  55. package/node_modules/express/node_modules/content-type/dist/index.d.ts +46 -0
  56. package/node_modules/express/node_modules/content-type/dist/index.js +176 -0
  57. package/node_modules/express/node_modules/content-type/dist/index.js.map +1 -0
  58. package/node_modules/express/node_modules/content-type/package.json +52 -0
  59. package/node_modules/express/package.json +10 -10
  60. package/node_modules/imapflow/CHANGELOG.md +33 -0
  61. package/node_modules/imapflow/dist/cjs/commands/append.d.ts +1 -1
  62. package/node_modules/imapflow/dist/cjs/commands/append.js +15 -4
  63. package/node_modules/imapflow/dist/cjs/commands/compress.js +2 -2
  64. package/node_modules/imapflow/dist/cjs/commands/id.js +5 -3
  65. package/node_modules/imapflow/dist/cjs/commands/idle.js +9 -9
  66. package/node_modules/imapflow/dist/cjs/commands/list.js +2 -2
  67. package/node_modules/imapflow/dist/cjs/commands/namespace.js +18 -14
  68. package/node_modules/imapflow/dist/cjs/commands/quota.js +2 -3
  69. package/node_modules/imapflow/dist/cjs/commands/status.js +2 -4
  70. package/node_modules/imapflow/dist/cjs/connection-deadline.d.ts +1 -1
  71. package/node_modules/imapflow/dist/cjs/connection-deadline.js +2 -4
  72. package/node_modules/imapflow/dist/cjs/download.js +66 -35
  73. package/node_modules/imapflow/dist/cjs/errors.d.ts +15 -0
  74. package/node_modules/imapflow/dist/cjs/errors.js +22 -0
  75. package/node_modules/imapflow/dist/cjs/handler/imap-compiler.d.ts +4 -4
  76. package/node_modules/imapflow/dist/cjs/handler/imap-compiler.js +18 -18
  77. package/node_modules/imapflow/dist/cjs/handler/imap-parser.js +28 -3
  78. package/node_modules/imapflow/dist/cjs/handler/imap-stream.js +9 -8
  79. package/node_modules/imapflow/dist/cjs/handler/limits.d.ts +1 -1
  80. package/node_modules/imapflow/dist/cjs/handler/limits.js +5 -7
  81. package/node_modules/imapflow/dist/cjs/handler/parser-instance.js +23 -38
  82. package/node_modules/imapflow/dist/cjs/handler/token-parser.d.ts +12 -0
  83. package/node_modules/imapflow/dist/cjs/handler/token-parser.js +44 -104
  84. package/node_modules/imapflow/dist/cjs/imap-flow.d.ts +4 -3
  85. package/node_modules/imapflow/dist/cjs/imap-flow.js +98 -72
  86. package/node_modules/imapflow/dist/cjs/package-info.d.ts +1 -1
  87. package/node_modules/imapflow/dist/cjs/package-info.js +1 -1
  88. package/node_modules/imapflow/dist/cjs/proxy-connection.js +2 -5
  89. package/node_modules/imapflow/dist/cjs/search-compiler.js +2 -3
  90. package/node_modules/imapflow/dist/cjs/tools.d.ts +14 -3
  91. package/node_modules/imapflow/dist/cjs/tools.js +30 -18
  92. package/node_modules/imapflow/dist/cjs/types.d.ts +11 -0
  93. package/node_modules/imapflow/dist/esm/commands/append.d.ts +1 -1
  94. package/node_modules/imapflow/dist/esm/commands/append.js +15 -4
  95. package/node_modules/imapflow/dist/esm/commands/compress.js +2 -2
  96. package/node_modules/imapflow/dist/esm/commands/id.js +6 -4
  97. package/node_modules/imapflow/dist/esm/commands/idle.js +9 -9
  98. package/node_modules/imapflow/dist/esm/commands/list.js +3 -3
  99. package/node_modules/imapflow/dist/esm/commands/namespace.js +19 -15
  100. package/node_modules/imapflow/dist/esm/commands/quota.js +3 -4
  101. package/node_modules/imapflow/dist/esm/commands/status.js +2 -4
  102. package/node_modules/imapflow/dist/esm/connection-deadline.d.ts +1 -1
  103. package/node_modules/imapflow/dist/esm/connection-deadline.js +2 -4
  104. package/node_modules/imapflow/dist/esm/download.js +66 -35
  105. package/node_modules/imapflow/dist/esm/errors.d.ts +15 -0
  106. package/node_modules/imapflow/dist/esm/errors.js +21 -0
  107. package/node_modules/imapflow/dist/esm/handler/imap-compiler.d.ts +4 -4
  108. package/node_modules/imapflow/dist/esm/handler/imap-compiler.js +18 -18
  109. package/node_modules/imapflow/dist/esm/handler/imap-parser.js +28 -3
  110. package/node_modules/imapflow/dist/esm/handler/imap-stream.js +9 -8
  111. package/node_modules/imapflow/dist/esm/handler/limits.d.ts +1 -1
  112. package/node_modules/imapflow/dist/esm/handler/limits.js +5 -7
  113. package/node_modules/imapflow/dist/esm/handler/parser-instance.js +23 -38
  114. package/node_modules/imapflow/dist/esm/handler/token-parser.d.ts +12 -0
  115. package/node_modules/imapflow/dist/esm/handler/token-parser.js +44 -104
  116. package/node_modules/imapflow/dist/esm/imap-flow.d.ts +4 -3
  117. package/node_modules/imapflow/dist/esm/imap-flow.js +99 -73
  118. package/node_modules/imapflow/dist/esm/package-info.d.ts +1 -1
  119. package/node_modules/imapflow/dist/esm/package-info.js +1 -1
  120. package/node_modules/imapflow/dist/esm/proxy-connection.js +2 -5
  121. package/node_modules/imapflow/dist/esm/search-compiler.js +2 -3
  122. package/node_modules/imapflow/dist/esm/tools.d.ts +14 -3
  123. package/node_modules/imapflow/dist/esm/tools.js +30 -19
  124. package/node_modules/imapflow/dist/esm/types.d.ts +11 -0
  125. package/node_modules/imapflow/package.json +6 -3
  126. package/node_modules/process-warning/.github/workflows/ci.yml +1 -1
  127. package/node_modules/process-warning/.github/workflows/lock-threads.yml +1 -1
  128. package/node_modules/process-warning/.github/workflows/release.yml +24 -0
  129. package/node_modules/process-warning/README.md +46 -46
  130. package/node_modules/process-warning/package.json +1 -1
  131. package/package.json +2 -2
  132. package/packages/install/dist/.tsbuildinfo +1 -1
  133. package/packages/install/dist/app-layout.d.ts +24 -0
  134. package/packages/install/dist/app-layout.d.ts.map +1 -1
  135. package/packages/install/dist/app-layout.js +64 -2
  136. package/packages/install/dist/app-layout.js.map +1 -1
  137. package/packages/install/dist/launcher.d.ts.map +1 -1
  138. package/packages/install/dist/launcher.js +89 -10
  139. package/packages/install/dist/launcher.js.map +1 -1
  140. package/packages/install/dist/linux-install.d.ts +41 -0
  141. package/packages/install/dist/linux-install.d.ts.map +1 -0
  142. package/packages/install/dist/linux-install.js +86 -0
  143. package/packages/install/dist/linux-install.js.map +1 -0
  144. package/packages/install/dist/runtime-info.d.ts +23 -0
  145. package/packages/install/dist/runtime-info.d.ts.map +1 -0
  146. package/packages/install/dist/runtime-info.js +69 -0
  147. package/packages/install/dist/runtime-info.js.map +1 -0
  148. package/packages/install/dist/supervisor.d.ts.map +1 -1
  149. package/packages/install/dist/supervisor.js +8 -6
  150. package/packages/install/dist/supervisor.js.map +1 -1
  151. package/packages/install/dist/uninstall.d.ts +20 -0
  152. package/packages/install/dist/uninstall.d.ts.map +1 -1
  153. package/packages/install/dist/uninstall.js +50 -1
  154. package/packages/install/dist/uninstall.js.map +1 -1
  155. package/packages/install/dist/upgrade.d.ts +4 -1
  156. package/packages/install/dist/upgrade.d.ts.map +1 -1
  157. package/packages/install/dist/upgrade.js +77 -51
  158. package/packages/install/dist/upgrade.js.map +1 -1
  159. package/packages/web/dist/assets/{index-oxmxR39o.js → index-DgpPl0TR.js} +57 -57
  160. package/packages/web/dist/assets/{lottie_light-DROBTM6Y.js → lottie_light-twPed1FM.js} +1 -1
  161. package/packages/web/dist/build.json +1 -1
  162. package/packages/web/dist/index.html +1 -1
  163. package/packages/web/dist/precache.json +3 -3
  164. package/node_modules/content-disposition/index.js +0 -536
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "express",
3
3
  "description": "Fast, unopinionated, minimalist web framework",
4
- "version": "5.2.1",
4
+ "version": "5.3.0",
5
5
  "author": "TJ Holowaychuk <tj@vision-media.ca>",
6
6
  "contributors": [
7
7
  "Aaron Heckmann <aaron.heckmann+github@gmail.com>",
@@ -33,9 +33,9 @@
33
33
  ],
34
34
  "dependencies": {
35
35
  "accepts": "^2.0.0",
36
- "body-parser": "^2.2.1",
37
- "content-disposition": "^1.0.0",
38
- "content-type": "^1.0.5",
36
+ "body-parser": "^2.3.0",
37
+ "content-disposition": "^2.0.1",
38
+ "content-type": "^2.0.0",
39
39
  "cookie": "^0.7.1",
40
40
  "cookie-signature": "^1.2.1",
41
41
  "debug": "^4.4.0",
@@ -51,14 +51,14 @@
51
51
  "on-finished": "^2.4.1",
52
52
  "once": "^1.4.0",
53
53
  "parseurl": "^1.3.3",
54
- "proxy-addr": "^2.0.7",
55
- "qs": "^6.14.0",
54
+ "proxy-addr": "^2.0.8",
55
+ "qs": "^6.16.0",
56
56
  "range-parser": "^1.2.1",
57
57
  "router": "^2.2.0",
58
58
  "send": "^1.1.0",
59
59
  "serve-static": "^2.2.0",
60
60
  "statuses": "^2.0.1",
61
- "type-is": "^2.0.1",
61
+ "type-is": "^2.1.0",
62
62
  "vary": "^1.1.2"
63
63
  },
64
64
  "devDependencies": {
@@ -69,11 +69,11 @@
69
69
  "ejs": "^3.1.10",
70
70
  "eslint": "8.47.0",
71
71
  "express-session": "^1.18.1",
72
- "hbs": "4.2.0",
72
+ "hbs": "4.3.1",
73
73
  "marked": "^15.0.3",
74
74
  "method-override": "3.0.0",
75
- "mocha": "^10.7.3",
76
- "morgan": "1.10.1",
75
+ "mocha": "^11.7.5",
76
+ "morgan": "1.12.1",
77
77
  "nyc": "^17.1.0",
78
78
  "pbkdf2-password": "1.2.1",
79
79
  "supertest": "^6.3.0",
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.4.0](https://github.com/postalsys/imapflow/compare/v2.3.0...v2.4.0) (2026-10-10)
4
+
5
+
6
+ ### Features
7
+
8
+ * re-raise unexpected parser exceptions as coded ParserErrorInternal errors ([dda3f7e](https://github.com/postalsys/imapflow/commit/dda3f7e71e3c227479763a8c6af617fd4f57679a))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * keep append literals in sync with their data and fail downloads cleanly ([752248e](https://github.com/postalsys/imapflow/commit/752248e07fd8930cb0943287c32e3bab88484d80))
14
+
15
+ ## [2.3.0](https://github.com/postalsys/imapflow/compare/v2.2.11...v2.3.0) (2026-10-08)
16
+
17
+
18
+ ### Features
19
+
20
+ * emit an alert event for ALERT response codes ([dcc0e42](https://github.com/postalsys/imapflow/commit/dcc0e42cef3c5c51226b29fd3a6bddd844728357))
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * a throttled login is not a rejected credential ([83fdbce](https://github.com/postalsys/imapflow/commit/83fdbce9968651c3d833b1e74bc20151c26fc769))
26
+ * ask the first chunk of a download again when the answer comes back empty ([83fdbce](https://github.com/postalsys/imapflow/commit/83fdbce9968651c3d833b1e74bc20151c26fc769))
27
+
28
+ ## [2.2.11](https://github.com/postalsys/imapflow/compare/v2.2.10...v2.2.11) (2026-10-08)
29
+
30
+
31
+ ### Bug Fixes
32
+
33
+ * ask a download chunk again when the answer comes back empty instead of failing the download ([0e6e46a](https://github.com/postalsys/imapflow/commit/0e6e46aa81a16a44ccb78df25382ab78a5708ce3))
34
+ * read NAMESPACE prefixes, QUOTAROOT names and ID values that the server sends as literals ([9c77594](https://github.com/postalsys/imapflow/commit/9c7759468f7d64ad6ef857b1861d4a846a8c7c04))
35
+
3
36
  ## [2.2.10](https://github.com/postalsys/imapflow/compare/v2.2.9...v2.2.10) (2026-10-07)
4
37
 
5
38
 
@@ -19,4 +19,4 @@ export interface AppendResult extends AppendResponseObject {
19
19
  * @returns Append result with UID info if available, or undefined if preconditions not met
20
20
  * @throws {Error} If the APPEND command fails or message exceeds APPENDLIMIT
21
21
  */
22
- export default function append(connection: ImapFlow, destination: string | string[], content: Buffer | string, flags?: string | string[] | undefined, idate?: Date | string | false | undefined): Promise<AppendResult | undefined>;
22
+ export default function append(connection: ImapFlow, destination: string | string[], content: Buffer | Uint8Array | string, flags?: string | string[] | undefined, idate?: Date | string | false | undefined): Promise<AppendResult | undefined>;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = append;
4
4
  const tools_js_1 = require("../tools.js");
5
+ const errors_js_1 = require("../errors.js");
5
6
  /**
6
7
  * Appends a message to a mailbox.
7
8
  *
@@ -18,14 +19,24 @@ async function append(connection, destination, content, flags, idate) {
18
19
  // nothing to do here
19
20
  return;
20
21
  }
22
+ // The literal size marker is announced from these bytes, so anything that is not
23
+ // bytes (a Readable, a plain object) is refused here instead of desyncing the session
24
+ let message;
21
25
  if (typeof content === 'string') {
22
- content = Buffer.from(content);
26
+ message = Buffer.from(content);
27
+ }
28
+ else if (content instanceof Uint8Array) {
29
+ // a Buffer as is, any other Uint8Array as a view over the same memory, not a copy
30
+ message = Buffer.isBuffer(content) ? content : Buffer.from(content.buffer, content.byteOffset, content.byteLength);
31
+ }
32
+ else {
33
+ throw (0, errors_js_1.createImapError)('Message content must be a Buffer, Uint8Array or string', 'InvalidMessageContent');
23
34
  }
24
35
  // APPENDLIMIT capability (RFC 7889): server may advertise the maximum message
25
36
  // size it accepts. Check before sending to avoid a wasted round-trip.
26
37
  if (connection.capabilities.has('APPENDLIMIT')) {
27
38
  let appendLimit = connection.capabilities.get('APPENDLIMIT');
28
- if (typeof appendLimit === 'number' && appendLimit < content.length) {
39
+ if (typeof appendLimit === 'number' && appendLimit < message.length) {
29
40
  let err = new Error('Message content too big for APPENDLIMIT=' + appendLimit);
30
41
  err.serverResponseCode = 'APPENDLIMIT';
31
42
  throw err;
@@ -82,9 +93,9 @@ async function append(connection, destination, content, flags, idate) {
82
93
  // Regular literals cannot contain NUL bytes per the IMAP grammar.
83
94
  let isLiteral8 = false;
84
95
  if (connection.capabilities.has('BINARY') && !connection.disableBinary) {
85
- isLiteral8 = content.indexOf(Buffer.from([0])) >= 0;
96
+ isLiteral8 = message.indexOf(0) >= 0;
86
97
  }
87
- attributes.push({ type: 'LITERAL', value: content, isLiteral8 });
98
+ attributes.push({ type: 'LITERAL', value: message, isLiteral8 });
88
99
  let map = { destination };
89
100
  if (connection.mailbox && connection.mailbox.path) {
90
101
  map.path = connection.mailbox.path;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = compress;
4
+ const errors_js_1 = require("../errors.js");
4
5
  /**
5
6
  * Requests DEFLATE compression from the server.
6
7
  *
@@ -36,8 +37,7 @@ async function compress(connection) {
36
37
  // back its unconsumed tail on unpipe so the transport can feed it into the
37
38
  // inflater - not something a command module can reach from here.
38
39
  if (response.hasTrailingData) {
39
- let error = new Error('Server sent data between the COMPRESS response and the compression layer switch');
40
- error.code = 'COMPRESS_TRAILING_DATA';
40
+ let error = (0, errors_js_1.createImapError)('Server sent data between the COMPRESS response and the compression layer switch', 'COMPRESS_TRAILING_DATA');
41
41
  connection.log.error({ err: error, cid: connection.id });
42
42
  // Schedule the close before releasing parser backpressure, so the buffered
43
43
  // deflate-framed bytes cannot settle anything before teardown begins. This is
@@ -39,17 +39,19 @@ async function id(connection, clientInfo) {
39
39
  let params = untagged.attributes && untagged.attributes[0];
40
40
  let key;
41
41
  (Array.isArray(params) ? params : [].concat(params || [])).forEach((val, i) => {
42
+ // keys are strings and values nstrings, either may come as a literal
43
+ let value = (0, tools_js_1.getStringValue)(val);
42
44
  if (i % 2 === 0) {
43
- key = val.value;
45
+ key = value;
44
46
  }
45
- else if (typeof key === 'string' && typeof val.value === 'string') {
47
+ else if (key !== undefined && value !== undefined) {
46
48
  // The server picks the keys of this object, which the caller reads
47
49
  // back as serverInfo: a prototype-chain name is skipped as it is for
48
50
  // every other server-named key, so it can neither be shadowed nor
49
51
  // written through
50
52
  let name = key.toLowerCase().trim();
51
53
  if (!(0, tools_js_1.isUnsafeKey)(name)) {
52
- map[name] = val.value;
54
+ map[name] = value;
53
55
  }
54
56
  }
55
57
  });
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = idle;
4
4
  const tools_js_1 = require("../tools.js");
5
+ const errors_js_1 = require("../errors.js");
5
6
  const NOOP_INTERVAL = 2 * 60 * 1000;
6
7
  /**
7
8
  * Marks the connection as idling on behalf of one session and returns a release function.
@@ -127,14 +128,15 @@ async function runIdle(connection) {
127
128
  }
128
129
  catch (err) {
129
130
  (0, tools_js_1.logConnectionError)(connection, 'IDLE session failed', err);
130
- // A tagged NO or BAD only means the server refused IDLE; the connection is still usable,
131
- // so the waiters are released by the finally block below and their own commands run.
132
- // Anything else (close, lost socket, parser failure) fails the waiters too.
133
- let refusedByServer = (0, tools_js_1.isServerRefusal)(err);
134
- if (refusedByServer) {
131
+ // A tagged NO or BAD only means the server did not run IDLE; the connection is still
132
+ // usable, so the waiters are released by the finally block below and their own commands
133
+ // run. Anything else (close, lost socket, parser failure) fails the waiters too. Only a
134
+ // refusal turns IDLE off for the session, a throttled IDLE is tried again later.
135
+ let answeredByServer = err.responseStatus === 'NO' || err.responseStatus === 'BAD';
136
+ if ((0, tools_js_1.isServerRefusal)(err)) {
135
137
  connection.skipIdle = true;
136
138
  }
137
- if (preCheckWaitQueue.length && !refusedByServer) {
139
+ if (preCheckWaitQueue.length && !answeredByServer) {
138
140
  // One error for the whole queue: every waiter failed at the same site, for the same
139
141
  // reason. Built inside the guard so a teardown with nothing queued - the common case -
140
142
  // does not pay for an Error and its stack capture.
@@ -191,9 +193,7 @@ async function pollOnce(connection, session) {
191
193
  highestModseq: true
192
194
  });
193
195
  if (!status) {
194
- let err = new Error('STATUS poll failed');
195
- err.code = 'PollFailed';
196
- throw err;
196
+ throw (0, errors_js_1.createImapError)('STATUS poll failed', 'PollFailed');
197
197
  }
198
198
  break;
199
199
  }
@@ -158,7 +158,7 @@ async function list(connection, reference, mailbox, options) {
158
158
  path: (0, tools_js_1.normalizePath)(connection, (0, tools_js_1.decodePath)(connection, rawPath)),
159
159
  pathAsListed: rawPath,
160
160
  flags: new Set((0, tools_js_1.getStringList)(untagged.attributes[0])),
161
- delimiter: (untagged.attributes[1] && untagged.attributes[1].value),
161
+ delimiter: (0, tools_js_1.getStringValue)(untagged.attributes[1]),
162
162
  listed: true
163
163
  };
164
164
  normalizeFlags(entry);
@@ -399,7 +399,7 @@ async function list(connection, reference, mailbox, options) {
399
399
  path: (0, tools_js_1.normalizePath)(connection, (0, tools_js_1.decodePath)(connection, rawPath)),
400
400
  pathAsListed: rawPath,
401
401
  flags: new Set((0, tools_js_1.getStringList)(untagged.attributes[0])),
402
- delimiter: (untagged.attributes[1] && untagged.attributes[1].value),
402
+ delimiter: (0, tools_js_1.getStringValue)(untagged.attributes[1]),
403
403
  subscribed: true
404
404
  };
405
405
  if (entry.path.toUpperCase() === 'INBOX') {
@@ -94,8 +94,9 @@ async function getListPrefix(connection) {
94
94
  return;
95
95
  }
96
96
  map.flags = new Set((0, tools_js_1.getStringList)(untagged.attributes[0]));
97
- map.delimiter = (untagged.attributes[1] && untagged.attributes[1].value);
98
- map.prefix = ((untagged.attributes[2] && untagged.attributes[2].value) || '');
97
+ // the name may be a literal, which arrives as a Buffer
98
+ map.delimiter = (0, tools_js_1.getStringValue)(untagged.attributes[1]) || null;
99
+ map.prefix = (0, tools_js_1.getStringValue)(untagged.attributes[2]) || '';
99
100
  if (map.delimiter && map.prefix.charAt(0) === map.delimiter) {
100
101
  map.prefix = map.prefix.slice(1);
101
102
  }
@@ -120,24 +121,27 @@ function getNamsepaceInfo(attribute) {
120
121
  if (!attribute || !Array.isArray(attribute) || !attribute.length) {
121
122
  return false;
122
123
  }
123
- return attribute
124
- .filter(entry => {
125
- let pair = entry;
126
- // RFC 2342 section 5 allows the delimiter to be NIL when the namespace has
127
- // no hierarchy. The token parser emits a literal `null` for NIL.
128
- return pair.length >= 2 && pair[0] && typeof pair[0].value === 'string' && (pair[1] === null || (pair[1] && typeof pair[1].value === 'string'));
129
- })
130
- .map(entry => {
124
+ let entries = [];
125
+ for (let entry of attribute) {
126
+ if (!Array.isArray(entry)) {
127
+ continue;
128
+ }
131
129
  let pair = entry;
132
- let prefix = pair[0].value;
133
- let delimiter = pair[1] === null ? null : pair[1].value;
130
+ let prefix = (0, tools_js_1.getStringValue)(pair[0]);
131
+ // RFC 2342 section 5 allows the delimiter to be NIL when the namespace has no hierarchy.
132
+ // The token parser emits a literal `null` for NIL.
133
+ let delimiter = pair[1] === null ? null : (0, tools_js_1.getStringValue)(pair[1]);
134
+ if (pair.length < 2 || prefix === undefined || delimiter === undefined) {
135
+ continue;
136
+ }
134
137
  // Append the delimiter to the prefix if it doesn't already end with one,
135
138
  // so callers can construct full paths by simply concatenating prefix + name.
136
139
  if (delimiter && prefix && prefix.charAt(prefix.length - 1) !== delimiter) {
137
140
  prefix += delimiter;
138
141
  }
139
- return { prefix, delimiter };
140
- });
142
+ entries.push({ prefix, delimiter });
143
+ }
144
+ return entries;
141
145
  }
142
146
  module.exports = exports.default;
143
147
  Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
@@ -82,9 +82,8 @@ async function quota(connection, path) {
82
82
  // QUOTAROOT response tells us which quota root applies to this mailbox.
83
83
  // A mailbox may have zero or one quota root.
84
84
  QUOTAROOT: async (untagged) => {
85
- let quotaRoot = untagged.attributes && untagged.attributes[1] && typeof untagged.attributes[1].value === 'string'
86
- ? untagged.attributes[1].value
87
- : false;
85
+ // the root name is an astring, which may come as a literal
86
+ let quotaRoot = (0, tools_js_1.getStringValue)(untagged.attributes && untagged.attributes[1]);
88
87
  if (quotaRoot) {
89
88
  map.quotaRoot = quotaRoot;
90
89
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = status;
4
4
  const tools_js_1 = require("../tools.js");
5
5
  const status_fields_js_1 = require("./status-fields.js");
6
+ const errors_js_1 = require("../errors.js");
6
7
  // STATUS fields that also refresh the live mailbox state when the queried mailbox is the
7
8
  // currently selected one. Keyed by the output property name parseStatusList() reports.
8
9
  const MAILBOX_UPDATERS = {
@@ -99,10 +100,7 @@ async function status(connection, path, query) {
99
100
  return false;
100
101
  });
101
102
  if (folders && !folders.length) {
102
- let error = new Error(`Mailbox doesn't exist: ${path}`);
103
- error.code = 'NotFound';
104
- error.response = err;
105
- throw error;
103
+ throw (0, errors_js_1.createImapError)(`Mailbox doesn't exist: ${path}`, 'NotFound', { response: err });
106
104
  }
107
105
  }
108
106
  connection.log.warn({ err, cid: connection.id });
@@ -1,4 +1,4 @@
1
- import type { ImapFlowError } from './errors.js';
1
+ import { type ImapFlowError } from './errors.js';
2
2
  export declare const CONNECT_TIMEOUT: number;
3
3
  /**
4
4
  * One deadline for an entire connection attempt.
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConnectionDeadline = exports.CONNECT_TIMEOUT = void 0;
4
4
  const tools_js_1 = require("./tools.js");
5
+ const errors_js_1 = require("./errors.js");
5
6
  // Default upper bound for establishing a usable transport, including DNS and proxy negotiation.
6
7
  exports.CONNECT_TIMEOUT = 90 * 1000;
7
8
  /**
@@ -31,10 +32,7 @@ class ConnectionDeadline {
31
32
  * @returns The shared `CONNECT_TIMEOUT` error.
32
33
  */
33
34
  error() {
34
- let err = new Error('Failed to establish connection in required time');
35
- err.code = 'CONNECT_TIMEOUT';
36
- err.details = { connectionTimeout: this.timeout };
37
- return err;
35
+ return (0, errors_js_1.createImapError)('Failed to establish connection in required time', 'CONNECT_TIMEOUT', { details: { connectionTimeout: this.timeout } });
38
36
  }
39
37
  /**
40
38
  * Maps a dependency's own expiry onto the shared `CONNECT_TIMEOUT` shape, so callers see one
@@ -18,6 +18,7 @@ const limited_passthrough_js_1 = require("./limited-passthrough.js");
18
18
  // What a wait on the head stream listens for: it can take more input, it failed, or it went
19
19
  // away (a consumer destroying it closes it without a 'drain')
20
20
  const DRAIN_WAIT_EVENTS = ['drain', 'error', 'close'];
21
+ const errors_js_1 = require("./errors.js");
21
22
  const tools_js_1 = require("./tools.js");
22
23
  const isEmptySection = (value) => !value?.length;
23
24
  /**
@@ -40,8 +41,9 @@ const partialStarts = (requests) => {
40
41
  }
41
42
  return starts;
42
43
  };
43
- // How many times a request is repeated after answers that belong to another request
44
- const MAX_FOREIGN_ANSWERS = 3;
44
+ // Attempts per request when the answer belongs to another request or, for a message known to
45
+ // exist, is empty
46
+ const MAX_FETCH_ATTEMPTS = 3;
45
47
  const requestedUid = (range, options) => options.uid && /^\d+$/.test(String(range)) ? Number(range) : undefined;
46
48
  const isForeignAnswer = (response, expected) => {
47
49
  if (expected.uid && response.uid && response.uid !== expected.uid) {
@@ -63,11 +65,19 @@ const isForeignAnswer = (response, expected) => {
63
65
  * within the answer to the next one. Taken at face value it would be the data of that next
64
66
  * request, and a download would end without an error but with misplaced bytes. An answer for
65
67
  * another UID, or with a partial section that starts at another offset than asked, is dropped
66
- * and the request repeated.
68
+ * and the request repeated. An empty answer is repeated too while `expected.retryEmpty()` says
69
+ * the message exists, as the data of a late answer arrives with the repeated request.
67
70
  */
68
71
  async function fetchExpected(client, range, query, options, expected) {
69
72
  for (let attempt = 1;; attempt++) {
70
73
  let response = await client.fetchOne(range, query, options);
74
+ if (response === false && expected.retryEmpty && expected.retryEmpty() && attempt < MAX_FETCH_ATTEMPTS) {
75
+ // The answer may come late instead (after the tagged OK, with the next answer), then
76
+ // it shows up in the answer to the repeated request. A message that is really gone
77
+ // answers empty every time, and the caller reports it.
78
+ client.log.warn({ msg: 'Server answered a request for an existing message with no data, asking again', attempt, cid: client.id });
79
+ continue;
80
+ }
71
81
  if (!response || !isForeignAnswer(response, expected)) {
72
82
  return response;
73
83
  }
@@ -78,11 +88,8 @@ async function fetchExpected(client, range, query, options, expected) {
78
88
  attempt,
79
89
  cid: client.id
80
90
  });
81
- if (attempt >= MAX_FOREIGN_ANSWERS) {
82
- let err = new Error('Server kept answering with data of another request');
83
- err.code = 'DownloadIncomplete';
84
- err.cid = client.id;
85
- throw err;
91
+ if (attempt >= MAX_FETCH_ATTEMPTS) {
92
+ throw (0, errors_js_1.createImapError)('Server kept answering with data of another request', 'DownloadIncomplete', { cid: client.id });
86
93
  }
87
94
  }
88
95
  }
@@ -108,7 +115,8 @@ async function refetchDroppedSections(client, response, range, options, sections
108
115
  let uid = response.uid;
109
116
  let retry = await fetchExpected(client, uid || range, { uid: true, bodyParts: sections }, uid ? { ...options, uid: true } : options, {
110
117
  uid,
111
- origins: partialStarts(sections)
118
+ origins: partialStarts(sections),
119
+ retryEmpty: () => true
112
120
  });
113
121
  if (!retry) {
114
122
  return;
@@ -154,7 +162,13 @@ async function downloadMessage(client, range, part, options) {
154
162
  // Special handling for part "1": in single-node emails (no childNodes),
155
163
  // the body is accessed via "TEXT" rather than "1", and headers via
156
164
  // "HEADER" instead of "1.MIME". Check bodyStructure to detect this.
157
- let response = await client.fetchOne(range, { uid: true, bodyStructure: true }, downloadOptions);
165
+ let expectedUid = requestedUid(range, downloadOptions);
166
+ let response = await fetchExpected(client, range, { uid: true, bodyStructure: true }, downloadOptions, {
167
+ uid: expectedUid,
168
+ origins: new Map(),
169
+ // a message addressed by UID is taken to exist, see getNextPart() below
170
+ retryEmpty: expectedUid ? () => true : undefined
171
+ });
158
172
  if (!response) {
159
173
  return {};
160
174
  }
@@ -170,6 +184,14 @@ async function downloadMessage(client, range, part, options) {
170
184
  part = 'TEXT';
171
185
  }
172
186
  }
187
+ // The decoder pipeline, built once the head chunk told what the part is (see below)
188
+ let stream;
189
+ let output;
190
+ let fetchAborted = false;
191
+ // A consumer that gave up: its 'close' may still be a tick away from setting fetchAborted,
192
+ // so the stream's own flag is checked as well
193
+ // `output` is unset until the head chunk is in, and the head chunk's retry asks already
194
+ let downloadAborted = () => fetchAborted || !!output?.destroyed;
173
195
  let getNextPart = async (query) => {
174
196
  query = query || {};
175
197
  let mimeKey;
@@ -203,12 +225,23 @@ async function downloadMessage(client, range, part, options) {
203
225
  };
204
226
  query.bodyParts.push(contentRequest);
205
227
  }
228
+ let expectedUid = uid || requestedUid(range, downloadOptions);
206
229
  let expected = {
207
- uid: uid || requestedUid(range, downloadOptions),
208
- origins: new Map([[part || '', processed]])
230
+ uid: expectedUid,
231
+ origins: new Map([[part || '', processed]]),
232
+ // Every chunk after the first is of a message that was there a moment ago, and a
233
+ // message the caller addresses by UID is taken to exist: an empty answer may be a
234
+ // late one (Apache James), a message that is really gone costs two more requests
235
+ retryEmpty: processed > 0 || expectedUid ? () => !downloadAborted() : undefined
209
236
  };
210
237
  let response = await fetchExpected(client, range, query, downloadOptions, expected);
211
238
  if (!response) {
239
+ // An empty answer also comes from a connection that closed while the consumer was
240
+ // paused between chunks (fetchOne() finds no mailbox then), and that is not a
241
+ // missing message: a caller that retries would skip it
242
+ if (!client.usable) {
243
+ throw client.createNoConnectionError(false, { rejectedFrom: 'download' });
244
+ }
212
245
  return { response: false, chunk: false };
213
246
  }
214
247
  if (!uid && response.uid) {
@@ -316,9 +349,6 @@ async function downloadMessage(client, range, part, options) {
316
349
  meta.filename = filename;
317
350
  }
318
351
  }
319
- let stream;
320
- let output;
321
- let fetchAborted = false;
322
352
  // Build a decoder pipeline that progressively transforms the raw FETCH data:
323
353
  // 1. Transfer-encoding decoder (base64 or quoted-printable -> binary)
324
354
  // 2. Format decoder (format=flowed -> plain text, if applicable)
@@ -348,13 +378,19 @@ async function downloadMessage(client, range, part, options) {
348
378
  // would still pull the entire part off the wire.
349
379
  let limiters = [];
350
380
  let isLimited = () => limiters.some(entry => entry.limited);
381
+ // Every stage after the head, so a download that ends early can release all of them
382
+ let stages = [];
351
383
  // Appending a stage means forwarding the current tail's errors to it before piping, so a
352
- // failure anywhere reaches the stream the caller is reading
384
+ // failure anywhere reaches the stream the caller is reading. The stage is destroyed with
385
+ // the error rather than only emitting it: pipe() does not end its destination on a source
386
+ // error, so a consumer that only listens for 'finish' or 'close' (a plain .pipe() into a
387
+ // file) would otherwise wait forever.
353
388
  let pipeStage = (stage) => {
354
389
  output.on('error', err => {
355
- stage.emit('error', err);
390
+ stage.destroy(err);
356
391
  });
357
392
  output = output.pipe(stage);
393
+ stages.push(stage);
358
394
  return stage;
359
395
  };
360
396
  let isTextNode = ['text/html', 'text/plain', 'text/x-amp-html'].includes(meta.contentType) || (part === '1' && !meta.contentType);
@@ -399,8 +435,8 @@ async function downloadMessage(client, range, part, options) {
399
435
  // Cleanup function
400
436
  const cleanup = () => {
401
437
  fetchAborted = true;
402
- if (stream && !stream.destroyed) {
403
- stream.destroy();
438
+ for (let entry of [stream, ...stages]) {
439
+ entry.destroy();
404
440
  }
405
441
  };
406
442
  // Listen for stream destruction
@@ -473,26 +509,18 @@ async function downloadMessage(client, range, part, options) {
473
509
  // Loud on purpose. Everything written downstream by this point holds
474
510
  // duplicated content, and a quiet stop is indistinguishable from a clean EOF,
475
511
  // so the consumer would store a corrupt body believing it intact.
476
- let err = new Error('Download exceeded the expected message size');
477
- err.code = 'DownloadOverflow';
478
- err.maxSize = maxTotalBytes;
479
- err.cid = client.id;
480
- throw err;
512
+ throw (0, errors_js_1.createImapError)('Download exceeded the expected message size', 'DownloadOverflow', { maxSize: maxTotalBytes, cid: client.id });
481
513
  }
482
514
  let { response, chunk } = await getNextPart();
483
- // A consumer that gave up while the chunk was in flight: its 'close' may still be a
484
- // tick away from setting fetchAborted, so the stream's own flag is checked as well
485
- if (fetchAborted || output.destroyed) {
515
+ // A consumer that gave up while the chunk was in flight
516
+ if (downloadAborted()) {
486
517
  break;
487
518
  }
488
519
  if (response === false) {
489
520
  // The message is gone mid-download (expunged by another client, or the
490
521
  // mailbox was closed). Ending the stream here would pass the truncated body
491
522
  // off as complete, so the consumer is told the same way as for an overflow.
492
- let err = new Error('Message disappeared before the download completed');
493
- err.code = 'DownloadIncomplete';
494
- err.cid = client.id;
495
- throw err;
523
+ throw (0, errors_js_1.createImapError)('Message disappeared before the download completed', 'DownloadIncomplete', { cid: client.id });
496
524
  }
497
525
  if (!chunk) {
498
526
  break;
@@ -522,7 +550,7 @@ async function downloadMessage(client, range, part, options) {
522
550
  fetchAllParts(head)
523
551
  .catch(err => {
524
552
  if (!fetchAborted && stream && !stream.destroyed) {
525
- stream.emit('error', err);
553
+ stream.destroy(err);
526
554
  /* c8 ignore start */ // the else logs when a fetch error arrives after the stream was already torn down (timing-dependent)
527
555
  }
528
556
  else {
@@ -547,7 +575,7 @@ async function downloadMessage(client, range, part, options) {
547
575
  // above rejects a promise nobody holds and takes the process down on
548
576
  // unhandledRejection. Reaching it always means an invariant broke - the head
549
577
  // stream kept pipeStage()'s error forwarder for the life of the download, so
550
- // emit('error') above has somewhere to go - which is why it logs at error even
578
+ // destroy(err) above has somewhere to go - which is why it logs at error even
551
579
  // for a routine-looking connection code.
552
580
  .catch(err => client.log.error({ msg: 'Failed to fail the download stream', err, cid: client.id }));
553
581
  };
@@ -586,9 +614,12 @@ async function downloadMessageParts(client, range, parts, options) {
586
614
  contentRequests.set(part, contentRequest);
587
615
  query.bodyParts.push(contentRequest);
588
616
  }
617
+ let expectedUid = requestedUid(range, downloadOptions);
589
618
  let response = await fetchExpected(client, range, query, downloadOptions, {
590
- uid: requestedUid(range, downloadOptions),
591
- origins: partialStarts(query.bodyParts)
619
+ uid: expectedUid,
620
+ origins: partialStarts(query.bodyParts),
621
+ // a message addressed by UID is taken to exist, as for download()
622
+ retryEmpty: expectedUid ? () => true : undefined
592
623
  });
593
624
  if (!response || !response.bodyParts) {
594
625
  return {};
@@ -29,6 +29,7 @@ export declare const ImapFlowErrorCode: {
29
29
  readonly MissingServerExtension: "MissingServerExtension";
30
30
  readonly ParserError: "ParserError";
31
31
  readonly ParserErrorExchange: "ParserErrorExchange";
32
+ readonly ParserErrorInternal: "ParserErrorInternal";
32
33
  readonly MAX_IMAP_NESTING_REACHED: "MAX_IMAP_NESTING_REACHED";
33
34
  readonly LineTooLarge: "LineTooLarge";
34
35
  readonly LiteralTooLarge: "LiteralTooLarge";
@@ -38,6 +39,7 @@ export declare const ImapFlowErrorCode: {
38
39
  readonly InvalidTextValue: "InvalidTextValue";
39
40
  readonly InvalidSequenceSet: "InvalidSequenceSet";
40
41
  readonly InvalidSearchQuery: "InvalidSearchQuery";
42
+ readonly InvalidMessageContent: "InvalidMessageContent";
41
43
  readonly DownloadOverflow: "DownloadOverflow";
42
44
  readonly DownloadIncomplete: "DownloadIncomplete";
43
45
  readonly ProxyError: "ProxyError";
@@ -48,6 +50,8 @@ export declare const ImapFlowErrorCode: {
48
50
  };
49
51
  /** One of the {@link ImapFlowErrorCode} values */
50
52
  export type ImapFlowErrorCode = (typeof ImapFlowErrorCode)[keyof typeof ImapFlowErrorCode];
53
+ /** A numbered parser error code, `ParserError1` and up */
54
+ export type ParserErrorCode = `ParserError${number}`;
51
55
  /**
52
56
  * An Error raised by ImapFlow, with the extra properties the library attaches to describe
53
57
  * the failure. Every property is optional: which ones are present depends on where the
@@ -132,3 +136,14 @@ export declare class AuthenticationFailure extends Error implements ImapFlowErro
132
136
  response?: string | undefined;
133
137
  oauthError?: any;
134
138
  }
139
+ /**
140
+ * Creates an {@link ImapFlowError} with a code and optional extra properties. The code is
141
+ * checked by the compiler against {@link ImapFlowErrorCode}, so a typo or an undeclared
142
+ * code fails the build instead of reaching consumers.
143
+ *
144
+ * @param message - The error message
145
+ * @param code - The error code
146
+ * @param props - Extra properties to set on the error
147
+ * @returns The error, ready to throw or reject with
148
+ */
149
+ export declare function createImapError(message: string, code: ImapFlowErrorCode | ParserErrorCode, props?: Omit<ImapFlowError, 'name' | 'message' | 'stack' | 'code'> | undefined): ImapFlowError;