imapflow 2.0.8 → 2.1.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.
@@ -3,5 +3,5 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.homepage = exports.version = exports.name = void 0;
5
5
  exports.name = "imapflow";
6
- exports.version = "2.0.8";
6
+ exports.version = "2.1.0";
7
7
  exports.homepage = "https://imapflow.com/";
@@ -1,6 +1,6 @@
1
1
  import type { Transform } from 'node:stream';
2
2
  import type { ImapFlow } from './imap-flow.js';
3
- import type { ConnectionErrorSite, ImapFlowError } from './errors.js';
3
+ import { type ConnectionErrorSite, type ImapFlowError } from './errors.js';
4
4
  import type { ImapAttributeList, ImapAttributeNode, ImapResponse } from './handler/types.js';
5
5
  import type { FetchMessageObject, ListResponse, ListTreeResponse, MailboxObject, MessageEnvelopeObject, MessageStructureObject, ImapFlowEvents, StatusQuery } from './types.js';
6
6
  export { AuthenticationFailure } from './errors.js';
package/dist/cjs/tools.js CHANGED
@@ -58,11 +58,12 @@ const imap_handler_js_1 = require("./handler/imap-handler.js");
58
58
  const node_crypto_1 = require("node:crypto");
59
59
  const jp_decoder_js_1 = require("./jp-decoder.js");
60
60
  const iconv_lite_1 = __importDefault(require("iconv-lite"));
61
- var errors_js_1 = require("./errors.js");
62
- Object.defineProperty(exports, "AuthenticationFailure", { enumerable: true, get: function () { return errors_js_1.AuthenticationFailure; } });
61
+ const errors_js_1 = require("./errors.js");
62
+ var errors_js_2 = require("./errors.js");
63
+ Object.defineProperty(exports, "AuthenticationFailure", { enumerable: true, get: function () { return errors_js_2.AuthenticationFailure; } });
63
64
  const FLAG_COLORS = ['red', 'orange', 'yellow', 'green', 'blue', 'purple', 'grey'];
64
65
  // Error codes that only mean the connection is no longer usable. See logConnectionError().
65
- const CONNECTION_GONE_CODES = new Set(['NoConnection', 'EConnectionClosed', 'StateLogout']);
66
+ const CONNECTION_GONE_CODES = new Set([errors_js_1.ImapFlowErrorCode.NoConnection, errors_js_1.ImapFlowErrorCode.EConnectionClosed, errors_js_1.ImapFlowErrorCode.StateLogout]);
66
67
  // Upper bound for expanding server-supplied sequence ranges (see expandRange). 2^24
67
68
  // entries in total is far beyond any legitimate mailbox while keeping the worst-case
68
69
  // expansion of a hostile range set bounded.
