imapflow 1.0.90 → 1.0.91

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,11 @@
1
1
  # CHANGELOG
2
2
 
3
+ - 1.0.91 - 2022-05-02
4
+
5
+ - Do not throw if literal includes a null byte
6
+ - Use UTF8 path names by default
7
+ - Overrides '\*' range query with then EXISTIS value
8
+
3
9
  - 1.0.90 - 2022-04-04
4
10
 
5
11
  - Added new configuration option `maxIdleTime`
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { normalizePath, getStatusCode, getErrorText } = require('../tools.js');
3
+ const { encodePath, normalizePath, getStatusCode, getErrorText } = require('../tools.js');
4
4
 
5
5
  // Creates a new mailbox
6
6
  module.exports = async (connection, path) => {
@@ -16,7 +16,7 @@ module.exports = async (connection, path) => {
16
16
  let map = {
17
17
  path
18
18
  };
19
- response = await connection.exec('CREATE', [{ type: 'ATOM', value: path }]);
19
+ response = await connection.exec('CREATE', [{ type: 'ATOM', value: encodePath(connection, path) }]);
20
20
 
21
21
  let section =
22
22
  response.response.attributes &&
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { normalizePath, getStatusCode, getErrorText } = require('../tools.js');
3
+ const { encodePath, normalizePath, getStatusCode, getErrorText } = require('../tools.js');
4
4
 
5
5
  // Deletes an existing mailbox
6
6
  module.exports = async (connection, path) => {
@@ -20,7 +20,7 @@ module.exports = async (connection, path) => {
20
20
  let map = {
21
21
  path
22
22
  };
23
- response = await connection.exec('DELETE', [{ type: 'ATOM', value: path }]);
23
+ response = await connection.exec('DELETE', [{ type: 'ATOM', value: encodePath(connection, path) }]);
24
24
  response.next();
25
25
  return map;
26
26
  } catch (err) {
@@ -18,6 +18,8 @@ module.exports = async (connection, range, query, options) => {
18
18
  list: []
19
19
  };
20
20
 
21
+ const commandKey = 'BODY';
22
+
21
23
  let response;
22
24
  try {
23
25
  let attributes = [{ type: 'SEQUENCE', value: (range || '*').toString() }];
@@ -27,7 +29,7 @@ module.exports = async (connection, range, query, options) => {
27
29
  let setBodyPeek = (attributes, partial) => {
28
30
  let bodyPeek = {
29
31
  type: 'ATOM',
30
- value: 'BODY.PEEK',
32
+ value: `${commandKey}.PEEK`,
31
33
  section: [],
32
34
  partial
33
35
  };
@@ -61,7 +63,7 @@ module.exports = async (connection, range, query, options) => {
61
63
  partial.push(Number(query.source.maxLength));
62
64
  }
63
65
  }
64
- queryStructure.push({ type: 'ATOM', value: 'BODY.PEEK', section: [], partial });
66
+ queryStructure.push({ type: 'ATOM', value: `${commandKey}.PEEK`, section: [], partial });
65
67
  }
66
68
 
67
69
  // if possible, always request for unique email id
@@ -1,8 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { normalizePath } = require('../tools.js');
3
+ const { decodePath, encodePath, normalizePath } = require('../tools.js');
4
4
  const { specialUse } = require('../special-use');
5
- const iconv = require('iconv-lite');
6
5
 
7
6
  // Lists mailboxes from server
8
7
  module.exports = async (connection, reference, mailbox) => {
@@ -14,7 +13,7 @@ module.exports = async (connection, reference, mailbox) => {
14
13
  let entries = [];
15
14
 
16
15
  let runList = async (reference, mailbox) => {
17
- response = await connection.exec(listCommand, [reference, mailbox], {
16
+ response = await connection.exec(listCommand, [encodePath(connection, reference), encodePath(connection, mailbox)], {
18
17
  untagged: {
19
18
  [listCommand]: async untagged => {
20
19
  if (!untagged.attributes || !untagged.attributes.length) {
@@ -22,7 +21,8 @@ module.exports = async (connection, reference, mailbox) => {
22
21
  }
23
22
 
24
23
  let entry = {
25
- path: (untagged.attributes[2] && untagged.attributes[2].value) || '',
24
+ path: normalizePath(connection, decodePath(connection, (untagged.attributes[2] && untagged.attributes[2].value) || '')),
25
+ pathAsListed: (untagged.attributes[2] && untagged.attributes[2].value) || '',
26
26
  flags: new Set(untagged.attributes[0].map(entry => entry.value)),
27
27
  delimiter: untagged.attributes[1] && untagged.attributes[1].value,
28
28
  listed: true
@@ -45,19 +45,8 @@ module.exports = async (connection, reference, mailbox) => {
45
45
  entry.path = entry.path.slice(1);
46
46
  }
47
47
 
48
- entry.parent = (entry.delimiter ? entry.path.split(entry.delimiter) : [entry.path]).map(folder => {
49
- if (!connection.enabled.has('UTF8=ACCEPT')) {
50
- try {
51
- return iconv.decode(Buffer.from(folder), 'utf-7-imap').toString();
52
- } catch (err) {
53
- return folder; // keep as is
54
- }
55
- } else {
56
- return folder; // keep as is
57
- }
58
- });
59
-
60
- entry.path = normalizePath(connection, entry.path);
48
+ entry.parentPath = entry.delimiter && entry.path ? entry.path.substr(0, entry.path.lastIndexOf(entry.delimiter)) : '';
49
+ entry.parent = entry.delimiter ? entry.path.split(entry.delimiter) : [entry.path];
61
50
  entry.name = entry.parent.pop();
62
51
 
63
52
  let specialUseFlag = specialUse(connection.capabilities.has('XLIST') || connection.capabilities.has('SPECIAL-USE'), entry);
@@ -80,59 +69,54 @@ module.exports = async (connection, reference, mailbox) => {
80
69
  await runList('', 'INBOX');
81
70
  }
82
71
 
83
- response = await connection.exec('LSUB', [normalizePath(connection, reference || ''), normalizePath(connection, mailbox || '', true)], {
84
- untagged: {
85
- LSUB: async untagged => {
86
- if (!untagged.attributes || !untagged.attributes.length) {
87
- return;
88
- }
89
-
90
- let entry = {
91
- flags: new Set(untagged.attributes[0].map(entry => entry.value)),
92
- delimiter: untagged.attributes[1] && untagged.attributes[1].value,
93
- path: (untagged.attributes[2] && untagged.attributes[2].value) || '',
94
- subscribed: true
95
- };
72
+ response = await connection.exec(
73
+ 'LSUB',
74
+ [encodePath(connection, normalizePath(connection, reference || '')), encodePath(connection, normalizePath(connection, mailbox || '', true))],
75
+ {
76
+ untagged: {
77
+ LSUB: async untagged => {
78
+ if (!untagged.attributes || !untagged.attributes.length) {
79
+ return;
80
+ }
96
81
 
97
- if (entry.path.toUpperCase() === 'INBOX') {
98
- entry.specialUse = '\\Inbox';
99
- }
82
+ let entry = {
83
+ path: normalizePath(connection, decodePath(connection, (untagged.attributes[2] && untagged.attributes[2].value) || '')),
84
+ pathAsListed: (untagged.attributes[2] && untagged.attributes[2].value) || '',
85
+ flags: new Set(untagged.attributes[0].map(entry => entry.value)),
86
+ delimiter: untagged.attributes[1] && untagged.attributes[1].value,
87
+ subscribed: true
88
+ };
100
89
 
101
- if (entry.delimiter && entry.path.charAt(0) === entry.delimiter) {
102
- entry.path = entry.path.slice(1);
103
- }
90
+ if (entry.path.toUpperCase() === 'INBOX') {
91
+ entry.specialUse = '\\Inbox';
92
+ }
104
93
 
105
- entry.parent = (entry.delimiter ? entry.path.split(entry.delimiter) : [entry.path]).map(folder => {
106
- if (!connection.enabled.has('UTF8=ACCEPT')) {
107
- try {
108
- return iconv.decode(Buffer.from(folder), 'utf-7-imap').toString();
109
- } catch (err) {
110
- return folder; // keep as is
111
- }
112
- } else {
113
- return folder; // keep as is
94
+ if (entry.delimiter && entry.path.charAt(0) === entry.delimiter) {
95
+ entry.path = entry.path.slice(1);
114
96
  }
115
- });
116
97
 
117
- entry.name = entry.parent.pop();
98
+ entry.parentPath = entry.delimiter && entry.path ? entry.path.substr(0, entry.path.lastIndexOf(entry.delimiter)) : '';
99
+ entry.parent = entry.delimiter ? entry.path.split(entry.delimiter) : [entry.path];
100
+ entry.name = entry.parent.pop();
118
101
 
119
- let existing = entries.find(existing => existing.path === entry.path);
120
- if (existing) {
121
- existing.subscribed = true;
122
- entry.flags.forEach(flag => existing.flags.add(flag));
123
- } else {
124
- // ignore non-listed folders
125
- /*
126
- let specialUseFlag = specialUse(connection.capabilities.has('XLIST') || connection.capabilities.has('SPECIAL-USE'), entry);
127
- if (specialUseFlag && !flagsSeen.has(specialUseFlag)) {
128
- entry.specialUse = specialUseFlag;
102
+ let existing = entries.find(existing => existing.path === entry.path);
103
+ if (existing) {
104
+ existing.subscribed = true;
105
+ entry.flags.forEach(flag => existing.flags.add(flag));
106
+ } else {
107
+ // ignore non-listed folders
108
+ /*
109
+ let specialUseFlag = specialUse(connection.capabilities.has('XLIST') || connection.capabilities.has('SPECIAL-USE'), entry);
110
+ if (specialUseFlag && !flagsSeen.has(specialUseFlag)) {
111
+ entry.specialUse = specialUseFlag;
112
+ }
113
+ entries.push(entry);
114
+ */
129
115
  }
130
- entries.push(entry);
131
- */
132
116
  }
133
117
  }
134
118
  }
135
- });
119
+ );
136
120
  response.next();
137
121
 
138
122
  let inboxEntry = entries.find(entry => entry.specialUse === '\\Inbox');
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { getStatusCode, normalizePath, getErrorText } = require('../tools.js');
3
+ const { encodePath, getStatusCode, normalizePath, getErrorText } = require('../tools.js');
4
4
 
5
5
  // Requests quota information for a mailbox
6
6
  module.exports = async (connection, path) => {
@@ -63,7 +63,7 @@ module.exports = async (connection, path) => {
63
63
  let quotaFound = false;
64
64
  let response;
65
65
  try {
66
- response = await connection.exec('GETQUOTAROOT', [{ type: 'ATOM', value: path }], {
66
+ response = await connection.exec('GETQUOTAROOT', [{ type: 'ATOM', value: encodePath(connection, path) }], {
67
67
  untagged: {
68
68
  QUOTAROOT: async untagged => {
69
69
  let quotaRoot =
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { normalizePath, getStatusCode, getErrorText } = require('../tools.js');
3
+ const { encodePath, normalizePath, getStatusCode, getErrorText } = require('../tools.js');
4
4
 
5
5
  // Renames existing mailbox
6
6
  module.exports = async (connection, path, newPath) => {
@@ -23,8 +23,8 @@ module.exports = async (connection, path, newPath) => {
23
23
  newPath
24
24
  };
25
25
  response = await connection.exec('RENAME', [
26
- { type: 'ATOM', value: path },
27
- { type: 'ATOM', value: newPath }
26
+ { type: 'ATOM', value: encodePath(connection, path) },
27
+ { type: 'ATOM', value: encodePath(connection, newPath) }
28
28
  ]);
29
29
  response.next();
30
30
  return map;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { normalizePath, getStatusCode, getErrorText } = require('../tools.js');
3
+ const { encodePath, normalizePath, getStatusCode, getErrorText } = require('../tools.js');
4
4
 
5
5
  // Selects a mailbox
6
6
  module.exports = async (connection, path, options) => {
@@ -47,101 +47,105 @@ module.exports = async (connection, path, options) => {
47
47
  map.qresync = true;
48
48
  }
49
49
 
50
- response = await connection.exec(!options.readOnly ? 'SELECT' : 'EXAMINE', [{ type: 'ATOM', value: path }].concat(extraArgs || []), {
51
- untagged: {
52
- OK: async untagged => {
53
- if (!untagged.attributes || !untagged.attributes.length) {
54
- return;
55
- }
56
- let section = !untagged.attributes[0].value && untagged.attributes[0].section;
57
- if (section && section.length > 1 && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
58
- let key = section[0].value.toLowerCase();
59
- let value;
60
-
61
- if (typeof section[1].value === 'string') {
62
- value = section[1].value;
63
- } else if (Array.isArray(section[1])) {
64
- value = section[1].map(entry => (typeof entry.value === 'string' ? entry.value : false)).filter(entry => entry);
50
+ response = await connection.exec(
51
+ !options.readOnly ? 'SELECT' : 'EXAMINE',
52
+ [{ type: 'ATOM', value: encodePath(connection, path) }].concat(extraArgs || []),
53
+ {
54
+ untagged: {
55
+ OK: async untagged => {
56
+ if (!untagged.attributes || !untagged.attributes.length) {
57
+ return;
65
58
  }
66
-
67
- switch (key) {
68
- case 'highestmodseq':
69
- key = 'highestModseq';
70
- if (/^[0-9]+$/.test(value)) {
71
- value = BigInt(value);
72
- }
73
- break;
74
-
75
- case 'mailboxid':
76
- key = 'mailboxId';
77
- if (Array.isArray(value) && value.length) {
78
- value = value[0];
79
- }
80
- break;
81
-
82
- case 'permanentflags':
83
- key = 'permanentFlags';
84
- value = new Set(value);
85
- break;
86
-
87
- case 'uidnext':
88
- key = 'uidNext';
89
- value = Number(value);
90
- break;
91
-
92
- case 'uidvalidity':
93
- key = 'uidValidity';
94
- if (/^[0-9]+$/.test(value)) {
95
- value = BigInt(value);
96
- }
97
- break;
59
+ let section = !untagged.attributes[0].value && untagged.attributes[0].section;
60
+ if (section && section.length > 1 && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
61
+ let key = section[0].value.toLowerCase();
62
+ let value;
63
+
64
+ if (typeof section[1].value === 'string') {
65
+ value = section[1].value;
66
+ } else if (Array.isArray(section[1])) {
67
+ value = section[1].map(entry => (typeof entry.value === 'string' ? entry.value : false)).filter(entry => entry);
68
+ }
69
+
70
+ switch (key) {
71
+ case 'highestmodseq':
72
+ key = 'highestModseq';
73
+ if (/^[0-9]+$/.test(value)) {
74
+ value = BigInt(value);
75
+ }
76
+ break;
77
+
78
+ case 'mailboxid':
79
+ key = 'mailboxId';
80
+ if (Array.isArray(value) && value.length) {
81
+ value = value[0];
82
+ }
83
+ break;
84
+
85
+ case 'permanentflags':
86
+ key = 'permanentFlags';
87
+ value = new Set(value);
88
+ break;
89
+
90
+ case 'uidnext':
91
+ key = 'uidNext';
92
+ value = Number(value);
93
+ break;
94
+
95
+ case 'uidvalidity':
96
+ key = 'uidValidity';
97
+ if (/^[0-9]+$/.test(value)) {
98
+ value = BigInt(value);
99
+ }
100
+ break;
101
+ }
102
+
103
+ map[key] = value;
98
104
  }
99
105
 
100
- map[key] = value;
101
- }
102
-
103
- if (section && section.length === 1 && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
104
- let key = section[0].value.toLowerCase();
105
- switch (key) {
106
- case 'nomodseq':
107
- key = 'noModseq';
108
- map[key] = true;
109
- break;
106
+ if (section && section.length === 1 && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
107
+ let key = section[0].value.toLowerCase();
108
+ switch (key) {
109
+ case 'nomodseq':
110
+ key = 'noModseq';
111
+ map[key] = true;
112
+ break;
113
+ }
114
+ }
115
+ },
116
+ FLAGS: async untagged => {
117
+ if (!untagged.attributes || (!untagged.attributes.length && Array.isArray(untagged.attributes[0]))) {
118
+ return;
119
+ }
120
+ let flags = untagged.attributes[0].map(flag => (typeof flag.value === 'string' ? flag.value : false)).filter(flag => flag);
121
+ map.flags = new Set(flags);
122
+ },
123
+ EXISTS: async untagged => {
124
+ let num = Number(untagged.command);
125
+ if (isNaN(num)) {
126
+ return false;
110
127
  }
111
- }
112
- },
113
- FLAGS: async untagged => {
114
- if (!untagged.attributes || (!untagged.attributes.length && Array.isArray(untagged.attributes[0]))) {
115
- return;
116
- }
117
- let flags = untagged.attributes[0].map(flag => (typeof flag.value === 'string' ? flag.value : false)).filter(flag => flag);
118
- map.flags = new Set(flags);
119
- },
120
- EXISTS: async untagged => {
121
- let num = Number(untagged.command);
122
- if (isNaN(num)) {
123
- return false;
124
- }
125
128
 
126
- map.exists = num;
127
- },
128
- VANISHED: async untagged => {
129
- await connection.untaggedVanished(
130
- untagged,
131
- // mailbox is not yet open, so use a dummy mailbox object
132
- { path, uidNext: false, uidValidity: false }
133
- );
134
- },
135
- // we should only get an untagged FETCH for a SELECT/EXAMINE if QRESYNC was asked for
136
- FETCH: async untagged => {
137
- await connection.untaggedFetch(
138
- untagged,
139
- // mailbox is not yet open, so use a dummy mailbox object
140
- { path, uidNext: false, uidValidity: false }
141
- );
129
+ map.exists = num;
130
+ },
131
+ VANISHED: async untagged => {
132
+ await connection.untaggedVanished(
133
+ untagged,
134
+ // mailbox is not yet open, so use a dummy mailbox object
135
+ { path, uidNext: false, uidValidity: false }
136
+ );
137
+ },
138
+ // we should only get an untagged FETCH for a SELECT/EXAMINE if QRESYNC was asked for
139
+ FETCH: async untagged => {
140
+ await connection.untaggedFetch(
141
+ untagged,
142
+ // mailbox is not yet open, so use a dummy mailbox object
143
+ { path, uidNext: false, uidValidity: false }
144
+ );
145
+ }
142
146
  }
143
147
  }
144
- });
148
+ );
145
149
 
146
150
  let section = !response.response.attributes[0].value && response.response.attributes[0].section;
147
151
  if (section && section.length && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { normalizePath } = require('../tools.js');
3
+ const { encodePath, normalizePath } = require('../tools.js');
4
4
 
5
5
  // Requests info about a mailbox
6
6
  module.exports = async (connection, path, query) => {
@@ -11,7 +11,7 @@ module.exports = async (connection, path, query) => {
11
11
 
12
12
  path = normalizePath(connection, path);
13
13
 
14
- let attributes = [{ type: 'ATOM', value: path }];
14
+ let attributes = [{ type: 'ATOM', value: encodePath(connection, path) }];
15
15
 
16
16
  let queryAttributes = [];
17
17
  Object.keys(query || {}).forEach(key => {
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { normalizePath, getStatusCode, getErrorText } = require('../tools.js');
3
+ const { encodePath, normalizePath, getStatusCode, getErrorText } = require('../tools.js');
4
4
 
5
5
  // Subscribes to a mailbox
6
6
  module.exports = async (connection, path) => {
@@ -13,7 +13,7 @@ module.exports = async (connection, path) => {
13
13
 
14
14
  let response;
15
15
  try {
16
- response = await connection.exec('SUBSCRIBE', [{ type: 'ATOM', value: path }]);
16
+ response = await connection.exec('SUBSCRIBE', [{ type: 'ATOM', value: encodePath(connection, path) }]);
17
17
  response.next();
18
18
  return true;
19
19
  } catch (err) {
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { normalizePath, getStatusCode, getErrorText } = require('../tools.js');
3
+ const { encodePath, normalizePath, getStatusCode, getErrorText } = require('../tools.js');
4
4
 
5
5
  // Unsubscribes from a mailbox
6
6
  module.exports = async (connection, path) => {
@@ -13,7 +13,7 @@ module.exports = async (connection, path) => {
13
13
 
14
14
  let response;
15
15
  try {
16
- response = await connection.exec('UNSUBSCRIBE', [{ type: 'ATOM', value: path }]);
16
+ response = await connection.exec('UNSUBSCRIBE', [{ type: 'ATOM', value: encodePath(connection, path) }]);
17
17
  response.next();
18
18
  return true;
19
19
  } catch (err) {
@@ -26,7 +26,7 @@ function excludeChars(source, exclude) {
26
26
  module.exports = {
27
27
  CHAR() {
28
28
  let value = expandRange(0x01, 0x7f);
29
- this.CHAR = function() {
29
+ this.CHAR = function () {
30
30
  return value;
31
31
  };
32
32
  return value;
@@ -34,7 +34,7 @@ module.exports = {
34
34
 
35
35
  CHAR8() {
36
36
  let value = expandRange(0x01, 0xff);
37
- this.CHAR8 = function() {
37
+ this.CHAR8 = function () {
38
38
  return value;
39
39
  };
40
40
  return value;
@@ -46,7 +46,7 @@ module.exports = {
46
46
 
47
47
  CTL() {
48
48
  let value = expandRange(0x00, 0x1f) + '\x7F';
49
- this.CTL = function() {
49
+ this.CTL = function () {
50
50
  return value;
51
51
  };
52
52
  return value;
@@ -58,7 +58,7 @@ module.exports = {
58
58
 
59
59
  ALPHA() {
60
60
  let value = expandRange(0x41, 0x5a) + expandRange(0x61, 0x7a);
61
- this.ALPHA = function() {
61
+ this.ALPHA = function () {
62
62
  return value;
63
63
  };
64
64
  return value;
@@ -66,7 +66,7 @@ module.exports = {
66
66
 
67
67
  DIGIT() {
68
68
  let value = expandRange(0x30, 0x39) + expandRange(0x61, 0x7a);
69
- this.DIGIT = function() {
69
+ this.DIGIT = function () {
70
70
  return value;
71
71
  };
72
72
  return value;
@@ -74,7 +74,7 @@ module.exports = {
74
74
 
75
75
  'ATOM-CHAR'() {
76
76
  let value = excludeChars(this.CHAR(), this['atom-specials']());
77
- this['ATOM-CHAR'] = function() {
77
+ this['ATOM-CHAR'] = function () {
78
78
  return value;
79
79
  };
80
80
  return value;
@@ -82,7 +82,7 @@ module.exports = {
82
82
 
83
83
  'ASTRING-CHAR'() {
84
84
  let value = this['ATOM-CHAR']() + this['resp-specials']();
85
- this['ASTRING-CHAR'] = function() {
85
+ this['ASTRING-CHAR'] = function () {
86
86
  return value;
87
87
  };
88
88
  return value;
@@ -90,7 +90,7 @@ module.exports = {
90
90
 
91
91
  'TEXT-CHAR'() {
92
92
  let value = excludeChars(this.CHAR(), '\r\n');
93
- this['TEXT-CHAR'] = function() {
93
+ this['TEXT-CHAR'] = function () {
94
94
  return value;
95
95
  };
96
96
  return value;
@@ -98,7 +98,7 @@ module.exports = {
98
98
 
99
99
  'atom-specials'() {
100
100
  let value = '(' + ')' + '{' + this.SP() + this.CTL() + this['list-wildcards']() + this['quoted-specials']() + this['resp-specials']();
101
- this['atom-specials'] = function() {
101
+ this['atom-specials'] = function () {
102
102
  return value;
103
103
  };
104
104
  return value;
@@ -109,8 +109,8 @@ module.exports = {
109
109
  },
110
110
 
111
111
  'quoted-specials'() {
112
- let value = this.DQUOTE() + '\\';
113
- this['quoted-specials'] = function() {
112
+ let value = this.DQUOTE() + "'\\";
113
+ this['quoted-specials'] = function () {
114
114
  return value;
115
115
  };
116
116
  return value;
@@ -122,7 +122,7 @@ module.exports = {
122
122
 
123
123
  tag() {
124
124
  let value = excludeChars(this['ASTRING-CHAR'](), '+');
125
- this.tag = function() {
125
+ this.tag = function () {
126
126
  return value;
127
127
  };
128
128
  return value;
@@ -130,7 +130,7 @@ module.exports = {
130
130
 
131
131
  command() {
132
132
  let value = this.ALPHA() + this.DIGIT() + '-';
133
- this.command = function() {
133
+ this.command = function () {
134
134
  return value;
135
135
  };
136
136
  return value;
@@ -189,6 +189,7 @@ class TokenParser {
189
189
  case '{':
190
190
  this.currentNode = this.createNode(this.currentNode, this.pos + i);
191
191
  this.currentNode.type = 'LITERAL';
192
+ this.currentNode.literalType = 'literal';
192
193
  this.state = 'LITERAL';
193
194
  this.currentNode.closed = false;
194
195
  break;
@@ -385,10 +386,13 @@ class TokenParser {
385
386
  if (this.currentNode.started) {
386
387
  // only relevant if literals are not already parsed out from input
387
388
 
388
- //if(imapFormalSyntax['CHAR8']().indexOf(chr) < 0){
389
+ // Disabled NUL check
390
+ // See https://github.com/emailjs/emailjs-imap-handler/commit/f11b2822bedabe492236e8263afc630134a3c41c
391
+ /*
389
392
  if (chr === '\u0000') {
390
393
  throw new Error('Unexpected \\x00 at position ' + (this.pos + i));
391
394
  }
395
+ */
392
396
 
393
397
  this.currentNode.chBuffer[this.currentNode.chPos++] = chr.charCodeAt(0);
394
398
 
package/lib/imap-flow.js CHANGED
@@ -261,7 +261,7 @@ class ImapFlow extends EventEmitter {
261
261
  this.currentLock = false;
262
262
  this.locks = [];
263
263
 
264
- this.maxIdleTime = options.maxIdleTime || false;
264
+ this.maxIdleTime = this.options.maxIdleTime || false;
265
265
 
266
266
  this.streamer.on('error', err => {
267
267
  if (['Z_BUF_ERROR', 'ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'EHOSTUNREACH'].includes(err.code)) {
@@ -1063,6 +1063,15 @@ class ImapFlow extends EventEmitter {
1063
1063
  range = range.toString();
1064
1064
  }
1065
1065
 
1066
+ // special case, some servers allow this, some do not, so replace it with the last known EXISTS value
1067
+ if (range === '*') {
1068
+ if (!this.mailbox.exists) {
1069
+ return false;
1070
+ }
1071
+ range = this.mailbox.exists.toString();
1072
+ options.uid = false; // sequence query
1073
+ }
1074
+
1066
1075
  if (range && typeof range === 'object' && !Array.isArray(range)) {
1067
1076
  if (range.all && Object.keys(range) === 1) {
1068
1077
  range = '1:*';
@@ -1357,9 +1366,12 @@ class ImapFlow extends EventEmitter {
1357
1366
  /**
1358
1367
  * @typedef {Object} ListResponse
1359
1368
  * @global
1360
- * @property {String} path mailbox path
1369
+ * @property {String} path mailbox path (unicode string)
1370
+ * @property {String} pathAsListed mailbox path as listed in the LIST/LSUB response
1361
1371
  * @property {String} name mailbox name (last part of path after delimiter)
1362
1372
  * @property {String} delimiter mailbox path delimiter, usually "." or "/"
1373
+ * @property {Array<string>} parent An array of parent folder names. All names are in unicode
1374
+ * @property {String} parentPath Same as `parent`, but as a complete string path (unicode string)
1363
1375
  * @property {Set<string>} flags a set of flags for this mailbox
1364
1376
  * @property {String} specialUse one of special-use flags (if applicable): "\All", "\Archive", "\Drafts", "\Flagged", "\Junk", "\Sent", "\Trash". Additionally INBOX has non-standard "\Inbox" flag set
1365
1377
  * @property {Boolean} listed `true` if mailbox was found from the output of LIST command
@@ -1563,7 +1575,7 @@ class ImapFlow extends EventEmitter {
1563
1575
  /**
1564
1576
  * Requests the status of the indicated mailbox. Only requested status values will be returned.
1565
1577
  *
1566
- * @param {String} path mailbox path to check for
1578
+ * @param {String} path mailbox path to check for (unicode string)
1567
1579
  * @param {Object} query defines requested status items
1568
1580
  * @param {Boolean} query.messages if `true` request count of messages
1569
1581
  * @param {Boolean} query.recent if `true` request count of messages with \\Recent tag
@@ -1786,7 +1798,7 @@ class ImapFlow extends EventEmitter {
1786
1798
  /**
1787
1799
  * Appends a new message to a mailbox
1788
1800
  *
1789
- * @param {String} path Mailbox path to upload the message to
1801
+ * @param {String} path Mailbox path to upload the message to (unicode string)
1790
1802
  * @param {string|Buffer} content RFC822 formatted email message
1791
1803
  * @param {string[]} [flags] an array of flags to be set for the uploaded message
1792
1804
  * @param {Date|string} [idate=now] internal date to be set for the message
@@ -2096,6 +2108,14 @@ class ImapFlow extends EventEmitter {
2096
2108
  return;
2097
2109
  }
2098
2110
 
2111
+ if (seq === '*') {
2112
+ if (!this.mailbox.exists) {
2113
+ return false;
2114
+ }
2115
+ seq = this.mailbox.exists.toString();
2116
+ options = Object.assign({}, options || {}, { uid: false }); // force into a sequence query
2117
+ }
2118
+
2099
2119
  let response = await this.run('FETCH', (seq || '').toString(), query, options);
2100
2120
 
2101
2121
  if (!response || !response.list || !response.list.length) {
package/lib/tools.js CHANGED
@@ -9,6 +9,30 @@ const { JPDecoder } = require('./jp-decoder');
9
9
  const iconv = require('iconv-lite');
10
10
 
11
11
  module.exports = {
12
+ encodePath(connection, path) {
13
+ path = (path || '').toString();
14
+ if (!connection.enabled.has('UTF8=ACCEPT') && /[&\x00-\x08\x0b-\x0c\x0e-\x1f\u0080-\uffff]/.test(path)) {
15
+ try {
16
+ path = iconv.encode(path, 'utf-7-imap').toString();
17
+ } catch (err) {
18
+ // ignore, keep name as is
19
+ }
20
+ }
21
+ return path;
22
+ },
23
+
24
+ decodePath(connection, path) {
25
+ path = (path || '').toString();
26
+ if (!connection.enabled.has('UTF8=ACCEPT') && /[&]/.test(path)) {
27
+ try {
28
+ path = iconv.decode(Buffer.from(path), 'utf-7-imap').toString();
29
+ } catch (err) {
30
+ // ignore, keep name as is
31
+ }
32
+ }
33
+ return path;
34
+ },
35
+
12
36
  normalizePath(connection, path, skipNamespace) {
13
37
  if (Array.isArray(path)) {
14
38
  path = path.join((connection.namespace && connection.namespace.delimiter) || '');
@@ -19,14 +43,6 @@ module.exports = {
19
43
  return 'INBOX';
20
44
  }
21
45
 
22
- if (!connection.enabled.has('UTF8=ACCEPT') && /[\x00-\x08\x0b-\x0c\x0e-\x1f\u0080-\uffff]/.test(path)) {
23
- try {
24
- path = iconv.encode(path, 'utf-7-imap').toString();
25
- } catch (err) {
26
- // ignore, keep name as is
27
- }
28
- }
29
-
30
46
  // ensure namespace prefix if needed
31
47
  if (!skipNamespace && connection.namespace && connection.namespace.prefix && path.indexOf(connection.namespace.prefix) !== 0) {
32
48
  path = connection.namespace.prefix + path;
@@ -290,9 +306,10 @@ module.exports = {
290
306
  break;
291
307
  }
292
308
 
293
- default:
294
- if (key.indexOf('body[') >= 0) {
295
- let partKey = key.replace(/^body\[|]$/g, '');
309
+ default: {
310
+ let match = key.match(/(body)\[/i);
311
+ if (match) {
312
+ let partKey = key.replace(/^(body)\[|]$/gi, '');
296
313
  partKey = partKey.replace(/\.fields.*$/g, '');
297
314
 
298
315
  let value = getBuffer(attribute);
@@ -308,6 +325,7 @@ module.exports = {
308
325
  break;
309
326
  }
310
327
  break;
328
+ }
311
329
  }
312
330
  }
313
331
 
package/lib/types.d.ts CHANGED
@@ -241,7 +241,7 @@ declare module "imapflow" {
241
241
  * let status = await client.status('INBOX', {unseen: true});
242
242
  * console.log(status.unseen);
243
243
  * // 123
244
- * @param path - mailbox path to check for
244
+ * @param path - mailbox path to check for (unicode string)
245
245
  * @param query - defines requested status items
246
246
  * @param query.messages - if `true` request count of messages
247
247
  * @param query.recent - if `true` request count of messages with \\Recent tag
@@ -342,7 +342,7 @@ declare module "imapflow" {
342
342
  * Appends a new message to a mailbox
343
343
  * @example
344
344
  * await client.append('INBOX', rawMessageBuffer, ['\\Seen'], new Date(2000, 1, 1));
345
- * @param path - Mailbox path to upload the message to
345
+ * @param path - Mailbox path to upload the message to (unicode string)
346
346
  * @param content - RFC822 formatted email message
347
347
  * @param [flags] - an array of flags to be set for the uploaded message
348
348
  * @param [idate = now] - internal date to be set for the message
@@ -565,9 +565,12 @@ declare type QuotaResponse = {
565
565
  };
566
566
 
567
567
  /**
568
- * @property path - mailbox path
568
+ * @property path - mailbox path (unicode string)
569
+ * @property pathAsListed - mailbox path as listed in the LIST/LSUB response
569
570
  * @property name - mailbox name (last part of path after delimiter)
570
571
  * @property delimiter - mailbox path delimiter, usually "." or "/"
572
+ * @property parent - An array of parent folder names. All names are in unicode
573
+ * @property parentPath - Same as `parent`, but as a complete string path (unicode string)
571
574
  * @property flags - a set of flags for this mailbox
572
575
  * @property specialUse - one of special-use flags (if applicable): "\All", "\Archive", "\Drafts", "\Flagged", "\Junk", "\Sent", "\Trash". Additionally INBOX has non-standard "\Inbox" flag set
573
576
  * @property listed - `true` if mailbox was found from the output of LIST command
@@ -575,8 +578,11 @@ declare type QuotaResponse = {
575
578
  */
576
579
  declare type ListResponse = {
577
580
  path: string;
581
+ pathAsListed: string;
578
582
  name: string;
579
583
  delimiter: string;
584
+ parent: string[];
585
+ parentPath: string;
580
586
  flags: Set<string>;
581
587
  specialUse: string;
582
588
  listed: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imapflow",
3
- "version": "1.0.90",
3
+ "version": "1.0.91",
4
4
  "description": "IMAP Client for Node",
5
5
  "main": "./lib/imap-flow.js",
6
6
  "scripts": {
@@ -8,7 +8,8 @@
8
8
  "prepare": "npm run build",
9
9
  "docs": "rm -rf docs && mkdir -p docs && jsdoc lib/imap-flow.js -c jsdoc.json -R README.md --destination docs/ && cp assets/favicon.ico docs",
10
10
  "dst": "node types.js",
11
- "build": "npm run docs && npm run dst"
11
+ "build": "npm run docs && npm run dst",
12
+ "st": "npm run docs && st -d docs -i index.html"
12
13
  },
13
14
  "repository": {
14
15
  "type": "git",
@@ -29,28 +30,29 @@
29
30
  "@babel/eslint-parser": "7.17.0",
30
31
  "@babel/eslint-plugin": "7.17.7",
31
32
  "@babel/plugin-syntax-class-properties": "7.12.13",
32
- "@babel/preset-env": "7.16.11",
33
- "@types/node": "17.0.23",
33
+ "@babel/preset-env": "7.17.10",
34
+ "@types/node": "17.0.31",
34
35
  "braintree-jsdoc-template": "3.3.0",
35
- "eslint": "8.12.0",
36
+ "eslint": "8.14.0",
36
37
  "eslint-config-nodemailer": "1.2.0",
37
38
  "eslint-config-prettier": "8.5.0",
38
- "grunt": "1.4.1",
39
+ "grunt": "1.5.2",
39
40
  "grunt-cli": "1.4.3",
40
41
  "grunt-contrib-nodeunit": "4.0.0",
41
42
  "grunt-eslint": "24.0.0",
42
43
  "jsdoc": "3.6.10",
44
+ "st": "3.0.0",
43
45
  "tsd-jsdoc": "2.5.0"
44
46
  },
45
47
  "dependencies": {
46
48
  "encoding-japanese": "2.0.0",
47
49
  "iconv-lite": "0.6.3",
48
50
  "libbase64": "1.2.1",
49
- "libmime": "5.0.0",
51
+ "libmime": "5.1.0",
50
52
  "libqp": "1.1.0",
51
- "mailsplit": "5.3.1",
52
- "nodemailer": "6.7.3",
53
- "pino": "7.9.2",
53
+ "mailsplit": "5.3.2",
54
+ "nodemailer": "6.7.4",
55
+ "pino": "7.11.0",
54
56
  "socks": "2.6.2"
55
57
  }
56
58
  }