imapflow 1.0.155 → 1.0.157

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.157](https://github.com/postalsys/imapflow/compare/v1.0.156...v1.0.157) (2024-03-19)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **logs:** Log mailbox lock description ([ce87cf5](https://github.com/postalsys/imapflow/commit/ce87cf5a256ddc324624893a1c17992475fafeb3))
9
+
10
+ ## [1.0.156](https://github.com/postalsys/imapflow/compare/v1.0.155...v1.0.156) (2024-03-07)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * micro-optimizations for parser ([3451ad2](https://github.com/postalsys/imapflow/commit/3451ad235532ca1a64a829f9f2f8e77bcef3a17d))
16
+
3
17
  ## [1.0.155](https://github.com/postalsys/imapflow/compare/v1.0.154...v1.0.155) (2024-03-06)
4
18
 
5
19
 
@@ -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 = 'NORMAL';
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 'NORMAL':
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 = 'STRING';
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 = 'ATOM';
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 = 'PARTIAL';
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 = 'ATOM';
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 = 'LITERAL';
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 = 'SEQUENCE';
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 = 'NORMAL';
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 = 'ATOM';
317
+ this.state = STATE_ATOM;
307
318
  break;
308
319
  }
309
320
  break;
310
321
 
311
- case 'ATOM':
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 = 'NORMAL';
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 = 'NORMAL';
342
+ this.state = STATE_NORMAL;
332
343
  checkSP();
333
344
 
334
345
  break;
335
346
  }
336
347
 
