imapflow 2.2.5 → 2.2.7
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 +17 -0
- package/dist/cjs/commands/append.js +10 -3
- package/dist/cjs/commands/authenticate.js +27 -18
- package/dist/cjs/commands/close.d.ts +12 -1
- package/dist/cjs/commands/close.js +4 -2
- package/dist/cjs/commands/copyuid-parser.js +16 -2
- package/dist/cjs/commands/delete.js +2 -1
- package/dist/cjs/commands/esearch-parser.js +8 -2
- package/dist/cjs/commands/fetch.js +35 -9
- package/dist/cjs/commands/id.js +8 -1
- package/dist/cjs/commands/idle.js +15 -5
- package/dist/cjs/commands/list.js +10 -1
- package/dist/cjs/commands/login.js +5 -1
- package/dist/cjs/commands/logout.js +7 -0
- package/dist/cjs/commands/namespace.js +7 -3
- package/dist/cjs/commands/quota.js +3 -1
- package/dist/cjs/commands/rename.js +2 -1
- package/dist/cjs/commands/select.js +5 -0
- package/dist/cjs/commands/status.js +6 -1
- package/dist/cjs/commands/store.d.ts +1 -1
- package/dist/cjs/commands/store.js +1 -2
- package/dist/cjs/download.js +82 -91
- package/dist/cjs/handler/imap-compiler.js +45 -29
- package/dist/cjs/handler/limits.d.ts +11 -0
- package/dist/cjs/handler/limits.js +16 -1
- package/dist/cjs/handler/parser-instance.d.ts +10 -0
- package/dist/cjs/handler/parser-instance.js +25 -10
- package/dist/cjs/handler/token-parser.js +162 -78
- package/dist/cjs/imap-flow.js +235 -89
- package/dist/cjs/package-info.d.ts +1 -1
- package/dist/cjs/package-info.js +1 -1
- package/dist/cjs/proxy-connection.js +7 -7
- package/dist/cjs/search-compiler.js +37 -16
- package/dist/cjs/special-use.js +10 -5
- package/dist/cjs/tools.d.ts +10 -1
- package/dist/cjs/tools.js +30 -3
- package/dist/cjs/types.d.ts +19 -4
- package/dist/esm/commands/append.js +11 -4
- package/dist/esm/commands/authenticate.js +28 -19
- package/dist/esm/commands/close.d.ts +12 -1
- package/dist/esm/commands/close.js +5 -3
- package/dist/esm/commands/copyuid-parser.js +16 -2
- package/dist/esm/commands/delete.js +2 -1
- package/dist/esm/commands/esearch-parser.js +8 -2
- package/dist/esm/commands/fetch.js +35 -9
- package/dist/esm/commands/id.js +9 -2
- package/dist/esm/commands/idle.js +16 -6
- package/dist/esm/commands/list.js +10 -1
- package/dist/esm/commands/login.js +6 -2
- package/dist/esm/commands/logout.js +7 -0
- package/dist/esm/commands/namespace.js +7 -3
- package/dist/esm/commands/quota.js +3 -1
- package/dist/esm/commands/rename.js +2 -1
- package/dist/esm/commands/select.js +5 -0
- package/dist/esm/commands/status.js +7 -2
- package/dist/esm/commands/store.d.ts +1 -1
- package/dist/esm/commands/store.js +1 -2
- package/dist/esm/download.js +82 -91
- package/dist/esm/handler/imap-compiler.js +45 -29
- package/dist/esm/handler/limits.d.ts +11 -0
- package/dist/esm/handler/limits.js +14 -0
- package/dist/esm/handler/parser-instance.d.ts +10 -0
- package/dist/esm/handler/parser-instance.js +25 -10
- package/dist/esm/handler/token-parser.js +163 -79
- package/dist/esm/imap-flow.js +235 -89
- package/dist/esm/package-info.d.ts +1 -1
- package/dist/esm/package-info.js +1 -1
- package/dist/esm/proxy-connection.js +7 -7
- package/dist/esm/search-compiler.js +37 -16
- package/dist/esm/special-use.js +10 -5
- package/dist/esm/tools.d.ts +10 -1
- package/dist/esm/tools.js +29 -3
- package/dist/esm/types.d.ts +19 -4
- package/package.json +3 -2
|
@@ -13,7 +13,6 @@ const STATE_NORMAL = 0x003;
|
|
|
13
13
|
const STATE_PARTIAL = 0x004;
|
|
14
14
|
const STATE_SEQUENCE = 0x005;
|
|
15
15
|
const STATE_STRING = 0x006;
|
|
16
|
-
const RE_DIGITS = /^\d+$/;
|
|
17
16
|
const RE_SINGLE_DIGIT = /^\d$/;
|
|
18
17
|
// Prevents stack overflow from maliciously crafted deeply-nested IMAP input (e.g., (((((...))))))
|
|
19
18
|
const MAX_NODE_DEPTH = 25;
|
|
@@ -74,7 +73,7 @@ class TokenParser {
|
|
|
74
73
|
if (!node.isClosed) {
|
|
75
74
|
let error = new Error(`Unexpected end of input at position ${this.pos + this.str.length - 1} [E9]`);
|
|
76
75
|
error.code = 'ParserError9';
|
|
77
|
-
error.parserContext = {
|
|
76
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + this.str.length - 1 };
|
|
78
77
|
throw error;
|
|
79
78
|
}
|
|
80
79
|
let type = (node.type || '').toString().toUpperCase();
|
|
@@ -148,7 +147,8 @@ class TokenParser {
|
|
|
148
147
|
if (node.depth > MAX_NODE_DEPTH) {
|
|
149
148
|
let error = new Error('Too much nesting in IMAP string');
|
|
150
149
|
error.code = 'MAX_IMAP_NESTING_REACHED';
|
|
151
|
-
error
|
|
150
|
+
// A bounded prefix only, the error travels whole into logs and rejections
|
|
151
|
+
error._imapStr = this.str.slice(0, limits_js_1.ERROR_CONTEXT_LENGTH);
|
|
152
152
|
throw error;
|
|
153
153
|
}
|
|
154
154
|
if (typeof startPos === 'number') {
|
|
@@ -175,6 +175,63 @@ class TokenParser {
|
|
|
175
175
|
i++;
|
|
176
176
|
}
|
|
177
177
|
};
|
|
178
|
+
// Whether chr is the closing delimiter of node: ")" ends a LIST, "]" ends a SECTION
|
|
179
|
+
const closesNode = (c, node) => !!node && ((c === ')' && node.type === 'LIST') || (c === ']' && node.type === 'SECTION'));
|
|
180
|
+
// Facts about the ATOM, SEQUENCE or PARTIAL token being read, kept up to date as its
|
|
181
|
+
// characters are appended. Reading them back from the value instead (value.at(-1),
|
|
182
|
+
// value.includes('*'), a regex test) makes V8 flatten the string built with += on every
|
|
183
|
+
// character, which is quadratic in the token length, and the server decides that length:
|
|
184
|
+
// a 600 KB ESEARCH result took seconds of event loop time
|
|
185
|
+
let tokenLength = 0;
|
|
186
|
+
let tokenLast = '';
|
|
187
|
+
let tokenPrev = '';
|
|
188
|
+
let tokenDigitsOnly = true;
|
|
189
|
+
let tokenHasStar = false;
|
|
190
|
+
let tokenHasDot = false;
|
|
191
|
+
const appendToToken = (c) => {
|
|
192
|
+
this.currentNode.value += c;
|
|
193
|
+
tokenLength++;
|
|
194
|
+
tokenPrev = tokenLast;
|
|
195
|
+
tokenLast = c;
|
|
196
|
+
tokenDigitsOnly = tokenDigitsOnly && RE_SINGLE_DIGIT.test(c);
|
|
197
|
+
tokenHasStar = tokenHasStar || c === '*';
|
|
198
|
+
tokenHasDot = tokenHasDot || c === '.';
|
|
199
|
+
};
|
|
200
|
+
// A digit-led token is only a guess at a sequence set. ":" and "," are ATOM-CHARs, so a
|
|
201
|
+
// server sends a mailbox name, keyword or label such as "2024:Q1" or "1,a" unquoted, and
|
|
202
|
+
// rejecting it would drop the whole response line: the mailbox missing from LIST, the
|
|
203
|
+
// message missing from FETCH. When the sequence grammar breaks, such a token is read on as
|
|
204
|
+
// an atom, and the atom rules decide whether the current character is acceptable. A token
|
|
205
|
+
// holding "*" can not be an atom ("*" is not an ATOM-CHAR), so it keeps the sequence errors
|
|
206
|
+
const continueAsAtom = () => {
|
|
207
|
+
this.currentNode.type = 'ATOM';
|
|
208
|
+
this.state = STATE_ATOM;
|
|
209
|
+
// read the current character again, in STATE_ATOM
|
|
210
|
+
i--;
|
|
211
|
+
};
|
|
212
|
+
// Starts the value of the current node as a new token, with its first character if any
|
|
213
|
+
const startToken = (first) => {
|
|
214
|
+
this.currentNode.value = '';
|
|
215
|
+
tokenLength = 0;
|
|
216
|
+
tokenLast = tokenPrev = '';
|
|
217
|
+
tokenDigitsOnly = true;
|
|
218
|
+
tokenHasStar = tokenHasDot = false;
|
|
219
|
+
if (first) {
|
|
220
|
+
appendToToken(first);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
// ImapStream supplies one buffer per literal marker it framed, {0} included. A marker
|
|
224
|
+
// without a buffer means the line and its literals do not match up, so fail with a parser
|
|
225
|
+
// error instead of reading a missing buffer or shifting every later literal by one
|
|
226
|
+
const takeLiteral = (literals) => {
|
|
227
|
+
if (!literals.length) {
|
|
228
|
+
let error = new Error(`Literal without data at position ${this.pos + i} [E35]`);
|
|
229
|
+
error.code = 'ParserError35';
|
|
230
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
return literals.shift();
|
|
234
|
+
};
|
|
178
235
|
// Any ATOM supported char starts a new Atom sequence, otherwise throw an error
|
|
179
236
|
// Allow \ as the first char for atom to support system flags
|
|
180
237
|
// Allow % to support LIST '' %
|
|
@@ -185,12 +242,12 @@ class TokenParser {
|
|
|
185
242
|
if (!imap_formal_syntax_js_1.default['ATOM-CHAR']().includes(chr) && chr !== '\\' && chr !== '%' && chr.charCodeAt(0) < 0x80) {
|
|
186
243
|
let error = new Error(`Unexpected char at position ${this.pos + i} [E13: ${JSON.stringify(chr)}]`);
|
|
187
244
|
error.code = 'ParserError13';
|
|
188
|
-
error.parserContext = {
|
|
245
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
189
246
|
throw error;
|
|
190
247
|
}
|
|
191
248
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
192
249
|
this.currentNode.type = 'ATOM';
|
|
193
|
-
|
|
250
|
+
startToken(chr);
|
|
194
251
|
this.state = STATE_ATOM;
|
|
195
252
|
};
|
|
196
253
|
for (i = 0, len = this.str.length; i < len; i++) {
|
|
@@ -216,7 +273,7 @@ class TokenParser {
|
|
|
216
273
|
if (this.currentNode.type !== 'LIST') {
|
|
217
274
|
let error = new Error(`Unexpected list terminator ) at position ${this.pos + i} [E10]`);
|
|
218
275
|
error.code = 'ParserError10';
|
|
219
|
-
error.parserContext = {
|
|
276
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
220
277
|
throw error;
|
|
221
278
|
}
|
|
222
279
|
this.currentNode.isClosed = true;
|
|
@@ -229,7 +286,7 @@ class TokenParser {
|
|
|
229
286
|
if (this.currentNode.type !== 'SECTION') {
|
|
230
287
|
let error = new Error(`Unexpected section terminator ] at position ${this.pos + i} [E11]`);
|
|
231
288
|
error.code = 'ParserError11';
|
|
232
|
-
error.parserContext = {
|
|
289
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
233
290
|
throw error;
|
|
234
291
|
}
|
|
235
292
|
this.currentNode.isClosed = true;
|
|
@@ -245,12 +302,13 @@ class TokenParser {
|
|
|
245
302
|
if (this.str.charAt(i - 1) !== ']') {
|
|
246
303
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
247
304
|
this.currentNode.type = 'ATOM';
|
|
248
|
-
|
|
305
|
+
startToken(chr);
|
|
249
306
|
this.state = STATE_ATOM;
|
|
250
307
|
}
|
|
251
308
|
else {
|
|
252
309
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
253
310
|
this.currentNode.type = 'PARTIAL';
|
|
311
|
+
startToken();
|
|
254
312
|
this.state = STATE_PARTIAL;
|
|
255
313
|
this.currentNode.isClosed = false;
|
|
256
314
|
}
|
|
@@ -260,17 +318,19 @@ class TokenParser {
|
|
|
260
318
|
case '~': {
|
|
261
319
|
let nextChr = this.str.charAt(i + 1);
|
|
262
320
|
if (nextChr !== '{') {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
321
|
+
// '~' is an ATOM-CHAR itself, so when it does not start a literal8
|
|
322
|
+
// it starts an atom, including the one-character atom "~" that a
|
|
323
|
+
// space, the end of the line or a closing delimiter ends
|
|
324
|
+
if (imap_formal_syntax_js_1.default['ATOM-CHAR']().includes(nextChr) ||
|
|
325
|
+
nextChr === ' ' ||
|
|
326
|
+
nextChr === '' ||
|
|
327
|
+
closesNode(nextChr, this.currentNode)) {
|
|
328
|
+
startAtom();
|
|
269
329
|
break;
|
|
270
330
|
}
|
|
271
331
|
let error = new Error(`Unexpected literal8 marker at position ${this.pos + i} [E12]`);
|
|
272
332
|
error.code = 'ParserError12';
|
|
273
|
-
error.parserContext = {
|
|
333
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
274
334
|
throw error;
|
|
275
335
|
}
|
|
276
336
|
// Mark the next literal as literal8 type; consumed when '{' is encountered
|
|
@@ -291,7 +351,7 @@ class TokenParser {
|
|
|
291
351
|
case '*':
|
|
292
352
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
293
353
|
this.currentNode.type = 'SEQUENCE';
|
|
294
|
-
|
|
354
|
+
startToken(chr);
|
|
295
355
|
this.currentNode.isClosed = false;
|
|
296
356
|
this.state = STATE_SEQUENCE;
|
|
297
357
|
break;
|
|
@@ -372,8 +432,7 @@ class TokenParser {
|
|
|
372
432
|
break;
|
|
373
433
|
}
|
|
374
434
|
// ')' or ']' terminates the atom AND closes the enclosing LIST or SECTION
|
|
375
|
-
if (this.currentNode.parentNode
|
|
376
|
-
((chr === ')' && this.currentNode.parentNode.type === 'LIST') || (chr === ']' && this.currentNode.parentNode.type === 'SECTION'))) {
|
|
435
|
+
if (closesNode(chr, this.currentNode.parentNode)) {
|
|
377
436
|
this.currentNode.endPos = this.pos + i - 1;
|
|
378
437
|
this.currentNode = this.currentNode.parentNode;
|
|
379
438
|
this.currentNode.isClosed = true;
|
|
@@ -409,14 +468,16 @@ class TokenParser {
|
|
|
409
468
|
}
|
|
410
469
|
// If the atom so far is all digits and we see ',' or ':', it is actually
|
|
411
470
|
// a sequence set (e.g., "1:5" or "1,3,5"), so reclassify and switch state
|
|
412
|
-
if ((chr === ',' || chr === ':') &&
|
|
471
|
+
if ((chr === ',' || chr === ':') && tokenLength && tokenDigitsOnly) {
|
|
413
472
|
this.currentNode.type = 'SEQUENCE';
|
|
414
473
|
this.currentNode.isClosed = true;
|
|
415
474
|
this.state = STATE_SEQUENCE;
|
|
416
475
|
}
|
|
417
476
|
// [ starts a section group for this element
|
|
418
477
|
// Allowed only for selected elements, otherwise falls through to regular ATOM processing
|
|
419
|
-
if (chr === '[' &&
|
|
478
|
+
if (chr === '[' &&
|
|
479
|
+
tokenLength <= 11 &&
|
|
480
|
+
['BODY', 'BODY.PEEK', 'BINARY', 'BINARY.PEEK'].includes(this.currentNode.value.toUpperCase())) {
|
|
420
481
|
this.currentNode.endPos = this.pos + i;
|
|
421
482
|
this.currentNode = this.createNode(this.currentNode.parentNode, this.pos + i);
|
|
422
483
|
this.currentNode.type = 'SECTION';
|
|
@@ -429,19 +490,19 @@ class TokenParser {
|
|
|
429
490
|
chr.charCodeAt(0) < 0x80 && // allow 8bit (presumably unicode) bytes
|
|
430
491
|
chr !== ']' &&
|
|
431
492
|
!(chr === '*' && this.currentNode.value === '\\') &&
|
|
432
|
-
(!this.parent || !this.parent.command || !['NO', 'BAD', 'OK'].includes(this.parent.command))) {
|
|
493
|
+
(!this.parent || !this.parent.command || !['NO', 'BAD', 'OK'].includes(this.parent.command.toUpperCase()))) {
|
|
433
494
|
let error = new Error(`Unexpected char at position ${this.pos + i} [E16: ${JSON.stringify(chr)}]`);
|
|
434
495
|
error.code = 'ParserError16';
|
|
435
|
-
error.parserContext = {
|
|
496
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
436
497
|
throw error;
|
|
437
498
|
}
|
|
438
499
|
else if (this.currentNode.value === '\\*') {
|
|
439
500
|
let error = new Error(`Unexpected char at position ${this.pos + i} [E17: ${JSON.stringify(chr)}]`);
|
|
440
501
|
error.code = 'ParserError17';
|
|
441
|
-
error.parserContext = {
|
|
502
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
442
503
|
throw error;
|
|
443
504
|
}
|
|
444
|
-
|
|
505
|
+
appendToToken(chr);
|
|
445
506
|
break;
|
|
446
507
|
case STATE_STRING:
|
|
447
508
|
// DQUOTE ends the string sequence
|
|
@@ -459,7 +520,7 @@ class TokenParser {
|
|
|
459
520
|
if (i >= len) {
|
|
460
521
|
let error = new Error(`Unexpected end of input at position ${this.pos + i} [E18]`);
|
|
461
522
|
error.code = 'ParserError18';
|
|
462
|
-
error.parserContext = {
|
|
523
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i };
|
|
463
524
|
throw error;
|
|
464
525
|
}
|
|
465
526
|
chr = this.str.charAt(i);
|
|
@@ -468,10 +529,10 @@ class TokenParser {
|
|
|
468
529
|
break;
|
|
469
530
|
case STATE_PARTIAL:
|
|
470
531
|
if (chr === '>') {
|
|
471
|
-
if (
|
|
532
|
+
if (tokenLast === '.') {
|
|
472
533
|
let error = new Error(`Unexpected end of partial at position ${this.pos + i} [E19]`);
|
|
473
534
|
error.code = 'ParserError19';
|
|
474
|
-
error.parserContext = {
|
|
535
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
475
536
|
throw error;
|
|
476
537
|
}
|
|
477
538
|
this.currentNode.endPos = this.pos + i;
|
|
@@ -481,25 +542,25 @@ class TokenParser {
|
|
|
481
542
|
checkSP();
|
|
482
543
|
break;
|
|
483
544
|
}
|
|
484
|
-
if (chr === '.' && (
|
|
545
|
+
if (chr === '.' && (!tokenLength || tokenHasDot)) {
|
|
485
546
|
let error = new Error(`Unexpected partial separator . at position ${this.pos + i} [E20]`);
|
|
486
547
|
error.code = 'ParserError20';
|
|
487
|
-
error.parserContext = {
|
|
548
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
488
549
|
throw error;
|
|
489
550
|
}
|
|
490
551
|
if (!imap_formal_syntax_js_1.default.DIGIT().includes(chr) && chr !== '.') {
|
|
491
552
|
let error = new Error(`Unexpected char at position ${this.pos + i} [E21: ${JSON.stringify(chr)}]`);
|
|
492
553
|
error.code = 'ParserError21';
|
|
493
|
-
error.parserContext = {
|
|
554
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
494
555
|
throw error;
|
|
495
556
|
}
|
|
496
|
-
if (
|
|
557
|
+
if (tokenLast === '0' && (tokenLength === 1 || tokenPrev === '.') && chr !== '.') {
|
|
497
558
|
let error = new Error(`Invalid partial at position ${this.pos + i} [E22: ${JSON.stringify(chr)}]`);
|
|
498
559
|
error.code = 'ParserError22';
|
|
499
|
-
error.parserContext = {
|
|
560
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
500
561
|
throw error;
|
|
501
562
|
}
|
|
502
|
-
|
|
563
|
+
appendToToken(chr);
|
|
503
564
|
break;
|
|
504
565
|
case STATE_LITERAL:
|
|
505
566
|
if (this.currentNode.started) {
|
|
@@ -531,7 +592,7 @@ class TokenParser {
|
|
|
531
592
|
if (!('literalLength' in this.currentNode)) {
|
|
532
593
|
let error = new Error(`Unexpected literal prefix end char } at position ${this.pos + i} [E23]`);
|
|
533
594
|
error.code = 'ParserError23';
|
|
534
|
-
error.parserContext = {
|
|
595
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
535
596
|
throw error;
|
|
536
597
|
}
|
|
537
598
|
if (this.str.charAt(i + 1) === '\n') {
|
|
@@ -543,19 +604,17 @@ class TokenParser {
|
|
|
543
604
|
else {
|
|
544
605
|
let error = new Error(`Unexpected char at position ${this.pos + i} [E24: ${JSON.stringify(chr)}]`);
|
|
545
606
|
error.code = 'ParserError24';
|
|
546
|
-
error.parserContext = {
|
|
607
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
547
608
|
throw error;
|
|
548
609
|
}
|
|
549
610
|
this.currentNode.literalLength = Number(this.currentNode.literalLength);
|
|
550
611
|
if (!this.currentNode.literalLength) {
|
|
551
612
|
// special case where literal content length is 0
|
|
552
613
|
// close the node right away, do not wait for additional input
|
|
553
|
-
if (this.options.literals
|
|
554
|
-
//
|
|
555
|
-
//
|
|
556
|
-
|
|
557
|
-
// with their markers instead of shifting by one
|
|
558
|
-
this.currentNode.value = this.options.literals.shift();
|
|
614
|
+
if (this.options.literals) {
|
|
615
|
+
// consume the queue entry of the {0} marker too, so later
|
|
616
|
+
// literals in the same response stay aligned with their markers
|
|
617
|
+
this.currentNode.value = takeLiteral(this.options.literals);
|
|
559
618
|
}
|
|
560
619
|
this.currentNode.endPos = this.pos + i;
|
|
561
620
|
this.currentNode.isClosed = true;
|
|
@@ -565,7 +624,7 @@ class TokenParser {
|
|
|
565
624
|
}
|
|
566
625
|
else if (this.options.literals) {
|
|
567
626
|
// use the next precached literal values
|
|
568
|
-
this.currentNode.value = this.options.literals
|
|
627
|
+
this.currentNode.value = takeLiteral(this.options.literals);
|
|
569
628
|
// only APPEND arguments are kept as Buffers
|
|
570
629
|
/*
|
|
571
630
|
if ((this.parent.command || '').toString().toUpperCase() !== 'APPEND') {
|
|
@@ -595,7 +654,7 @@ class TokenParser {
|
|
|
595
654
|
if (literalLength > this.maxLiteralSize || literalLength > available) {
|
|
596
655
|
let overMax = literalLength > this.maxLiteralSize;
|
|
597
656
|
let error = (0, limits_js_1.createLiteralTooLargeError)(literalLength, overMax ? this.maxLiteralSize : available, overMax ? null : `the ${available} bytes available in the input`);
|
|
598
|
-
error.parserContext = {
|
|
657
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
599
658
|
throw error;
|
|
600
659
|
}
|
|
601
660
|
this.currentNode.started = true;
|
|
@@ -609,96 +668,121 @@ class TokenParser {
|
|
|
609
668
|
if (!imap_formal_syntax_js_1.default.DIGIT().includes(chr)) {
|
|
610
669
|
let error = new Error(`Unexpected char at position ${this.pos + i} [E25: ${JSON.stringify(chr)}]`);
|
|
611
670
|
error.code = 'ParserError25';
|
|
612
|
-
error.parserContext = {
|
|
671
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
613
672
|
throw error;
|
|
614
673
|
}
|
|
615
674
|
if (this.currentNode.literalLength === '0') {
|
|
616
675
|
let error = new Error(`Invalid literal at position ${this.pos + i} [E26]`);
|
|
617
676
|
error.code = 'ParserError26';
|
|
618
|
-
error.parserContext = {
|
|
677
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
619
678
|
throw error;
|
|
620
679
|
}
|
|
621
680
|
this.currentNode.literalLength = (this.currentNode.literalLength || '') + chr;
|
|
622
681
|
break;
|
|
623
|
-
case STATE_SEQUENCE:
|
|
624
|
-
// space
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
682
|
+
case STATE_SEQUENCE: {
|
|
683
|
+
// A space ends the sequence set, and so does the closing delimiter of the
|
|
684
|
+
// enclosing list or section ("PARTIAL (1:100 5,7,9)", "[COPYUID 1 1:3 4,5]")
|
|
685
|
+
let closesParent = closesNode(chr, this.currentNode.parentNode);
|
|
686
|
+
if (chr === ' ' || closesParent) {
|
|
687
|
+
if (!RE_SINGLE_DIGIT.test(tokenLast) && tokenLast !== '*') {
|
|
688
|
+
if (!tokenHasStar) {
|
|
689
|
+
// a dangling separator, "10:" or "1,"
|
|
690
|
+
continueAsAtom();
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
let error = new Error(`Unexpected end of sequence at position ${this.pos + i} [E27: ${JSON.stringify(chr)}]`);
|
|
628
694
|
error.code = 'ParserError27';
|
|
629
|
-
error.parserContext = {
|
|
695
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
630
696
|
throw error;
|
|
631
697
|
}
|
|
632
|
-
if (this.currentNode.value !== '*' &&
|
|
633
|
-
this.
|
|
634
|
-
this.currentNode.value.at(-2) !== ':') {
|
|
635
|
-
let error = new Error(`Unexpected whitespace at position ${this.pos + i} [E28]`);
|
|
698
|
+
if (this.currentNode.value !== '*' && tokenLast === '*' && tokenPrev !== ':') {
|
|
699
|
+
let error = new Error(`Unexpected end of sequence at position ${this.pos + i} [E28: ${JSON.stringify(chr)}]`);
|
|
636
700
|
error.code = 'ParserError28';
|
|
637
|
-
error.parserContext = {
|
|
701
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
638
702
|
throw error;
|
|
639
703
|
}
|
|
640
704
|
this.currentNode.isClosed = true;
|
|
641
705
|
this.currentNode.endPos = this.pos + i - 1;
|
|
642
706
|
this.currentNode = this.currentNode.parentNode;
|
|
643
707
|
this.state = STATE_NORMAL;
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
this.currentNode.endPos = this.pos + i;
|
|
651
|
-
this.currentNode = this.currentNode.parentNode;
|
|
652
|
-
this.state = STATE_NORMAL;
|
|
653
|
-
checkSP();
|
|
708
|
+
if (closesParent) {
|
|
709
|
+
this.currentNode.isClosed = true;
|
|
710
|
+
this.currentNode.endPos = this.pos + i;
|
|
711
|
+
this.currentNode = this.currentNode.parentNode;
|
|
712
|
+
checkSP();
|
|
713
|
+
}
|
|
654
714
|
break;
|
|
655
715
|
}
|
|
656
716
|
if (chr === ':') {
|
|
657
|
-
if (!RE_SINGLE_DIGIT.test(
|
|
717
|
+
if (!RE_SINGLE_DIGIT.test(tokenLast) && tokenLast !== '*') {
|
|
718
|
+
if (!tokenHasStar) {
|
|
719
|
+
continueAsAtom();
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
658
722
|
let error = new Error(`Unexpected range separator : at position ${this.pos + i} [E29]`);
|
|
659
723
|
error.code = 'ParserError29';
|
|
660
|
-
error.parserContext = {
|
|
724
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
661
725
|
throw error;
|
|
662
726
|
}
|
|
663
727
|
}
|
|
664
728
|
else if (chr === '*') {
|
|
665
|
-
if (![',', ':'].includes(
|
|
729
|
+
if (![',', ':'].includes(tokenLast)) {
|
|
666
730
|
let error = new Error(`Unexpected range wildcard at position ${this.pos + i} [E30]`);
|
|
667
731
|
error.code = 'ParserError30';
|
|
668
|
-
error.parserContext = {
|
|
732
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
669
733
|
throw error;
|
|
670
734
|
}
|
|
671
735
|
}
|
|
672
736
|
else if (chr === ',') {
|
|
673
|
-
if (!RE_SINGLE_DIGIT.test(
|
|
737
|
+
if (!RE_SINGLE_DIGIT.test(tokenLast) && tokenLast !== '*') {
|
|
738
|
+
if (!tokenHasStar) {
|
|
739
|
+
continueAsAtom();
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
674
742
|
let error = new Error(`Unexpected sequence separator , at position ${this.pos + i} [E31]`);
|
|
675
743
|
error.code = 'ParserError31';
|
|
676
|
-
error.parserContext = {
|
|
744
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
677
745
|
throw error;
|
|
678
746
|
}
|
|
679
|
-
if (
|
|
747
|
+
if (tokenLast === '*' && tokenPrev !== ':') {
|
|
680
748
|
let error = new Error(`Unexpected sequence separator , at position ${this.pos + i} [E32]`);
|
|
681
749
|
error.code = 'ParserError32';
|
|
682
|
-
error.parserContext = {
|
|
750
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
683
751
|
throw error;
|
|
684
752
|
}
|
|
685
753
|
}
|
|
686
754
|
else if (!RE_SINGLE_DIGIT.test(chr)) {
|
|
755
|
+
if (!tokenHasStar) {
|
|
756
|
+
continueAsAtom();
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
687
759
|
let error = new Error(`Unexpected char at position ${this.pos + i} [E33: ${JSON.stringify(chr)}]`);
|
|
688
760
|
error.code = 'ParserError33';
|
|
689
|
-
error.parserContext = {
|
|
761
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
690
762
|
throw error;
|
|
691
763
|
}
|
|
692
|
-
if (RE_SINGLE_DIGIT.test(chr) &&
|
|
764
|
+
if (RE_SINGLE_DIGIT.test(chr) && tokenLast === '*') {
|
|
693
765
|
let error = new Error(`Unexpected number at position ${this.pos + i} [E34: ${JSON.stringify(chr)}]`);
|
|
694
766
|
error.code = 'ParserError34';
|
|
695
|
-
error.parserContext = {
|
|
767
|
+
error.parserContext = { ...(0, limits_js_1.boundedInput)(this.str), pos: this.pos + i, chr };
|
|
696
768
|
throw error;
|
|
697
769
|
}
|
|
698
|
-
|
|
770
|
+
appendToToken(chr);
|
|
699
771
|
break;
|
|
772
|
+
}
|
|
700
773
|
}
|
|
701
774
|
}
|
|
775
|
+
// the same applies to a digit-led token that ends the input on a dangling separator ("10:")
|
|
776
|
+
if (this.state === STATE_SEQUENCE && (tokenLast === ':' || tokenLast === ',') && !tokenHasStar) {
|
|
777
|
+
this.currentNode.type = 'ATOM';
|
|
778
|
+
}
|
|
779
|
+
// A star-led sequence set ("*:4") is only closed by a space or a closing delimiter, so one
|
|
780
|
+
// that ends the input is closed here, under the same rules a space applies to it. A bare "*"
|
|
781
|
+
// is left to getAttributes(), which reads it as an atom
|
|
782
|
+
if (this.state === STATE_SEQUENCE && !this.currentNode.isClosed && (RE_SINGLE_DIGIT.test(tokenLast) || (tokenLast === '*' && tokenPrev === ':'))) {
|
|
783
|
+
this.currentNode.isClosed = true;
|
|
784
|
+
this.currentNode.endPos = this.pos + this.str.length - 1;
|
|
785
|
+
}
|
|
702
786
|
}
|
|
703
787
|
}
|
|
704
788
|
exports.TokenParser = TokenParser;
|