imapflow 1.0.191 → 1.0.193
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 +17 -0
- package/lib/handler/imap-stream.js +15 -1
- package/lib/imap-flow.js +47 -14
- package/package.json +6 -6
- package/test/authentication-test.js +83 -0
- package/test/commands-test.js +73 -0
- package/test/connection-test.js +162 -0
- package/test/integration-test.js +83 -0
- package/test/search-compiler-test.js +81 -0
- package/test/special-use-test.js +49 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.193](https://github.com/postalsys/imapflow/compare/v1.0.192...v1.0.193) (2025-07-31)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* Improved exception handling ([0eb77ac](https://github.com/postalsys/imapflow/commit/0eb77ac28a0f36b085769b6f9a20fd89d9214060))
|
|
9
|
+
|
|
10
|
+
## [1.0.192](https://github.com/postalsys/imapflow/compare/v1.0.191...v1.0.192) (2025-07-31)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* destroy _inflate and _deflate streams if set ([eab274b](https://github.com/postalsys/imapflow/commit/eab274b15b2ea2ee93aa027fd874249b1860167e))
|
|
16
|
+
* do not allow too large IMAP literals ([680a44d](https://github.com/postalsys/imapflow/commit/680a44d6550d51fc00602c49d6c5a377046c3504))
|
|
17
|
+
* Improved socket handler listener setup/cleanup ([0b72714](https://github.com/postalsys/imapflow/commit/0b727140866d220ff1df07d7245c60aa72e3be4a))
|
|
18
|
+
* remove unneeded timer ([1867606](https://github.com/postalsys/imapflow/commit/1867606aaf5ea37935c460aca1b615ab2a2b6534))
|
|
19
|
+
|
|
3
20
|
## [1.0.191](https://github.com/postalsys/imapflow/compare/v1.0.190...v1.0.191) (2025-07-10)
|
|
4
21
|
|
|
5
22
|
|
|
@@ -13,6 +13,9 @@ const NUM_9 = 0x39;
|
|
|
13
13
|
const CURLY_OPEN = 0x7b;
|
|
14
14
|
const CURLY_CLOSE = 0x7d;
|
|
15
15
|
|
|
16
|
+
// Maximum allowed literal size: 1GB (1073741824 bytes)
|
|
17
|
+
const MAX_LITERAL_SIZE = 1024 * 1024 * 1024;
|
|
18
|
+
|
|
16
19
|
class ImapStream extends Transform {
|
|
17
20
|
constructor(options) {
|
|
18
21
|
super({
|
|
@@ -80,8 +83,19 @@ class ImapStream extends Transform {
|
|
|
80
83
|
continue;
|
|
81
84
|
}
|
|
82
85
|
if (c === CURLY_OPEN && numBytes.length) {
|
|
86
|
+
const literalSize = Number(Buffer.from(numBytes).toString());
|
|
87
|
+
|
|
88
|
+
if (literalSize > MAX_LITERAL_SIZE) {
|
|
89
|
+
const err = new Error(`Literal size ${literalSize} exceeds maximum allowed size of ${MAX_LITERAL_SIZE} bytes`);
|
|
90
|
+
err.code = 'LiteralTooLarge';
|
|
91
|
+
err.literalSize = literalSize;
|
|
92
|
+
err.maxSize = MAX_LITERAL_SIZE;
|
|
93
|
+
this.emit('error', err);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
83
97
|
this.state = LITERAL;
|
|
84
|
-
this.literalWaiting =
|
|
98
|
+
this.literalWaiting = literalSize;
|
|
85
99
|
return true;
|
|
86
100
|
}
|
|
87
101
|
return false;
|
package/lib/imap-flow.js
CHANGED
|
@@ -4,10 +4,6 @@
|
|
|
4
4
|
* @module imapflow
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
// TODO:
|
|
8
|
-
// * Use buffers for compiled commands
|
|
9
|
-
// * OAuth2 authentication
|
|
10
|
-
|
|
11
7
|
const tls = require('tls');
|
|
12
8
|
const net = require('net');
|
|
13
9
|
const crypto = require('crypto');
|
|
@@ -377,7 +373,6 @@ class ImapFlow extends EventEmitter {
|
|
|
377
373
|
}
|
|
378
374
|
|
|
379
375
|
this.log.error({ err, cid: this.id });
|
|
380
|
-
setImmediate(() => this.close());
|
|
381
376
|
this.emitError(err);
|
|
382
377
|
});
|
|
383
378
|
|
|
@@ -386,6 +381,12 @@ class ImapFlow extends EventEmitter {
|
|
|
386
381
|
}
|
|
387
382
|
|
|
388
383
|
emitError(err) {
|
|
384
|
+
if (!err) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
err._connId = err._connId || this.id;
|
|
388
|
+
|
|
389
|
+
setImmediate(() => this.close());
|
|
389
390
|
this.emit('error', err);
|
|
390
391
|
}
|
|
391
392
|
|
|
@@ -754,11 +755,13 @@ class ImapFlow extends EventEmitter {
|
|
|
754
755
|
}
|
|
755
756
|
|
|
756
757
|
setSocketHandlers() {
|
|
758
|
+
// Clear any existing handlers first to prevent duplicates
|
|
759
|
+
this.clearSocketHandlers();
|
|
760
|
+
|
|
757
761
|
this._socketError =
|
|
758
762
|
this._socketError ||
|
|
759
763
|
(err => {
|
|
760
764
|
this.log.error({ err, cid: this.id });
|
|
761
|
-
setImmediate(() => this.close());
|
|
762
765
|
this.emitError(err);
|
|
763
766
|
});
|
|
764
767
|
this._socketClose =
|
|
@@ -787,6 +790,10 @@ class ImapFlow extends EventEmitter {
|
|
|
787
790
|
err.code = 'ETIMEOUT';
|
|
788
791
|
|
|
789
792
|
if (this.idling) {
|
|
793
|
+
if (!this.usable || !this.socket || this.socket.destroyed) {
|
|
794
|
+
this.emitError(err);
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
790
797
|
// Attempt to recover IDLE connections
|
|
791
798
|
this.run('NOOP')
|
|
792
799
|
.then(() => this.idle())
|
|
@@ -794,8 +801,7 @@ class ImapFlow extends EventEmitter {
|
|
|
794
801
|
} else {
|
|
795
802
|
// Close immediately for non-IDLE operations
|
|
796
803
|
this.log.debug({ msg: 'Socket timeout', cid: this.id });
|
|
797
|
-
this.
|
|
798
|
-
this.close();
|
|
804
|
+
this.emitError(err);
|
|
799
805
|
}
|
|
800
806
|
});
|
|
801
807
|
|
|
@@ -806,14 +812,22 @@ class ImapFlow extends EventEmitter {
|
|
|
806
812
|
this.socket.on('tlsClientError', this._socketError);
|
|
807
813
|
this.socket.on('timeout', this._socketTimeout);
|
|
808
814
|
|
|
809
|
-
this.writeSocket.
|
|
815
|
+
if (this.writeSocket && this.writeSocket !== this.socket) {
|
|
816
|
+
this.writeSocket.on('error', this._socketError);
|
|
817
|
+
}
|
|
810
818
|
}
|
|
811
819
|
|
|
812
820
|
clearSocketHandlers() {
|
|
821
|
+
if (!this.socket) {
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
|
|
813
825
|
if (this._socketError) {
|
|
814
826
|
this.socket.removeListener('error', this._socketError);
|
|
815
827
|
this.socket.removeListener('tlsClientError', this._socketError);
|
|
816
|
-
this.writeSocket.
|
|
828
|
+
if (this.writeSocket && this.writeSocket !== this.socket) {
|
|
829
|
+
this.writeSocket.removeListener('error', this._socketError);
|
|
830
|
+
}
|
|
817
831
|
}
|
|
818
832
|
if (this._socketTimeout) {
|
|
819
833
|
this.socket.removeListener('timeout', this._socketTimeout);
|
|
@@ -1164,10 +1178,7 @@ class ImapFlow extends EventEmitter {
|
|
|
1164
1178
|
}
|
|
1165
1179
|
|
|
1166
1180
|
// ALWAYS emit the error so users can handle it
|
|
1167
|
-
|
|
1168
|
-
this.emit('error', err);
|
|
1169
|
-
this.close();
|
|
1170
|
-
});
|
|
1181
|
+
this.emitError(err);
|
|
1171
1182
|
});
|
|
1172
1183
|
}
|
|
1173
1184
|
|
|
@@ -1614,6 +1625,7 @@ class ImapFlow extends EventEmitter {
|
|
|
1614
1625
|
clearTimeout(this.idleStartTimer);
|
|
1615
1626
|
clearTimeout(this.upgradeTimeout);
|
|
1616
1627
|
clearTimeout(this.connectTimeout);
|
|
1628
|
+
clearTimeout(this.greetingTimeout);
|
|
1617
1629
|
|
|
1618
1630
|
this.usable = false;
|
|
1619
1631
|
this.idling = false;
|
|
@@ -1673,6 +1685,27 @@ class ImapFlow extends EventEmitter {
|
|
|
1673
1685
|
}
|
|
1674
1686
|
}
|
|
1675
1687
|
|
|
1688
|
+
// cleanup compression streams if they exist
|
|
1689
|
+
if (this._inflate) {
|
|
1690
|
+
try {
|
|
1691
|
+
this._inflate.unpipe();
|
|
1692
|
+
this._inflate.destroy();
|
|
1693
|
+
this._inflate = null;
|
|
1694
|
+
} catch (err) {
|
|
1695
|
+
this.log.error({ err, info: 'Failed to destroy inflate stream', cid: this.id });
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
if (this._deflate) {
|
|
1700
|
+
try {
|
|
1701
|
+
this._deflate.unpipe();
|
|
1702
|
+
this._deflate.destroy();
|
|
1703
|
+
this._deflate = null;
|
|
1704
|
+
} catch (err) {
|
|
1705
|
+
this.log.error({ err, info: 'Failed to destroy deflate stream', cid: this.id });
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1676
1709
|
if (this.socket && !this.socket.destroyed && this.writeSocket !== this.socket) {
|
|
1677
1710
|
try {
|
|
1678
1711
|
this.socket.destroy();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imapflow",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.193",
|
|
4
4
|
"description": "IMAP Client for Node",
|
|
5
5
|
"main": "lib/imap-flow.js",
|
|
6
6
|
"types": "lib/imap-flow.d.ts",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@babel/eslint-plugin": "7.27.1",
|
|
33
33
|
"@babel/plugin-syntax-class-properties": "7.12.13",
|
|
34
34
|
"@babel/preset-env": "7.28.0",
|
|
35
|
-
"@types/node": "24.0
|
|
35
|
+
"@types/node": "24.1.0",
|
|
36
36
|
"eslint": "8.57.0",
|
|
37
37
|
"eslint-config-nodemailer": "1.2.0",
|
|
38
38
|
"eslint-config-prettier": "9.1.0",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"grunt-cli": "1.5.0",
|
|
41
41
|
"grunt-contrib-nodeunit": "5.0.0",
|
|
42
42
|
"grunt-eslint": "24.3.0",
|
|
43
|
-
"imapflow-jsdoc-template": "3.4.0-imapflow.
|
|
44
|
-
"jsdoc": "
|
|
43
|
+
"imapflow-jsdoc-template": "3.4.0-imapflow.3",
|
|
44
|
+
"jsdoc": "4.0.4",
|
|
45
45
|
"st": "3.0.2",
|
|
46
46
|
"typescript": "5.8.3"
|
|
47
47
|
},
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"libbase64": "1.3.0",
|
|
52
52
|
"libmime": "5.3.7",
|
|
53
53
|
"libqp": "2.1.1",
|
|
54
|
-
"mailsplit": "5.4.
|
|
54
|
+
"mailsplit": "5.4.6",
|
|
55
55
|
"nodemailer": "7.0.5",
|
|
56
56
|
"pino": "9.7.0",
|
|
57
|
-
"socks": "2.8.
|
|
57
|
+
"socks": "2.8.6"
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ImapFlow } = require('../lib/imap-flow');
|
|
4
|
+
const { AuthenticationFailure } = require('../lib/tools');
|
|
5
|
+
|
|
6
|
+
module.exports['Authentication: Password auth configuration'] = test => {
|
|
7
|
+
let client = new ImapFlow({
|
|
8
|
+
host: 'imap.example.com',
|
|
9
|
+
auth: {
|
|
10
|
+
user: 'testuser',
|
|
11
|
+
pass: 'testpass'
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test.equal(client.options.auth.user, 'testuser');
|
|
16
|
+
test.equal(client.options.auth.pass, 'testpass');
|
|
17
|
+
test.done();
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
module.exports['Authentication: OAuth2 auth configuration'] = test => {
|
|
21
|
+
let client = new ImapFlow({
|
|
22
|
+
host: 'imap.example.com',
|
|
23
|
+
auth: {
|
|
24
|
+
user: 'testuser',
|
|
25
|
+
accessToken: 'oauth2_token_here'
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test.equal(client.options.auth.user, 'testuser');
|
|
30
|
+
test.equal(client.options.auth.accessToken, 'oauth2_token_here');
|
|
31
|
+
test.done();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
module.exports['Authentication: Login method specification'] = test => {
|
|
35
|
+
let client = new ImapFlow({
|
|
36
|
+
host: 'imap.example.com',
|
|
37
|
+
auth: {
|
|
38
|
+
user: 'testuser',
|
|
39
|
+
pass: 'testpass',
|
|
40
|
+
loginMethod: 'AUTH=PLAIN'
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test.equal(client.options.auth.loginMethod, 'AUTH=PLAIN');
|
|
45
|
+
test.done();
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
module.exports['Authentication: AuthenticationFailure error structure'] = test => {
|
|
49
|
+
let error = new AuthenticationFailure('Invalid credentials');
|
|
50
|
+
|
|
51
|
+
test.ok(error instanceof Error);
|
|
52
|
+
test.equal(error.constructor.name, 'AuthenticationFailure');
|
|
53
|
+
test.equal(error.message, 'Invalid credentials');
|
|
54
|
+
test.done();
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
module.exports['Authentication: Verify-only mode'] = test => {
|
|
58
|
+
let client = new ImapFlow({
|
|
59
|
+
host: 'imap.example.com',
|
|
60
|
+
auth: {
|
|
61
|
+
user: 'testuser',
|
|
62
|
+
pass: 'testpass'
|
|
63
|
+
},
|
|
64
|
+
verifyOnly: true
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test.equal(client.options.verifyOnly, true);
|
|
68
|
+
test.done();
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
module.exports['Authentication: Disable auto IDLE'] = test => {
|
|
72
|
+
let client = new ImapFlow({
|
|
73
|
+
host: 'imap.example.com',
|
|
74
|
+
auth: {
|
|
75
|
+
user: 'testuser',
|
|
76
|
+
pass: 'testpass'
|
|
77
|
+
},
|
|
78
|
+
disableAutoIdle: true
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test.equal(client.options.disableAutoIdle, true);
|
|
82
|
+
test.done();
|
|
83
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ImapFlow } = require('../lib/imap-flow');
|
|
4
|
+
|
|
5
|
+
module.exports['Commands: Client instantiation'] = test => {
|
|
6
|
+
let client = new ImapFlow({
|
|
7
|
+
host: 'imap.example.com',
|
|
8
|
+
auth: { user: 'test', pass: 'test' }
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test.ok(client);
|
|
12
|
+
test.equal(typeof client.exec, 'function');
|
|
13
|
+
test.done();
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
module.exports['Commands: Method availability'] = test => {
|
|
17
|
+
let client = new ImapFlow({
|
|
18
|
+
host: 'imap.example.com',
|
|
19
|
+
auth: { user: 'test', pass: 'test' }
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Check that key IMAP methods exist
|
|
23
|
+
test.equal(typeof client.connect, 'function');
|
|
24
|
+
test.equal(typeof client.logout, 'function');
|
|
25
|
+
test.equal(typeof client.list, 'function');
|
|
26
|
+
test.equal(typeof client.mailboxOpen, 'function');
|
|
27
|
+
test.equal(typeof client.mailboxClose, 'function');
|
|
28
|
+
test.equal(typeof client.search, 'function');
|
|
29
|
+
test.equal(typeof client.fetch, 'function');
|
|
30
|
+
test.done();
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
module.exports['Commands: State management methods'] = test => {
|
|
34
|
+
let client = new ImapFlow({
|
|
35
|
+
host: 'imap.example.com',
|
|
36
|
+
auth: { user: 'test', pass: 'test' }
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test.equal(typeof client.mailboxCreate, 'function');
|
|
40
|
+
test.equal(typeof client.mailboxDelete, 'function');
|
|
41
|
+
test.equal(typeof client.mailboxRename, 'function');
|
|
42
|
+
test.equal(typeof client.mailboxSubscribe, 'function');
|
|
43
|
+
test.equal(typeof client.mailboxUnsubscribe, 'function');
|
|
44
|
+
test.done();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
module.exports['Commands: Message operation methods'] = test => {
|
|
48
|
+
let client = new ImapFlow({
|
|
49
|
+
host: 'imap.example.com',
|
|
50
|
+
auth: { user: 'test', pass: 'test' }
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test.equal(typeof client.messageFlagsSet, 'function');
|
|
54
|
+
test.equal(typeof client.messageFlagsAdd, 'function');
|
|
55
|
+
test.equal(typeof client.messageFlagsRemove, 'function');
|
|
56
|
+
test.equal(typeof client.messageCopy, 'function');
|
|
57
|
+
test.equal(typeof client.messageMove, 'function');
|
|
58
|
+
test.equal(typeof client.messageDelete, 'function');
|
|
59
|
+
test.done();
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
module.exports['Commands: Utility methods'] = test => {
|
|
63
|
+
let client = new ImapFlow({
|
|
64
|
+
host: 'imap.example.com',
|
|
65
|
+
auth: { user: 'test', pass: 'test' }
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test.equal(typeof client.noop, 'function');
|
|
69
|
+
test.equal(typeof client.getQuota, 'function');
|
|
70
|
+
test.equal(typeof client.stats, 'function');
|
|
71
|
+
test.equal(typeof client.getRandomId, 'function');
|
|
72
|
+
test.done();
|
|
73
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ImapFlow } = require('../lib/imap-flow');
|
|
4
|
+
|
|
5
|
+
module.exports['Connection: Basic connection options'] = test => {
|
|
6
|
+
let client = new ImapFlow({
|
|
7
|
+
host: 'imap.example.com',
|
|
8
|
+
port: 993,
|
|
9
|
+
auth: { user: 'test', pass: 'test' }
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test.equal(client.host, 'imap.example.com');
|
|
13
|
+
test.equal(client.port, 993);
|
|
14
|
+
test.done();
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
module.exports['Connection: Default options'] = test => {
|
|
18
|
+
let client = new ImapFlow({
|
|
19
|
+
host: 'imap.example.com',
|
|
20
|
+
auth: { user: 'test', pass: 'test' }
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test.equal(client.port, 110);
|
|
24
|
+
test.equal(client.secureConnection, false);
|
|
25
|
+
test.done();
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
module.exports['Connection: Secure connection defaults'] = test => {
|
|
29
|
+
let client = new ImapFlow({
|
|
30
|
+
host: 'imap.example.com',
|
|
31
|
+
port: 993,
|
|
32
|
+
auth: { user: 'test', pass: 'test' }
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test.equal(client.secureConnection, true);
|
|
36
|
+
test.done();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
module.exports['Connection: TLS options'] = test => {
|
|
40
|
+
let client = new ImapFlow({
|
|
41
|
+
host: 'imap.example.com',
|
|
42
|
+
auth: { user: 'test', pass: 'test' },
|
|
43
|
+
tls: {
|
|
44
|
+
rejectUnauthorized: false,
|
|
45
|
+
minVersion: 'TLSv1.3'
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test.equal(client.options.tls.rejectUnauthorized, false);
|
|
50
|
+
test.equal(client.options.tls.minVersion, 'TLSv1.3');
|
|
51
|
+
test.done();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
module.exports['Connection: Authentication options'] = test => {
|
|
55
|
+
let client = new ImapFlow({
|
|
56
|
+
host: 'imap.example.com',
|
|
57
|
+
auth: {
|
|
58
|
+
user: 'testuser',
|
|
59
|
+
pass: 'testpass',
|
|
60
|
+
accessToken: 'token123'
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test.equal(client.options.auth.user, 'testuser');
|
|
65
|
+
test.equal(client.options.auth.pass, 'testpass');
|
|
66
|
+
test.equal(client.options.auth.accessToken, 'token123');
|
|
67
|
+
test.done();
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
module.exports['Connection: Proxy configuration'] = test => {
|
|
71
|
+
let client = new ImapFlow({
|
|
72
|
+
host: 'imap.example.com',
|
|
73
|
+
auth: { user: 'test', pass: 'test' },
|
|
74
|
+
proxy: 'socks5://proxy.example.com:1080'
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test.equal(client.options.proxy, 'socks5://proxy.example.com:1080');
|
|
78
|
+
test.done();
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
module.exports['Connection: Client info'] = test => {
|
|
82
|
+
let clientInfo = {
|
|
83
|
+
name: 'Test Client',
|
|
84
|
+
version: '1.0.0',
|
|
85
|
+
vendor: 'Test Corp'
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
let client = new ImapFlow({
|
|
89
|
+
host: 'imap.example.com',
|
|
90
|
+
auth: { user: 'test', pass: 'test' },
|
|
91
|
+
clientInfo
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test.equal(client.clientInfo.name, 'Test Client');
|
|
95
|
+
test.equal(client.clientInfo.version, '1.0.0');
|
|
96
|
+
test.equal(client.clientInfo.vendor, 'Test Corp');
|
|
97
|
+
test.done();
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
module.exports['Connection: Logger configuration'] = test => {
|
|
101
|
+
let client = new ImapFlow({
|
|
102
|
+
host: 'imap.example.com',
|
|
103
|
+
auth: { user: 'test', pass: 'test' },
|
|
104
|
+
logger: false
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test.equal(client.options.logger, false);
|
|
108
|
+
test.done();
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
module.exports['Connection: Stats tracking'] = test => {
|
|
112
|
+
let client = new ImapFlow({
|
|
113
|
+
host: 'imap.example.com',
|
|
114
|
+
auth: { user: 'test', pass: 'test' }
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
let stats = client.stats();
|
|
118
|
+
test.ok(Object.prototype.hasOwnProperty.call(stats, 'sent'));
|
|
119
|
+
test.ok(Object.prototype.hasOwnProperty.call(stats, 'received'));
|
|
120
|
+
test.equal(typeof stats.sent, 'number');
|
|
121
|
+
test.equal(typeof stats.received, 'number');
|
|
122
|
+
test.done();
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
module.exports['Connection: Random ID generation'] = test => {
|
|
126
|
+
let client = new ImapFlow({
|
|
127
|
+
host: 'imap.example.com',
|
|
128
|
+
auth: { user: 'test', pass: 'test' }
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
let id1 = client.getRandomId();
|
|
132
|
+
let id2 = client.getRandomId();
|
|
133
|
+
|
|
134
|
+
test.ok(typeof id1 === 'string' && id1.length > 0);
|
|
135
|
+
test.ok(typeof id2 === 'string' && id2.length > 0);
|
|
136
|
+
test.notEqual(id1, id2, 'IDs should be unique');
|
|
137
|
+
test.done();
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
module.exports['Connection: State management'] = test => {
|
|
141
|
+
let client = new ImapFlow({
|
|
142
|
+
host: 'imap.example.com',
|
|
143
|
+
auth: { user: 'test', pass: 'test' }
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test.equal(client.state, client.states.NOT_AUTHENTICATED);
|
|
147
|
+
test.equal(client.authenticated, false);
|
|
148
|
+
test.ok(client.capabilities instanceof Map);
|
|
149
|
+
test.done();
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
module.exports['Connection: Event emitter setup'] = test => {
|
|
153
|
+
let client = new ImapFlow({
|
|
154
|
+
host: 'imap.example.com',
|
|
155
|
+
auth: { user: 'test', pass: 'test' }
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test.equal(typeof client.on, 'function');
|
|
159
|
+
test.equal(typeof client.emit, 'function');
|
|
160
|
+
test.equal(typeof client.removeListener, 'function');
|
|
161
|
+
test.done();
|
|
162
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ImapFlow } = require('../lib/imap-flow');
|
|
4
|
+
|
|
5
|
+
module.exports['Integration: Basic client creation'] = test => {
|
|
6
|
+
let client = new ImapFlow({
|
|
7
|
+
host: 'imap.example.com',
|
|
8
|
+
auth: { user: 'test', pass: 'test' }
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test.ok(client);
|
|
12
|
+
test.equal(client.state, client.states.NOT_AUTHENTICATED);
|
|
13
|
+
test.equal(client.authenticated, false);
|
|
14
|
+
test.done();
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
module.exports['Integration: Multiple client instances'] = test => {
|
|
18
|
+
let client1 = new ImapFlow({
|
|
19
|
+
host: 'imap1.example.com',
|
|
20
|
+
auth: { user: 'test1', pass: 'test1' }
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
let client2 = new ImapFlow({
|
|
24
|
+
host: 'imap2.example.com',
|
|
25
|
+
auth: { user: 'test2', pass: 'test2' }
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test.ok(client1);
|
|
29
|
+
test.ok(client2);
|
|
30
|
+
test.notEqual(client1.id, client2.id);
|
|
31
|
+
test.done();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
module.exports['Integration: Client configuration'] = test => {
|
|
35
|
+
let client = new ImapFlow({
|
|
36
|
+
host: 'imap.example.com',
|
|
37
|
+
port: 993,
|
|
38
|
+
secure: true,
|
|
39
|
+
auth: { user: 'test', pass: 'test' },
|
|
40
|
+
logger: false
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test.equal(client.host, 'imap.example.com');
|
|
44
|
+
test.equal(client.port, 993);
|
|
45
|
+
test.equal(client.secureConnection, true);
|
|
46
|
+
test.done();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
module.exports['Integration: Event emitter functionality'] = test => {
|
|
50
|
+
let client = new ImapFlow({
|
|
51
|
+
host: 'imap.example.com',
|
|
52
|
+
auth: { user: 'test', pass: 'test' }
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
let eventFired = false;
|
|
56
|
+
client.on('test-event', () => {
|
|
57
|
+
eventFired = true;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
client.emit('test-event');
|
|
61
|
+
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
test.ok(eventFired);
|
|
64
|
+
test.done();
|
|
65
|
+
}, 10);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
module.exports['Integration: Stats functionality'] = test => {
|
|
69
|
+
let client = new ImapFlow({
|
|
70
|
+
host: 'imap.example.com',
|
|
71
|
+
auth: { user: 'test', pass: 'test' }
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
let stats = client.stats();
|
|
75
|
+
test.ok(typeof stats === 'object');
|
|
76
|
+
test.ok(Object.prototype.hasOwnProperty.call(stats, 'sent'));
|
|
77
|
+
test.ok(Object.prototype.hasOwnProperty.call(stats, 'received'));
|
|
78
|
+
|
|
79
|
+
// Reset stats
|
|
80
|
+
let resetStats = client.stats(true);
|
|
81
|
+
test.ok(typeof resetStats === 'object');
|
|
82
|
+
test.done();
|
|
83
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { searchCompiler } = require('../lib/search-compiler');
|
|
4
|
+
|
|
5
|
+
// Mock connection for testing
|
|
6
|
+
let createMockConnection = () => ({
|
|
7
|
+
capabilities: new Map([
|
|
8
|
+
['IMAP4rev1', true],
|
|
9
|
+
['UTF8=ACCEPT', true]
|
|
10
|
+
]),
|
|
11
|
+
enabled: new Set(['UTF8=ACCEPT'])
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Mock mailbox for testing
|
|
15
|
+
let createMockMailbox = () => ({
|
|
16
|
+
flags: new Set(['\\Seen', '\\Answered', '\\Flagged', '\\Deleted', '\\Draft']),
|
|
17
|
+
permanentFlags: new Set(['\\*'])
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
module.exports['Search Compiler: Basic functionality'] = test => {
|
|
21
|
+
let connection = createMockConnection();
|
|
22
|
+
let mailbox = createMockMailbox();
|
|
23
|
+
|
|
24
|
+
test.doesNotThrow(() => {
|
|
25
|
+
let compiled = searchCompiler(connection, { seen: false }, mailbox);
|
|
26
|
+
test.ok(Array.isArray(compiled));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test.done();
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
module.exports['Search Compiler: Text searches'] = test => {
|
|
33
|
+
let connection = createMockConnection();
|
|
34
|
+
let mailbox = createMockMailbox();
|
|
35
|
+
|
|
36
|
+
let compiled = searchCompiler(connection, {
|
|
37
|
+
from: 'user@example.com',
|
|
38
|
+
subject: 'Test'
|
|
39
|
+
}, mailbox);
|
|
40
|
+
|
|
41
|
+
test.ok(Array.isArray(compiled));
|
|
42
|
+
test.ok(compiled.length > 0);
|
|
43
|
+
test.done();
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
module.exports['Search Compiler: Flag searches'] = test => {
|
|
47
|
+
let connection = createMockConnection();
|
|
48
|
+
let mailbox = createMockMailbox();
|
|
49
|
+
|
|
50
|
+
let compiled = searchCompiler(connection, {
|
|
51
|
+
seen: true,
|
|
52
|
+
answered: false
|
|
53
|
+
}, mailbox);
|
|
54
|
+
|
|
55
|
+
test.ok(Array.isArray(compiled));
|
|
56
|
+
test.ok(compiled.length > 0);
|
|
57
|
+
test.done();
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
module.exports['Search Compiler: Date searches'] = test => {
|
|
61
|
+
let connection = createMockConnection();
|
|
62
|
+
let mailbox = createMockMailbox();
|
|
63
|
+
|
|
64
|
+
let compiled = searchCompiler(connection, {
|
|
65
|
+
since: new Date('2023-01-01')
|
|
66
|
+
}, mailbox);
|
|
67
|
+
|
|
68
|
+
test.ok(Array.isArray(compiled));
|
|
69
|
+
test.ok(compiled.length > 0);
|
|
70
|
+
test.done();
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
module.exports['Search Compiler: Empty query'] = test => {
|
|
74
|
+
let connection = createMockConnection();
|
|
75
|
+
let mailbox = createMockMailbox();
|
|
76
|
+
|
|
77
|
+
let compiled = searchCompiler(connection, {}, mailbox);
|
|
78
|
+
|
|
79
|
+
test.ok(Array.isArray(compiled));
|
|
80
|
+
test.done();
|
|
81
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const specialUse = require('../lib/special-use');
|
|
4
|
+
|
|
5
|
+
module.exports['Special Use: flags array'] = test => {
|
|
6
|
+
test.ok(Array.isArray(specialUse.flags));
|
|
7
|
+
test.ok(specialUse.flags.includes('\\Sent'));
|
|
8
|
+
test.ok(specialUse.flags.includes('\\Drafts'));
|
|
9
|
+
test.ok(specialUse.flags.includes('\\Trash'));
|
|
10
|
+
test.ok(specialUse.flags.includes('\\Archive'));
|
|
11
|
+
test.done();
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
module.exports['Special Use: names object'] = test => {
|
|
15
|
+
test.ok(typeof specialUse.names === 'object');
|
|
16
|
+
test.ok(specialUse.names['\\Sent']);
|
|
17
|
+
test.ok(Array.isArray(specialUse.names['\\Sent']));
|
|
18
|
+
test.ok(specialUse.names['\\Sent'].includes('sent'));
|
|
19
|
+
test.done();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
module.exports['Special Use: Sent folder names'] = test => {
|
|
23
|
+
let sentNames = specialUse.names['\\Sent'];
|
|
24
|
+
test.ok(sentNames.includes('sent'));
|
|
25
|
+
test.ok(sentNames.includes('sent items'));
|
|
26
|
+
test.ok(sentNames.includes('sent messages'));
|
|
27
|
+
test.done();
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
module.exports['Special Use: Drafts folder names'] = test => {
|
|
31
|
+
let draftsNames = specialUse.names['\\Drafts'];
|
|
32
|
+
test.ok(draftsNames.includes('drafts'));
|
|
33
|
+
test.done();
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
module.exports['Special Use: Trash folder names'] = test => {
|
|
37
|
+
let trashNames = specialUse.names['\\Trash'];
|
|
38
|
+
test.ok(trashNames.includes('trash'));
|
|
39
|
+
test.ok(trashNames.includes('deleted items'));
|
|
40
|
+
test.ok(trashNames.includes('deleted messages'));
|
|
41
|
+
test.done();
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
module.exports['Special Use: Junk folder names'] = test => {
|
|
45
|
+
let junkNames = specialUse.names['\\Junk'];
|
|
46
|
+
test.ok(junkNames.includes('spam'));
|
|
47
|
+
test.ok(junkNames.includes('junk'));
|
|
48
|
+
test.done();
|
|
49
|
+
};
|