337
- if ((chr === ',' || chr === ':') && this.currentNode.value.match(/^\d+$/)) {
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 = 'SEQUENCE';
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 = 'NORMAL';
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 'STRING':
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 = 'NORMAL';
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 'PARTIAL':
427
+ case STATE_PARTIAL:
417
428
  if (chr === '>') {
418
- if (this.currentNode.value.substr(-1) === '.') {
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 = 'NORMAL';
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 'LITERAL':
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 = 'NORMAL';
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 = 'NORMAL';
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 = 'NORMAL';
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 'SEQUENCE':
568
+ case STATE_SEQUENCE:
558
569
  // space finishes the sequence set
559
570
  if (chr === ' ') {
560
- if (!this.currentNode.value.substr(-1).match(/\d/) && this.currentNode.value.substr(-1) !== '*') {
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.substr(-1) === '*' && this.currentNode.value.substr(-2, 1) !== ':') {
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 = 'NORMAL';
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 = 'NORMAL';
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.substr(-1).match(/\d/) && this.currentNode.value.substr(-1) !== '*') {
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.substr(-1)) < 0) {
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.substr(-1).match(/\d/) && this.currentNode.value.substr(-1) !== '*') {
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.substr(-1) === '*' && this.currentNode.value.substr(-2, 1) !== ':') {
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 (!chr.match(/\d/)) {
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 (chr.match(/\d/) && this.currentNode.value.substr(-1) === '*') {
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 'TEXT':
647
+ case STATE_TEXT:
637
648
  this.currentNode.value += chr;
638
649
  break;
639
650
  }
package/lib/imap-flow.js CHANGED
@@ -1313,7 +1313,7 @@ class ImapFlow extends EventEmitter {
1313
1313
 
1314
1314
  await new Promise((resolve, reject) => {
1315
1315
  this.connectTimeout = setTimeout(() => {
1316
- let err = new Error('Failed to established connection in required time');
1316
+ let err = new Error('Failed to establish connection in required time');
1317
1317
  err.code = 'CONNECT_TIMEOUT';
1318
1318
  err.details = {
1319
1319
  connectionTimeout: this.options.connectionTimeout || CONNECT_TIMEOUT
@@ -2840,17 +2840,35 @@ class ImapFlow extends EventEmitter {
2840
2840
 
2841
2841
  async processLocks(force) {
2842
2842
  if (!force && this.processingLock) {
2843
+ this.log.trace({
2844
+ msg: 'Mailbox locking queued',
2845
+ path: this.mailbox && this.mailbox.path,
2846
+ pending: this.locks.length,
2847
+ idling: this.idling
2848
+ });
2843
2849
  return;
2844
2850
  }
2851
+
2845
2852
  if (!this.locks.length) {
2846
2853
  this.processingLock = false;
2854
+ this.log.trace({
2855
+ msg: 'Mailbox locking queue processed',
2856
+ idling: this.idling
2857
+ });
2847
2858
  return;
2848
2859
  }
2860
+
2849
2861
  this.processingLock = true;
2850
2862
 
2851
2863
  const release = () => {
2852
2864
  if (this.currentLockId) {
2853
- this.log.trace({ msg: 'Mailbox lock released', lockId: this.currentLockId, path: this.mailbox && this.mailbox.path });
2865
+ this.log.trace({
2866
+ msg: 'Mailbox lock released',
2867
+ lockId: this.currentLockId,
2868
+ path: this.mailbox && this.mailbox.path,
2869
+ pending: this.locks.length,
2870
+ idling: this.idling
2871
+ });
2854
2872
  this.currentLockId = 0;
2855
2873
  }
2856
2874
  this.processLocks(true).catch(err => this.log.error({ err, cid: this.id }));
@@ -2859,7 +2877,7 @@ class ImapFlow extends EventEmitter {
2859
2877
  const { resolve, reject, path, options, lockId } = this.locks.shift();
2860
2878
 
2861
2879
  if (!this.usable || this.socket.destroyed) {
2862
- this.log.trace({ msg: 'Failed to acquire mailbox lock', path, lockId });
2880
+ this.log.trace({ msg: 'Failed to acquire mailbox lock', path, lockId, idling: this.idling });
2863
2881
  // reject all
2864
2882
  let error = new Error('Connection not available');
2865
2883
  error.code = 'NoConnection';
@@ -2869,14 +2887,14 @@ class ImapFlow extends EventEmitter {
2869
2887
 
2870
2888
  if (this.mailbox && this.mailbox.path === path && !!this.mailbox.readOnly === !!options.readOnly) {
2871
2889
  // nothing to do here, already selected
2872
- this.log.trace({ msg: 'Mailbox lock acquired', path, lockId });
2890
+ this.log.trace({ msg: 'Mailbox lock acquired', path, lockId, idling: this.idling });
2873
2891
  this.currentLockId = lockId;
2874
2892
  return resolve({ path, release });
2875
2893
  } else {
2876
2894
  try {
2877
2895
  // Try to open. Throws if mailbox does not exists or can't open
2878
2896
  await this.mailboxOpen(path, options);
2879
- this.log.trace({ msg: 'Lock acquired', path, lockId });
2897
+ this.log.trace({ msg: 'Lock acquired', path, lockId, idling: this.idling, ...(options.description && { description: options.description }) });
2880
2898
  this.currentLockId = lockId;
2881
2899
  return resolve({ path, release });
2882
2900
  } catch (err) {
@@ -2891,7 +2909,7 @@ class ImapFlow extends EventEmitter {
2891
2909
  }
2892
2910
  }
2893
2911
 
2894
- this.log.trace({ msg: 'Failed to acquire mailbox lock', path, lockId });
2912
+ this.log.trace({ msg: 'Failed to acquire mailbox lock', path, lockId, idling: this.idling });
2895
2913
  reject(err);
2896
2914
  await this.processLocks(true);
2897
2915
  }
@@ -2925,7 +2943,7 @@ class ImapFlow extends EventEmitter {
2925
2943
  path = normalizePath(this, path);
2926
2944
 
2927
2945
  let lockId = ++this.lockCounter;
2928
- this.log.trace({ msg: 'Requesting lock', path, lockId });
2946
+ this.log.trace({ msg: 'Requesting lock', path, lockId, ...(options.description && { description: options.description }) });
2929
2947
 
2930
2948
  return await new Promise((resolve, reject) => {
2931
2949
  this.locks.push({ resolve, reject, path, options, lockId });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imapflow",
3
- "version": "1.0.155",
3
+ "version": "1.0.157",
4
4
  "description": "IMAP Client for Node",
5
5
  "main": "./lib/imap-flow.js",
6
6
  "scripts": {
@@ -32,7 +32,7 @@
32
32
  "@babel/eslint-plugin": "7.23.5",
33
33
  "@babel/plugin-syntax-class-properties": "7.12.13",
34
34
  "@babel/preset-env": "7.24.0",
35
- "@types/node": "20.11.24",
35
+ "@types/node": "20.11.29",
36
36
  "eslint": "8.57.0",
37
37
  "eslint-config-nodemailer": "1.2.0",
38
38
  "eslint-config-prettier": "9.1.0",
@@ -52,7 +52,7 @@
52
52
  "libmime": "5.3.4",
53
53
  "libqp": "2.1.0",
54
54
  "mailsplit": "5.4.0",
55
- "nodemailer": "6.9.11",
55
+ "nodemailer": "6.9.12",
56
56
  "pino": "8.19.0",
57
57
  "socks": "2.8.1"
58
58
  }