imapflow 1.0.132 → 1.0.134

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,9 @@
1
1
  # CHANGELOG
2
2
 
3
+ - 1.0.134 - 2023-07-20
4
+
5
+ - Fixed unicode search if UTF8=ACCEPT extension was enabled
6
+
3
7
  - 1.0.131 - 2023-06-29
4
8
 
5
9
  - Fixed issue with labels that start with tilde
@@ -14,7 +14,7 @@ module.exports = async connection => {
14
14
 
15
15
  let currentMailbox = connection.mailbox;
16
16
  connection.mailbox = false;
17
- connection.mailboxOptions = false;
17
+ connection.currentSelectCommand = false;
18
18
  connection.state = connection.states.AUTHENTICATED;
19
19
 
20
20
  if (currentMailbox) {
@@ -138,8 +138,11 @@ module.exports = async (connection, maxIdleTime) => {
138
138
  }
139
139
 
140
140
  let idleTimer;
141
-
142
141
  return new Promise(resolve => {
142
+ if (!connection.currentSelectCommand) {
143
+ return resolve();
144
+ }
145
+
143
146
  // no IDLE support, fallback to NOOP'ing
144
147
  connection.preCheck = async () => {
145
148
  connection.preCheck = false; // unset itself
@@ -149,13 +152,28 @@ module.exports = async (connection, maxIdleTime) => {
149
152
  resolve();
150
153
  };
151
154
 
155
+ let selectCommand = connection.currentSelectCommand;
156
+
152
157
  let idleCheck = async () => {
153
158
  let response;
154
159
  switch (connection.missingIdleCommand) {
155
160
  case 'SELECT':
161
+ // FIXME: somehow a loop occurs after some time of idling with SELECT
156
162
  connection.log.debug({ src: 'c', msg: `Running SELECT to detect changes in folder` });
157
- response = await connection.exec('SELECT', connection.mailbox.path, connection.mailboxOptions);
163
+ response = await connection.exec(selectCommand.command, selectCommand.arguments);
158
164
  break;
165
+
166
+ case 'STATUS':
167
+ {
168
+ let statusArgs = [selectCommand.arguments[0], []]; // path
169
+ for (let key of ['MESSAGES', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN']) {
170
+ statusArgs[1].push({ type: 'ATOM', value: key.toUpperCase() });
171
+ }
172
+ connection.log.debug({ src: 'c', msg: `Running STATUS to detect changes in folder` });
173
+ response = await connection.exec('STATUS', statusArgs);
174
+ }
175
+ break;
176
+
159
177
  case 'NOOP':
160
178
  default:
161
179
  response = await connection.exec('NOOP', false, { comment: 'IDLE not supported' });
@@ -49,105 +49,106 @@ module.exports = async (connection, path, options) => {
49
49
 
50
50
  let encodedPath = encodePath(connection, path);
51
51
 
52
- response = await connection.exec(
53
- !options.readOnly ? 'SELECT' : 'EXAMINE',
54
- [{ type: encodedPath.indexOf('&') >= 0 ? 'STRING' : 'ATOM', value: encodedPath }].concat(extraArgs || []),
55
- {
56
- untagged: {
57
- OK: async untagged => {
58
- if (!untagged.attributes || !untagged.attributes.length) {
59
- return;
60
- }
61
- let section = !untagged.attributes[0].value && untagged.attributes[0].section;
62
- if (section && section.length > 1 && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
63
- let key = section[0].value.toLowerCase();
64
- let value;
65
-
66
- if (typeof section[1].value === 'string') {
67
- value = section[1].value;
68
- } else if (Array.isArray(section[1])) {
69
- value = section[1].map(entry => (typeof entry.value === 'string' ? entry.value : false)).filter(entry => entry);
70
- }
71
-
72
- switch (key) {
73
- case 'highestmodseq':
74
- key = 'highestModseq';
75
- if (/^[0-9]+$/.test(value)) {
76
- value = BigInt(value);
77
- }
78
- break;
79
-
80
- case 'mailboxid':
81
- key = 'mailboxId';
82
- if (Array.isArray(value) && value.length) {
83
- value = value[0];
84
- }
85
- break;
86
-
87
- case 'permanentflags':
88
- key = 'permanentFlags';
89
- value = new Set(value);
90
- break;
91
-
92
- case 'uidnext':
93
- key = 'uidNext';
94
- value = Number(value);
95
- break;
96
-
97
- case 'uidvalidity':
98
- key = 'uidValidity';
99
- if (/^[0-9]+$/.test(value)) {
100
- value = BigInt(value);
101
- }
102
- break;
103
- }
104
-
105
- map[key] = value;
52
+ let selectCommand = {
53
+ command: !options.readOnly ? 'SELECT' : 'EXAMINE',
54
+ arguments: [{ type: encodedPath.indexOf('&') >= 0 ? 'STRING' : 'ATOM', value: encodedPath }].concat(extraArgs || [])
55
+ };
56
+
57
+ response = await connection.exec(selectCommand.command, selectCommand.arguments, {
58
+ untagged: {
59
+ OK: async untagged => {
60
+ if (!untagged.attributes || !untagged.attributes.length) {
61
+ return;
62
+ }
63
+ let section = !untagged.attributes[0].value && untagged.attributes[0].section;
64
+ if (section && section.length > 1 && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
65
+ let key = section[0].value.toLowerCase();
66
+ let value;
67
+
68
+ if (typeof section[1].value === 'string') {
69
+ value = section[1].value;
70
+ } else if (Array.isArray(section[1])) {
71
+ value = section[1].map(entry => (typeof entry.value === 'string' ? entry.value : false)).filter(entry => entry);
106
72
  }
107
73
 
108
- if (section && section.length === 1 && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
109
- let key = section[0].value.toLowerCase();
110
- switch (key) {
111
- case 'nomodseq':
112
- key = 'noModseq';
113
- map[key] = true;
114
- break;
115
- }
116
- }
117
- },
118
- FLAGS: async untagged => {
119
- if (!untagged.attributes || (!untagged.attributes.length && Array.isArray(untagged.attributes[0]))) {
120
- return;
121
- }
122
- let flags = untagged.attributes[0].map(flag => (typeof flag.value === 'string' ? flag.value : false)).filter(flag => flag);
123
- map.flags = new Set(flags);
124
- },
125
- EXISTS: async untagged => {
126
- let num = Number(untagged.command);
127
- if (isNaN(num)) {
128
- return false;
74
+ switch (key) {
75
+ case 'highestmodseq':
76
+ key = 'highestModseq';
77
+ if (/^[0-9]+$/.test(value)) {
78
+ value = BigInt(value);
79
+ }
80
+ break;
81
+
82
+ case 'mailboxid':
83
+ key = 'mailboxId';
84
+ if (Array.isArray(value) && value.length) {
85
+ value = value[0];
86
+ }
87
+ break;
88
+
89
+ case 'permanentflags':
90
+ key = 'permanentFlags';
91
+ value = new Set(value);
92
+ break;
93
+
94
+ case 'uidnext':
95
+ key = 'uidNext';
96
+ value = Number(value);
97
+ break;
98
+
99
+ case 'uidvalidity':
100
+ key = 'uidValidity';
101
+ if (/^[0-9]+$/.test(value)) {
102
+ value = BigInt(value);
103
+ }
104
+ break;
129
105
  }
130
106
 
131
- map.exists = num;
132
- },
133
- VANISHED: async untagged => {
134
- await connection.untaggedVanished(
135
- untagged,
136
- // mailbox is not yet open, so use a dummy mailbox object
137
- { path, uidNext: false, uidValidity: false }
138
- );
139
- },
140
- // we should only get an untagged FETCH for a SELECT/EXAMINE if QRESYNC was asked for
141
- FETCH: async untagged => {
142
- await connection.untaggedFetch(
143
- untagged,
144
- // mailbox is not yet open, so use a dummy mailbox object
145
- { path, uidNext: false, uidValidity: false }
146
- );
107
+ map[key] = value;
147
108
  }
109
+
110
+ if (section && section.length === 1 && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
111
+ let key = section[0].value.toLowerCase();
112
+ switch (key) {
113
+ case 'nomodseq':
114
+ key = 'noModseq';
115
+ map[key] = true;
116
+ break;
117
+ }
118
+ }
119
+ },
120
+ FLAGS: async untagged => {
121
+ if (!untagged.attributes || (!untagged.attributes.length && Array.isArray(untagged.attributes[0]))) {
122
+ return;
123
+ }
124
+ let flags = untagged.attributes[0].map(flag => (typeof flag.value === 'string' ? flag.value : false)).filter(flag => flag);
125
+ map.flags = new Set(flags);
126
+ },
127
+ EXISTS: async untagged => {
128
+ let num = Number(untagged.command);
129
+ if (isNaN(num)) {
130
+ return false;
131
+ }
132
+
133
+ map.exists = num;
134
+ },
135
+ VANISHED: async untagged => {
136
+ await connection.untaggedVanished(
137
+ untagged,
138
+ // mailbox is not yet open, so use a dummy mailbox object
139
+ { path, uidNext: false, uidValidity: false }
140
+ );
141
+ },
142
+ // we should only get an untagged FETCH for a SELECT/EXAMINE if QRESYNC was asked for
143
+ FETCH: async untagged => {
144
+ await connection.untaggedFetch(
145
+ untagged,
146
+ // mailbox is not yet open, so use a dummy mailbox object
147
+ { path, uidNext: false, uidValidity: false }
148
+ );
148
149
  }
149
150
  }
150
- );
151
+ });
151
152
 
152
153
  let section = !response.response.attributes[0].value && response.response.attributes[0].section;
153
154
  if (section && section.length && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
@@ -183,7 +184,7 @@ module.exports = async (connection, path, options) => {
183
184
  }
184
185
 
185
186
  connection.mailbox = map;
186
- connection.mailboxOptions = options;
187
+ connection.currentSelectCommand = selectCommand;
187
188
  connection.state = connection.states.SELECTED;
188
189
 
189
190
  if (!currentMailbox || currentMailbox.path !== path) {
@@ -205,7 +206,7 @@ module.exports = async (connection, path, options) => {
205
206
  let currentMailbox = connection.mailbox;
206
207
 
207
208
  connection.mailbox = false;
208
- connection.mailboxOptions = false;
209
+ connection.currentSelectCommand = false;
209
210
  connection.state = connection.states.AUTHENTICATED;
210
211
 
211
212
  if (currentMailbox) {
package/lib/imap-flow.js CHANGED
@@ -248,7 +248,7 @@ class ImapFlow extends EventEmitter {
248
248
  * @type {MailboxObject|Boolean}
249
249
  */
250
250
  this.mailbox = false;
251
- this.mailboxOptions = false;
251
+ this.currentSelectCommand = false;
252
252
 
253
253
  /**
254
254
  * Is current mailbox idling (`true`) or not (`false`)
@@ -259,7 +259,8 @@ module.exports.searchCompiler = (connection, query) => {
259
259
 
260
260
  walk(query);
261
261
 
262
- if (hasUnicode) {
262
+ if (hasUnicode && !connection.enabled.has('UTF8=ACCEPT')) {
263
+ // Prepend search query with `CHARSET UTF-8`
263
264
  attributes.unshift({ type: 'ATOM', value: 'UTF-8' });
264
265
  attributes.unshift({ type: 'ATOM', value: 'CHARSET' });
265
266
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imapflow",
3
- "version": "1.0.132",
3
+ "version": "1.0.134",
4
4
  "description": "IMAP Client for Node",
5
5
  "main": "./lib/imap-flow.js",
6
6
  "scripts": {
@@ -27,19 +27,19 @@
27
27
  },
28
28
  "homepage": "https://imapflow.com/",
29
29
  "devDependencies": {
30
- "@babel/eslint-parser": "7.22.5",
30
+ "@babel/eslint-parser": "7.22.9",
31
31
  "@babel/eslint-plugin": "7.22.5",
32
32
  "@babel/plugin-syntax-class-properties": "7.12.13",
33
- "@babel/preset-env": "7.22.5",
34
- "@types/node": "20.3.2",
33
+ "@babel/preset-env": "7.22.9",
34
+ "@types/node": "20.4.2",
35
35
  "braintree-jsdoc-template": "3.3.0",
36
- "eslint": "8.43.0",
36
+ "eslint": "8.45.0",
37
37
  "eslint-config-nodemailer": "1.2.0",
38
38
  "eslint-config-prettier": "8.8.0",
39
39
  "grunt": "1.6.1",
40
40
  "grunt-cli": "1.4.3",
41
41
  "grunt-contrib-nodeunit": "5.0.0",
42
- "grunt-eslint": "24.2.0",
42
+ "grunt-eslint": "24.3.0",
43
43
  "jsdoc": "3.6.11",
44
44
  "st": "3.0.0",
45
45
  "tsd-jsdoc": "2.5.0"
@@ -51,7 +51,7 @@
51
51
  "libmime": "5.2.1",
52
52
  "libqp": "2.0.1",
53
53
  "mailsplit": "5.4.0",
54
- "nodemailer": "6.9.3",
54
+ "nodemailer": "6.9.4",
55
55
  "pino": "8.14.1",
56
56
  "socks": "2.7.1"
57
57
  }