/** * Reading `* n FETCH (...)` responses whole. * * This exists because the two ad hoc readers it replaces each searched a FETCH * response for one thing and made an assumption about everything else, and both * assumptions were wrong against a real server. * * What a real server actually sends * ───────────────────────────────── * A `BODY[...]` section arrives as a `{N}` literal, RFC 3501 §4.3, which is a * byte count on the end of the line followed by exactly that many bytes: * * * 3 FETCH (UID 307 BODY[HEADER.FIELDS (FROM SUBJECT)] {58} * From: a@b.test * Subject: Hello * * ) * * `ImapSession` reads the count, takes the bytes, and hands the response up as * ONE string with the payload welded onto the line that announced it. That is * the correct thing to do, the payload is not lines, it is bytes, and it may * contain anything including a line that looks like a response. But it means * every reader downstream sees a `* 3 FETCH (` line with a whole header block * hanging off the end of it, and a reader that expected the payload on lines of * its own finds nothing. * * The old `parseFetchHeaders` did worse than find nothing: it tested whether * the text after `* n FETCH ` started with `(` and discarded it if so. Against * a folded literal that text is `(UID 307 BODY[...] From: a@b.test…`, so the * whole header block went out with the data items it was welded to, and the * client built envelopes with every field empty while reporting success. * * Where the UID is, is the server's choice * ──────────────────────────────────────── * `UID FETCH` makes the server include a `UID` data item whether or not it was * asked for (§6.4.8), and the server chooses where in the list it goes. Both of * these are conformant and both are in the wild: * * * 3 FETCH (UID 307 BODY[HEADER.FIELDS (…)] {58}…) * * 3 FETCH (BODY[HEADER.FIELDS (…)] {58}… UID 307) * * In the second, the `UID` lands AFTER the literal, which means it is not on * the `* n FETCH` line at all, it is on the line that closes the response. The * old `parseFetchUids` searched `line.slice(0, line.indexOf('BODY'))` of the * start line only, found nothing, and returned an empty map; the client then * produced no envelopes and the caller could not tell that from an empty * mailbox. * * So this reads the response as a response * ──────────────────────────────────────── * One pass over the lines, tracking parenthesis depth, quoted strings and * `[section]` brackets, collecting the data items at depth 1 as structural text * and every `BODY[...]` payload as opaque bytes. A `UID` is then read from the * structural text, from wherever in the response it appeared, and never from * inside a payload, because payload text is never structural text. * * A response that cannot be read says so * ────────────────────────────────────── * `parseError` is the field this module exists for as much as the payloads are. * A caller advancing a cursor has to distinguish "the server did not mention * this message" from "the server mentioned it and we could not read the answer", * and it can only do that if the second one is reported rather than silently * looking like the first. */ /** One `* n FETCH (...)` response, read whole. */ export interface ImapFetchResponse { /** * The response's own sequence number. * * NOT a UID, even under `UID FETCH`, the prefix is a sequence number in both * commands, which is exactly why `uid` is read separately and joined rather * than assumed equal. */ readonly seq: number; /** The `UID` data item, from wherever in the response it appeared. */ readonly uid: number | null; /** * `BODY[]` payloads, keyed by the section spec upper-cased with runs of * whitespace collapsed: `HEADER.FIELDS (FROM SUBJECT)`, `TEXT`, or `''` for * `BODY[]`. A `` suffix is not part of the key. */ readonly sections: ReadonlyMap; /** * Null when the response was read whole; a plain-language reason when it was * not. * * A response carrying this describes NOTHING about the message it named. A * caller must not read the resulting absence as the message being gone. */ readonly parseError: string | null; } /** * Read every `* n FETCH (...)` response in a command's collected lines. * * Responses come back in the order the server sent them. Anything that is not * part of a FETCH response, the tagged completion, other untagged data, is * ignored rather than guessed at. */ export declare function parseFetchResponses(lines: readonly string[]): ImapFetchResponse[]; /** The first section payload whose normalized spec the predicate accepts. */ export declare function fetchSection(response: ImapFetchResponse, matches: (spec: string) => boolean): string | null; //# sourceMappingURL=imap-fetch-response.d.ts.map