imapflow 2.3.0 → 2.4.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cjs/commands/append.d.ts +1 -1
  3. package/dist/cjs/commands/append.js +15 -4
  4. package/dist/cjs/commands/compress.js +2 -2
  5. package/dist/cjs/commands/idle.js +2 -3
  6. package/dist/cjs/commands/status.js +2 -4
  7. package/dist/cjs/connection-deadline.d.ts +1 -1
  8. package/dist/cjs/connection-deadline.js +2 -4
  9. package/dist/cjs/download.js +22 -19
  10. package/dist/cjs/errors.d.ts +15 -0
  11. package/dist/cjs/errors.js +22 -0
  12. package/dist/cjs/handler/imap-compiler.d.ts +4 -4
  13. package/dist/cjs/handler/imap-compiler.js +18 -18
  14. package/dist/cjs/handler/imap-parser.js +28 -3
  15. package/dist/cjs/handler/imap-stream.js +9 -8
  16. package/dist/cjs/handler/limits.d.ts +1 -1
  17. package/dist/cjs/handler/limits.js +5 -7
  18. package/dist/cjs/handler/parser-instance.js +23 -38
  19. package/dist/cjs/handler/token-parser.d.ts +12 -0
  20. package/dist/cjs/handler/token-parser.js +44 -104
  21. package/dist/cjs/imap-flow.d.ts +4 -3
  22. package/dist/cjs/imap-flow.js +72 -72
  23. package/dist/cjs/package-info.d.ts +1 -1
  24. package/dist/cjs/package-info.js +1 -1
  25. package/dist/cjs/proxy-connection.js +2 -5
  26. package/dist/cjs/search-compiler.js +2 -3
  27. package/dist/cjs/tools.d.ts +2 -2
  28. package/dist/cjs/tools.js +8 -11
  29. package/dist/esm/commands/append.d.ts +1 -1
  30. package/dist/esm/commands/append.js +15 -4
  31. package/dist/esm/commands/compress.js +2 -2
  32. package/dist/esm/commands/idle.js +2 -3
  33. package/dist/esm/commands/status.js +2 -4
  34. package/dist/esm/connection-deadline.d.ts +1 -1
  35. package/dist/esm/connection-deadline.js +2 -4
  36. package/dist/esm/download.js +22 -19
  37. package/dist/esm/errors.d.ts +15 -0
  38. package/dist/esm/errors.js +21 -0
  39. package/dist/esm/handler/imap-compiler.d.ts +4 -4
  40. package/dist/esm/handler/imap-compiler.js +18 -18
  41. package/dist/esm/handler/imap-parser.js +28 -3
  42. package/dist/esm/handler/imap-stream.js +9 -8
  43. package/dist/esm/handler/limits.d.ts +1 -1
  44. package/dist/esm/handler/limits.js +5 -7
  45. package/dist/esm/handler/parser-instance.js +23 -38
  46. package/dist/esm/handler/token-parser.d.ts +12 -0
  47. package/dist/esm/handler/token-parser.js +44 -104
  48. package/dist/esm/imap-flow.d.ts +4 -3
  49. package/dist/esm/imap-flow.js +73 -73
  50. package/dist/esm/package-info.d.ts +1 -1
  51. package/dist/esm/package-info.js +1 -1
  52. package/dist/esm/proxy-connection.js +2 -5
  53. package/dist/esm/search-compiler.js +2 -3
  54. package/dist/esm/tools.d.ts +2 -2
  55. package/dist/esm/tools.js +9 -12
  56. package/package.json +3 -3
@@ -1,6 +1,7 @@
1
1
  /* eslint new-cap: 0 */
2
2
  import imapFormalSyntax from './imap-formal-syntax.js';
3
3
  import { MAX_LITERAL_SIZE, ERROR_CONTEXT_LENGTH, boundedInput, normalizeLimit, createLiteralTooLargeError } from './limits.js';
4
+ import { createImapError } from '../errors.js';
4
5
  const STATE_ATOM = 0x001;
5
6
  const STATE_LITERAL = 0x002;
6
7
  const STATE_NORMAL = 0x003;
@@ -42,6 +43,23 @@ export class TokenParser {
42
43
  this.currentNode.type = 'TREE';
43
44
  this.state = STATE_NORMAL;
44
45
  }
