import type { Transform } from 'node:stream'; import type { ImapFlow } from './imap-flow.js'; import { ImapFlowErrorCode, type ConnectionErrorSite, type ImapFlowError } from './errors.js'; import type { ImapAttributeList, ImapAttributeNode, ImapResponse } from './handler/types.js'; import type { FetchMessageObject, ListResponse, ListTreeResponse, MailboxObject, MessageEnvelopeObject, MessageStructureObject, ImapFlowEvents, StatusQuery } from './types.js'; export { AuthenticationFailure } from './errors.js'; export declare const EXPANDED_RANGE_LIMIT = 16777216; export declare const MAX_COLLECTED_RESPONSES = 1000000; export declare const MAX_UINT32_DIGITS = 10; export declare const noop: () => void; /** * Tells an IP literal from a hostname without `node:net`, which some runtimes do not implement. * Only used to decide whether a host can be a TLS servername: a dotted quad, or anything with a * colon (an IPv6 literal, a hostname never contains one). * * @param host - Hostname or IP address * @returns true for an IP literal */ export declare function isIpLiteral(host: string): boolean; /** * A stream decoder returned by getDecoder(). The Japanese decoder reports the `limited` * flag once it has buffered all it will accept; a streaming iconv decoder never sets it. */ export type CharsetDecoder = Transform & { limited?: boolean | undefined; }; /** * Builds an error describing a connection that is gone, stamped so it can be traced back to * where it came from: the connection id always travels on it, and each site names itself * through `meta` (`rejectedFrom`, plus the command or mailbox path it belongs to). * * A stack trace only records where an error was built, and close() hands a rejection to every * pending request and every queued lock in the same tick, so without these an error that * reaches a global unhandledRejection handler arrives with nothing that identifies the * connection it came from, let alone which of the rejected promises carried it. * * Takes the connection id rather than the connection, so the stamping stays in one place * without every caller having to be a full ImapFlow instance. * * @param cid - Connection id * @param code - Error code, e.g. 'NoConnection' * @param message - Error message * @param meta - Fields to stamp on the error * @returns The stamped error */ export declare function buildConnectionError(cid: string, code: ImapFlowErrorCode, message: string, meta?: ConnectionErrorSite | undefined): ImapFlowError; /** * Re-stamps an existing connection error for a different rejection site. * * The same failure can be handed to more than one promise - close() rejects the in-flight * command, and runIdle() then rejects everything queued behind it - and each of those is a * separate promise with a separate consumer. Sharing one error object reports whichever of * them escapes under the first site's marker, which is the attribution these markers exist to * give. * * Everything describing *what went wrong* is carried over, because a re-stamped error reaches * user code through run() and callers branch on `responseStatus`, `serverResponseCode` and * friends. Everything describing *where it was rejected* is dropped, because the new site owns * those and a leftover `command` from the previous site is exactly as misleading as a leftover * `rejectedFrom`. The original travels on as `cause`. * * @param err - The error being re-stamped * @param meta - Fields for the new site, e.g. { rejectedFrom: 'preCheckWaiter' } * @returns A separate error describing the same failure at the new site */ export declare function restampConnectionError(err: ImapFlowError, meta?: ConnectionErrorSite | undefined): ImapFlowError; /** * Creates a promise whose rejection is observed as soon as it exists. * * close() rejects every promise it owns - the in-flight and queued commands, the pending * connect(), the queued mailbox locks, the waiters for an IDLE break - synchronously, from a * socket event. A consumer that only reaches its `await` a microtask later has not attached a * handler yet at the moment Node decides whether the rejection was observed, and the whole * worker dies on the resulting unhandledRejection. The pre-attached observer settles that * question; the rejection still propagates normally to whoever awaits the returned promise. * * Creation and guarding are one call because splitting them is what actually goes wrong: the * guard was hand-attached at three of the four sites and the fourth (the IDLE-break waiter) * went unguarded, on exactly the path a server BYE takes. * * @param executor - Promise executor, (resolve, reject) => {} * @returns The promise, with its rejection already observed */ export declare function guardedPromise(executor: (resolve: (value: T | PromiseLike) => void, reject: (reason?: any) => void) => void): Promise; /** * The already-rejected form of guardedPromise(), for a call that has to hand back a rejected * promise rather than throw. * * @param error - Rejection reason * @returns Rejected promise, with its rejection already observed */ export declare function guardedReject(error: Error): Promise; /** * Clears a timer that may already have been dropped. `clearTimeout()` accepts undefined but not * null, and the connection nulls its timer fields once cleared, so every site clears through here. * * @param timer - Timer handle returned by setTimeout, or null/undefined when none is armed */ export declare function clearTimer(timer: NodeJS.Timeout | null | undefined): void; /** * Detaches a background timer from the event loop, so it cannot keep the process alive on its * own. Applied to every background timer (auto-IDLE, IDLE restart, fallback polling, throttle * back-off, held-lock diagnostics); connection and greeting deadlines are deliberately left * attached, because a caller is waiting for connect() to settle. * * @param timer - Timer handle returned by setTimeout * @returns The same timer handle */ export declare function unrefTimer(timer: T): T; /** * Logs a failure from background connection work at the level its cause deserves. * * Background work (IDLE sessions, polling timers, auto-IDLE) is interrupted by every normal * disconnect, so a rejection carrying one of the CONNECTION_GONE_CODES is expected rather * than notable and goes to debug. The three codes describe the same situation reached * through different guards: write() throws NoConnection or StateLogout, exec() rejects * EConnectionClosed for the window where the socket is destroyed but close() has not run * yet, and close() rejects pending requests with NoConnection. * * A connection error carrying `reason` is the exception. That field holds the server's * untagged BYE text ("Too many simultaneous connections", "Account is disabled"), which * serverBye() only records - this log call is the one place it becomes visible, and it is * usually the answer to why a client is reconnecting in a loop. Those stay at warn. * * Shared so the classification cannot drift between the call sites that make this decision. * * @param connection - IMAP connection instance * @param msg - What failed, so the entries stay distinguishable in the log * @param err - The error to log */ export declare function logConnectionError(connection: ImapFlow, msg: string, err: ImapFlowError | null | undefined): void; /** * Whether a command failed because the server answered it with a tagged NO or BAD, as opposed * to a lost connection, a timeout or a local failure that says nothing about the server's answer. * A throttled command (ETHROTTLE, Microsoft 365 answers it with BAD) was not refused: the server * asks to try again later, so a throttled login is not a rejected credential and a throttled * IDLE does not mean the server lacks IDLE. * * @param err - The error the command failed with * @returns True for a tagged NO or BAD that is not throttling */ export declare function isServerRefusal(err: ImapFlowError | null | undefined): boolean; /** * Checks whether IMAP4rev2 semantics are active for the connection: either the * client enabled IMAP4rev2 explicitly, or the server is rev2-only (advertises * IMAP4rev2 without IMAP4rev1), in which case rev2 is the base protocol without * any ENABLE (RFC 9051 Appendix A). UTF-8 mailbox names apply in both cases. * * @param connection - IMAP connection instance * @returns True if IMAP4rev2 semantics apply to this session */ export declare function isRev2Active(connection: ImapFlow): boolean; /** * Checks a capability, accounting for extensions that RFC 9051 folds into base * IMAP4rev2. Falls back to the plain capability lookup on IMAP4rev1 sessions, * so behavior against rev1 servers is unchanged. * * @param connection - IMAP connection instance * @param capability - Capability name, e.g. 'UIDPLUS' * @returns True if the capability (or its rev2-folded equivalent) is available */ export declare function hasCapability(connection: ImapFlow, capability: string): boolean; /** * Builds the attribute list for a STATUS request - the standalone STATUS command * or the LIST-STATUS return option - from a status query object. Items the current * session cannot request (RECENT under IMAP4rev2, HIGHESTMODSEQ without CONDSTORE) * are silently dropped. * * @param connection - IMAP connection instance * @param statusQuery - Status data items to request, e.g. {messages: true} * @returns Attribute token list for the command compiler */ export declare function buildStatusQueryAttributes(connection: ImapFlow, statusQuery: StatusQuery | undefined): ImapAttributeNode[]; /** * Encodes a mailbox path to modified UTF-7 if the server does not support UTF8=ACCEPT. * * @param connection - IMAP connection instance * @param path - Mailbox path to encode * @returns Encoded mailbox path */ export declare function encodePath(connection: ImapFlow, path: string | undefined): string; /** * Decodes a mailbox path from modified UTF-7 if the server does not support UTF8=ACCEPT. * * @param connection - IMAP connection instance * @param path - Mailbox path to decode * @returns Decoded mailbox path */ export declare function decodePath(connection: ImapFlow, path: string | undefined): string; /** * Normalizes a mailbox path by joining array segments with the namespace delimiter, * uppercasing INBOX, and prepending the namespace prefix if needed. * * @param connection - IMAP connection instance * @param path - Mailbox path or array of path segments * @param skipNamespace - If true, skips prepending the namespace prefix * @returns Normalized mailbox path */ export declare function normalizePath(connection: ImapFlow, path: string | string[], skipNamespace?: boolean): string; /** * Compares two mailbox paths for equality after normalization. * * @param connection - IMAP connection instance * @param a - First mailbox path * @param b - Second mailbox path * @returns True if the paths are equal after normalization */ export declare function comparePaths(connection: ImapFlow, a: string | undefined, b: string | undefined): boolean; /** * Parses a capability response list into a Map of capability names to values. * * @param list - Array of capability objects from IMAP response * @returns Map of capability names to `true` or numeric values */ export declare function updateCapabilities(list: ImapAttributeList | null | undefined): Map; /** * Extracts the IMAP response status code (e.g. AUTHENTICATIONFAILED, NONEXISTENT) * from a parsed server response. * * @param response - Parsed IMAP server response * @returns Uppercase status code string, or false if not found */ export declare function getStatusCode(response: ImapResponse | string | false | undefined): string | false; /** * Emits a state-change event from inside the command pipeline. A listener that throws must not * abort the code that emitted it: select() emits before it releases the response, so the throw * would leave the reader loop waiting forever, and close() would never get to emit 'close'. The * error is logged instead, the same contract untagged handlers and the 'response' event get. * * @param connection - IMAP connection instance * @param event - Event name * @param args - Event arguments */ export declare function emitSafe(connection: ImapFlow, event: K, ...args: ImapFlowEvents[K]): void; /** * Collects the values of the TEXT tokens of a parsed response (the human-readable * part of a status response, a greeting or a BYE). * * @param attributes - Attributes of a parsed IMAP response * @returns Values of the TEXT tokens, in order */ export declare function getTextValues(attributes: ImapAttributeList | undefined): string[]; /** * Compiles an IMAP response object back into a human-readable string. * * @param response - Parsed IMAP server response * @returns Compiled response text, or false if no response */ export declare function getErrorText(response: ImapResponse | string | false | undefined): Promise; /** * Enhances an IMAP command error with the server response code and text. * * @param err - Error object with a `response` property * @returns The enhanced error with `serverResponseCode` and string `response` */ export declare function enhanceCommandError(err: ImapFlowError): Promise; /** * Enhances a failed command's error (see enhanceCommandError()) and logs it, the shared first * step of every command's failure path. The caller decides whether to throw or return. * * @param connection - IMAP connection instance * @param err - The command error */ export declare function reportCommandError(connection: ImapFlow, err: ImapFlowError): Promise; /** * Whether the session is authenticated, that is in the AUTHENTICATED or SELECTED state, which * every mailbox-level command requires. * * @param connection - IMAP connection instance */ export declare function isAuthenticatedState(connection: ImapFlow): boolean; /** * Returns the selected mailbox, or false when the connection is not in the SELECTED state. * Message-level commands use it as their precondition, which also narrows the mailbox type. * * @param connection - IMAP connection instance */ export declare function getSelectedMailbox(connection: ImapFlow): MailboxObject | false; /** * Converts a flat list of mailbox folders into a tree structure. * * @param folders - Array of folder objects from LIST/LSUB response * @returns Tree structure with a `root` flag and nested `folders` arrays */ export declare function getFolderTree(folders: ListResponse[]): ListTreeResponse; /** * Derives a flag color name from a message's flags Set using Apple Mail color flag rules. * * @param flags - Message flags Set * @returns Color name (e.g. 'red', 'orange') or null if not flagged */ export declare function getFlagColor(flags: Set): string | null; /** * Converts a color name to the corresponding flag add/remove operations for Apple Mail color flags. * * @param color - Color name (e.g. 'red', 'orange', 'yellow') * @returns Object with `add` and `remove` arrays of flag strings, or null if invalid color */ export declare function getColorFlags(color: string | null | undefined): { add: string[]; remove: string[]; } | null; /** * Merges the FETCH rows a server sent for one message into one message object. A server may * split the data items of a message over several FETCH responses, and may put an unsolicited * one (a flag change made by another session) in between (RFC 9051 sections 7.5.2 and 5.2). * Later rows win for single values, bodyParts, binaryParts and partialOrigins are combined. * * @param rows - Formatted rows of the same message, in the order they arrived * @returns One message object holding the data of every row */ export declare function mergeFetchRows(rows: FetchMessageObject[]): FetchMessageObject; /** * Formats a raw untagged FETCH response into a structured message object. * * @param untagged - Parsed untagged IMAP response * @param mailbox - Current mailbox state object * @param idHashAlgorithm - Hash for the fallback message id, `md5` unless the client was told otherwise * @param connection - Connection the response arrived on, decodes Gmail labels like mailbox names * @returns Formatted message object with properties like seq, uid, flags, envelope, etc. */ export declare function formatMessageResponse(untagged: ImapResponse, mailbox: MailboxObject, idHashAlgorithm?: string, connection?: ImapFlow): Promise; /** * Strips surrounding double quotes from a name string. * * @param name - Raw name string potentially wrapped in quotes * @returns Name with surrounding quotes removed */ export declare function processName(name: unknown): string; /** * Decodes an ENVELOPE text field for display: encoded words first, then the * surrounding quotes some servers leave in place. * * @param value - Raw field value from an ENVELOPE response * @returns Decoded, unquoted text */ export declare function decodeText(value: string): string; /** * Parses a raw IMAP ENVELOPE response into a structured envelope object. * * @param entry - Raw envelope data array from IMAP response * @returns Parsed envelope with date, subject, from, to, cc, bcc, messageId, etc. */ export declare function parseEnvelope(entry: ImapAttributeList): MessageEnvelopeObject; /** * Parses structured MIME parameter arrays (including RFC 2231 continuations) * into a flat key-value object. * * @param arr - Raw parameter array from BODYSTRUCTURE response * @returns Key-value object of decoded parameters */ export declare function getStructuredParams(arr: ImapAttributeList | null | undefined): { [key: string]: string; }; /** * Parses a raw IMAP BODYSTRUCTURE response into a structured tree of body parts. * * @param entry - Raw BODYSTRUCTURE data array from IMAP response * @returns Parsed body structure tree with part numbers, types, parameters, and child nodes */ export declare function parseBodystructure(entry: ImapAttributeList): MessageStructureObject; /** * Checks if a value is a Date object. * * @param obj - Value to check * @returns True if the value is a Date object */ export declare function isDate(obj: unknown): obj is Date; /** * Converts a value to a valid Date object, or returns null. * * @param value - Date object or date string to convert * @returns Valid Date object, or null if conversion fails */ export declare function toValidDate(value: unknown): Date | null; /** * Formats a date value into IMAP date format (DD-Mon-YYYY). * * @param value - Date to format * @returns Formatted date string, or undefined if invalid */ export declare function formatDate(value: unknown): string | undefined; /** * Formats a date value into IMAP date-time format (DD-Mon-YYYY HH:MM:SS +0000). * * @param value - Date to format * @returns Formatted date-time string, or undefined if invalid */ export declare function formatDateTime(value: unknown): string | undefined; /** * Normalizes a flag string. Returns false for non-settable flags (e.g. \Recent) and for * values that are not valid flags (keywords must be atoms), and capitalizes system flags properly. * * @param flag - Flag string to normalize * @returns Normalized flag string, or false if the flag cannot be set */ export declare function formatFlag(flag: string): string | false; /** * Checks if a flag can be used in the given mailbox based on permanent flags. * * @param mailbox - Mailbox object with permanentFlags * @param flag - Flag to check * @returns True if the flag is allowed */ export declare function canUseFlag(mailbox: MailboxObject | false | null | undefined, flag: string): boolean; /** * Checks that a value is a valid IMAP sequence number or UID: a non-zero * 32-bit unsigned integer (nz-number in the RFC 9051 grammar). Guards range * expansion against untrusted server input such as 'Infinity' or '0:*'. * * @param value - Value to check * @returns True if the value is a valid sequence number/UID */ export declare function isValidSequenceValue(value: unknown): value is number; /** * Checks that an untrusted response value is a pure decimal digit run no longer than * the given bound. * * `!isNaN(value)` is not usable for this: it also passes '1e5', ' 12 ', '0x10' and * 'Infinity'. BigInt() throws on all of them and Number() silently returns a value the * grammar never allowed, so both are wrong in a response handler that is only trying to * read one field. The length bound is checked before the pattern so an arbitrarily long * digit run is rejected without any conversion work. * * @param value - Raw value from the response. * @param maxDigits - Maximum number of digits accepted. * @returns True if the value is a decimal string within the bound. */ export declare function isDecimalString(value: unknown, maxDigits: number): value is string; /** * Checks whether a server-supplied string is unsafe to use as a key on a plain object. * Assigning "__proto__" writes through the prototype setter instead of creating an own * property, and reading "constructor" or "prototype" resolves to an inherited member. * * @param key - Candidate key from a server response. * @returns True if the key must not be used. */ export declare function isUnsafeKey(key: unknown): boolean; /** * Reads a parsed attribute list of atoms or strings (a flag list, a capability list) into * an array of strings. Any element can be a parsed NIL, and the list itself can be NIL, * so both levels are guarded here rather than at each call site. A literal (a Gmail label * the server could not send quoted) arrives as a Buffer and is decoded as UTF-8. * * @param list - Parsed attribute list from a response. * @returns The string values, in order, with unusable entries dropped. */ export declare function getStringList(list: unknown): string[]; /** * Reads the text of a string value from a response. A server may send any string as a literal, * which arrives as a Buffer and is decoded as UTF-8. * * @param attribute - Parsed attribute from a response * @returns The string, or undefined when the attribute holds no string (NIL, a list) */ export declare function getStringValue(attribute: unknown): string | undefined; /** * Parses an untrusted decimal value from a server response into a BigInt. * * @param value - Raw value from the response. * @param maxDigits - Maximum number of digits accepted. Defaults to MAX_NUMBER64_DIGITS. * @returns The parsed value, or false when it is not usable. */ export declare function parseBigIntValue(value: unknown, maxDigits?: number): bigint | false; /** * Parses an untrusted decimal value from a server response into a Number. Values beyond * the safe integer range are rejected rather than rounded: a silently rounded count or * UID corrupts every range computation derived from it. * * @param value - Raw value from the response. * @param maxDigits - Maximum number of digits accepted. Defaults to MAX_NUMBER64_DIGITS. * @returns The parsed value, or false when it is not usable. */ export declare function parseUintValue(value: unknown, maxDigits?: number): number | false; /** * Walks an IMAP sequence range string (e.g. "1:3,5,7:9") one number at a time, without * materializing it. A single "1:16777216" is a few bytes on the wire but sixteen million * entries expanded, so a consumer that only needs to visit each number (the VANISHED handler) * iterates instead of calling expandRange(). * * The same bound as expandRange() applies: at most EXPANDED_RANGE_LIMIT numbers in total are * produced, over all entries of the set. * * @param range - IMAP sequence range string * @returns The sequence numbers, in the order the set lists them */ export declare function iterateRange(range: unknown): Generator; /** * Counts the numbers iterateRange() produces for a sequence range string, without walking it. * * @param range - IMAP sequence range string * @returns The number of entries, capped at EXPANDED_RANGE_LIMIT like the expansion itself */ export declare function countRange(range: unknown): number; /** * Expands an IMAP sequence range string (e.g. "1:3,5,7:9") into an array of numbers. * * Entries with endpoints that are not valid nz-numbers are skipped - the input * may come from an untrusted server, and 'Infinity' or similar garbage would * otherwise loop without bound. The whole set is expanded to at most * EXPANDED_RANGE_LIMIT entries in total: legitimate responses never reach the limit * (the mailbox would need that many messages), while hostile input is cut off * instead of exhausting memory. The total is capped, not just each range - * otherwise "1:16777216,1:16777216,..." would multiply the per-range bound by an * unbounded number of ranges. * * @param range - IMAP sequence range string * @returns Array of expanded sequence numbers */ export declare function expandRange(range: unknown): number[]; /** * Returns a stream decoder for the given charset. Uses a special Japanese * charset decoder for JIS/ISO-2022-JP, otherwise delegates to iconv-lite. * * @param charset - Character set name. Defaults to 'ascii'. * @param maxBytes - Bound for the bytes the decoder may buffer. Only * relevant for the Japanese decoder, which must buffer its whole input before * it can decode: without the bound a server could defeat a caller's maxBytes * download limit simply by labelling the part with a Japanese charset. * @returns A stream decoder (Transform stream) for the charset */ export declare function getDecoder(charset?: string | undefined, maxBytes?: number | undefined): CharsetDecoder; /** * Packs an array of message sequence numbers into a compact IMAP range string * (e.g. [1,2,3,5,7,8] becomes "1:3,5,7:8"). * * @param list - Sequence number or array of sequence numbers * @returns Packed IMAP sequence range string */ export declare function packMessageRange(list: number | number[] | null | undefined): string;