@@ -1147,7 +1148,7 @@ function parseBodystructure(entry) {
1147
1148
  curNode.type = 'multipart/' + ((node[i++] || {}).value || '').toString().toLowerCase();
1148
1149
  // extension data (not available for BODY requests)
1149
1150
  // body parameter parenthesized list
1150
- if (i < node.length - 1) {
1151
+ if (i < node.length) {
1151
1152
  if (node[i]) {
1152
1153
  curNode.parameters = getStructuredParams(node[i]);
1153
1154
  }
@@ -1229,7 +1230,7 @@ function parseBodystructure(entry) {
1229
1230
  }
1230
1231
  // extension data (not available for BODY requests)
1231
1232
  // md5
1232
- if (i < node.length - 1) {
1233
+ if (i < node.length) {
1233
1234
  if (node[i]) {
1234
1235
  curNode.md5 = (node[i].value || '').toString().toLowerCase();
1235
1236
  }
@@ -1239,7 +1240,7 @@ function parseBodystructure(entry) {
1239
1240
  // the following are shared extension values (for both multipart and non-multipart parts)
1240
1241
  // not available for BODY requests
1241
1242
  // body disposition
1242
- if (i < node.length - 1) {
1243
+ if (i < node.length) {
1243
1244
  let disposition = node[i];
1244
1245
  if (Array.isArray(disposition) && disposition.length) {
1245
1246
  curNode.disposition = ((disposition[0] && disposition[0].value) || '').toString().toLowerCase();
@@ -1250,7 +1251,7 @@ function parseBodystructure(entry) {
1250
1251
  i++;
1251
1252
  }
1252
1253
  // body language
1253
- if (i < node.length - 1) {
1254
+ if (i < node.length) {
1254
1255
  if (node[i]) {
1255
1256
  /* c8 ignore next */ // node[i] is truthy inside this guard, so the [] fallback is unreachable
1256
1257
  curNode.language = [].concat(node[i] || []).map(val => ((val && val.value) || '').toString().toLowerCase());
@@ -1260,7 +1261,7 @@ function parseBodystructure(entry) {
1260
1261
  // body location
1261
1262
  // NB! defined as a "string list" in RFC3501 but replaced in errata document with "string"
1262
1263
  // Errata: http://www.rfc-editor.org/errata_search.php?rfc=3501
1263
- if (i < node.length - 1) {
1264
+ if (i < node.length) {
1264
1265
  if (node[i]) {
1265
1266
  curNode.location = (node[i].value || '').toString();
1266
1267
  }
@@ -444,23 +444,27 @@ export default async function list(connection, reference, mailbox, options) {
444
444
  connection.log.warn({ msg: 'Failed to request subscription info', err, cid: connection.id });
445
445
  }
446
446
  }
447
- // Resolve special-use conflicts: for each type, pick the best candidate
448
- // based on source priority (user > extension > name), then alphabetically.
449
- // Only the winning entry gets the specialUse property set.
450
- for (let type of Object.keys(specialUseMatches)) {
451
- let sortedEntries = specialUseMatches[type].sort((a, b) => {
452
- let aSource = SOURCE_SORT_ORDER.indexOf(a.source);
453
- let bSource = SOURCE_SORT_ORDER.indexOf(b.source);
454
- if (aSource === bSource) {
455
- return a.entry.path.localeCompare(b.entry.path);
456
- }
457
- return aSource - bSource;
458
- });
459
- if (!sortedEntries[0].entry.specialUse) {
460
- let source = sortedEntries[0].source;
461
- sortedEntries[0].entry.specialUse = type;
462
- sortedEntries[0].entry.specialUseSource = PUBLIC_SOURCE[source] || source;
447
+ // Resolve special-use conflicts. Each type goes to one mailbox and each mailbox gets
448
+ // at most one type. Candidates are taken in priority order across all types (user >
449
+ // extension > name, then alphabetically), so a mailbox claimed by a stronger match
450
+ // leaves its other type to that type's next candidate instead of to nobody.
451
+ let candidates = Object.entries(specialUseMatches).flatMap(([type, matches]) => matches.map(match => ({ type, ...match })));
452
+ candidates.sort((a, b) => {
453
+ let aSource = SOURCE_SORT_ORDER.indexOf(a.source);
454
+ let bSource = SOURCE_SORT_ORDER.indexOf(b.source);
455
+ if (aSource === bSource) {
456
+ return a.entry.path.localeCompare(b.entry.path);
457
+ }
458
+ return aSource - bSource;
459
+ });
460
+ let assignedTypes = new Set();
461
+ for (let { type, entry, source } of candidates) {
462
+ if (assignedTypes.has(type) || entry.specialUse) {
463
+ continue;
463
464
  }
465
+ entry.specialUse = type;
466
+ entry.specialUseSource = PUBLIC_SOURCE[source] || source;
467
+ assignedTypes.add(type);
464
468
  }
465
469
  // No source answered, so "not subscribed" was never actually reported for any of
466
470
  // these folders - the state is unknown, not false. Reporting the whole listing as
@@ -92,12 +92,16 @@ export default async function select(connection, pathInput, options) {
92
92
  // QRESYNC (RFC 7162): allows efficient mailbox resynchronization by sending
93
93
  // the last known UIDVALIDITY and HIGHESTMODSEQ. Server responds with only
94
94
  // the changes (new flags, expunged UIDs) since that point.
95
+ // The caller may pass UIDVALIDITY as a bigint, a number or a string. It is parsed once,
96
+ // so the value that is sent is also the one checked against the server's below, and a
97
+ // value that is not a plain decimal skips QRESYNC instead of reaching the server.
98
+ let uidValidity = options.uidValidity !== undefined ? parseBigIntValue(String(options.uidValidity)) : false;
95
99
  let extraArgs = [];
96
- if (connection.enabled.has('QRESYNC') && options.changedSince && options.uidValidity) {
100
+ if (connection.enabled.has('QRESYNC') && options.changedSince && uidValidity) {
97
101
  extraArgs.push([
98
102
  { type: 'ATOM', value: 'QRESYNC' },
99
103
  [
100
- { type: 'ATOM', value: options.uidValidity?.toString() },
104
+ { type: 'ATOM', value: uidValidity.toString() },
101
105
  { type: 'ATOM', value: options.changedSince.toString() }
102
106
  ]
103
107
  ]);
@@ -202,7 +206,7 @@ export default async function select(connection, pathInput, options) {
202
206
  // QRESYNC results are only valid if UIDVALIDITY matches, HIGHESTMODSEQ is
203
207
  // present, and the mailbox supports mod-sequences. If any condition fails,
204
208
  // the client cannot trust the incremental updates and must do a full resync.
205
- if (map.qresync && (options.uidValidity !== map.uidValidity || !map.highestModseq || map.noModseq)) {
209
+ if (map.qresync && (uidValidity !== map.uidValidity || !map.highestModseq || map.noModseq)) {
206
210
  map.qresync = false;
207
211
  }
208
212
  // Transition mailbox state: save previous mailbox reference, temporarily
@@ -1,11 +1,58 @@
1
1
  import type { ImapResponse } from './handler/types.js';
2
+ /**
3
+ * The `code` values ImapFlow sets on the errors it raises, so they can be matched without
4
+ * string literals: `if (err.code === ImapFlowErrorCode.NoConnection)`.
5
+ *
6
+ * Parser failures use `ParserError` followed by a number (`ParserError11`) and are not listed
7
+ * one by one, test them with `err.code?.startsWith('ParserError')`. Errors from the socket, TLS
8
+ * or DNS layer pass through with Node's own code (`ECONNREFUSED`, `ENOTFOUND`, ...).
9
+ */
10
+ export declare const ImapFlowErrorCode: {
11
+ readonly NoConnection: "NoConnection";
12
+ readonly EConnectionClosed: "EConnectionClosed";
13
+ readonly StateLogout: "StateLogout";
14
+ readonly ClosedAfterConnectText: "ClosedAfterConnectText";
15
+ readonly ClosedAfterConnectTLS: "ClosedAfterConnectTLS";
16
+ readonly CONNECT_TIMEOUT: "CONNECT_TIMEOUT";
17
+ readonly GREETING_TIMEOUT: "GREETING_TIMEOUT";
18
+ readonly UPGRADE_TIMEOUT: "UPGRADE_TIMEOUT";
19
+ readonly ETIMEOUT: "ETIMEOUT";
20
+ readonly LockTimeout: "LockTimeout";
21
+ readonly ETHROTTLE: "ETHROTTLE";
22
+ readonly UnexpectedTag: "UnexpectedTag";
23
+ readonly InvalidResponse: "InvalidResponse";
24
+ readonly ResponseProcessingFailed: "ResponseProcessingFailed";
25
+ readonly STARTTLS_INJECTION: "STARTTLS_INJECTION";
26
+ readonly COMPRESS_TRAILING_DATA: "COMPRESS_TRAILING_DATA";
27
+ readonly PollFailed: "PollFailed";
28
+ readonly NotFound: "NotFound";
29
+ readonly MissingServerExtension: "MissingServerExtension";
30
+ readonly ParserError: "ParserError";
31
+ readonly ParserErrorExchange: "ParserErrorExchange";
32
+ readonly MAX_IMAP_NESTING_REACHED: "MAX_IMAP_NESTING_REACHED";
33
+ readonly LineTooLarge: "LineTooLarge";
34
+ readonly LiteralTooLarge: "LiteralTooLarge";
35
+ readonly ResponseTooLarge: "ResponseTooLarge";
36
+ readonly InvalidStringValue: "InvalidStringValue";
37
+ readonly InvalidTokenValue: "InvalidTokenValue";
38
+ readonly InvalidTextValue: "InvalidTextValue";
39
+ readonly InvalidSequenceSet: "InvalidSequenceSet";
40
+ readonly DownloadOverflow: "DownloadOverflow";
41
+ readonly DownloadIncomplete: "DownloadIncomplete";
42
+ readonly ProxyError: "ProxyError";
43
+ readonly EPROXY: "EPROXY";
44
+ readonly UnsupportedProxyAddress: "UnsupportedProxyAddress";
45
+ readonly ERR_INVALID_URL: "ERR_INVALID_URL";
46
+ };
47
+ /** One of the {@link ImapFlowErrorCode} values */
48
+ export type ImapFlowErrorCode = (typeof ImapFlowErrorCode)[keyof typeof ImapFlowErrorCode];
2
49
  /**
3
50
  * An Error raised by ImapFlow, with the extra properties the library attaches to describe
4
51
  * the failure. Every property is optional: which ones are present depends on where the
5
52
  * error came from.
6
53
  */
7
54
  export interface ImapFlowError extends Error {
8
- /** Error code, e.g. 'NoConnection', 'ETIMEOUT', 'LockTimeout' or a parser error code */
55
+ /** Error code, one of {@link ImapFlowErrorCode}, a parser error code or a code from Node */
9
56
  code?: string | undefined;
10
57
  /** Connection id the error belongs to */
11
58
  cid?: string | undefined;
@@ -1,3 +1,55 @@
1
+ /**
2
+ * The `code` values ImapFlow sets on the errors it raises, so they can be matched without
3
+ * string literals: `if (err.code === ImapFlowErrorCode.NoConnection)`.
4
+ *
5
+ * Parser failures use `ParserError` followed by a number (`ParserError11`) and are not listed
6
+ * one by one, test them with `err.code?.startsWith('ParserError')`. Errors from the socket, TLS
7
+ * or DNS layer pass through with Node's own code (`ECONNREFUSED`, `ENOTFOUND`, ...).
8
+ */
9
+ export const ImapFlowErrorCode = {
10
+ // the connection is gone, the command was not (or can no longer be) completed
11
+ NoConnection: 'NoConnection',
12
+ EConnectionClosed: 'EConnectionClosed',
13
+ StateLogout: 'StateLogout',
14
+ ClosedAfterConnectText: 'ClosedAfterConnectText',
15
+ ClosedAfterConnectTLS: 'ClosedAfterConnectTLS',
16
+ // timeouts
17
+ CONNECT_TIMEOUT: 'CONNECT_TIMEOUT',
18
+ GREETING_TIMEOUT: 'GREETING_TIMEOUT',
19
+ UPGRADE_TIMEOUT: 'UPGRADE_TIMEOUT',
20
+ ETIMEOUT: 'ETIMEOUT',
21
+ LockTimeout: 'LockTimeout',
22
+ // the server
23
+ ETHROTTLE: 'ETHROTTLE',
24
+ UnexpectedTag: 'UnexpectedTag',
25
+ InvalidResponse: 'InvalidResponse',
26
+ ResponseProcessingFailed: 'ResponseProcessingFailed',
27
+ STARTTLS_INJECTION: 'STARTTLS_INJECTION',
28
+ COMPRESS_TRAILING_DATA: 'COMPRESS_TRAILING_DATA',
29
+ PollFailed: 'PollFailed',
30
+ NotFound: 'NotFound',
31
+ MissingServerExtension: 'MissingServerExtension',
32
+ // response parsing and size limits
33
+ ParserError: 'ParserError',
34
+ ParserErrorExchange: 'ParserErrorExchange',
35
+ MAX_IMAP_NESTING_REACHED: 'MAX_IMAP_NESTING_REACHED',
36
+ LineTooLarge: 'LineTooLarge',
37
+ LiteralTooLarge: 'LiteralTooLarge',
38
+ ResponseTooLarge: 'ResponseTooLarge',
39
+ // invalid values in a command
40
+ InvalidStringValue: 'InvalidStringValue',
41
+ InvalidTokenValue: 'InvalidTokenValue',
42
+ InvalidTextValue: 'InvalidTextValue',
43
+ InvalidSequenceSet: 'InvalidSequenceSet',
44
+ // download()
45
+ DownloadOverflow: 'DownloadOverflow',
46
+ DownloadIncomplete: 'DownloadIncomplete',
47
+ // proxy connections
48
+ ProxyError: 'ProxyError',
49
+ EPROXY: 'EPROXY',
50
+ UnsupportedProxyAddress: 'UnsupportedProxyAddress',
51
+ ERR_INVALID_URL: 'ERR_INVALID_URL'
52
+ };
1
53
  /**
2
54
  * Error subclass thrown when IMAP authentication fails.
3
55
  */
@@ -145,13 +145,24 @@ export declare class ImapStream extends Transform {
145
145
  * pushed downstream as a readable object.
146
146
  *
147
147
  * @param chunk - The raw data chunk to process.
148
- * @param startPos - The byte offset within the chunk to start processing from.
149
148
  */
150
- processInputChunk(chunk: Buffer, startPos?: number | undefined): Promise<void>;
149
+ processInputChunk(chunk: Buffer): Promise<void>;
150
+ /**
151
+ * Processes the chunk from `startPos` until the parser state changes or the chunk ends.
152
+ *
153
+ * @returns The offset to continue from after a state switch, or `null` when done with the chunk.
154
+ */
155
+ processChunkSegment(chunk: Buffer, startPos: number): Promise<number | null>;
151
156
  /**
152
157
  * Drains the input queue by processing each queued chunk sequentially.
153
158
  * Yields to the event loop every 10 chunks to prevent CPU blocking on
154
159
  * large bursts of incoming data.
160
+ *
161
+ * The `processingInput` guard is cleared in the same synchronous step that finds the queue
162
+ * empty. Clearing it later (in a promise handler) leaves a gap of a few microtasks where a
163
+ * chunk delivered by the writable side is queued but no loop is started for it, so its
164
+ * transform callback is never called and the socket is never read again. Workers deliver
165
+ * the next chunk inside that gap.
155
166
  */
156
167
  processInput(): Promise<void>;
157
168
  /**
@@ -217,12 +217,23 @@ export class ImapStream extends Transform {
217
217
  * pushed downstream as a readable object.
218
218
  *
219
219
  * @param chunk - The raw data chunk to process.
220
- * @param startPos - The byte offset within the chunk to start processing from.
221
220
  */
222
- async processInputChunk(chunk, startPos) {
223
- startPos = startPos || 0;
221
+ async processInputChunk(chunk) {
222
+ // Every state switch hands back the offset to resume from instead of recursing, so a
223
+ // chunk packed with thousands of small literals can not exhaust the call stack
224
+ let nextPos = 0;
225
+ while (nextPos !== null) {
226
+ nextPos = await this.processChunkSegment(chunk, nextPos);
227
+ }
228
+ }
229
+ /**
230
+ * Processes the chunk from `startPos` until the parser state changes or the chunk ends.
231
+ *
232
+ * @returns The offset to continue from after a state switch, or `null` when done with the chunk.
233
+ */
234
+ async processChunkSegment(chunk, startPos) {
224
235
  if (this.destroyed || startPos >= chunk.length) {
225
- return;
236
+ return null;
226
237
  }
227
238
  switch (this.state) {
228
239
  case LINE: {
@@ -234,7 +245,7 @@ export class ImapStream extends Transform {
234
245
  // TCP chunk boundaries happen to fall.
235
246
  let segment = chunk.subarray(lineStart, i + 1);
236
247
  if (!this.checkLineLength(this.lineBytes + segment.length)) {
237
- return;
248
+ return null;
238
249
  }
239
250
  this.lineBuffer.push(segment);
240
251
  lineStart = i + 1;
@@ -246,18 +257,18 @@ export class ImapStream extends Transform {
246
257
  // would otherwise be emitted as part of the rejected command.
247
258
  let isLiteralMarker = this.checkLiteralMarker(line);
248
259
  if (this.destroyed) {
249
- return;
260
+ return null;
250
261
  }
251
262
  // Count the line itself and, for a literal marker, the declared
252
263
  // literal bytes against the cumulative per-response budget, so a
253
264
  // response assembled from many tokens stays bounded as a whole
254
265
  if (!this.checkResponseSize(line.length + (isLiteralMarker ? this.literalWaiting : 0))) {
255
- return;
266
+ return null;
256
267
  }
257
268
  this.inputBuffer.push(line);
258
269
  if (isLiteralMarker) {
259
270
  // switch into literal mode and start over
260
- return await this.processInputChunk(chunk, lineStart);
271
+ return lineStart;
261
272
  }
262
273
  // reached end of command input, emit it
263
274
  let payload = this.inputBuffer.length === 1 ? this.inputBuffer[0] : Buffer.concat(this.inputBuffer);
@@ -291,7 +302,7 @@ export class ImapStream extends Transform {
291
302
  });
292
303
  this.pendingPush = null;
293
304
  if (this.destroyed) {
294
- return;
305
+ return null;
295
306
  }
296
307
  }
297
308
  }
@@ -307,7 +318,7 @@ export class ImapStream extends Transform {
307
318
  // while a server streams a line that never terminates - only the much
308
319
  // larger line cap would hold it back.
309
320
  if (!this.checkLineLength(this.lineBytes + tail.length) || !this.checkResponseSize(this.lineBytes + tail.length, true)) {
310
- return;
321
+ return null;
311
322
  }
312
323
  this.lineBytes += tail.length;
313
324
  this.lineBuffer.push(tail);
@@ -325,33 +336,45 @@ export class ImapStream extends Transform {
325
336
  this.literalBuffer = [];
326
337
  this.state = LINE;
327
338
  if (remainingInChunk > bytesToRead) {
328
- return await this.processInputChunk(chunk, startPos + bytesToRead);
339
+ return startPos + bytesToRead;
329
340
  }
330
341
  }
331
342
  break;
332
343
  }
333
344
  }
345
+ return null;
334
346
  }
335
347
  /**
336
348
  * Drains the input queue by processing each queued chunk sequentially.
337
349
  * Yields to the event loop every 10 chunks to prevent CPU blocking on
338
350
  * large bursts of incoming data.
351
+ *
352
+ * The `processingInput` guard is cleared in the same synchronous step that finds the queue
353
+ * empty. Clearing it later (in a promise handler) leaves a gap of a few microtasks where a
354
+ * chunk delivered by the writable side is queued but no loop is started for it, so its
355
+ * transform callback is never called and the socket is never read again. Workers deliver
356
+ * the next chunk inside that gap.
339
357
  */
340
358
  async processInput() {
341
- let data;
342
- let processedCount = 0;
343
- while (!this.destroyed && (data = this.inputQueue.shift())) {
344
- this.activeInput = data;
345
- await this.processInputChunk(data.chunk);
346
- this.activeInput = null;
347
- // mark chunk as processed
348
- this.releaseInput(data);
349
- // Yield to event loop every 10 chunks to prevent CPU blocking
350
- processedCount++;
351
- if (processedCount % 10 === 0) {
352
- await new Promise(resolve => setImmediate(resolve));
359
+ try {
360
+ let data;
361
+ let processedCount = 0;
362
+ while (!this.destroyed && (data = this.inputQueue.shift())) {
363
+ this.activeInput = data;
364
+ await this.processInputChunk(data.chunk);
365
+ this.activeInput = null;
366
+ // mark chunk as processed
367
+ this.releaseInput(data);
368
+ // Yield to event loop every 10 chunks to prevent CPU blocking
369
+ processedCount++;
370
+ if (processedCount % 10 === 0) {
371
+ await new Promise(resolve => setImmediate(resolve));
372
+ }
353
373
  }
354
374
  }
375
+ finally {
376
+ this.processingInput = false;
377
+ }
355
378
  }
356
379
  /**
357
380
  * Transform stream implementation. Receives raw data chunks from the writable side,
@@ -391,9 +414,7 @@ export class ImapStream extends Transform {
391
414
  this.inputQueue.push({ chunk, next });
392
415
  if (!this.processingInput) {
393
416
  this.processingInput = true;
394
- this.processInput()
395
- .catch(err => this.failStream(err))
396
- .finally(() => (this.processingInput = false));
417
+ this.processInput().catch(err => this.failStream(err));
397
418
  }
398
419
  }
399
420
  /**
@@ -321,15 +321,21 @@ export class TokenParser {
321
321
  this.currentNode = this.createNode(this.currentNode, this.pos + i + 10);
322
322
  // just call this an ATOM, even though IMAPURL might be more correct
323
323
  this.currentNode.type = 'ATOM';
324
- // jump i to the ']'
325
- i = this.str.indexOf(']', i + 10);
326
- if (i < 0) {
327
- // Malformed REFERRAL with no closing ']'. Consume the rest
328
- // of the string (there is no ']' to exclude) instead of
329
- // computing a negative-index substring, which would yield
330
- // garbage.
331
- i = this.str.length;
324
+ // jump i to the ']' that closes the section. The URL itself can
325
+ // hold a bracketed IPv6 host (imap://[::1]/INBOX), so brackets
326
+ // opened inside the URL are matched before the closing one.
327
+ let depth = 0;
328
+ for (i = i + 10; i < this.str.length; i++) {
329
+ let urlChr = this.str.charAt(i);
330
+ if (urlChr === '[') {
331
+ depth++;
332
+ }
333
+ else if (urlChr === ']' && depth-- === 0) {
334
+ break;
335
+ }
332
336
  }
337
+ // A malformed REFERRAL with no closing ']' leaves i at the end of
338
+ // the string, so the URL takes the rest of it.
333
339
  this.currentNode.endPos = this.pos + i - 1;
334
340
  this.currentNode.value = this.str.substring(this.currentNode.startPos - this.pos, this.currentNode.endPos - this.pos + 1);
335
341
  this.currentNode = this.currentNode.parentNode;
@@ -9,7 +9,7 @@ import { AuthenticationFailure } from './errors.js';
9
9
  import type { AppendResponseObject, CopyResponseObject, DownloadManyOptions, DownloadManyResult, DownloadObject, DownloadNotFound, DownloadOptions, ESearchResult, FetchMessageObject, FetchOptions, FetchQueryObject, IdInfoObject, ImapFlowEvents, ImapFlowOptions, InternalLogger, ListOptions, ListResponse, ListTreeResponse, MailboxCreateResponse, MailboxDeleteResponse, MailboxLockObject, MailboxLockOptions, MailboxObject, MailboxOpenOptions, MailboxRenameResponse, MessageRange, MessageRangeOptions, NamespaceObject, NamespacesObject, QuotaResponse, SearchObject, SearchOptions, SearchReturnOption, SequenceString, StatusObject, StatusQuery, StoreOptions, TlsInfo } from './types.js';
10
10
  export type * from './types.js';
11
11
  export type { ImapFlowError } from './errors.js';
12
- export { AuthenticationFailure } from './errors.js';
12
+ export { AuthenticationFailure, ImapFlowErrorCode } from './errors.js';
13
13
  export type { ImapAttribute, ImapAttributeList, ImapAttributeNode, ImapResponse } from './handler/types.js';
14
14
  declare const stateValues: {
15
15
  readonly NOT_AUTHENTICATED: 1;
@@ -61,6 +61,17 @@ export interface ImapFlow {
61
61
  prependOnceListener(event: string | symbol, listener: (...args: any[]) => void): this;
62
62
  emit<K extends keyof ImapFlowEvents>(event: K, ...args: ImapFlowEvents[K]): boolean;
63
63
  emit(event: string | symbol, ...args: any[]): boolean;
64
+ /**
65
+ * Logs out and closes the connection when the scope of an `await using` declaration ends.
66
+ * Never throws, the connection is closed whether LOGOUT succeeds or not. Only present on
67
+ * runtimes that define `Symbol.asyncDispose` (Node.js 20.4 and newer).
68
+ *
69
+ * @example
70
+ * await using client = new ImapFlow({...});
71
+ * await client.connect();
72
+ * // client.logout() runs automatically when the scope exits, even on a throw
73
+ */
74
+ [Symbol.asyncDispose](): Promise<void>;
64
75
  }
65
76
  /**
66
77
  * IMAP client class for accessing IMAP mailboxes
@@ -592,7 +603,8 @@ export declare class ImapFlow extends EventEmitter {
592
603
  getMailboxLock(path: string | string[], options?: MailboxLockOptions | undefined): Promise<MailboxLockObject>;
593
604
  /**
594
605
  * Detaches sockets from the IMAP pipeline. Useful for upgrading the connection
595
- * (e.g., STARTTLS) or transferring socket ownership.
606
+ * (e.g., STARTTLS) or transferring socket ownership. Call it while the connection is not
607
+ * idling: an IDLE in progress is not broken first, so the server still expects `DONE`.
596
608
  *
597
609
  * @returns Socket objects: `readSocket` is the read socket (inflated socket if compression is enabled, raw socket otherwise),
598
610
  * `writeSocket` the write socket and `socket` the raw underlying socket (same as readSocket/writeSocket when compression is disabled)
@@ -603,81 +615,6 @@ export declare class ImapFlow extends EventEmitter {
603
615
  socket: ImapSocket;
604
616
  };
605
617
  }
606
- /**
607
- * Connection close event. **NB!** ImapFlow does not handle reconnects automatically.
608
- * So whenever a 'close' event occurs you must create a new connection yourself.
609
- *
610
- * @event ImapFlow#close
611
- */
612
- /**
613
- * Error event. In most cases getting an error event also means that connection is closed
614
- * and pending operations should return with a failure.
615
- *
616
- * @event ImapFlow#error
617
- * @example
618
- * client.on('error', err=>{
619
- * console.log(`Error occurred: ${err.message}`);
620
- * });
621
- */
622
- /**
623
- * Message count in currently opened mailbox changed
624
- *
625
- * @event ImapFlow#exists
626
- * @example
627
- * client.on('exists', data=>{
628
- * console.log(`Message count in "${data.path}" is ${data.count}`);
629
- * });
630
- */
631
- /**
632
- * Deleted message sequence number in currently opened mailbox. One event is fired for every deleted email.
633
- *
634
- * @event ImapFlow#expunge
635
- * @example
636
- * client.on('expunge', data=>{
637
- * console.log(`Message #${data.seq} was deleted from "${data.path}"`);
638
- * });
639
- */
640
- /**
641
- * Flags were updated for a message. Not all servers fire this event.
642
- *
643
- * @event ImapFlow#flags
644
- * @example
645
- * client.on('flags', data=>{
646
- * console.log(`Flag set for #${data.seq} is now "${Array.from(data.flags).join(', ')}"`);
647
- * });
648
- */
649
- /**
650
- * Mailbox was opened
651
- *
652
- * @event ImapFlow#mailboxOpen
653
- * @example
654
- * client.on('mailboxOpen', mailbox => {
655
- * console.log(`Mailbox ${mailbox.path} was opened`);
656
- * });
657
- */
658
- /**
659
- * Mailbox was closed
660
- *
661
- * Emitted both when a selected mailbox is closed explicitly, by `mailboxClose()` or by
662
- * selecting a different mailbox, and when the connection itself goes away while a mailbox
663
- * was still selected, whether through a clean logout or a lost transport. The transition is
664
- * reported once per selected mailbox, before the `close` event.
665
- *
666
- * @event ImapFlow#mailboxClose
667
- * @example
668
- * client.on('mailboxClose', mailbox => {
669
- * console.log(`Mailbox ${mailbox.path} was closed`);
670
- * });
671
- */
672
- /**
673
- * Log event if `emitLogs=true`
674
- *
675
- * @event ImapFlow#log
676
- * @example
677
- * client.on('log', entry => {
678
- * console.log(`${entry.cid} ${entry.msg}`);
679
- * });
680
- */
681
618
  declare const imapflow: {
682
619
  ImapFlow: typeof ImapFlow;
683
620
  AuthenticationFailure: typeof AuthenticationFailure;