46
+ /**
47
+ * Builds a numbered parser error that carries a bounded copy of the input and the position
48
+ * (and character, when there is one) where parsing failed.
49
+ *
50
+ * @param message - The error message
51
+ * @param code - The numbered parser error code
52
+ * @param pos - Position of the failure in the original input
53
+ * @param chr - The offending character
54
+ * @returns The error to throw
55
+ */
56
+ parserError(message, code, pos, chr) {
57
+ const parserContext = { ...boundedInput(this.str), pos };
58
+ if (chr !== undefined) {
59
+ parserContext.chr = chr;
60
+ }
61
+ return createImapError(message, code, { parserContext });
62
+ }
45
63
  /**
46
64
  * Processes the input string and returns the parsed attributes as a flat array of typed objects.
47
65
  * Each attribute is an object with a `type` (e.g., "ATOM", "STRING", "LITERAL", "SEQUENCE")
@@ -65,10 +83,7 @@ export class TokenParser {
65
83
  }
66
84
  // If the node was never closed, throw it
67
85
  if (!node.isClosed) {
68
- let error = new Error(`Unexpected end of input at position ${this.pos + this.str.length - 1} [E9]`);
69
- error.code = 'ParserError9';
70
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + this.str.length - 1 };
71
- throw error;
86
+ throw this.parserError(`Unexpected end of input at position ${this.pos + this.str.length - 1} [E9]`, 'ParserError9', this.pos + this.str.length - 1);
72
87
  }
73
88
  let type = (node.type || '').toString().toUpperCase();
74
89
  switch (type) {
@@ -139,11 +154,8 @@ export class TokenParser {
139
154
  node.depth = 0;
140
155
  }
141
156
  if (node.depth > MAX_NODE_DEPTH) {
142
- let error = new Error('Too much nesting in IMAP string');
143
- error.code = 'MAX_IMAP_NESTING_REACHED';
144
157
  // A bounded prefix only, the error travels whole into logs and rejections
145
- error._imapStr = this.str.slice(0, ERROR_CONTEXT_LENGTH);
146
- throw error;
158
+ throw createImapError('Too much nesting in IMAP string', 'MAX_IMAP_NESTING_REACHED', { _imapStr: this.str.slice(0, ERROR_CONTEXT_LENGTH) });
147
159
  }
148
160
  if (typeof startPos === 'number') {
149
161
  node.startPos = startPos;
@@ -219,10 +231,7 @@ export class TokenParser {
219
231
  // error instead of reading a missing buffer or shifting every later literal by one
220
232
  const takeLiteral = (literals) => {
221
233
  if (!literals.length) {
222
- let error = new Error(`Literal without data at position ${this.pos + i} [E35]`);
223
- error.code = 'ParserError35';
224
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
225
- throw error;
234
+ throw this.parserError(`Literal without data at position ${this.pos + i} [E35]`, 'ParserError35', this.pos + i, chr);
226
235
  }
227
236
  return literals.shift();
228
237
  };
@@ -234,10 +243,7 @@ export class TokenParser {
234
243
  // used to fall through into the default branch
235
244
  const startAtom = () => {
236
245
  if (!imapFormalSyntax['ATOM-CHAR']().includes(chr) && chr !== '\\' && chr !== '%' && chr.charCodeAt(0) < 0x80) {
237
- let error = new Error(`Unexpected char at position ${this.pos + i} [E13: ${JSON.stringify(chr)}]`);
238
- error.code = 'ParserError13';
239
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
240
- throw error;
246
+ throw this.parserError(`Unexpected char at position ${this.pos + i} [E13: ${JSON.stringify(chr)}]`, 'ParserError13', this.pos + i, chr);
241
247
  }
242
248
  this.currentNode = this.createNode(this.currentNode, this.pos + i);
243
249
  this.currentNode.type = 'ATOM';
@@ -265,10 +271,7 @@ export class TokenParser {
265
271
  // ) closes a list
266
272
  case ')':
267
273
  if (this.currentNode.type !== 'LIST') {
268
- let error = new Error(`Unexpected list terminator ) at position ${this.pos + i} [E10]`);
269
- error.code = 'ParserError10';
270
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
271
- throw error;
274
+ throw this.parserError(`Unexpected list terminator ) at position ${this.pos + i} [E10]`, 'ParserError10', this.pos + i, chr);
272
275
  }
273
276
  this.currentNode.isClosed = true;
274
277
  this.currentNode.endPos = this.pos + i;
@@ -278,10 +281,7 @@ export class TokenParser {
278
281
  // ] closes section group
279
282
  case ']':
280
283
  if (this.currentNode.type !== 'SECTION') {
281
- let error = new Error(`Unexpected section terminator ] at position ${this.pos + i} [E11]`);
282
- error.code = 'ParserError11';
283
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
284
- throw error;
284
+ throw this.parserError(`Unexpected section terminator ] at position ${this.pos + i} [E11]`, 'ParserError11', this.pos + i, chr);
285
285
  }
286
286
  this.currentNode.isClosed = true;
287
287
  this.currentNode.endPos = this.pos + i;
@@ -322,10 +322,7 @@ export class TokenParser {
322
322
  startAtom();
323
323
  break;
324
324
  }
325
- let error = new Error(`Unexpected literal8 marker at position ${this.pos + i} [E12]`);
326
- error.code = 'ParserError12';
327
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
328
- throw error;
325
+ throw this.parserError(`Unexpected literal8 marker at position ${this.pos + i} [E12]`, 'ParserError12', this.pos + i, chr);
329
326
  }
330
327
  // Mark the next literal as literal8 type; consumed when '{' is encountered
331
328
  this.expectedLiteralType = 'literal8';
@@ -485,16 +482,10 @@ export class TokenParser {
485
482
  chr !== ']' &&
486
483
  !(chr === '*' && this.currentNode.value === '\\') &&
487
484
  (!this.parent || !this.parent.command || !['NO', 'BAD', 'OK'].includes(this.parent.command.toUpperCase()))) {
488
- let error = new Error(`Unexpected char at position ${this.pos + i} [E16: ${JSON.stringify(chr)}]`);
489
- error.code = 'ParserError16';
490
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
491
- throw error;
485
+ throw this.parserError(`Unexpected char at position ${this.pos + i} [E16: ${JSON.stringify(chr)}]`, 'ParserError16', this.pos + i, chr);
492
486
  }
493
487
  else if (this.currentNode.value === '\\*') {
494
- let error = new Error(`Unexpected char at position ${this.pos + i} [E17: ${JSON.stringify(chr)}]`);
495
- error.code = 'ParserError17';
496
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
497
- throw error;
488
+ throw this.parserError(`Unexpected char at position ${this.pos + i} [E17: ${JSON.stringify(chr)}]`, 'ParserError17', this.pos + i, chr);
498
489
  }
499
490
  appendToToken(chr);
500
491
  break;
@@ -512,10 +503,7 @@ export class TokenParser {
512
503
  if (chr === '\\') {
513
504
  i++;
514
505
  if (i >= len) {
515
- let error = new Error(`Unexpected end of input at position ${this.pos + i} [E18]`);
516
- error.code = 'ParserError18';
517
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i };
518
- throw error;
506
+ throw this.parserError(`Unexpected end of input at position ${this.pos + i} [E18]`, 'ParserError18', this.pos + i);
519
507
  }
520
508
  chr = this.str.charAt(i);
521
509
  }
@@ -524,10 +512,7 @@ export class TokenParser {
524
512
  case STATE_PARTIAL:
525
513
  if (chr === '>') {
526
514
  if (tokenLast === '.') {
527
- let error = new Error(`Unexpected end of partial at position ${this.pos + i} [E19]`);
528
- error.code = 'ParserError19';
529
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
530
- throw error;
515
+ throw this.parserError(`Unexpected end of partial at position ${this.pos + i} [E19]`, 'ParserError19', this.pos + i, chr);
531
516
  }
532
517
  this.currentNode.endPos = this.pos + i;
533
518
  this.currentNode.isClosed = true;
@@ -537,22 +522,13 @@ export class TokenParser {
537
522
  break;
538
523
  }
539
524
  if (chr === '.' && (!tokenLength || tokenHasDot)) {
540
- let error = new Error(`Unexpected partial separator . at position ${this.pos + i} [E20]`);
541
- error.code = 'ParserError20';
542
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
543
- throw error;
525
+ throw this.parserError(`Unexpected partial separator . at position ${this.pos + i} [E20]`, 'ParserError20', this.pos + i, chr);
544
526
  }
545
527
  if (!imapFormalSyntax.DIGIT().includes(chr) && chr !== '.') {
546
- let error = new Error(`Unexpected char at position ${this.pos + i} [E21: ${JSON.stringify(chr)}]`);
547
- error.code = 'ParserError21';
548
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
549
- throw error;
528
+ throw this.parserError(`Unexpected char at position ${this.pos + i} [E21: ${JSON.stringify(chr)}]`, 'ParserError21', this.pos + i, chr);
550
529
  }
551
530
  if (tokenLast === '0' && (tokenLength === 1 || tokenPrev === '.') && chr !== '.') {
552
- let error = new Error(`Invalid partial at position ${this.pos + i} [E22: ${JSON.stringify(chr)}]`);
553
- error.code = 'ParserError22';
554
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
555
- throw error;
531
+ throw this.parserError(`Invalid partial at position ${this.pos + i} [E22: ${JSON.stringify(chr)}]`, 'ParserError22', this.pos + i, chr);
556
532
  }
557
533
  appendToToken(chr);
558
534
  break;
@@ -584,10 +560,7 @@ export class TokenParser {
584
560
  }
585
561
  if (chr === '}') {
586
562
  if (!('literalLength' in this.currentNode)) {
587
- let error = new Error(`Unexpected literal prefix end char } at position ${this.pos + i} [E23]`);
588
- error.code = 'ParserError23';
589
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
590
- throw error;
563
+ throw this.parserError(`Unexpected literal prefix end char } at position ${this.pos + i} [E23]`, 'ParserError23', this.pos + i, chr);
591
564
  }
592
565
  if (this.str.charAt(i + 1) === '\n') {
593
566
  i++;
@@ -596,10 +569,7 @@ export class TokenParser {
596
569
  i += 2;
597
570
  }
598
571
  else {
599
- let error = new Error(`Unexpected char at position ${this.pos + i} [E24: ${JSON.stringify(chr)}]`);
600
- error.code = 'ParserError24';
601
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
602
- throw error;
572
+ throw this.parserError(`Unexpected char at position ${this.pos + i} [E24: ${JSON.stringify(chr)}]`, 'ParserError24', this.pos + i, chr);
603
573
  }
604
574
  this.currentNode.literalLength = Number(this.currentNode.literalLength);
605
575
  if (!this.currentNode.literalLength) {
@@ -660,16 +630,10 @@ export class TokenParser {
660
630
  break;
661
631
  }
662
632
  if (!imapFormalSyntax.DIGIT().includes(chr)) {
663
- let error = new Error(`Unexpected char at position ${this.pos + i} [E25: ${JSON.stringify(chr)}]`);
664
- error.code = 'ParserError25';
665
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
666
- throw error;
633
+ throw this.parserError(`Unexpected char at position ${this.pos + i} [E25: ${JSON.stringify(chr)}]`, 'ParserError25', this.pos + i, chr);
667
634
  }
668
635
  if (this.currentNode.literalLength === '0') {
669
- let error = new Error(`Invalid literal at position ${this.pos + i} [E26]`);
670
- error.code = 'ParserError26';
671
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
672
- throw error;
636
+ throw this.parserError(`Invalid literal at position ${this.pos + i} [E26]`, 'ParserError26', this.pos + i, chr);
673
637
  }
674
638
  this.currentNode.literalLength = (this.currentNode.literalLength || '') + chr;
675
639
  break;
@@ -684,16 +648,10 @@ export class TokenParser {
684
648
  continueAsAtom();
685
649
  break;
686
650
  }
687
- let error = new Error(`Unexpected end of sequence at position ${this.pos + i} [E27: ${JSON.stringify(chr)}]`);
688
- error.code = 'ParserError27';
689
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
690
- throw error;
651
+ throw this.parserError(`Unexpected end of sequence at position ${this.pos + i} [E27: ${JSON.stringify(chr)}]`, 'ParserError27', this.pos + i, chr);
691
652
  }
692
653
  if (this.currentNode.value !== '*' && tokenLast === '*' && tokenPrev !== ':') {
693
- let error = new Error(`Unexpected end of sequence at position ${this.pos + i} [E28: ${JSON.stringify(chr)}]`);
694
- error.code = 'ParserError28';
695
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
696
- throw error;
654
+ throw this.parserError(`Unexpected end of sequence at position ${this.pos + i} [E28: ${JSON.stringify(chr)}]`, 'ParserError28', this.pos + i, chr);
697
655
  }
698
656
  this.currentNode.isClosed = true;
699
657
  this.currentNode.endPos = this.pos + i - 1;
@@ -713,18 +671,12 @@ export class TokenParser {
713
671
  continueAsAtom();
714
672
  break;
715
673
  }
716
- let error = new Error(`Unexpected range separator : at position ${this.pos + i} [E29]`);
717
- error.code = 'ParserError29';
718
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
719
- throw error;
674
+ throw this.parserError(`Unexpected range separator : at position ${this.pos + i} [E29]`, 'ParserError29', this.pos + i, chr);
720
675
  }
721
676
  }
722
677
  else if (chr === '*') {
723
678
  if (![',', ':'].includes(tokenLast)) {
724
- let error = new Error(`Unexpected range wildcard at position ${this.pos + i} [E30]`);
725
- error.code = 'ParserError30';
726
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
727
- throw error;
679
+ throw this.parserError(`Unexpected range wildcard at position ${this.pos + i} [E30]`, 'ParserError30', this.pos + i, chr);
728
680
  }
729
681
  }
730
682
  else if (chr === ',') {
@@ -733,16 +685,10 @@ export class TokenParser {
733
685
  continueAsAtom();
734
686
  break;
735
687
  }
736
- let error = new Error(`Unexpected sequence separator , at position ${this.pos + i} [E31]`);
737
- error.code = 'ParserError31';
738
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
739
- throw error;
688
+ throw this.parserError(`Unexpected sequence separator , at position ${this.pos + i} [E31]`, 'ParserError31', this.pos + i, chr);
740
689
  }
741
690
  if (tokenLast === '*' && tokenPrev !== ':') {
742
- let error = new Error(`Unexpected sequence separator , at position ${this.pos + i} [E32]`);
743
- error.code = 'ParserError32';
744
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
745
- throw error;
691
+ throw this.parserError(`Unexpected sequence separator , at position ${this.pos + i} [E32]`, 'ParserError32', this.pos + i, chr);
746
692
  }
747
693
  }
748
694
  else if (!RE_SINGLE_DIGIT.test(chr)) {
@@ -750,16 +696,10 @@ export class TokenParser {
750
696
  continueAsAtom();
751
697
  break;
752
698
  }
753
- let error = new Error(`Unexpected char at position ${this.pos + i} [E33: ${JSON.stringify(chr)}]`);
754
- error.code = 'ParserError33';
755
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
756
- throw error;
699
+ throw this.parserError(`Unexpected char at position ${this.pos + i} [E33: ${JSON.stringify(chr)}]`, 'ParserError33', this.pos + i, chr);
757
700
  }
758
701
  if (RE_SINGLE_DIGIT.test(chr) && tokenLast === '*') {
759
- let error = new Error(`Unexpected number at position ${this.pos + i} [E34: ${JSON.stringify(chr)}]`);
760
- error.code = 'ParserError34';
761
- error.parserContext = { ...boundedInput(this.str), pos: this.pos + i, chr };
762
- throw error;
702
+ throw this.parserError(`Unexpected number at position ${this.pos + i} [E34: ${JSON.stringify(chr)}]`, 'ParserError34', this.pos + i, chr);
763
703
  }
764
704
  appendToToken(chr);
765
705
  break;
@@ -426,7 +426,7 @@ export declare class ImapFlow extends EventEmitter {
426
426
  * Appends a new message to a mailbox
427
427
  *
428
428
  * @param path Mailbox path to upload the message to (unicode string). If value is an array then it is joined using current delimiter symbols. Namespace prefix is added automatically if required.
429
- * @param content RFC822 formatted email message
429
+ * @param content RFC822 formatted email message, as a Buffer, Uint8Array or string
430
430
  * @param flags an array of flags to be set for the uploaded message
431
431
  * @param idate internal date to be set for the message
432
432
  * @returns info about uploaded message
@@ -434,7 +434,7 @@ export declare class ImapFlow extends EventEmitter {
434
434
  * @example
435
435
  * await client.append('INBOX', rawMessageBuffer, ['\\Seen'], new Date(2000, 1, 1));
436
436
  */
437
- append(path: string | string[], content: string | Buffer, flags?: string[] | undefined, idate?: Date | string | undefined): Promise<AppendResponseObject | false>;
437
+ append(path: string | string[], content: string | Buffer | Uint8Array, flags?: string[] | undefined, idate?: Date | string | undefined): Promise<AppendResponseObject | false>;
438
438
  /**
439
439
  * Copies messages from current mailbox to destination mailbox
440
440
  *
@@ -562,7 +562,8 @@ export declare class ImapFlow extends EventEmitter {
562
562
  * // download body part nr '1.2' from latest message
563
563
  * let download = await client.download('*', '1.2');
564
564
  * if (download.content) {
565
- * download.content.pipe(fs.createWriteStream(download.meta.filename));
565
+ * // pipeline() rejects when the download fails, a plain .pipe() would not report it
566
+ * await stream.promises.pipeline(download.content, fs.createWriteStream(download.meta.filename));
566
567
  * }
567
568
  */
568
569
  download(range: SequenceString, part?: string | undefined, options?: DownloadOptions | undefined): Promise<DownloadObject | DownloadNotFound>;