imapflow 1.0.183 → 1.0.185
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 +15 -0
- package/lib/commands/delete.js +1 -1
- package/lib/commands/rename.js +1 -1
- package/lib/imap-flow.js +34 -18
- package/lib/search-compiler.js +13 -0
- package/lib/tools.js +9 -4
- package/lib/types.d.ts +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.185](https://github.com/postalsys/imapflow/compare/v1.0.184...v1.0.185) (2025-04-14)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **search:** add a `not` option for search options ([#281](https://github.com/postalsys/imapflow/issues/281)) ([000686b](https://github.com/postalsys/imapflow/commit/000686b2ace1ef0e21cca64d8e056d1f7f7f8914))
|
|
9
|
+
|
|
10
|
+
## [1.0.184](https://github.com/postalsys/imapflow/compare/v1.0.183...v1.0.184) (2025-03-11)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* Allow to call the connect method only once ([ca7ec96](https://github.com/postalsys/imapflow/commit/ca7ec969f19a93c5dc574bf3873cd7afc4d7af47))
|
|
16
|
+
* **Authentication:** Report errors; Fix LOGINDISABLED ([#275](https://github.com/postalsys/imapflow/issues/275)) ([a3a3fb9](https://github.com/postalsys/imapflow/commit/a3a3fb993dee66c02b34bba0e0b26fc349487b26))
|
|
17
|
+
|
|
3
18
|
## [1.0.183](https://github.com/postalsys/imapflow/compare/v1.0.182...v1.0.183) (2025-03-09)
|
|
4
19
|
|
|
5
20
|
|
package/lib/commands/delete.js
CHANGED
|
@@ -11,7 +11,7 @@ module.exports = async (connection, path) => {
|
|
|
11
11
|
|
|
12
12
|
path = normalizePath(connection, path);
|
|
13
13
|
|
|
14
|
-
if (connection.states.SELECTED && connection.mailbox.path === path) {
|
|
14
|
+
if (connection.state === connection.states.SELECTED && connection.mailbox.path === path) {
|
|
15
15
|
await connection.run('CLOSE');
|
|
16
16
|
}
|
|
17
17
|
|
package/lib/commands/rename.js
CHANGED
|
@@ -12,7 +12,7 @@ module.exports = async (connection, path, newPath) => {
|
|
|
12
12
|
path = normalizePath(connection, path);
|
|
13
13
|
newPath = normalizePath(connection, newPath);
|
|
14
14
|
|
|
15
|
-
if (connection.states.SELECTED && connection.mailbox.path === path) {
|
|
15
|
+
if (connection.state === connection.states.SELECTED && connection.mailbox.path === path) {
|
|
16
16
|
await connection.run('CLOSE');
|
|
17
17
|
}
|
|
18
18
|
|
package/lib/imap-flow.js
CHANGED
|
@@ -38,6 +38,7 @@ const {
|
|
|
38
38
|
packMessageRange,
|
|
39
39
|
normalizePath,
|
|
40
40
|
expandRange,
|
|
41
|
+
AuthenticationFailure,
|
|
41
42
|
getColorFlags
|
|
42
43
|
} = require('./tools');
|
|
43
44
|
|
|
@@ -280,6 +281,8 @@ class ImapFlow extends EventEmitter {
|
|
|
280
281
|
this.socket = false;
|
|
281
282
|
this.writeSocket = false;
|
|
282
283
|
|
|
284
|
+
this.isClosed = false;
|
|
285
|
+
|
|
283
286
|
this.states = states;
|
|
284
287
|
this.state = this.states.NOT_AUTHENTICATED;
|
|
285
288
|
|
|
@@ -370,6 +373,9 @@ class ImapFlow extends EventEmitter {
|
|
|
370
373
|
setImmediate(() => this.close());
|
|
371
374
|
this.emitError(err);
|
|
372
375
|
});
|
|
376
|
+
|
|
377
|
+
// Has the `connect` method already been called
|
|
378
|
+
this._connectCalled = false;
|
|
373
379
|
}
|
|
374
380
|
|
|
375
381
|
emitError(err) {
|
|
@@ -387,7 +393,7 @@ class ImapFlow extends EventEmitter {
|
|
|
387
393
|
}
|
|
388
394
|
|
|
389
395
|
write(chunk) {
|
|
390
|
-
if (this.socket.destroyed) {
|
|
396
|
+
if (!this.socket || this.socket.destroyed) {
|
|
391
397
|
// do not write after connection end or logout
|
|
392
398
|
const error = new Error('Socket is already closed');
|
|
393
399
|
error.code = 'NoConnection';
|
|
@@ -503,7 +509,7 @@ class ImapFlow extends EventEmitter {
|
|
|
503
509
|
}
|
|
504
510
|
|
|
505
511
|
async exec(command, attributes, options) {
|
|
506
|
-
if (this.socket.destroyed) {
|
|
512
|
+
if (!this.socket || this.socket.destroyed) {
|
|
507
513
|
let error = new Error('Connection closed');
|
|
508
514
|
error.code = 'EConnectionClosed';
|
|
509
515
|
throw error;
|
|
@@ -796,11 +802,7 @@ class ImapFlow extends EventEmitter {
|
|
|
796
802
|
|
|
797
803
|
await this.upgradeToSTARTTLS();
|
|
798
804
|
|
|
799
|
-
|
|
800
|
-
if (!authenticated) {
|
|
801
|
-
// nothing to do here
|
|
802
|
-
return await this.logout();
|
|
803
|
-
}
|
|
805
|
+
await this.authenticate();
|
|
804
806
|
|
|
805
807
|
if (!this.idRequested && this.capabilities.has('ID')) {
|
|
806
808
|
// re-request ID after LOGIN
|
|
@@ -812,8 +814,7 @@ class ImapFlow extends EventEmitter {
|
|
|
812
814
|
if (nsResponse && nsResponse.error && nsResponse.status === 'BAD' && /User is authenticated but not connected/i.test(nsResponse.text)) {
|
|
813
815
|
// Not a NAMESPACE failure but authentication failure, so report as
|
|
814
816
|
this.authenticated = false;
|
|
815
|
-
let err = new
|
|
816
|
-
err.authenticationFailed = true;
|
|
817
|
+
let err = new AuthenticationFailure('Authentication failed');
|
|
817
818
|
err.response = nsResponse.text;
|
|
818
819
|
throw err;
|
|
819
820
|
}
|
|
@@ -874,7 +875,7 @@ class ImapFlow extends EventEmitter {
|
|
|
874
875
|
};
|
|
875
876
|
|
|
876
877
|
Object.defineProperty(this.writeSocket, 'destroyed', {
|
|
877
|
-
get: () => this.socket.destroyed
|
|
878
|
+
get: () => !this.socket || this.socket.destroyed
|
|
878
879
|
});
|
|
879
880
|
|
|
880
881
|
// we need to force flush deflated data to socket so we can't
|
|
@@ -1043,14 +1044,17 @@ class ImapFlow extends EventEmitter {
|
|
|
1043
1044
|
}
|
|
1044
1045
|
|
|
1045
1046
|
async authenticate() {
|
|
1047
|
+
if (this.state === this.states.LOGOUT) {
|
|
1048
|
+
throw new AuthenticationFailure('Already logged out');
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1046
1051
|
if (this.state !== this.states.NOT_AUTHENTICATED) {
|
|
1047
1052
|
// nothing to do here, usually happens with PREAUTH greeting
|
|
1048
|
-
return
|
|
1053
|
+
return true;
|
|
1049
1054
|
}
|
|
1050
1055
|
|
|
1051
|
-
if (
|
|
1052
|
-
|
|
1053
|
-
return false;
|
|
1056
|
+
if (!this.options.auth) {
|
|
1057
|
+
throw new AuthenticationFailure('Please configure the login');
|
|
1054
1058
|
}
|
|
1055
1059
|
|
|
1056
1060
|
this.expectCapabilityUpdate = true;
|
|
@@ -1067,8 +1071,13 @@ class ImapFlow extends EventEmitter {
|
|
|
1067
1071
|
if ((this.capabilities.has('AUTH=LOGIN') || this.capabilities.has('AUTH=PLAIN')) && loginMethod !== 'LOGIN') {
|
|
1068
1072
|
this.authenticated = await this.run('AUTHENTICATE', this.options.auth.user, { password: this.options.auth.pass, loginMethod });
|
|
1069
1073
|
} else {
|
|
1074
|
+
if (this.capabilities.has('LOGINDISABLED')) {
|
|
1075
|
+
throw new AuthenticationFailure('Login is disabled');
|
|
1076
|
+
}
|
|
1070
1077
|
this.authenticated = await this.run('LOGIN', this.options.auth.user, this.options.auth.pass);
|
|
1071
1078
|
}
|
|
1079
|
+
} else {
|
|
1080
|
+
throw new AuthenticationFailure('No password configured');
|
|
1072
1081
|
}
|
|
1073
1082
|
|
|
1074
1083
|
if (this.authenticated) {
|
|
@@ -1082,7 +1091,7 @@ class ImapFlow extends EventEmitter {
|
|
|
1082
1091
|
return true;
|
|
1083
1092
|
}
|
|
1084
1093
|
|
|
1085
|
-
|
|
1094
|
+
throw new AuthenticationFailure('No matching authentication method');
|
|
1086
1095
|
}
|
|
1087
1096
|
|
|
1088
1097
|
async initialOK(message) {
|
|
@@ -1405,6 +1414,12 @@ class ImapFlow extends EventEmitter {
|
|
|
1405
1414
|
* await client.connect();
|
|
1406
1415
|
*/
|
|
1407
1416
|
async connect() {
|
|
1417
|
+
if (this._connectCalled) {
|
|
1418
|
+
// Prevent re-using ImapFlow instances by allowing to call connect just once.
|
|
1419
|
+
throw new Error('Can not re-use ImapFlow instance');
|
|
1420
|
+
}
|
|
1421
|
+
this._connectCalled = true;
|
|
1422
|
+
|
|
1408
1423
|
let connector = this.secureConnection ? tls : net;
|
|
1409
1424
|
|
|
1410
1425
|
let opts = Object.assign(
|
|
@@ -1977,6 +1992,7 @@ class ImapFlow extends EventEmitter {
|
|
|
1977
1992
|
* @property {String} [keyword] Matches messages that have the custom flag set
|
|
1978
1993
|
* @property {String} [unKeyword] Matches messages that do not have the custom flag set
|
|
1979
1994
|
* @property {Object.<string, Boolean|String>} [header] Matches messages with header key set if value is `true` (**NB!** not supported by all servers) or messages where header partially matches a string value
|
|
1995
|
+
* @property {SearchObject} [not] A {@link SearchObject} object. It must not match.
|
|
1980
1996
|
* @property {SearchObject[]} [or] An array of 2 or more {@link SearchObject} objects. At least on of these must match
|
|
1981
1997
|
*/
|
|
1982
1998
|
|
|
@@ -2467,7 +2483,7 @@ class ImapFlow extends EventEmitter {
|
|
|
2467
2483
|
|
|
2468
2484
|
let res;
|
|
2469
2485
|
while ((res = await getNext())) {
|
|
2470
|
-
if (this.isClosed || this.socket.destroyed) {
|
|
2486
|
+
if (this.isClosed || !this.socket || this.socket.destroyed) {
|
|
2471
2487
|
let error = new Error('Connection closed');
|
|
2472
2488
|
error.code = 'EConnectionClosed';
|
|
2473
2489
|
throw error;
|
|
@@ -2987,7 +3003,7 @@ class ImapFlow extends EventEmitter {
|
|
|
2987
3003
|
return false;
|
|
2988
3004
|
}
|
|
2989
3005
|
|
|
2990
|
-
if (this.socket.destroyed) {
|
|
3006
|
+
if (!this.socket || this.socket.destroyed) {
|
|
2991
3007
|
const error = new Error('Connection not available');
|
|
2992
3008
|
error.code = 'NoConnection';
|
|
2993
3009
|
throw error;
|
|
@@ -3056,7 +3072,7 @@ class ImapFlow extends EventEmitter {
|
|
|
3056
3072
|
const lock = this.locks.shift();
|
|
3057
3073
|
const { resolve, reject, path, options, lockId } = lock;
|
|
3058
3074
|
|
|
3059
|
-
if (!this.usable || this.socket.destroyed) {
|
|
3075
|
+
if (!this.usable || !this.socket || this.socket.destroyed) {
|
|
3060
3076
|
this.log.trace({ msg: 'Failed to acquire mailbox lock', path, lockId, idling: this.idling });
|
|
3061
3077
|
// reject all
|
|
3062
3078
|
let error = new Error('Connection not available');
|
package/lib/search-compiler.js
CHANGED
|
@@ -190,6 +190,19 @@ module.exports.searchCompiler = (connection, query) => {
|
|
|
190
190
|
}
|
|
191
191
|
break;
|
|
192
192
|
|
|
193
|
+
case 'NOT':
|
|
194
|
+
{
|
|
195
|
+
if (!params[term]) {
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (typeof params[term] === 'object') {
|
|
200
|
+
attributes.push({ type: 'ATOM', value: 'NOT' });
|
|
201
|
+
walk(params[term]);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
|
|
193
206
|
case 'OR':
|
|
194
207
|
{
|
|
195
208
|
if (!params[term] || !Array.isArray(params[term]) || !params[term].length) {
|
package/lib/tools.js
CHANGED
|
@@ -11,6 +11,10 @@ const iconv = require('iconv-lite');
|
|
|
11
11
|
|
|
12
12
|
const FLAG_COLORS = ['red', 'orange', 'yellow', 'green', 'blue', 'purple', 'grey'];
|
|
13
13
|
|
|
14
|
+
class AuthenticationFailure extends Error {
|
|
15
|
+
authenticationFailed = true;
|
|
16
|
+
}
|
|
17
|
+
|
|
14
18
|
const tools = {
|
|
15
19
|
encodePath(connection, path) {
|
|
16
20
|
path = (path || '').toString();
|
|
@@ -90,6 +94,8 @@ const tools = {
|
|
|
90
94
|
return map;
|
|
91
95
|
},
|
|
92
96
|
|
|
97
|
+
AuthenticationFailure,
|
|
98
|
+
|
|
93
99
|
getStatusCode(response) {
|
|
94
100
|
return response &&
|
|
95
101
|
response.attributes &&
|
|
@@ -843,10 +849,9 @@ const tools = {
|
|
|
843
849
|
},
|
|
844
850
|
|
|
845
851
|
packMessageRange(list) {
|
|
846
|
-
if (
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
}
|
|
852
|
+
if (!Array.isArray(list)) {
|
|
853
|
+
list = [].concat(list || []);
|
|
854
|
+
}
|
|
850
855
|
|
|
851
856
|
if (!list.length) {
|
|
852
857
|
return '';
|
package/lib/types.d.ts
CHANGED
|
@@ -904,6 +904,7 @@ declare type SequenceString = string;
|
|
|
904
904
|
* @property [keyword] - Matches messages that have the custom flag set
|
|
905
905
|
* @property [unKeyword] - Matches messages that do not have the custom flag set
|
|
906
906
|
* @property [header] - Matches messages with header key set if value is `true` (**NB!** not supported by all servers) or messages where header partially matches a string value
|
|
907
|
+
* @property [not] - A {@link SearchObject} object. It must not match.
|
|
907
908
|
* @property [or] - An array of 2 or more {@link SearchObject} objects. At least on of these must match
|
|
908
909
|
*/
|
|
909
910
|
declare type SearchObject = {
|
|
@@ -940,6 +941,7 @@ declare type SearchObject = {
|
|
|
940
941
|
header?: {
|
|
941
942
|
[key: string]: Boolean | String;
|
|
942
943
|
};
|
|
944
|
+
not?: SearchObject;
|
|
943
945
|
or?: SearchObject[];
|
|
944
946
|
};
|
|
945
947
|
|