imapflow 1.0.155 → 1.0.156
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 +7 -0
- package/lib/handler/token-parser.js +48 -37
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.156](https://github.com/postalsys/imapflow/compare/v1.0.155...v1.0.156) (2024-03-07)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* micro-optimizations for parser ([3451ad2](https://github.com/postalsys/imapflow/commit/3451ad235532ca1a64a829f9f2f8e77bcef3a17d))
|
|
9
|
+
|
|
3
10
|
## [1.0.155](https://github.com/postalsys/imapflow/compare/v1.0.154...v1.0.155) (2024-03-06)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -4,6 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
const imapFormalSyntax = require('./imap-formal-syntax');
|
|
6
6
|
|
|
7
|
+
const STATE_ATOM = 0x001;
|
|
8
|
+
const STATE_LITERAL = 0x002;
|
|
9
|
+
const STATE_NORMAL = 0x003;
|
|
10
|
+
const STATE_PARTIAL = 0x004;
|
|
11
|
+
const STATE_SEQUENCE = 0x005;
|
|
12
|
+
const STATE_STRING = 0x006;
|
|
13
|
+
const STATE_TEXT = 0x007;
|
|
14
|
+
|
|
15
|
+
const RE_DIGITS = /^\d+$/;
|
|
16
|
+
const RE_SINGLE_DIGIT = /^\d$/;
|
|
17
|
+
|
|
7
18
|
class TokenParser {
|
|
8
19
|
constructor(parent, startPos, str, options) {
|
|
9
20
|
this.str = (str || '').toString();
|
|
@@ -15,7 +26,7 @@ class TokenParser {
|
|
|
15
26
|
|
|
16
27
|
this.currentNode.type = 'TREE';
|
|
17
28
|
|
|
18
|
-
this.state =
|
|
29
|
+
this.state = STATE_NORMAL;
|
|
19
30
|
}
|
|
20
31
|
|
|
21
32
|
async getAttributes() {
|
|
@@ -132,13 +143,13 @@ class TokenParser {
|
|
|
132
143
|
chr = this.str.charAt(i);
|
|
133
144
|
|
|
134
145
|
switch (this.state) {
|
|
135
|
-
case
|
|
146
|
+
case STATE_NORMAL:
|
|
136
147
|
switch (chr) {
|
|
137
148
|
// DQUOTE starts a new string
|
|
138
149
|
case '"':
|
|
139
150
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
140
151
|
this.currentNode.type = 'string';
|
|
141
|
-
this.state =
|
|
152
|
+
this.state = STATE_STRING;
|
|
142
153
|
this.currentNode.isClosed = false;
|
|
143
154
|
break;
|
|
144
155
|
|
|
@@ -186,11 +197,11 @@ class TokenParser {
|
|
|
186
197
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
187
198
|
this.currentNode.type = 'ATOM';
|
|
188
199
|
this.currentNode.value = chr;
|
|
189
|
-
this.state =
|
|
200
|
+
this.state = STATE_ATOM;
|
|
190
201
|
} else {
|
|
191
202
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
192
203
|
this.currentNode.type = 'PARTIAL';
|
|
193
|
-
this.state =
|
|
204
|
+
this.state = STATE_PARTIAL;
|
|
194
205
|
this.currentNode.isClosed = false;
|
|
195
206
|
}
|
|
196
207
|
break;
|
|
@@ -204,7 +215,7 @@ class TokenParser {
|
|
|
204
215
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
205
216
|
this.currentNode.type = 'ATOM';
|
|
206
217
|
this.currentNode.value = chr;
|
|
207
|
-
this.state =
|
|
218
|
+
this.state = STATE_ATOM;
|
|
208
219
|
break;
|
|
209
220
|
}
|
|
210
221
|
|
|
@@ -223,7 +234,7 @@ class TokenParser {
|
|
|
223
234
|
this.currentNode.type = 'LITERAL';
|
|
224
235
|
this.currentNode.literalType = this.expectedLiteralType || 'literal';
|
|
225
236
|
this.expectedLiteralType = false;
|
|
226
|
-
this.state =
|
|
237
|
+
this.state = STATE_LITERAL;
|
|
227
238
|
this.currentNode.isClosed = false;
|
|
228
239
|
break;
|
|
229
240
|
|
|
@@ -233,7 +244,7 @@ class TokenParser {
|
|
|
233
244
|
this.currentNode.type = 'SEQUENCE';
|
|
234
245
|
this.currentNode.value = chr;
|
|
235
246
|
this.currentNode.isClosed = false;
|
|
236
|
-
this.state =
|
|
247
|
+
this.state = STATE_SEQUENCE;
|
|
237
248
|
break;
|
|
238
249
|
|
|
239
250
|
// normally a space should never occur
|
|
@@ -253,7 +264,7 @@ class TokenParser {
|
|
|
253
264
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
254
265
|
this.currentNode.type = 'SECTION';
|
|
255
266
|
this.currentNode.isClosed = false;
|
|
256
|
-
this.state =
|
|
267
|
+
this.state = STATE_NORMAL;
|
|
257
268
|
|
|
258
269
|
// RFC2221 defines a response code REFERRAL whose payload is an
|
|
259
270
|
// RFC2192/RFC5092 imapurl that we will try to parse as an ATOM but
|
|
@@ -303,17 +314,17 @@ class TokenParser {
|
|
|
303
314
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
304
315
|
this.currentNode.type = 'ATOM';
|
|
305
316
|
this.currentNode.value = chr;
|
|
306
|
-
this.state =
|
|
317
|
+
this.state = STATE_ATOM;
|
|
307
318
|
break;
|
|
308
319
|
}
|
|
309
320
|
break;
|
|
310
321
|
|
|
311
|
-
case
|
|
322
|
+
case STATE_ATOM:
|
|
312
323
|
// space finishes an atom
|
|
313
324
|
if (chr === ' ') {
|
|
314
325
|
this.currentNode.endPos = this.pos + i - 1;
|
|
315
326
|
this.currentNode = this.currentNode.parentNode;
|
|
316
|
-
this.state =
|
|
327
|
+
this.state = STATE_NORMAL;
|
|
317
328
|
break;
|
|
318
329
|
}
|
|
319
330
|
|
|
@@ -328,16 +339,16 @@ class TokenParser {
|
|
|
328
339
|
this.currentNode.isClosed = true;
|
|
329
340
|
this.currentNode.endPos = this.pos + i;
|
|
330
341
|
this.currentNode = this.currentNode.parentNode;
|
|
331
|
-
this.state =
|
|
342
|
+
this.state = STATE_NORMAL;
|
|
332
343
|
checkSP();
|
|
333
344
|
|
|
334
345
|
break;
|
|
335
346
|
}
|
|
336
347
|
|
|
337
|
-
if ((chr === ',' || chr === ':') && this.currentNode.value
|
|
348
|
+
if ((chr === ',' || chr === ':') && RE_DIGITS.test(this.currentNode.value)) {
|
|
338
349
|
this.currentNode.type = 'SEQUENCE';
|
|
339
350
|
this.currentNode.isClosed = true;
|
|
340
|
-
this.state =
|
|
351
|
+
this.state = STATE_SEQUENCE;
|
|
341
352
|
}
|
|
342
353
|
|
|
343
354
|
// [ starts a section group for this element
|
|
@@ -353,7 +364,7 @@ class TokenParser {
|
|
|
353
364
|
this.currentNode = this.createNode(this.currentNode.parentNode, this.pos + i);
|
|
354
365
|
this.currentNode.type = 'SECTION';
|
|
355
366
|
this.currentNode.isClosed = false;
|
|
356
|
-
this.state =
|
|
367
|
+
this.state = STATE_NORMAL;
|
|
357
368
|
break;
|
|
358
369
|
}
|
|
359
370
|
|
|
@@ -386,13 +397,13 @@ class TokenParser {
|
|
|
386
397
|
this.currentNode.value += chr;
|
|
387
398
|
break;
|
|
388
399
|
|
|
389
|
-
case
|
|
400
|
+
case STATE_STRING:
|
|
390
401
|
// DQUOTE ends the string sequence
|
|
391
402
|
if (chr === '"') {
|
|
392
403
|
this.currentNode.endPos = this.pos + i;
|
|
393
404
|
this.currentNode.isClosed = true;
|
|
394
405
|
this.currentNode = this.currentNode.parentNode;
|
|
395
|
-
this.state =
|
|
406
|
+
this.state = STATE_NORMAL;
|
|
396
407
|
|
|
397
408
|
checkSP();
|
|
398
409
|
break;
|
|
@@ -413,9 +424,9 @@ class TokenParser {
|
|
|
413
424
|
this.currentNode.value += chr;
|
|
414
425
|
break;
|
|
415
426
|
|
|
416
|
-
case
|
|
427
|
+
case STATE_PARTIAL:
|
|
417
428
|
if (chr === '>') {
|
|
418
|
-
if (this.currentNode.value.
|
|
429
|
+
if (this.currentNode.value.at(-1) === '.') {
|
|
419
430
|
let error = new Error(`Unexpected end of partial at position ${this.pos + 1} [E19]`);
|
|
420
431
|
error.code = 'ParserError19';
|
|
421
432
|
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
@@ -424,7 +435,7 @@ class TokenParser {
|
|
|
424
435
|
this.currentNode.endPos = this.pos + i;
|
|
425
436
|
this.currentNode.isClosed = true;
|
|
426
437
|
this.currentNode = this.currentNode.parentNode;
|
|
427
|
-
this.state =
|
|
438
|
+
this.state = STATE_NORMAL;
|
|
428
439
|
checkSP();
|
|
429
440
|
break;
|
|
430
441
|
}
|
|
@@ -453,7 +464,7 @@ class TokenParser {
|
|
|
453
464
|
this.currentNode.value += chr;
|
|
454
465
|
break;
|
|
455
466
|
|
|
456
|
-
case
|
|
467
|
+
case STATE_LITERAL:
|
|
457
468
|
if (this.currentNode.started) {
|
|
458
469
|
// only relevant if literals are not already parsed out from input
|
|
459
470
|
|
|
@@ -473,7 +484,7 @@ class TokenParser {
|
|
|
473
484
|
this.currentNode.value = this.currentNode.chBuffer.toString('binary');
|
|
474
485
|
this.currentNode.chBuffer = Buffer.alloc(0);
|
|
475
486
|
this.currentNode = this.currentNode.parentNode;
|
|
476
|
-
this.state =
|
|
487
|
+
this.state = STATE_NORMAL;
|
|
477
488
|
checkSP();
|
|
478
489
|
}
|
|
479
490
|
break;
|
|
@@ -510,7 +521,7 @@ class TokenParser {
|
|
|
510
521
|
this.currentNode.endPos = this.pos + i;
|
|
511
522
|
this.currentNode.isClosed = true;
|
|
512
523
|
this.currentNode = this.currentNode.parentNode;
|
|
513
|
-
this.state =
|
|
524
|
+
this.state = STATE_NORMAL;
|
|
514
525
|
checkSP();
|
|
515
526
|
} else if (this.options.literals) {
|
|
516
527
|
// use the next precached literal values
|
|
@@ -528,7 +539,7 @@ class TokenParser {
|
|
|
528
539
|
this.currentNode.started = false;
|
|
529
540
|
this.currentNode.isClosed = true;
|
|
530
541
|
this.currentNode = this.currentNode.parentNode;
|
|
531
|
-
this.state =
|
|
542
|
+
this.state = STATE_NORMAL;
|
|
532
543
|
checkSP();
|
|
533
544
|
} else {
|
|
534
545
|
this.currentNode.started = true;
|
|
@@ -554,17 +565,17 @@ class TokenParser {
|
|
|
554
565
|
this.currentNode.literalLength = (this.currentNode.literalLength || '') + chr;
|
|
555
566
|
break;
|
|
556
567
|
|
|
557
|
-
case
|
|
568
|
+
case STATE_SEQUENCE:
|
|
558
569
|
// space finishes the sequence set
|
|
559
570
|
if (chr === ' ') {
|
|
560
|
-
if (!this.currentNode.value.
|
|
571
|
+
if (!RE_SINGLE_DIGIT.test(this.currentNode.value.at(-1)) && this.currentNode.value.at(-1) !== '*') {
|
|
561
572
|
let error = new Error(`Unexpected whitespace at position ${this.pos + i} [E27]`);
|
|
562
573
|
error.code = 'ParserError27';
|
|
563
574
|
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
564
575
|
throw error;
|
|
565
576
|
}
|
|
566
577
|
|
|
567
|
-
if (this.currentNode.value !== '*' && this.currentNode.value.
|
|
578
|
+
if (this.currentNode.value !== '*' && this.currentNode.value.at(-1) === '*' && this.currentNode.value.at(-2) !== ':') {
|
|
568
579
|
let error = new Error(`Unexpected whitespace at position ${this.pos + i} [E28]`);
|
|
569
580
|
error.code = 'ParserError28';
|
|
570
581
|
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
@@ -574,7 +585,7 @@ class TokenParser {
|
|
|
574
585
|
this.currentNode.isClosed = true;
|
|
575
586
|
this.currentNode.endPos = this.pos + i - 1;
|
|
576
587
|
this.currentNode = this.currentNode.parentNode;
|
|
577
|
-
this.state =
|
|
588
|
+
this.state = STATE_NORMAL;
|
|
578
589
|
break;
|
|
579
590
|
} else if (this.currentNode.parentNode && chr === ']' && this.currentNode.parentNode.type === 'SECTION') {
|
|
580
591
|
this.currentNode.endPos = this.pos + i - 1;
|
|
@@ -583,47 +594,47 @@ class TokenParser {
|
|
|
583
594
|
this.currentNode.isClosed = true;
|
|
584
595
|
this.currentNode.endPos = this.pos + i;
|
|
585
596
|
this.currentNode = this.currentNode.parentNode;
|
|
586
|
-
this.state =
|
|
597
|
+
this.state = STATE_NORMAL;
|
|
587
598
|
|
|
588
599
|
checkSP();
|
|
589
600
|
break;
|
|
590
601
|
}
|
|
591
602
|
|
|
592
603
|
if (chr === ':') {
|
|
593
|
-
if (!this.currentNode.value.
|
|
604
|
+
if (!RE_SINGLE_DIGIT.test(this.currentNode.value.at(-1)) && this.currentNode.value.at(-1) !== '*') {
|
|
594
605
|
let error = new Error(`Unexpected range separator : at position ${this.pos + i} [E29]`);
|
|
595
606
|
error.code = 'ParserError29';
|
|
596
607
|
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
597
608
|
throw error;
|
|
598
609
|
}
|
|
599
610
|
} else if (chr === '*') {
|
|
600
|
-
if ([',', ':'].indexOf(this.currentNode.value.
|
|
611
|
+
if ([',', ':'].indexOf(this.currentNode.value.at(-1)) < 0) {
|
|
601
612
|
let error = new Error(`Unexpected range wildcard at position ${this.pos + i} [E30]`);
|
|
602
613
|
error.code = 'ParserError30';
|
|
603
614
|
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
604
615
|
throw error;
|
|
605
616
|
}
|
|
606
617
|
} else if (chr === ',') {
|
|
607
|
-
if (!this.currentNode.value.
|
|
618
|
+
if (!RE_SINGLE_DIGIT.test(this.currentNode.value.at(-1)) && this.currentNode.value.at(-1) !== '*') {
|
|
608
619
|
let error = new Error(`Unexpected sequence separator , at position ${this.pos + i} [E31]`);
|
|
609
620
|
error.code = 'ParserError31';
|
|
610
621
|
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
611
622
|
throw error;
|
|
612
623
|
}
|
|
613
|
-
if (this.currentNode.value.
|
|
624
|
+
if (this.currentNode.value.at(-1) === '*' && this.currentNode.value.at(-2) !== ':') {
|
|
614
625
|
let error = new Error(`Unexpected sequence separator , at position ${this.pos + i} [E32]`);
|
|
615
626
|
error.code = 'ParserError32';
|
|
616
627
|
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
617
628
|
throw error;
|
|
618
629
|
}
|
|
619
|
-
} else if (!
|
|
630
|
+
} else if (!RE_SINGLE_DIGIT.test(chr)) {
|
|
620
631
|
let error = new Error(`Unexpected char at position ${this.pos + i} [E33: ${JSON.stringify(chr)}]`);
|
|
621
632
|
error.code = 'ParserError33';
|
|
622
633
|
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
623
634
|
throw error;
|
|
624
635
|
}
|
|
625
636
|
|
|
626
|
-
if (
|
|
637
|
+
if (RE_SINGLE_DIGIT.test(chr) && this.currentNode.value.at(-1) === '*') {
|
|
627
638
|
let error = new Error(`Unexpected number at position ${this.pos + i} [E34: ${JSON.stringify(chr)}]`);
|
|
628
639
|
error.code = 'ParserError34';
|
|
629
640
|
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
@@ -633,7 +644,7 @@ class TokenParser {
|
|
|
633
644
|
this.currentNode.value += chr;
|
|
634
645
|
break;
|
|
635
646
|
|
|
636
|
-
case
|
|
647
|
+
case STATE_TEXT:
|
|
637
648
|
this.currentNode.value += chr;
|
|
638
649
|
break;
|
|
639
650
|
}
|