imapflow 1.7.7 → 2.0.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.
- package/CHANGELOG.md +27 -0
- package/README.md +8 -2
- package/dist/cjs/charsets.d.ts +1 -0
- package/dist/cjs/charsets.js +294 -0
- package/dist/cjs/commands/append.d.ts +22 -0
- package/dist/cjs/commands/append.js +151 -0
- package/dist/cjs/commands/authenticate.d.ts +24 -0
- package/dist/cjs/commands/authenticate.js +223 -0
- package/dist/cjs/commands/capability.d.ts +8 -0
- package/dist/cjs/commands/capability.js +32 -0
- package/dist/cjs/commands/close.d.ts +8 -0
- package/dist/cjs/commands/close.js +39 -0
- package/dist/cjs/commands/compress.d.ts +8 -0
- package/dist/cjs/commands/compress.js +56 -0
- package/dist/cjs/commands/copy.d.ts +13 -0
- package/dist/cjs/commands/copy.js +44 -0
- package/dist/cjs/commands/copyuid-parser.d.ts +11 -0
- package/dist/cjs/commands/copyuid-parser.js +32 -0
- package/dist/cjs/commands/create.d.ts +11 -0
- package/dist/cjs/commands/create.js +80 -0
- package/dist/cjs/commands/delete.d.ts +11 -0
- package/dist/cjs/commands/delete.js +40 -0
- package/dist/cjs/commands/enable.d.ts +9 -0
- package/dist/cjs/commands/enable.js +61 -0
- package/dist/cjs/commands/esearch-parser.d.ts +17 -0
- package/dist/cjs/commands/esearch-parser.js +91 -0
- package/dist/cjs/commands/expunge.d.ts +12 -0
- package/dist/cjs/commands/expunge.js +60 -0
- package/dist/cjs/commands/fetch.d.ts +30 -0
- package/dist/cjs/commands/fetch.js +241 -0
- package/dist/cjs/commands/id.d.ts +10 -0
- package/dist/cjs/commands/id.js +80 -0
- package/dist/cjs/commands/idle.d.ts +9 -0
- package/dist/cjs/commands/idle.js +347 -0
- package/dist/cjs/commands/list.d.ts +16 -0
- package/dist/cjs/commands/list.js +518 -0
- package/dist/cjs/commands/login.d.ts +11 -0
- package/dist/cjs/commands/login.js +42 -0
- package/dist/cjs/commands/logout.d.ts +8 -0
- package/dist/cjs/commands/logout.js +47 -0
- package/dist/cjs/commands/move.d.ts +13 -0
- package/dist/cjs/commands/move.js +57 -0
- package/dist/cjs/commands/namespace.d.ts +25 -0
- package/dist/cjs/commands/namespace.js +139 -0
- package/dist/cjs/commands/noop.d.ts +8 -0
- package/dist/cjs/commands/noop.js +22 -0
- package/dist/cjs/commands/quota.d.ts +10 -0
- package/dist/cjs/commands/quota.js +119 -0
- package/dist/cjs/commands/rename.d.ts +12 -0
- package/dist/cjs/commands/rename.js +48 -0
- package/dist/cjs/commands/search.d.ts +15 -0
- package/dist/cjs/commands/search.js +228 -0
- package/dist/cjs/commands/select.d.ts +25 -0
- package/dist/cjs/commands/select.js +250 -0
- package/dist/cjs/commands/starttls.d.ts +8 -0
- package/dist/cjs/commands/starttls.js +30 -0
- package/dist/cjs/commands/status-fields.d.ts +14 -0
- package/dist/cjs/commands/status-fields.js +61 -0
- package/dist/cjs/commands/status.d.ts +12 -0
- package/dist/cjs/commands/status.js +108 -0
- package/dist/cjs/commands/store.d.ts +19 -0
- package/dist/cjs/commands/store.js +93 -0
- package/dist/cjs/commands/subscribe.d.ts +9 -0
- package/dist/cjs/commands/subscribe.js +31 -0
- package/dist/cjs/commands/unsubscribe.d.ts +9 -0
- package/dist/cjs/commands/unsubscribe.js +31 -0
- package/dist/cjs/connection-deadline.d.ts +49 -0
- package/dist/cjs/connection-deadline.js +91 -0
- package/dist/cjs/errors.d.ts +83 -0
- package/dist/cjs/errors.js +13 -0
- package/dist/cjs/handler/imap-compiler.d.ts +24 -0
- package/dist/cjs/handler/imap-compiler.js +285 -0
- package/dist/cjs/handler/imap-formal-syntax.d.ts +28 -0
- package/dist/cjs/handler/imap-formal-syntax.js +121 -0
- package/dist/cjs/handler/imap-handler.d.ts +9 -0
- package/dist/cjs/handler/imap-handler.js +10 -0
- package/dist/cjs/handler/imap-parser.d.ts +16 -0
- package/dist/cjs/handler/imap-parser.js +90 -0
- package/dist/cjs/handler/imap-stream.d.ts +181 -0
- package/dist/cjs/handler/imap-stream.js +446 -0
- package/dist/cjs/handler/limits.d.ts +25 -0
- package/dist/cjs/handler/limits.js +51 -0
- package/dist/cjs/handler/parser-instance.d.ts +68 -0
- package/dist/cjs/handler/parser-instance.js +223 -0
- package/dist/cjs/handler/token-parser.d.ts +91 -0
- package/dist/cjs/handler/token-parser.js +673 -0
- package/dist/cjs/handler/types.d.ts +91 -0
- package/dist/cjs/handler/types.js +4 -0
- package/dist/cjs/imap-commands.d.ts +16 -0
- package/dist/cjs/imap-commands.js +74 -0
- package/dist/cjs/imap-flow.d.ts +676 -0
- package/dist/cjs/imap-flow.js +3949 -0
- package/dist/cjs/jp-decoder.d.ts +12 -0
- package/dist/cjs/jp-decoder.js +79 -0
- package/dist/cjs/limited-passthrough.d.ts +25 -0
- package/dist/cjs/limited-passthrough.js +54 -0
- package/dist/cjs/logger.d.ts +3 -0
- package/dist/cjs/logger.js +11 -0
- package/dist/cjs/package-info.d.ts +3 -0
- package/dist/cjs/package-info.js +7 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/proxy-connection.d.ts +33 -0
- package/dist/cjs/proxy-connection.js +392 -0
- package/dist/cjs/search-compiler.d.ts +34 -0
- package/dist/cjs/search-compiler.js +476 -0
- package/dist/cjs/special-use.d.ts +22 -0
- package/dist/cjs/special-use.js +911 -0
- package/dist/cjs/tools.d.ts +427 -0
- package/dist/cjs/tools.js +1496 -0
- package/{lib/imap-flow.d.ts → dist/cjs/types.d.ts} +386 -516
- package/dist/cjs/types.js +5 -0
- package/dist/esm/charsets.d.ts +1 -0
- package/{lib → dist/esm}/charsets.js +1 -6
- package/dist/esm/commands/append.d.ts +22 -0
- package/{lib → dist/esm}/commands/append.js +22 -52
- package/dist/esm/commands/authenticate.d.ts +24 -0
- package/{lib → dist/esm}/commands/authenticate.js +62 -87
- package/dist/esm/commands/capability.d.ts +8 -0
- package/{lib → dist/esm}/commands/capability.js +6 -9
- package/dist/esm/commands/close.d.ts +8 -0
- package/{lib → dist/esm}/commands/close.js +6 -10
- package/dist/esm/commands/compress.d.ts +8 -0
- package/{lib → dist/esm}/commands/compress.js +7 -11
- package/dist/esm/commands/copy.d.ts +13 -0
- package/{lib → dist/esm}/commands/copy.js +12 -20
- package/dist/esm/commands/copyuid-parser.d.ts +11 -0
- package/{lib → dist/esm}/commands/copyuid-parser.js +9 -15
- package/dist/esm/commands/create.d.ts +11 -0
- package/{lib → dist/esm}/commands/create.js +13 -27
- package/dist/esm/commands/delete.d.ts +11 -0
- package/{lib → dist/esm}/commands/delete.js +9 -14
- package/dist/esm/commands/enable.d.ts +9 -0
- package/{lib → dist/esm}/commands/enable.js +23 -30
- package/dist/esm/commands/esearch-parser.d.ts +17 -0
- package/dist/esm/commands/esearch-parser.js +88 -0
- package/dist/esm/commands/expunge.d.ts +12 -0
- package/{lib → dist/esm}/commands/expunge.js +17 -22
- package/dist/esm/commands/fetch.d.ts +30 -0
- package/{lib → dist/esm}/commands/fetch.js +32 -64
- package/dist/esm/commands/id.d.ts +10 -0
- package/{lib → dist/esm}/commands/id.js +17 -23
- package/dist/esm/commands/idle.d.ts +9 -0
- package/{lib → dist/esm}/commands/idle.js +47 -81
- package/dist/esm/commands/list.d.ts +16 -0
- package/{lib → dist/esm}/commands/list.js +56 -121
- package/dist/esm/commands/login.d.ts +11 -0
- package/{lib → dist/esm}/commands/login.js +10 -15
- package/dist/esm/commands/logout.d.ts +8 -0
- package/{lib → dist/esm}/commands/logout.js +9 -11
- package/dist/esm/commands/move.d.ts +13 -0
- package/{lib → dist/esm}/commands/move.js +13 -21
- package/dist/esm/commands/namespace.d.ts +25 -0
- package/{lib → dist/esm}/commands/namespace.js +34 -44
- package/dist/esm/commands/noop.d.ts +8 -0
- package/{lib → dist/esm}/commands/noop.js +6 -7
- package/dist/esm/commands/quota.d.ts +10 -0
- package/{lib → dist/esm}/commands/quota.js +18 -36
- package/dist/esm/commands/rename.d.ts +12 -0
- package/{lib → dist/esm}/commands/rename.js +10 -15
- package/dist/esm/commands/search.d.ts +15 -0
- package/{lib → dist/esm}/commands/search.js +36 -135
- package/dist/esm/commands/select.d.ts +25 -0
- package/{lib → dist/esm}/commands/select.js +33 -64
- package/dist/esm/commands/starttls.d.ts +8 -0
- package/{lib → dist/esm}/commands/starttls.js +6 -8
- package/dist/esm/commands/status-fields.d.ts +14 -0
- package/{lib → dist/esm}/commands/status-fields.js +5 -16
- package/dist/esm/commands/status.d.ts +12 -0
- package/{lib → dist/esm}/commands/status.js +18 -29
- package/dist/esm/commands/store.d.ts +19 -0
- package/{lib → dist/esm}/commands/store.js +24 -37
- package/dist/esm/commands/subscribe.d.ts +9 -0
- package/{lib → dist/esm}/commands/subscribe.js +8 -12
- package/dist/esm/commands/unsubscribe.d.ts +9 -0
- package/{lib → dist/esm}/commands/unsubscribe.js +8 -12
- package/dist/esm/connection-deadline.d.ts +49 -0
- package/{lib → dist/esm}/connection-deadline.js +14 -25
- package/dist/esm/errors.d.ts +83 -0
- package/dist/esm/errors.js +9 -0
- package/dist/esm/handler/imap-compiler.d.ts +24 -0
- package/{lib → dist/esm}/handler/imap-compiler.js +22 -80
- package/dist/esm/handler/imap-formal-syntax.d.ts +28 -0
- package/dist/esm/handler/imap-formal-syntax.js +117 -0
- package/dist/esm/handler/imap-handler.d.ts +9 -0
- package/dist/esm/handler/imap-handler.js +9 -0
- package/dist/esm/handler/imap-parser.d.ts +16 -0
- package/{lib → dist/esm}/handler/imap-parser.js +31 -44
- package/dist/esm/handler/imap-stream.d.ts +181 -0
- package/{lib → dist/esm}/handler/imap-stream.js +29 -121
- package/dist/esm/handler/limits.d.ts +25 -0
- package/{lib → dist/esm}/handler/limits.js +13 -22
- package/dist/esm/handler/parser-instance.d.ts +68 -0
- package/{lib → dist/esm}/handler/parser-instance.js +19 -47
- package/dist/esm/handler/token-parser.d.ts +91 -0
- package/{lib → dist/esm}/handler/token-parser.js +71 -155
- package/dist/esm/handler/types.d.ts +91 -0
- package/dist/esm/handler/types.js +3 -0
- package/dist/esm/imap-commands.d.ts +16 -0
- package/dist/esm/imap-commands.js +67 -0
- package/dist/esm/imap-flow.d.ts +676 -0
- package/{lib → dist/esm}/imap-flow.js +785 -1802
- package/dist/esm/jp-decoder.d.ts +12 -0
- package/{lib → dist/esm}/jp-decoder.js +6 -21
- package/dist/esm/limited-passthrough.d.ts +25 -0
- package/{lib → dist/esm}/limited-passthrough.js +7 -20
- package/dist/esm/logger.d.ts +3 -0
- package/dist/esm/logger.js +4 -0
- package/dist/esm/package-info.d.ts +3 -0
- package/dist/esm/package-info.js +4 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/proxy-connection.d.ts +33 -0
- package/{lib → dist/esm}/proxy-connection.js +56 -127
- package/dist/esm/search-compiler.d.ts +34 -0
- package/{lib → dist/esm}/search-compiler.js +54 -110
- package/dist/esm/special-use.d.ts +22 -0
- package/dist/esm/special-use.js +907 -0
- package/dist/esm/tools.d.ts +427 -0
- package/dist/esm/tools.js +1446 -0
- package/dist/esm/types.d.ts +828 -0
- package/dist/esm/types.js +4 -0
- package/package.json +60 -20
- package/.gitattributes +0 -1
- package/.github/CODE_OF_CONDUCT.md +0 -76
- package/.github/FUNDING.yml +0 -4
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- package/.github/contributing.md +0 -17
- package/.github/workflows/release.yaml +0 -36
- package/.github/workflows/stale.yml +0 -29
- package/.github/workflows/test.yml +0 -51
- package/.ncurc.js +0 -4
- package/.prettierignore +0 -4
- package/.prettierrc.js +0 -8
- package/.release-please-manifest.json +0 -3
- package/CLAUDE.md +0 -104
- package/Gruntfile.js +0 -23
- package/eslint.config.js +0 -45
- package/lib/handler/imap-formal-syntax.js +0 -189
- package/lib/handler/imap-handler.js +0 -17
- package/lib/imap-commands.js +0 -45
- package/lib/logger.js +0 -5
- package/lib/special-use.js +0 -923
- package/lib/tools.js +0 -1612
- package/release-please-config.json +0 -10
- package/test/authentication-test.js +0 -101
- package/test/auto-idle-test.js +0 -470
- package/test/bodystructure-test.js +0 -899
- package/test/charsets-test.js +0 -161
- package/test/commands-branches-test.js +0 -1095
- package/test/commands-integration-test.js +0 -11124
- package/test/commands-test.js +0 -73
- package/test/connection-edge-cases-test.js +0 -1828
- package/test/connection-test.js +0 -162
- package/test/copyuid-parser-test.js +0 -173
- package/test/fetch-generator-test.js +0 -218
- package/test/fixtures/fake-timers.js +0 -115
- package/test/fixtures/serialized-mimetorture.js +0 -2738
- package/test/fixtures/test-client.js +0 -57
- package/test/fixtures/test-tls.js +0 -8
- package/test/handler-branches-test.js +0 -310
- package/test/idle-polling-test.js +0 -518
- package/test/imap-compiler-test.js +0 -809
- package/test/imap-flow-compress-test.js +0 -166
- package/test/imap-flow-coverage-test.js +0 -612
- package/test/imap-flow-fetch-download-test.js +0 -873
- package/test/imap-flow-internals-test.js +0 -725
- package/test/imap-flow-methods-test.js +0 -889
- package/test/imap-flow-proxy-paths-test.js +0 -366
- package/test/imap-flow-secure-test.js +0 -573
- package/test/imap-flow-server-test.js +0 -1474
- package/test/imap-formal-syntax-test.js +0 -293
- package/test/imap-parser-test.js +0 -1474
- package/test/imap-stream-edge-cases-test.js +0 -666
- package/test/imap-stream-test.js +0 -177
- package/test/imapflow-test.js +0 -258
- package/test/integration/README.md +0 -52
- package/test/integration/dovecot-test.conf +0 -27
- package/test/integration/rev2-live-test.js +0 -431
- package/test/integration/run-rev2-tests.sh +0 -75
- package/test/integration-test.js +0 -83
- package/test/jp-decoder-test.js +0 -304
- package/test/limited-passthrough-test.js +0 -299
- package/test/memory-cleanup-test.js +0 -144
- package/test/memory-leak-test.js +0 -667
- package/test/parser-limits-test.js +0 -292
- package/test/proxy-connection-test.js +0 -738
- package/test/reliability-improvements-test.js +0 -548
- package/test/search-compiler-test.js +0 -1300
- package/test/search-test.js +0 -329
- package/test/special-use-test.js +0 -418
- package/test/starttls-injection-test.js +0 -181
- package/test/tag-correlation-test.js +0 -333
- package/test/timer-policy-test.js +0 -227
- package/test/token-parser-test.js +0 -456
- package/test/tools-test.js +0 -2013
- package/test/unhandled-rejection-test.js +0 -593
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Shared response-size limits for the IMAP parser. Kept in one place so the streaming parser
|
|
3
|
+
// (ImapStream) and the standalone token parser cannot drift apart, and so the documented
|
|
4
|
+
// defaults in the ImapFlowOptions type describe both paths.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createLiteralTooLargeError = exports.normalizeLimit = exports.MAX_RESPONSE_SIZE = exports.MAX_LINE_SIZE = exports.MAX_LITERAL_SIZE = void 0;
|
|
7
|
+
// Maximum allowed literal size: 1GB (1073741824 bytes)
|
|
8
|
+
exports.MAX_LITERAL_SIZE = 1024 * 1024 * 1024;
|
|
9
|
+
// Default maximum length of a single line (a response without a literal). Matches the literal cap:
|
|
10
|
+
// large literal-free responses (e.g. big SEARCH/LIST results) are legitimate, so this bound exists
|
|
11
|
+
// only to stop a server that never sends a line terminator, not to constrain normal traffic.
|
|
12
|
+
exports.MAX_LINE_SIZE = exports.MAX_LITERAL_SIZE;
|
|
13
|
+
// Default maximum total size of a single assembled response: every line segment and literal of
|
|
14
|
+
// one response combined. The per-line and per-literal caps alone cannot stop a server that
|
|
15
|
+
// spreads attacker-controlled bytes across an unbounded number of tokens of a single response
|
|
16
|
+
// (e.g. one FETCH answer carrying many maximum-size literals).
|
|
17
|
+
//
|
|
18
|
+
// Deliberately above the literal cap: the response total also carries the literal's marker line
|
|
19
|
+
// and the rest of the response framing, so a cap equal to MAX_LITERAL_SIZE would make a literal
|
|
20
|
+
// of exactly the maximum permitted size impossible to receive. Configuring both limits calls for
|
|
21
|
+
// the same headroom - set maxResponseSize above maxLiteralSize, not equal to it.
|
|
22
|
+
exports.MAX_RESPONSE_SIZE = 2 * exports.MAX_LITERAL_SIZE;
|
|
23
|
+
/**
|
|
24
|
+
* Normalizes a configured size limit. A non-negative integer is honored as-is (including 0, which
|
|
25
|
+
* means "reject anything non-empty"), and `Infinity` disables the limit; anything else falls back
|
|
26
|
+
* to the default, so an explicit 0 is not silently swallowed the way `value || DEFAULT` would
|
|
27
|
+
* swallow it.
|
|
28
|
+
*
|
|
29
|
+
* @param value - The configured value.
|
|
30
|
+
* @param defaultValue - Fallback when the value is not a usable limit.
|
|
31
|
+
* @returns The normalized limit.
|
|
32
|
+
*/
|
|
33
|
+
const normalizeLimit = (value, defaultValue) => (Number.isInteger(value) || value === Infinity) && value >= 0 ? value : defaultValue;
|
|
34
|
+
exports.normalizeLimit = normalizeLimit;
|
|
35
|
+
/**
|
|
36
|
+
* Builds the `LiteralTooLarge` error. One shape for every place a literal is refused, so callers
|
|
37
|
+
* can rely on `code`, `literalSize` and `maxSize` regardless of which parser rejected it.
|
|
38
|
+
*
|
|
39
|
+
* @param literalSize - The declared literal size.
|
|
40
|
+
* @param maxSize - The bound that was exceeded.
|
|
41
|
+
* @param reason - What the bound was, when it is not the configured maximum.
|
|
42
|
+
* @returns The error to emit or throw.
|
|
43
|
+
*/
|
|
44
|
+
const createLiteralTooLargeError = (literalSize, maxSize, reason) => {
|
|
45
|
+
const err = new Error(`Literal size ${literalSize} exceeds ${reason || `maximum allowed size of ${maxSize} bytes`}`);
|
|
46
|
+
err.code = 'LiteralTooLarge';
|
|
47
|
+
err.literalSize = literalSize;
|
|
48
|
+
err.maxSize = maxSize;
|
|
49
|
+
return err;
|
|
50
|
+
};
|
|
51
|
+
exports.createLiteralTooLargeError = createLiteralTooLargeError;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ImapAttributeList, ParserOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parses a single IMAP response line into its structural components: tag, command,
|
|
4
|
+
* and attributes. Handles status responses (OK, NO, BAD, PREAUTH, BYE) with their
|
|
5
|
+
* human-readable text and response codes, as well as continuation responses ("+").
|
|
6
|
+
*/
|
|
7
|
+
export declare class ParserInstance {
|
|
8
|
+
input: string;
|
|
9
|
+
options: ParserOptions;
|
|
10
|
+
remainder: string;
|
|
11
|
+
pos: number;
|
|
12
|
+
tag?: string | undefined;
|
|
13
|
+
command?: string | undefined;
|
|
14
|
+
humanReadable?: string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new ParserInstance for parsing an IMAP response line.
|
|
17
|
+
*
|
|
18
|
+
* @param input - The raw IMAP response line to parse.
|
|
19
|
+
* @param options - Parser options passed through to the TokenParser for attribute parsing.
|
|
20
|
+
* @param options.literalPlus - Whether the LITERAL+ extension is in use.
|
|
21
|
+
* @param options.literals - Pre-parsed literal values from the stream.
|
|
22
|
+
*/
|
|
23
|
+
constructor(input?: Buffer | string | null | undefined, options?: ParserOptions | undefined);
|
|
24
|
+
/**
|
|
25
|
+
* Extracts and returns the IMAP tag from the beginning of the response.
|
|
26
|
+
* The tag is typically "*" for untagged responses, "+" for continuation requests,
|
|
27
|
+
* or a client-assigned command tag like "A1".
|
|
28
|
+
*
|
|
29
|
+
* @returns The parsed tag string.
|
|
30
|
+
* @throws {Error} If the tag contains invalid characters.
|
|
31
|
+
*/
|
|
32
|
+
getTag(): Promise<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Extracts and returns the IMAP command or response name from the input.
|
|
35
|
+
* For continuation responses (tag "+"), returns an empty string and stores
|
|
36
|
+
* the remainder as human-readable text. For status responses (OK, NO, BAD,
|
|
37
|
+
* PREAUTH, BYE), separates the optional response code from the human-readable text.
|
|
38
|
+
*
|
|
39
|
+
* @returns The parsed command string.
|
|
40
|
+
* @throws {Error} If the command contains invalid characters or input ends unexpectedly.
|
|
41
|
+
*/
|
|
42
|
+
getCommand(): Promise<string>;
|
|
43
|
+
/**
|
|
44
|
+
* Extracts the next whitespace-delimited element from the input and validates it
|
|
45
|
+
* against the given syntax character set. Advances the parser position past the element.
|
|
46
|
+
*
|
|
47
|
+
* @param syntax - A string of allowed characters for the element (as returned by imap-formal-syntax methods).
|
|
48
|
+
* @returns The extracted element string.
|
|
49
|
+
* @throws {Error} If the element contains characters not in the syntax set, or if input ends unexpectedly.
|
|
50
|
+
*/
|
|
51
|
+
getElement(syntax: string): Promise<string>;
|
|
52
|
+
/**
|
|
53
|
+
* Consumes a single space character from the current position in the input.
|
|
54
|
+
* Advances the parser position by one.
|
|
55
|
+
*
|
|
56
|
+
* @throws {Error} If the current character is not a space, or if input has ended unexpectedly.
|
|
57
|
+
*/
|
|
58
|
+
getSpace(): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Parses the remaining input as IMAP attributes using the TokenParser.
|
|
61
|
+
* This handles complex structures including nested lists, literals, strings,
|
|
62
|
+
* atoms, sections, sequences, and partial ranges.
|
|
63
|
+
*
|
|
64
|
+
* @returns A promise that resolves to an array of parsed attribute objects.
|
|
65
|
+
* @throws {Error} If the input contains unexpected whitespace, invalid characters, or ends unexpectedly.
|
|
66
|
+
*/
|
|
67
|
+
getAttributes(): Promise<ImapAttributeList>;
|
|
68
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint new-cap: 0 */
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.ParserInstance = void 0;
|
|
8
|
+
const imap_formal_syntax_js_1 = __importDefault(require("./imap-formal-syntax.js"));
|
|
9
|
+
const token_parser_js_1 = require("./token-parser.js");
|
|
10
|
+
/**
|
|
11
|
+
* Parses a single IMAP response line into its structural components: tag, command,
|
|
12
|
+
* and attributes. Handles status responses (OK, NO, BAD, PREAUTH, BYE) with their
|
|
13
|
+
* human-readable text and response codes, as well as continuation responses ("+").
|
|
14
|
+
*/
|
|
15
|
+
class ParserInstance {
|
|
16
|
+
/**
|
|
17
|
+
* Creates a new ParserInstance for parsing an IMAP response line.
|
|
18
|
+
*
|
|
19
|
+
* @param input - The raw IMAP response line to parse.
|
|
20
|
+
* @param options - Parser options passed through to the TokenParser for attribute parsing.
|
|
21
|
+
* @param options.literalPlus - Whether the LITERAL+ extension is in use.
|
|
22
|
+
* @param options.literals - Pre-parsed literal values from the stream.
|
|
23
|
+
*/
|
|
24
|
+
constructor(input, options) {
|
|
25
|
+
this.input = (input || '').toString();
|
|
26
|
+
this.options = options || {};
|
|
27
|
+
this.remainder = this.input;
|
|
28
|
+
this.pos = 0;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Extracts and returns the IMAP tag from the beginning of the response.
|
|
32
|
+
* The tag is typically "*" for untagged responses, "+" for continuation requests,
|
|
33
|
+
* or a client-assigned command tag like "A1".
|
|
34
|
+
*
|
|
35
|
+
* @returns The parsed tag string.
|
|
36
|
+
* @throws {Error} If the tag contains invalid characters.
|
|
37
|
+
*/
|
|
38
|
+
async getTag() {
|
|
39
|
+
if (!this.tag) {
|
|
40
|
+
this.tag = await this.getElement(imap_formal_syntax_js_1.default.tag() + '*+');
|
|
41
|
+
}
|
|
42
|
+
return this.tag;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Extracts and returns the IMAP command or response name from the input.
|
|
46
|
+
* For continuation responses (tag "+"), returns an empty string and stores
|
|
47
|
+
* the remainder as human-readable text. For status responses (OK, NO, BAD,
|
|
48
|
+
* PREAUTH, BYE), separates the optional response code from the human-readable text.
|
|
49
|
+
*
|
|
50
|
+
* @returns The parsed command string.
|
|
51
|
+
* @throws {Error} If the command contains invalid characters or input ends unexpectedly.
|
|
52
|
+
*/
|
|
53
|
+
async getCommand() {
|
|
54
|
+
if (this.tag === '+') {
|
|
55
|
+
// special case
|
|
56
|
+
this.humanReadable = this.remainder.trim();
|
|
57
|
+
this.remainder = '';
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
if (!this.command) {
|
|
61
|
+
this.command = await this.getElement(imap_formal_syntax_js_1.default.command());
|
|
62
|
+
}
|
|
63
|
+
// Status responses have the format: TAG OK/NO/BAD [response-code] human-readable text
|
|
64
|
+
// Example: * OK [CAPABILITY IMAP4rev1] Server ready
|
|
65
|
+
// Example: A1 NO [AUTHENTICATIONFAILED] Invalid credentials
|
|
66
|
+
// We need to separate the optional [response-code] from the human-readable text.
|
|
67
|
+
switch ((this.command || '').toString().toUpperCase()) {
|
|
68
|
+
case 'OK':
|
|
69
|
+
case 'NO':
|
|
70
|
+
case 'BAD':
|
|
71
|
+
case 'PREAUTH':
|
|
72
|
+
case 'BYE':
|
|
73
|
+
{
|
|
74
|
+
let match = this.remainder.match(/^\s+\[/);
|
|
75
|
+
if (match) {
|
|
76
|
+
// Find the ']' that closes the response code. Inner brackets are
|
|
77
|
+
// tracked because servers do put bracketed values inside a code
|
|
78
|
+
// (e.g. a "[css3-page]" keyword in a PERMANENTFLAGS list), which a
|
|
79
|
+
// first-']' scan would cut in half.
|
|
80
|
+
let nesting = 1;
|
|
81
|
+
let end = -1;
|
|
82
|
+
for (let i = match[0].length; i < this.remainder.length; i++) {
|
|
83
|
+
let c = this.remainder[i];
|
|
84
|
+
if (c === '[') {
|
|
85
|
+
nesting++;
|
|
86
|
+
}
|
|
87
|
+
else if (c === ']') {
|
|
88
|
+
nesting--;
|
|
89
|
+
}
|
|
90
|
+
if (!nesting) {
|
|
91
|
+
end = i;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Unbalanced '[' inside the code: the RFC 9051 free-text form
|
|
96
|
+
// (`atom [SP 1*<any TEXT-CHAR except "]">]`) permits '[' but not
|
|
97
|
+
// ']', so the code really does end at the first ']' here. Without
|
|
98
|
+
// this fallback the scan finds no closing bracket at all and the
|
|
99
|
+
// human-readable text - what every error message is built from -
|
|
100
|
+
// is swallowed into the response code.
|
|
101
|
+
if (end < 0) {
|
|
102
|
+
end = this.remainder.indexOf(']', match[0].length);
|
|
103
|
+
}
|
|
104
|
+
if (end >= 0) {
|
|
105
|
+
this.humanReadable = this.remainder.substring(end + 1).trim();
|
|
106
|
+
this.remainder = this.remainder.substring(0, end + 1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
this.humanReadable = this.remainder.trim();
|
|
111
|
+
this.remainder = '';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
return this.command;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Extracts the next whitespace-delimited element from the input and validates it
|
|
120
|
+
* against the given syntax character set. Advances the parser position past the element.
|
|
121
|
+
*
|
|
122
|
+
* @param syntax - A string of allowed characters for the element (as returned by imap-formal-syntax methods).
|
|
123
|
+
* @returns The extracted element string.
|
|
124
|
+
* @throws {Error} If the element contains characters not in the syntax set, or if input ends unexpectedly.
|
|
125
|
+
*/
|
|
126
|
+
async getElement(syntax) {
|
|
127
|
+
let match, element, errPos;
|
|
128
|
+
if (/^\s/.test(this.remainder)) {
|
|
129
|
+
let error = new Error(`Unexpected whitespace at position ${this.pos} [E1]`);
|
|
130
|
+
error.code = 'ParserError1';
|
|
131
|
+
error.parserContext = { input: this.input, pos: this.pos };
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
if ((match = this.remainder.match(/^\s*[^\s]+(?=\s|$)/))) {
|
|
135
|
+
element = match[0];
|
|
136
|
+
if ((errPos = imap_formal_syntax_js_1.default.verify(element, syntax)) >= 0) {
|
|
137
|
+
if (this.tag === 'Server' && element === 'Unavailable.') {
|
|
138
|
+
// Microsoft Exchange sometimes sends a non-standard response
|
|
139
|
+
// "Server Unavailable." instead of a proper IMAP tagged/untagged response.
|
|
140
|
+
// We detect this specific pattern and convert it into a synthetic BAD response
|
|
141
|
+
// so the rest of the parser can handle it gracefully.
|
|
142
|
+
let error = new Error(`Server returned an error: ${this.input}`);
|
|
143
|
+
error.code = 'ParserErrorExchange';
|
|
144
|
+
error.parserContext = {
|
|
145
|
+
input: this.input,
|
|
146
|
+
element,
|
|
147
|
+
pos: this.pos,
|
|
148
|
+
value: {
|
|
149
|
+
tag: '*',
|
|
150
|
+
command: 'BAD',
|
|
151
|
+
attributes: [{ type: 'TEXT', value: this.input }]
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
let error = new Error(`Unexpected char at position ${this.pos + errPos} [E2: ${JSON.stringify(element.charAt(errPos))}]`);
|
|
157
|
+
error.code = 'ParserError2';
|
|
158
|
+
error.parserContext = { input: this.input, element, pos: this.pos };
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
let error = new Error(`Unexpected end of input at position ${this.pos} [E3]`);
|
|
164
|
+
error.code = 'ParserError3';
|
|
165
|
+
error.parserContext = { input: this.input, pos: this.pos };
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
this.pos += match[0].length;
|
|
169
|
+
this.remainder = this.remainder.substr(match[0].length);
|
|
170
|
+
return element;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Consumes a single space character from the current position in the input.
|
|
174
|
+
* Advances the parser position by one.
|
|
175
|
+
*
|
|
176
|
+
* @throws {Error} If the current character is not a space, or if input has ended unexpectedly.
|
|
177
|
+
*/
|
|
178
|
+
async getSpace() {
|
|
179
|
+
if (!this.remainder.length) {
|
|
180
|
+
if (this.tag === '+' && this.pos === 1) {
|
|
181
|
+
// special case, empty + response
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
let error = new Error(`Unexpected end of input at position ${this.pos} [E4]`);
|
|
185
|
+
error.code = 'ParserError4';
|
|
186
|
+
error.parserContext = { input: this.input, pos: this.pos };
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
if (imap_formal_syntax_js_1.default.verify(this.remainder.charAt(0), imap_formal_syntax_js_1.default.SP()) >= 0) {
|
|
190
|
+
let error = new Error(`Unexpected char at position ${this.pos} [E5: ${JSON.stringify(this.remainder.charAt(0))}]`);
|
|
191
|
+
error.code = 'ParserError5';
|
|
192
|
+
error.parserContext = { input: this.input, element: this.remainder, pos: this.pos };
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
this.pos++;
|
|
196
|
+
this.remainder = this.remainder.substr(1);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Parses the remaining input as IMAP attributes using the TokenParser.
|
|
200
|
+
* This handles complex structures including nested lists, literals, strings,
|
|
201
|
+
* atoms, sections, sequences, and partial ranges.
|
|
202
|
+
*
|
|
203
|
+
* @returns A promise that resolves to an array of parsed attribute objects.
|
|
204
|
+
* @throws {Error} If the input contains unexpected whitespace, invalid characters, or ends unexpectedly.
|
|
205
|
+
*/
|
|
206
|
+
async getAttributes() {
|
|
207
|
+
if (!this.remainder.length) {
|
|
208
|
+
let error = new Error(`Unexpected end of input at position ${this.pos} [E6]`);
|
|
209
|
+
error.code = 'ParserError6';
|
|
210
|
+
error.parserContext = { input: this.input, pos: this.pos };
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
if (/^\s/.test(this.remainder)) {
|
|
214
|
+
let error = new Error(`Unexpected whitespace at position ${this.pos} [E7]`);
|
|
215
|
+
error.code = 'ParserError7';
|
|
216
|
+
error.parserContext = { input: this.input, element: this.remainder, pos: this.pos };
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
const tokenParser = new token_parser_js_1.TokenParser(this, this.pos, this.remainder, this.options);
|
|
220
|
+
return await tokenParser.getAttributes();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
exports.ParserInstance = ParserInstance;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { ImapAttributeList, ParserOptions } from './types.js';
|
|
2
|
+
import type { ParserInstance } from './parser-instance.js';
|
|
3
|
+
/**
|
|
4
|
+
* A node of the parse tree built by TokenParser. `type` is false for a node that has not
|
|
5
|
+
* been classified yet, 'TREE' for the root, and otherwise the token or structure type
|
|
6
|
+
* (ATOM, string, LITERAL, SEQUENCE, LIST, SECTION, PARTIAL).
|
|
7
|
+
*/
|
|
8
|
+
export interface TokenNode {
|
|
9
|
+
childNodes: TokenNode[];
|
|
10
|
+
type: string | false;
|
|
11
|
+
value: string | Buffer;
|
|
12
|
+
isClosed: boolean;
|
|
13
|
+
parentNode?: TokenNode | undefined;
|
|
14
|
+
depth: number;
|
|
15
|
+
startPos?: number | undefined;
|
|
16
|
+
endPos?: number | undefined;
|
|
17
|
+
literalType?: string | undefined;
|
|
18
|
+
/** Digits accumulated as a string while the literal marker is read, converted to a number once the marker closes */
|
|
19
|
+
literalLength?: string | number | undefined;
|
|
20
|
+
literalPlus?: boolean | undefined;
|
|
21
|
+
started?: boolean | undefined;
|
|
22
|
+
chBuffer?: Buffer | undefined;
|
|
23
|
+
chPos?: number | undefined;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The parent object a TokenParser reads the parsed command from
|
|
27
|
+
*/
|
|
28
|
+
export interface TokenParserParent {
|
|
29
|
+
command?: string | undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Tokenizes an IMAP attribute string into a tree of typed nodes.
|
|
33
|
+
* Handles all IMAP data types: atoms, quoted strings, literals (including literal8),
|
|
34
|
+
* sequences, lists (parenthesized groups), sections (bracketed groups), and partial ranges.
|
|
35
|
+
* Enforces a maximum nesting depth of {@link MAX_NODE_DEPTH} to prevent stack overflow
|
|
36
|
+
* from malicious input.
|
|
37
|
+
*/
|
|
38
|
+
export declare class TokenParser {
|
|
39
|
+
str: string;
|
|
40
|
+
options: ParserOptions;
|
|
41
|
+
parent: TokenParserParent | ParserInstance;
|
|
42
|
+
maxLiteralSize: number;
|
|
43
|
+
tree: TokenNode;
|
|
44
|
+
currentNode: TokenNode;
|
|
45
|
+
pos: number;
|
|
46
|
+
state: number;
|
|
47
|
+
expectedLiteralType?: string | false | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Creates a new TokenParser.
|
|
50
|
+
*
|
|
51
|
+
* @param parent - The parent ParserInstance that owns this token parser. Used to access the parsed command for context-sensitive parsing.
|
|
52
|
+
* @param startPos - The starting position offset in the original input, used for error reporting.
|
|
53
|
+
* @param str - The attribute string to tokenize.
|
|
54
|
+
* @param options - Parser options.
|
|
55
|
+
* @param options.literalPlus - Whether the LITERAL+ extension is in use.
|
|
56
|
+
* @param options.literals - Pre-parsed literal values from the input stream.
|
|
57
|
+
* @param options.maxLiteralSize - Maximum size (in bytes) of a literal parsed inline
|
|
58
|
+
* from the input, i.e. when no pre-parsed literal buffers were supplied. Defaults to 1GB.
|
|
59
|
+
*/
|
|
60
|
+
constructor(parent: TokenParserParent | ParserInstance, startPos?: number | undefined, str?: string | null | undefined, options?: ParserOptions | undefined);
|
|
61
|
+
/**
|
|
62
|
+
* Processes the input string and returns the parsed attributes as a flat array of typed objects.
|
|
63
|
+
* Each attribute is an object with a `type` (e.g., "ATOM", "STRING", "LITERAL", "SEQUENCE")
|
|
64
|
+
* and a `value` property. Lists are represented as nested arrays. Sections and partials are
|
|
65
|
+
* attached as properties on the preceding attribute object.
|
|
66
|
+
*
|
|
67
|
+
* @returns A promise that resolves to an array of parsed attribute objects and nested arrays.
|
|
68
|
+
* @throws {Error} If the input contains syntax errors or unclosed nodes.
|
|
69
|
+
*/
|
|
70
|
+
getAttributes(): Promise<ImapAttributeList>;
|
|
71
|
+
/**
|
|
72
|
+
* Creates a new node in the parse tree. Each node represents a token or structural
|
|
73
|
+
* element (e.g., atom, string, literal, list, section, partial). The node is automatically
|
|
74
|
+
* appended to the parent's childNodes array if a parent is provided.
|
|
75
|
+
*
|
|
76
|
+
* @param parentNode - The parent node to attach this node to. If omitted, creates a root node.
|
|
77
|
+
* @param startPos - The starting position of this node in the original input string.
|
|
78
|
+
* @returns The newly created node with childNodes, type, value, and isClosed properties.
|
|
79
|
+
* @throws {Error} If the nesting depth exceeds MAX_NODE_DEPTH.
|
|
80
|
+
*/
|
|
81
|
+
createNode(parentNode?: TokenNode | undefined, startPos?: number | undefined): TokenNode;
|
|
82
|
+
/**
|
|
83
|
+
* Processes the entire input string character by character using a state machine.
|
|
84
|
+
* Transitions between states (NORMAL, ATOM, STRING, LITERAL, SEQUENCE, PARTIAL, TEXT)
|
|
85
|
+
* based on the current character and builds the parse tree. This is the main parsing
|
|
86
|
+
* loop that drives the tokenization.
|
|
87
|
+
*
|
|
88
|
+
* @throws {Error} If the input contains unexpected characters, unclosed structures, or other syntax errors.
|
|
89
|
+
*/
|
|
90
|
+
processString(): Promise<void>;
|
|
91
|
+
}
|