imapflow 1.7.8 → 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 +20 -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 +761 -1789
- 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 -101
- 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 -909
- 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 -661
|
@@ -0,0 +1,673 @@
|
|
|
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.TokenParser = void 0;
|
|
8
|
+
const imap_formal_syntax_js_1 = __importDefault(require("./imap-formal-syntax.js"));
|
|
9
|
+
const limits_js_1 = require("./limits.js");
|
|
10
|
+
const STATE_ATOM = 0x001;
|
|
11
|
+
const STATE_LITERAL = 0x002;
|
|
12
|
+
const STATE_NORMAL = 0x003;
|
|
13
|
+
const STATE_PARTIAL = 0x004;
|
|
14
|
+
const STATE_SEQUENCE = 0x005;
|
|
15
|
+
const STATE_STRING = 0x006;
|
|
16
|
+
const RE_DIGITS = /^\d+$/;
|
|
17
|
+
const RE_SINGLE_DIGIT = /^\d$/;
|
|
18
|
+
// Prevents stack overflow from maliciously crafted deeply-nested IMAP input (e.g., (((((...))))))
|
|
19
|
+
const MAX_NODE_DEPTH = 25;
|
|
20
|
+
/**
|
|
21
|
+
* Tokenizes an IMAP attribute string into a tree of typed nodes.
|
|
22
|
+
* Handles all IMAP data types: atoms, quoted strings, literals (including literal8),
|
|
23
|
+
* sequences, lists (parenthesized groups), sections (bracketed groups), and partial ranges.
|
|
24
|
+
* Enforces a maximum nesting depth of {@link MAX_NODE_DEPTH} to prevent stack overflow
|
|
25
|
+
* from malicious input.
|
|
26
|
+
*/
|
|
27
|
+
class TokenParser {
|
|
28
|
+
/**
|
|
29
|
+
* Creates a new TokenParser.
|
|
30
|
+
*
|
|
31
|
+
* @param parent - The parent ParserInstance that owns this token parser. Used to access the parsed command for context-sensitive parsing.
|
|
32
|
+
* @param startPos - The starting position offset in the original input, used for error reporting.
|
|
33
|
+
* @param str - The attribute string to tokenize.
|
|
34
|
+
* @param options - Parser options.
|
|
35
|
+
* @param options.literalPlus - Whether the LITERAL+ extension is in use.
|
|
36
|
+
* @param options.literals - Pre-parsed literal values from the input stream.
|
|
37
|
+
* @param options.maxLiteralSize - Maximum size (in bytes) of a literal parsed inline
|
|
38
|
+
* from the input, i.e. when no pre-parsed literal buffers were supplied. Defaults to 1GB.
|
|
39
|
+
*/
|
|
40
|
+
constructor(parent, startPos, str, options) {
|
|
41
|
+
this.str = (str || '').toString();
|
|
42
|
+
this.options = options || {};
|
|
43
|
+
this.parent = parent;
|
|
44
|
+
// Same normalization and default as the streaming parser, so a direct user of this parser
|
|
45
|
+
// gets the same bound (an explicit 0 means "reject any non-empty inline literal").
|
|
46
|
+
this.maxLiteralSize = (0, limits_js_1.normalizeLimit)(this.options.maxLiteralSize, limits_js_1.MAX_LITERAL_SIZE);
|
|
47
|
+
this.tree = this.currentNode = this.createNode();
|
|
48
|
+
this.pos = startPos || 0;
|
|
49
|
+
this.currentNode.type = 'TREE';
|
|
50
|
+
this.state = STATE_NORMAL;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Processes the input string and returns the parsed attributes as a flat array of typed objects.
|
|
54
|
+
* Each attribute is an object with a `type` (e.g., "ATOM", "STRING", "LITERAL", "SEQUENCE")
|
|
55
|
+
* and a `value` property. Lists are represented as nested arrays. Sections and partials are
|
|
56
|
+
* attached as properties on the preceding attribute object.
|
|
57
|
+
*
|
|
58
|
+
* @returns A promise that resolves to an array of parsed attribute objects and nested arrays.
|
|
59
|
+
* @throws {Error} If the input contains syntax errors or unclosed nodes.
|
|
60
|
+
*/
|
|
61
|
+
async getAttributes() {
|
|
62
|
+
await this.processString();
|
|
63
|
+
const attributes = [];
|
|
64
|
+
let branch = attributes;
|
|
65
|
+
let walk = async (node) => {
|
|
66
|
+
let curBranch = branch;
|
|
67
|
+
let elm;
|
|
68
|
+
let partial;
|
|
69
|
+
if (!node.isClosed && node.type === 'SEQUENCE' && node.value === '*') {
|
|
70
|
+
node.isClosed = true;
|
|
71
|
+
node.type = 'ATOM';
|
|
72
|
+
}
|
|
73
|
+
// If the node was never closed, throw it
|
|
74
|
+
if (!node.isClosed) {
|
|
75
|
+
let error = new Error(`Unexpected end of input at position ${this.pos + this.str.length - 1} [E9]`);
|
|
76
|
+
error.code = 'ParserError9';
|
|
77
|
+
error.parserContext = { input: this.str, pos: this.pos + this.str.length - 1 };
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
let type = (node.type || '').toString().toUpperCase();
|
|
81
|
+
switch (type) {
|
|
82
|
+
case 'LITERAL':
|
|
83
|
+
case 'STRING':
|
|
84
|
+
case 'SEQUENCE':
|
|
85
|
+
elm = {
|
|
86
|
+
type: node.type.toUpperCase(),
|
|
87
|
+
value: node.value
|
|
88
|
+
};
|
|
89
|
+
branch.push(elm);
|
|
90
|
+
break;
|
|
91
|
+
case 'ATOM':
|
|
92
|
+
if (node.value.toUpperCase() === 'NIL') {
|
|
93
|
+
branch.push(null);
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
elm = {
|
|
97
|
+
type: node.type.toUpperCase(),
|
|
98
|
+
value: node.value
|
|
99
|
+
};
|
|
100
|
+
branch.push(elm);
|
|
101
|
+
break;
|
|
102
|
+
case 'SECTION':
|
|
103
|
+
branch = branch[branch.length - 1].section = [];
|
|
104
|
+
break;
|
|
105
|
+
case 'LIST':
|
|
106
|
+
elm = [];
|
|
107
|
+
branch.push(elm);
|
|
108
|
+
branch = elm;
|
|
109
|
+
break;
|
|
110
|
+
case 'PARTIAL':
|
|
111
|
+
partial = node.value.split('.').map(Number);
|
|
112
|
+
branch[branch.length - 1].partial = partial;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
for (let childNode of node.childNodes) {
|
|
116
|
+
await walk(childNode);
|
|
117
|
+
}
|
|
118
|
+
branch = curBranch;
|
|
119
|
+
};
|
|
120
|
+
await walk(this.tree);
|
|
121
|
+
return attributes;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Creates a new node in the parse tree. Each node represents a token or structural
|
|
125
|
+
* element (e.g., atom, string, literal, list, section, partial). The node is automatically
|
|
126
|
+
* appended to the parent's childNodes array if a parent is provided.
|
|
127
|
+
*
|
|
128
|
+
* @param parentNode - The parent node to attach this node to. If omitted, creates a root node.
|
|
129
|
+
* @param startPos - The starting position of this node in the original input string.
|
|
130
|
+
* @returns The newly created node with childNodes, type, value, and isClosed properties.
|
|
131
|
+
* @throws {Error} If the nesting depth exceeds MAX_NODE_DEPTH.
|
|
132
|
+
*/
|
|
133
|
+
createNode(parentNode, startPos) {
|
|
134
|
+
let node = {
|
|
135
|
+
childNodes: [],
|
|
136
|
+
type: false,
|
|
137
|
+
value: '',
|
|
138
|
+
isClosed: true,
|
|
139
|
+
depth: 0
|
|
140
|
+
};
|
|
141
|
+
if (parentNode) {
|
|
142
|
+
node.parentNode = parentNode;
|
|
143
|
+
node.depth = parentNode.depth + 1;
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
node.depth = 0;
|
|
147
|
+
}
|
|
148
|
+
if (node.depth > MAX_NODE_DEPTH) {
|
|
149
|
+
let error = new Error('Too much nesting in IMAP string');
|
|
150
|
+
error.code = 'MAX_IMAP_NESTING_REACHED';
|
|
151
|
+
error._imapStr = this.str;
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
if (typeof startPos === 'number') {
|
|
155
|
+
node.startPos = startPos;
|
|
156
|
+
}
|
|
157
|
+
if (parentNode) {
|
|
158
|
+
parentNode.childNodes.push(node);
|
|
159
|
+
}
|
|
160
|
+
return node;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Processes the entire input string character by character using a state machine.
|
|
164
|
+
* Transitions between states (NORMAL, ATOM, STRING, LITERAL, SEQUENCE, PARTIAL, TEXT)
|
|
165
|
+
* based on the current character and builds the parse tree. This is the main parsing
|
|
166
|
+
* loop that drives the tokenization.
|
|
167
|
+
*
|
|
168
|
+
* @throws {Error} If the input contains unexpected characters, unclosed structures, or other syntax errors.
|
|
169
|
+
*/
|
|
170
|
+
async processString() {
|
|
171
|
+
let chr, i, len;
|
|
172
|
+
const checkSP = () => {
|
|
173
|
+
// jump to the next non whitespace pos
|
|
174
|
+
while (this.str.charAt(i + 1) === ' ') {
|
|
175
|
+
i++;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
// Any ATOM supported char starts a new Atom sequence, otherwise throw an error
|
|
179
|
+
// Allow \ as the first char for atom to support system flags
|
|
180
|
+
// Allow % to support LIST '' %
|
|
181
|
+
// Allow 8bit characters (presumably unicode)
|
|
182
|
+
// Shared by the default branch and by a '[' that is not a response code section, which
|
|
183
|
+
// used to fall through into the default branch
|
|
184
|
+
const startAtom = () => {
|
|
185
|
+
if (!imap_formal_syntax_js_1.default['ATOM-CHAR']().includes(chr) && chr !== '\\' && chr !== '%' && chr.charCodeAt(0) < 0x80) {
|
|
186
|
+
let error = new Error(`Unexpected char at position ${this.pos + i} [E13: ${JSON.stringify(chr)}]`);
|
|
187
|
+
error.code = 'ParserError13';
|
|
188
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
192
|
+
this.currentNode.type = 'ATOM';
|
|
193
|
+
this.currentNode.value = chr;
|
|
194
|
+
this.state = STATE_ATOM;
|
|
195
|
+
};
|
|
196
|
+
for (i = 0, len = this.str.length; i < len; i++) {
|
|
197
|
+
chr = this.str.charAt(i);
|
|
198
|
+
switch (this.state) {
|
|
199
|
+
case STATE_NORMAL:
|
|
200
|
+
switch (chr) {
|
|
201
|
+
// DQUOTE starts a new string
|
|
202
|
+
case '"':
|
|
203
|
+
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
204
|
+
this.currentNode.type = 'string';
|
|
205
|
+
this.state = STATE_STRING;
|
|
206
|
+
this.currentNode.isClosed = false;
|
|
207
|
+
break;
|
|
208
|
+
// ( starts a new list
|
|
209
|
+
case '(':
|
|
210
|
+
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
211
|
+
this.currentNode.type = 'LIST';
|
|
212
|
+
this.currentNode.isClosed = false;
|
|
213
|
+
break;
|
|
214
|
+
// ) closes a list
|
|
215
|
+
case ')':
|
|
216
|
+
if (this.currentNode.type !== 'LIST') {
|
|
217
|
+
let error = new Error(`Unexpected list terminator ) at position ${this.pos + i} [E10]`);
|
|
218
|
+
error.code = 'ParserError10';
|
|
219
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
this.currentNode.isClosed = true;
|
|
223
|
+
this.currentNode.endPos = this.pos + i;
|
|
224
|
+
this.currentNode = this.currentNode.parentNode;
|
|
225
|
+
checkSP();
|
|
226
|
+
break;
|
|
227
|
+
// ] closes section group
|
|
228
|
+
case ']':
|
|
229
|
+
if (this.currentNode.type !== 'SECTION') {
|
|
230
|
+
let error = new Error(`Unexpected section terminator ] at position ${this.pos + i} [E11]`);
|
|
231
|
+
error.code = 'ParserError11';
|
|
232
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
this.currentNode.isClosed = true;
|
|
236
|
+
this.currentNode.endPos = this.pos + i;
|
|
237
|
+
this.currentNode = this.currentNode.parentNode;
|
|
238
|
+
checkSP();
|
|
239
|
+
break;
|
|
240
|
+
// < starts a new partial byte range (e.g., BODY[]<0.1024>)
|
|
241
|
+
case '<':
|
|
242
|
+
// '<' is only a partial range marker when it immediately follows ']',
|
|
243
|
+
// which occurs in BODY[section]<origin.length> responses.
|
|
244
|
+
// In all other contexts, '<' is treated as the start of an ATOM.
|
|
245
|
+
if (this.str.charAt(i - 1) !== ']') {
|
|
246
|
+
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
247
|
+
this.currentNode.type = 'ATOM';
|
|
248
|
+
this.currentNode.value = chr;
|
|
249
|
+
this.state = STATE_ATOM;
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
253
|
+
this.currentNode.type = 'PARTIAL';
|
|
254
|
+
this.state = STATE_PARTIAL;
|
|
255
|
+
this.currentNode.isClosed = false;
|
|
256
|
+
}
|
|
257
|
+
break;
|
|
258
|
+
// literal8 (RFC 3516): uses ~{size} prefix instead of {size}
|
|
259
|
+
// literal8 allows binary data containing NUL bytes, unlike regular literals
|
|
260
|
+
case '~': {
|
|
261
|
+
let nextChr = this.str.charAt(i + 1);
|
|
262
|
+
if (nextChr !== '{') {
|
|
263
|
+
if (imap_formal_syntax_js_1.default['ATOM-CHAR']().includes(nextChr)) {
|
|
264
|
+
// '~' not followed by '{' but followed by an ATOM char: treat as ATOM
|
|
265
|
+
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
266
|
+
this.currentNode.type = 'ATOM';
|
|
267
|
+
this.currentNode.value = chr;
|
|
268
|
+
this.state = STATE_ATOM;
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
let error = new Error(`Unexpected literal8 marker at position ${this.pos + i} [E12]`);
|
|
272
|
+
error.code = 'ParserError12';
|
|
273
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
274
|
+
throw error;
|
|
275
|
+
}
|
|
276
|
+
// Mark the next literal as literal8 type; consumed when '{' is encountered
|
|
277
|
+
this.expectedLiteralType = 'literal8';
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
// { starts a new literal (regular {size}\r\n or literal8 ~{size}\r\n)
|
|
281
|
+
case '{':
|
|
282
|
+
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
283
|
+
this.currentNode.type = 'LITERAL';
|
|
284
|
+
// Use literal8 type if '~' was seen immediately before, otherwise standard literal
|
|
285
|
+
this.currentNode.literalType = this.expectedLiteralType || 'literal';
|
|
286
|
+
this.expectedLiteralType = false;
|
|
287
|
+
this.state = STATE_LITERAL;
|
|
288
|
+
this.currentNode.isClosed = false;
|
|
289
|
+
break;
|
|
290
|
+
// * starts a new sequence
|
|
291
|
+
case '*':
|
|
292
|
+
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
293
|
+
this.currentNode.type = 'SEQUENCE';
|
|
294
|
+
this.currentNode.value = chr;
|
|
295
|
+
this.currentNode.isClosed = false;
|
|
296
|
+
this.state = STATE_SEQUENCE;
|
|
297
|
+
break;
|
|
298
|
+
// normally a space should never occur
|
|
299
|
+
case ' ':
|
|
300
|
+
// just ignore
|
|
301
|
+
break;
|
|
302
|
+
// [ starts section
|
|
303
|
+
case '[':
|
|
304
|
+
// If it is the *first* element after response command, then process as a response argument list
|
|
305
|
+
// Status responses (OK/NO/BAD/BYE/PREAUTH) use [code] for response codes
|
|
306
|
+
if (['OK', 'NO', 'BAD', 'BYE', 'PREAUTH'].includes(this.parent.command.toUpperCase()) &&
|
|
307
|
+
this.currentNode === this.tree) {
|
|
308
|
+
this.currentNode.endPos = this.pos + i;
|
|
309
|
+
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
310
|
+
this.currentNode.type = 'ATOM';
|
|
311
|
+
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
312
|
+
this.currentNode.type = 'SECTION';
|
|
313
|
+
this.currentNode.isClosed = false;
|
|
314
|
+
this.state = STATE_NORMAL;
|
|
315
|
+
// RFC 2221 REFERRAL special case: the payload is an RFC 2192/RFC 5092
|
|
316
|
+
// IMAP URL (e.g., imap://user@host/mailbox) which contains characters
|
|
317
|
+
// that would break normal ATOM parsing (colons, slashes, etc.).
|
|
318
|
+
// We handle this by consuming everything up to ']' as a single ATOM value.
|
|
319
|
+
if (this.str.substr(i + 1, 9).toUpperCase() === 'REFERRAL ') {
|
|
320
|
+
// create the REFERRAL atom
|
|
321
|
+
this.currentNode = this.createNode(this.currentNode, this.pos + i + 1);
|
|
322
|
+
this.currentNode.type = 'ATOM';
|
|
323
|
+
this.currentNode.endPos = this.pos + i + 8;
|
|
324
|
+
this.currentNode.value = 'REFERRAL';
|
|
325
|
+
this.currentNode = this.currentNode.parentNode;
|
|
326
|
+
// eat all the way through the ] to be the IMAPURL token.
|
|
327
|
+
this.currentNode = this.createNode(this.currentNode, this.pos + i + 10);
|
|
328
|
+
// just call this an ATOM, even though IMAPURL might be more correct
|
|
329
|
+
this.currentNode.type = 'ATOM';
|
|
330
|
+
// jump i to the ']'
|
|
331
|
+
i = this.str.indexOf(']', i + 10);
|
|
332
|
+
if (i < 0) {
|
|
333
|
+
// Malformed REFERRAL with no closing ']'. Consume the rest
|
|
334
|
+
// of the string (there is no ']' to exclude) instead of
|
|
335
|
+
// computing a negative-index substring, which would yield
|
|
336
|
+
// garbage.
|
|
337
|
+
i = this.str.length;
|
|
338
|
+
}
|
|
339
|
+
this.currentNode.endPos = this.pos + i - 1;
|
|
340
|
+
this.currentNode.value = this.str.substring(this.currentNode.startPos - this.pos, this.currentNode.endPos - this.pos + 1);
|
|
341
|
+
this.currentNode = this.currentNode.parentNode;
|
|
342
|
+
// close out the SECTION
|
|
343
|
+
this.currentNode.isClosed = true;
|
|
344
|
+
this.currentNode = this.currentNode.parentNode;
|
|
345
|
+
checkSP();
|
|
346
|
+
}
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
// Not a response code section: handled like any other atom start
|
|
350
|
+
startAtom();
|
|
351
|
+
break;
|
|
352
|
+
default:
|
|
353
|
+
startAtom();
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
break;
|
|
357
|
+
case STATE_ATOM:
|
|
358
|
+
// An atom is terminated by: space, closing delimiter of parent node,
|
|
359
|
+
// or encountering a '[' that starts a section for BODY/BINARY commands.
|
|
360
|
+
// space finishes an atom
|
|
361
|
+
if (chr === ' ') {
|
|
362
|
+
this.currentNode.endPos = this.pos + i - 1;
|
|
363
|
+
this.currentNode = this.currentNode.parentNode;
|
|
364
|
+
this.state = STATE_NORMAL;
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
// ')' or ']' terminates the atom AND closes the enclosing LIST or SECTION
|
|
368
|
+
if (this.currentNode.parentNode &&
|
|
369
|
+
((chr === ')' && this.currentNode.parentNode.type === 'LIST') || (chr === ']' && this.currentNode.parentNode.type === 'SECTION'))) {
|
|
370
|
+
this.currentNode.endPos = this.pos + i - 1;
|
|
371
|
+
this.currentNode = this.currentNode.parentNode;
|
|
372
|
+
this.currentNode.isClosed = true;
|
|
373
|
+
this.currentNode.endPos = this.pos + i;
|
|
374
|
+
this.currentNode = this.currentNode.parentNode;
|
|
375
|
+
this.state = STATE_NORMAL;
|
|
376
|
+
checkSP();
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
// If the atom so far is all digits and we see ',' or ':', it is actually
|
|
380
|
+
// a sequence set (e.g., "1:5" or "1,3,5"), so reclassify and switch state
|
|
381
|
+
if ((chr === ',' || chr === ':') && RE_DIGITS.test(this.currentNode.value)) {
|
|
382
|
+
this.currentNode.type = 'SEQUENCE';
|
|
383
|
+
this.currentNode.isClosed = true;
|
|
384
|
+
this.state = STATE_SEQUENCE;
|
|
385
|
+
}
|
|
386
|
+
// [ starts a section group for this element
|
|
387
|
+
// Allowed only for selected elements, otherwise falls through to regular ATOM processing
|
|
388
|
+
if (chr === '[' && ['BODY', 'BODY.PEEK', 'BINARY', 'BINARY.PEEK'].includes(this.currentNode.value.toUpperCase())) {
|
|
389
|
+
this.currentNode.endPos = this.pos + i;
|
|
390
|
+
this.currentNode = this.createNode(this.currentNode.parentNode, this.pos + i);
|
|
391
|
+
this.currentNode.type = 'SECTION';
|
|
392
|
+
this.currentNode.isClosed = false;
|
|
393
|
+
this.state = STATE_NORMAL;
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
// if the char is not ATOM compatible, throw. Allow \* as an exception
|
|
397
|
+
if (!imap_formal_syntax_js_1.default['ATOM-CHAR']().includes(chr) &&
|
|
398
|
+
chr.charCodeAt(0) < 0x80 && // allow 8bit (presumably unicode) bytes
|
|
399
|
+
chr !== ']' &&
|
|
400
|
+
!(chr === '*' && this.currentNode.value === '\\') &&
|
|
401
|
+
(!this.parent || !this.parent.command || !['NO', 'BAD', 'OK'].includes(this.parent.command))) {
|
|
402
|
+
let error = new Error(`Unexpected char at position ${this.pos + i} [E16: ${JSON.stringify(chr)}]`);
|
|
403
|
+
error.code = 'ParserError16';
|
|
404
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
407
|
+
else if (this.currentNode.value === '\\*') {
|
|
408
|
+
let error = new Error(`Unexpected char at position ${this.pos + i} [E17: ${JSON.stringify(chr)}]`);
|
|
409
|
+
error.code = 'ParserError17';
|
|
410
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
this.currentNode.value += chr;
|
|
414
|
+
break;
|
|
415
|
+
case STATE_STRING:
|
|
416
|
+
// DQUOTE ends the string sequence
|
|
417
|
+
if (chr === '"') {
|
|
418
|
+
this.currentNode.endPos = this.pos + i;
|
|
419
|
+
this.currentNode.isClosed = true;
|
|
420
|
+
this.currentNode = this.currentNode.parentNode;
|
|
421
|
+
this.state = STATE_NORMAL;
|
|
422
|
+
checkSP();
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
// \ Escapes the following char
|
|
426
|
+
if (chr === '\\') {
|
|
427
|
+
i++;
|
|
428
|
+
if (i >= len) {
|
|
429
|
+
let error = new Error(`Unexpected end of input at position ${this.pos + i} [E18]`);
|
|
430
|
+
error.code = 'ParserError18';
|
|
431
|
+
error.parserContext = { input: this.str, pos: this.pos + i };
|
|
432
|
+
throw error;
|
|
433
|
+
}
|
|
434
|
+
chr = this.str.charAt(i);
|
|
435
|
+
}
|
|
436
|
+
this.currentNode.value += chr;
|
|
437
|
+
break;
|
|
438
|
+
case STATE_PARTIAL:
|
|
439
|
+
if (chr === '>') {
|
|
440
|
+
if (this.currentNode.value.at(-1) === '.') {
|
|
441
|
+
let error = new Error(`Unexpected end of partial at position ${this.pos + i} [E19]`);
|
|
442
|
+
error.code = 'ParserError19';
|
|
443
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
444
|
+
throw error;
|
|
445
|
+
}
|
|
446
|
+
this.currentNode.endPos = this.pos + i;
|
|
447
|
+
this.currentNode.isClosed = true;
|
|
448
|
+
this.currentNode = this.currentNode.parentNode;
|
|
449
|
+
this.state = STATE_NORMAL;
|
|
450
|
+
checkSP();
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
if (chr === '.' && (this.currentNode.value === '' || this.currentNode.value.includes('.'))) {
|
|
454
|
+
let error = new Error(`Unexpected partial separator . at position ${this.pos + i} [E20]`);
|
|
455
|
+
error.code = 'ParserError20';
|
|
456
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
457
|
+
throw error;
|
|
458
|
+
}
|
|
459
|
+
if (!imap_formal_syntax_js_1.default.DIGIT().includes(chr) && chr !== '.') {
|
|
460
|
+
let error = new Error(`Unexpected char at position ${this.pos + i} [E21: ${JSON.stringify(chr)}]`);
|
|
461
|
+
error.code = 'ParserError21';
|
|
462
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
463
|
+
throw error;
|
|
464
|
+
}
|
|
465
|
+
if ((this.currentNode.value === '0' || this.currentNode.value.endsWith('.0')) && chr !== '.') {
|
|
466
|
+
let error = new Error(`Invalid partial at position ${this.pos + i} [E22: ${JSON.stringify(chr)}]`);
|
|
467
|
+
error.code = 'ParserError22';
|
|
468
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
469
|
+
throw error;
|
|
470
|
+
}
|
|
471
|
+
this.currentNode.value += chr;
|
|
472
|
+
break;
|
|
473
|
+
case STATE_LITERAL:
|
|
474
|
+
if (this.currentNode.started) {
|
|
475
|
+
// only relevant if literals are not already parsed out from input
|
|
476
|
+
// Disabled NULL byte check
|
|
477
|
+
// See https://github.com/emailjs/emailjs-imap-handler/commit/f11b2822bedabe492236e8263afc630134a3c41c
|
|
478
|
+
/*
|
|
479
|
+
if (chr === '\u0000') {
|
|
480
|
+
throw new Error('Unexpected \\x00 at position ' + (this.pos + i));
|
|
481
|
+
}
|
|
482
|
+
*/
|
|
483
|
+
this.currentNode.chBuffer[this.currentNode.chPos++] = chr.charCodeAt(0);
|
|
484
|
+
if (this.currentNode.chPos >= this.currentNode.literalLength) {
|
|
485
|
+
this.currentNode.endPos = this.pos + i;
|
|
486
|
+
this.currentNode.isClosed = true;
|
|
487
|
+
this.currentNode.value = this.currentNode.chBuffer.toString('binary');
|
|
488
|
+
this.currentNode.chBuffer = Buffer.alloc(0);
|
|
489
|
+
this.currentNode = this.currentNode.parentNode;
|
|
490
|
+
this.state = STATE_NORMAL;
|
|
491
|
+
checkSP();
|
|
492
|
+
}
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
if (chr === '+' && this.options.literalPlus) {
|
|
496
|
+
this.currentNode.literalPlus = true;
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
if (chr === '}') {
|
|
500
|
+
if (!('literalLength' in this.currentNode)) {
|
|
501
|
+
let error = new Error(`Unexpected literal prefix end char } at position ${this.pos + i} [E23]`);
|
|
502
|
+
error.code = 'ParserError23';
|
|
503
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
504
|
+
throw error;
|
|
505
|
+
}
|
|
506
|
+
if (this.str.charAt(i + 1) === '\n') {
|
|
507
|
+
i++;
|
|
508
|
+
}
|
|
509
|
+
else if (this.str.charAt(i + 1) === '\r' && this.str.charAt(i + 2) === '\n') {
|
|
510
|
+
i += 2;
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
let error = new Error(`Unexpected char at position ${this.pos + i} [E24: ${JSON.stringify(chr)}]`);
|
|
514
|
+
error.code = 'ParserError24';
|
|
515
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
516
|
+
throw error;
|
|
517
|
+
}
|
|
518
|
+
this.currentNode.literalLength = Number(this.currentNode.literalLength);
|
|
519
|
+
if (!this.currentNode.literalLength) {
|
|
520
|
+
// special case where literal content length is 0
|
|
521
|
+
// close the node right away, do not wait for additional input
|
|
522
|
+
if (this.options.literals && this.options.literals.length) {
|
|
523
|
+
// ImapStream queues a Buffer for every literal marker it
|
|
524
|
+
// extracts, including {0} - consume the queue entry so
|
|
525
|
+
// subsequent literals in the same response stay aligned
|
|
526
|
+
// with their markers instead of shifting by one
|
|
527
|
+
this.currentNode.value = this.options.literals.shift();
|
|
528
|
+
}
|
|
529
|
+
this.currentNode.endPos = this.pos + i;
|
|
530
|
+
this.currentNode.isClosed = true;
|
|
531
|
+
this.currentNode = this.currentNode.parentNode;
|
|
532
|
+
this.state = STATE_NORMAL;
|
|
533
|
+
checkSP();
|
|
534
|
+
}
|
|
535
|
+
else if (this.options.literals) {
|
|
536
|
+
// use the next precached literal values
|
|
537
|
+
this.currentNode.value = this.options.literals.shift();
|
|
538
|
+
// only APPEND arguments are kept as Buffers
|
|
539
|
+
/*
|
|
540
|
+
if ((this.parent.command || '').toString().toUpperCase() !== 'APPEND') {
|
|
541
|
+
this.currentNode.value = this.currentNode.value.toString('binary');
|
|
542
|
+
}
|
|
543
|
+
*/
|
|
544
|
+
this.currentNode.endPos = this.pos + i + this.currentNode.value.length;
|
|
545
|
+
this.currentNode.started = false;
|
|
546
|
+
this.currentNode.isClosed = true;
|
|
547
|
+
this.currentNode = this.currentNode.parentNode;
|
|
548
|
+
this.state = STATE_NORMAL;
|
|
549
|
+
checkSP();
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
// No pre-parsed literal buffers were supplied, so the literal is read
|
|
553
|
+
// inline from this input and its declared length decides an
|
|
554
|
+
// allocation. ImapStream always supplies buffers (and has already
|
|
555
|
+
// enforced its own cap), so this branch means the parser is being used
|
|
556
|
+
// directly and the declared length is untrusted: bound it before
|
|
557
|
+
// allocating anything.
|
|
558
|
+
// Two bounds apply: the configured maximum, and the bytes actually
|
|
559
|
+
// available here - an inline literal has to be present in the input
|
|
560
|
+
// being parsed, so a longer declaration can never be satisfied and
|
|
561
|
+
// must not reserve memory for itself.
|
|
562
|
+
let available = this.str.length - i - 1;
|
|
563
|
+
let literalLength = this.currentNode.literalLength;
|
|
564
|
+
if (literalLength > this.maxLiteralSize || literalLength > available) {
|
|
565
|
+
let overMax = literalLength > this.maxLiteralSize;
|
|
566
|
+
let error = (0, limits_js_1.createLiteralTooLargeError)(literalLength, overMax ? this.maxLiteralSize : available, overMax ? null : `the ${available} bytes available in the input`);
|
|
567
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
568
|
+
throw error;
|
|
569
|
+
}
|
|
570
|
+
this.currentNode.started = true;
|
|
571
|
+
// Allocate expected size buffer.
|
|
572
|
+
// Maybe should use allocUnsafe instead?
|
|
573
|
+
this.currentNode.chBuffer = Buffer.alloc(this.currentNode.literalLength);
|
|
574
|
+
this.currentNode.chPos = 0;
|
|
575
|
+
}
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
if (!imap_formal_syntax_js_1.default.DIGIT().includes(chr)) {
|
|
579
|
+
let error = new Error(`Unexpected char at position ${this.pos + i} [E25: ${JSON.stringify(chr)}]`);
|
|
580
|
+
error.code = 'ParserError25';
|
|
581
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
582
|
+
throw error;
|
|
583
|
+
}
|
|
584
|
+
if (this.currentNode.literalLength === '0') {
|
|
585
|
+
let error = new Error(`Invalid literal at position ${this.pos + i} [E26]`);
|
|
586
|
+
error.code = 'ParserError26';
|
|
587
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
588
|
+
throw error;
|
|
589
|
+
}
|
|
590
|
+
this.currentNode.literalLength = (this.currentNode.literalLength || '') + chr;
|
|
591
|
+
break;
|
|
592
|
+
case STATE_SEQUENCE:
|
|
593
|
+
// space finishes the sequence set
|
|
594
|
+
if (chr === ' ') {
|
|
595
|
+
if (!RE_SINGLE_DIGIT.test(this.currentNode.value.at(-1)) && this.currentNode.value.at(-1) !== '*') {
|
|
596
|
+
let error = new Error(`Unexpected whitespace at position ${this.pos + i} [E27]`);
|
|
597
|
+
error.code = 'ParserError27';
|
|
598
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
599
|
+
throw error;
|
|
600
|
+
}
|
|
601
|
+
if (this.currentNode.value !== '*' &&
|
|
602
|
+
this.currentNode.value.at(-1) === '*' &&
|
|
603
|
+
this.currentNode.value.at(-2) !== ':') {
|
|
604
|
+
let error = new Error(`Unexpected whitespace at position ${this.pos + i} [E28]`);
|
|
605
|
+
error.code = 'ParserError28';
|
|
606
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
607
|
+
throw error;
|
|
608
|
+
}
|
|
609
|
+
this.currentNode.isClosed = true;
|
|
610
|
+
this.currentNode.endPos = this.pos + i - 1;
|
|
611
|
+
this.currentNode = this.currentNode.parentNode;
|
|
612
|
+
this.state = STATE_NORMAL;
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
else if (this.currentNode.parentNode && chr === ']' && this.currentNode.parentNode.type === 'SECTION') {
|
|
616
|
+
this.currentNode.endPos = this.pos + i - 1;
|
|
617
|
+
this.currentNode = this.currentNode.parentNode;
|
|
618
|
+
this.currentNode.isClosed = true;
|
|
619
|
+
this.currentNode.endPos = this.pos + i;
|
|
620
|
+
this.currentNode = this.currentNode.parentNode;
|
|
621
|
+
this.state = STATE_NORMAL;
|
|
622
|
+
checkSP();
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
if (chr === ':') {
|
|
626
|
+
if (!RE_SINGLE_DIGIT.test(this.currentNode.value.at(-1)) && this.currentNode.value.at(-1) !== '*') {
|
|
627
|
+
let error = new Error(`Unexpected range separator : at position ${this.pos + i} [E29]`);
|
|
628
|
+
error.code = 'ParserError29';
|
|
629
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
630
|
+
throw error;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
else if (chr === '*') {
|
|
634
|
+
if (![',', ':'].includes(this.currentNode.value.at(-1))) {
|
|
635
|
+
let error = new Error(`Unexpected range wildcard at position ${this.pos + i} [E30]`);
|
|
636
|
+
error.code = 'ParserError30';
|
|
637
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
638
|
+
throw error;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
else if (chr === ',') {
|
|
642
|
+
if (!RE_SINGLE_DIGIT.test(this.currentNode.value.at(-1)) && this.currentNode.value.at(-1) !== '*') {
|
|
643
|
+
let error = new Error(`Unexpected sequence separator , at position ${this.pos + i} [E31]`);
|
|
644
|
+
error.code = 'ParserError31';
|
|
645
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
646
|
+
throw error;
|
|
647
|
+
}
|
|
648
|
+
if (this.currentNode.value.at(-1) === '*' && this.currentNode.value.at(-2) !== ':') {
|
|
649
|
+
let error = new Error(`Unexpected sequence separator , at position ${this.pos + i} [E32]`);
|
|
650
|
+
error.code = 'ParserError32';
|
|
651
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
652
|
+
throw error;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
else if (!RE_SINGLE_DIGIT.test(chr)) {
|
|
656
|
+
let error = new Error(`Unexpected char at position ${this.pos + i} [E33: ${JSON.stringify(chr)}]`);
|
|
657
|
+
error.code = 'ParserError33';
|
|
658
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
659
|
+
throw error;
|
|
660
|
+
}
|
|
661
|
+
if (RE_SINGLE_DIGIT.test(chr) && this.currentNode.value.at(-1) === '*') {
|
|
662
|
+
let error = new Error(`Unexpected number at position ${this.pos + i} [E34: ${JSON.stringify(chr)}]`);
|
|
663
|
+
error.code = 'ParserError34';
|
|
664
|
+
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
665
|
+
throw error;
|
|
666
|
+
}
|
|
667
|
+
this.currentNode.value += chr;
|
|
668
|
+
break;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
exports.TokenParser = TokenParser;
|