imapflow 1.0.168 → 1.0.170
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 +14 -0
- package/lib/commands/authenticate.js +3 -4
- package/lib/commands/select.js +1 -1
- package/lib/imap-flow.js +68 -12
- package/lib/tools.js +5 -1
- package/lib/types.d.ts +19 -2
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.170](https://github.com/postalsys/imapflow/compare/v1.0.169...v1.0.170) (2024-12-05)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **uidvalidity:** Do not expect UIDVALIDITY to always exist ([7761653](https://github.com/postalsys/imapflow/commit/7761653e92965a09be8a872813363eb81f7f3d9a))
|
|
9
|
+
|
|
10
|
+
## [1.0.169](https://github.com/postalsys/imapflow/compare/v1.0.168...v1.0.169) (2024-11-08)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **fetc:** Treat 'Some of the requested messages no longer exist' as a success, not a failure ([565f988](https://github.com/postalsys/imapflow/commit/565f988178fc4a4d3989fe027451980f3ee0d412))
|
|
16
|
+
|
|
3
17
|
## [1.0.168](https://github.com/postalsys/imapflow/compare/v1.0.167...v1.0.168) (2024-11-07)
|
|
4
18
|
|
|
5
19
|
|
|
@@ -151,13 +151,12 @@ module.exports = async (connection, username, { accessToken, password }) => {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
if (password) {
|
|
154
|
-
if (connection.capabilities.has('AUTH=LOGIN')) {
|
|
155
|
-
return await authLogin(connection, username, password);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
154
|
if (connection.capabilities.has('AUTH=PLAIN')) {
|
|
159
155
|
return await authPlain(connection, username, password);
|
|
160
156
|
}
|
|
157
|
+
if (connection.capabilities.has('AUTH=LOGIN')) {
|
|
158
|
+
return await authLogin(connection, username, password);
|
|
159
|
+
}
|
|
161
160
|
}
|
|
162
161
|
|
|
163
162
|
throw new Error('Unsupported authentication mechanism');
|
package/lib/commands/select.js
CHANGED
|
@@ -40,7 +40,7 @@ module.exports = async (connection, path, options) => {
|
|
|
40
40
|
extraArgs.push([
|
|
41
41
|
{ type: 'ATOM', value: 'QRESYNC' },
|
|
42
42
|
[
|
|
43
|
-
{ type: 'ATOM', value: options.uidValidity
|
|
43
|
+
{ type: 'ATOM', value: options.uidValidity?.toString() },
|
|
44
44
|
{ type: 'ATOM', value: options.changedSince.toString() }
|
|
45
45
|
]
|
|
46
46
|
]);
|
package/lib/imap-flow.js
CHANGED
|
@@ -119,8 +119,19 @@ class ImapFlow extends EventEmitter {
|
|
|
119
119
|
* @param {Object} options IMAP connection options
|
|
120
120
|
* @param {String} options.host Hostname of the IMAP server
|
|
121
121
|
* @param {Number} options.port Port number for the IMAP server
|
|
122
|
-
* @param {Boolean} [options.secure=false] Should the connection be established over TLS.
|
|
123
|
-
*
|
|
122
|
+
* @param {Boolean} [options.secure=false] Should the connection be established immediately and directly over TLS? Typically on port 993.
|
|
123
|
+
* @param {Boolean} [options.doSTARTTLS=undefined] Should the connection be established using STARTTLS?
|
|
124
|
+
* * If `true`, the connection is first established as unencrypted and then upgraded to TLS using STARTTLS, before authentication.
|
|
125
|
+
* If the server does not advertize the `STARTTLS` `CAPABILITY`, or the upgrade fails for other reasons, then the connection fails.
|
|
126
|
+
* Note: The combination `secure=true` (direct TLS) and `doSTARTTLS=true` is invalid.
|
|
127
|
+
* * If `false`, then STARTTLS will not be used, even if the server advertizes it in IMAP `CAPABILITY`.
|
|
128
|
+
* This helps with servers that have a broken TLS configuration.
|
|
129
|
+
* If `doSTARTTLS=false` and `secure=false`, then a plain unencrypted socket is used.
|
|
130
|
+
* Be sure to clearly warn the user about the consequences.
|
|
131
|
+
* * If `undefined` (default) and `secure=false` (default), the connection is upgraded using STARTTLS before authentication, /only if possible/ .
|
|
132
|
+
* If not possible, the connection will use an unencrypted plain socket.
|
|
133
|
+
* This can mean TLS is used under normal circumstances, but a serious attacker can force an unencrypted connection and steal passwords,
|
|
134
|
+
* called "downgrade attack". This can lead to a false sense of security. Be sure to warn the user.
|
|
124
135
|
* @param {String} [options.servername] Servername for SNI (or when host is set to an IP address)
|
|
125
136
|
* @param {Boolean} [options.disableCompression=false] if `true` then client does not try to use COMPRESS=DEFLATE extension
|
|
126
137
|
* @param {Object} options.auth Authentication options. Authentication is requested automatically during <code>connect()</code>
|
|
@@ -571,9 +582,29 @@ class ImapFlow extends EventEmitter {
|
|
|
571
582
|
let err = new Error('Command failed');
|
|
572
583
|
err.response = parsed;
|
|
573
584
|
err.responseStatus = parsed.command.toUpperCase();
|
|
585
|
+
|
|
586
|
+
try {
|
|
587
|
+
err.executedCommand =
|
|
588
|
+
parsed.tag +
|
|
589
|
+
(
|
|
590
|
+
await compiler(request, {
|
|
591
|
+
isLogging: true
|
|
592
|
+
})
|
|
593
|
+
).toString();
|
|
594
|
+
} catch (err) {
|
|
595
|
+
// ignore
|
|
596
|
+
}
|
|
597
|
+
|
|
574
598
|
if (txt) {
|
|
575
599
|
err.responseText = txt;
|
|
576
600
|
|
|
601
|
+
if (err.responseStatus === 'NO' && txt.includes('Some of the requested messages no longer exist')) {
|
|
602
|
+
// Treat as successful response
|
|
603
|
+
this.log.warn({ msg: 'Partial FETCH response', cid: this.id, err });
|
|
604
|
+
await new Promise(resolve => request.resolve({ response: parsed, next: resolve }));
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
|
|
577
608
|
let throttleDelay = false;
|
|
578
609
|
|
|
579
610
|
// MS365 throttling
|
|
@@ -596,7 +627,7 @@ class ImapFlow extends EventEmitter {
|
|
|
596
627
|
delayResponse = 5 * 60 * 1000;
|
|
597
628
|
}
|
|
598
629
|
|
|
599
|
-
this.log.warn({ msg: 'Throttling detected',
|
|
630
|
+
this.log.warn({ msg: 'Throttling detected', cid: this.id, throttleDelay, delayResponse, err });
|
|
600
631
|
await new Promise(r => setTimeout(r, delayResponse));
|
|
601
632
|
}
|
|
602
633
|
}
|
|
@@ -695,10 +726,7 @@ class ImapFlow extends EventEmitter {
|
|
|
695
726
|
this.idRequested = await this.run('ID', this.clientInfo);
|
|
696
727
|
}
|
|
697
728
|
|
|
698
|
-
|
|
699
|
-
if (!this.secureConnection) {
|
|
700
|
-
await this.upgradeConnection();
|
|
701
|
-
}
|
|
729
|
+
await this.upgradeToSTARTTLS();
|
|
702
730
|
|
|
703
731
|
let authenticated = await this.authenticate();
|
|
704
732
|
if (!authenticated) {
|
|
@@ -817,21 +845,47 @@ class ImapFlow extends EventEmitter {
|
|
|
817
845
|
});
|
|
818
846
|
}
|
|
819
847
|
|
|
820
|
-
|
|
848
|
+
_failSTARTTLS() {
|
|
849
|
+
if (this.options.doSTARTTLS === true) {
|
|
850
|
+
// STARTTLS configured as requirement
|
|
851
|
+
let err = new Error('Server does not support STARTTLS');
|
|
852
|
+
err.tlsFailed = true;
|
|
853
|
+
throw err;
|
|
854
|
+
} else {
|
|
855
|
+
// Opportunistic STARTTLS. But it's not possible right now.
|
|
856
|
+
// Attention: Could be a downgrade attack.
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Tries to upgrade the connection to TLS using STARTTLS.
|
|
863
|
+
* @throws if STARTTLS is required, but not possible.
|
|
864
|
+
* @returns {boolean} true, if the connection is now protected by TLS, either direct TLS or STARTTLS.
|
|
865
|
+
*/
|
|
866
|
+
async upgradeToSTARTTLS() {
|
|
867
|
+
if (this.options.doSTARTTLS === true && this.options.secure === true) {
|
|
868
|
+
throw new Error('Misconfiguration: Cannot set both secure=true for TLS and doSTARTTLS=true for STARTTLS.');
|
|
869
|
+
}
|
|
870
|
+
|
|
821
871
|
if (this.secureConnection) {
|
|
822
|
-
//
|
|
872
|
+
// Already using direct TLS. No need for STARTTLS.
|
|
823
873
|
return true;
|
|
824
874
|
}
|
|
825
875
|
|
|
826
|
-
if (
|
|
827
|
-
//
|
|
876
|
+
if (this.options.doSTARTTLS === false) {
|
|
877
|
+
// STARTTLS explictly disabled by config
|
|
828
878
|
return false;
|
|
829
879
|
}
|
|
830
880
|
|
|
881
|
+
if (!this.capabilities.has('STARTTLS')) {
|
|
882
|
+
return this._failSTARTTLS();
|
|
883
|
+
}
|
|
884
|
+
|
|
831
885
|
this.expectCapabilityUpdate = true;
|
|
832
886
|
let canUpgrade = await this.run('STARTTLS');
|
|
833
887
|
if (!canUpgrade) {
|
|
834
|
-
return;
|
|
888
|
+
return this._failSTARTTLS();
|
|
835
889
|
}
|
|
836
890
|
|
|
837
891
|
this.socket.unpipe(this.streamer);
|
|
@@ -855,6 +909,7 @@ class ImapFlow extends EventEmitter {
|
|
|
855
909
|
}
|
|
856
910
|
setImmediate(() => this.close());
|
|
857
911
|
this.upgrading = false;
|
|
912
|
+
err.tlsFailed = true;
|
|
858
913
|
reject(err);
|
|
859
914
|
});
|
|
860
915
|
|
|
@@ -864,6 +919,7 @@ class ImapFlow extends EventEmitter {
|
|
|
864
919
|
}
|
|
865
920
|
setImmediate(() => this.close());
|
|
866
921
|
let err = new Error('Failed to upgrade connection in required time');
|
|
922
|
+
err.tlsFailed = true;
|
|
867
923
|
err.code = 'UPGRADE_TIMEOUT';
|
|
868
924
|
reject(err);
|
|
869
925
|
}, UPGRADE_TIMEOUT);
|
package/lib/tools.js
CHANGED
|
@@ -390,7 +390,11 @@ module.exports = {
|
|
|
390
390
|
}
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
map.id =
|
|
393
|
+
map.id =
|
|
394
|
+
map.emailId ||
|
|
395
|
+
createHash('md5')
|
|
396
|
+
.update([path, mailbox.uidValidity?.toString() || '', map.uid.toString()].join(':'))
|
|
397
|
+
.digest('hex');
|
|
394
398
|
}
|
|
395
399
|
|
|
396
400
|
if (map.flags) {
|
package/lib/types.d.ts
CHANGED
|
@@ -8,8 +8,19 @@ declare module "imapflow" {
|
|
|
8
8
|
* @param options - IMAP connection options
|
|
9
9
|
* @param options.host - Hostname of the IMAP server
|
|
10
10
|
* @param options.port - Port number for the IMAP server
|
|
11
|
-
* @param [options.secure = false] - Should the connection be established over TLS.
|
|
12
|
-
*
|
|
11
|
+
* @param [options.secure = false] - Should the connection be established immediately and directly over TLS? Typically on port 993.
|
|
12
|
+
* @param [options.doSTARTTLS] - Should the connection be established using STARTTLS?
|
|
13
|
+
* * If `true`, the connection is first established as unencrypted and then upgraded to TLS using STARTTLS, before authentication.
|
|
14
|
+
* If the server does not advertize the `STARTTLS` `CAPABILITY`, or the upgrade fails for other reasons, then the connection fails.
|
|
15
|
+
* Note: The combination `secure=true` (direct TLS) and `doSTARTTLS=true` is invalid.
|
|
16
|
+
* * If `false`, then STARTTLS will not be used, even if the server advertizes it in IMAP `CAPABILITY`.
|
|
17
|
+
* This helps with servers that have a broken TLS configuration.
|
|
18
|
+
* If `doSTARTTLS=false` and `secure=false`, then a plain unencrypted socket is used.
|
|
19
|
+
* Be sure to clearly warn the user about the consequences.
|
|
20
|
+
* * If `undefined` (default) and `secure=false` (default), the connection is upgraded using STARTTLS before authentication, /only if possible/ .
|
|
21
|
+
* If not possible, the connection will use an unencrypted plain socket.
|
|
22
|
+
* This can mean TLS is used under normal circumstances, but a serious attacker can force an unencrypted connection and steal passwords,
|
|
23
|
+
* called "downgrade attack". This can lead to a false sense of security. Be sure to warn the user.
|
|
13
24
|
* @param [options.servername] - Servername for SNI (or when host is set to an IP address)
|
|
14
25
|
* @param [options.disableCompression = false] - if `true` then client does not try to use COMPRESS=DEFLATE extension
|
|
15
26
|
* @param options.auth - Authentication options. Authentication is requested automatically during <code>connect()</code>
|
|
@@ -41,6 +52,7 @@ declare module "imapflow" {
|
|
|
41
52
|
host: string;
|
|
42
53
|
port: number;
|
|
43
54
|
secure?: boolean;
|
|
55
|
+
doSTARTTLS?: boolean;
|
|
44
56
|
servername?: string;
|
|
45
57
|
disableCompression?: boolean;
|
|
46
58
|
auth: {
|
|
@@ -122,6 +134,11 @@ declare module "imapflow" {
|
|
|
122
134
|
* If `true` then in addition of sending data to logger, ImapFlow emits 'log' events with the same data
|
|
123
135
|
*/
|
|
124
136
|
emitLogs: boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Tries to upgrade the connection to TLS using STARTTLS.
|
|
139
|
+
* @returns true, if the connection is now protected by TLS, either direct TLS or STARTTLS.
|
|
140
|
+
*/
|
|
141
|
+
upgradeToSTARTTLS(): boolean;
|
|
125
142
|
/**
|
|
126
143
|
* Initiates a connection against IMAP server. Throws if anything goes wrong. This is something you have to call before you can run any IMAP commands
|
|
127
144
|
* @example
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imapflow",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.170",
|
|
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.25.9",
|
|
33
33
|
"@babel/plugin-syntax-class-properties": "7.12.13",
|
|
34
34
|
"@babel/preset-env": "7.26.0",
|
|
35
|
-
"@types/node": "22.
|
|
35
|
+
"@types/node": "22.10.1",
|
|
36
36
|
"eslint": "8.57.0",
|
|
37
37
|
"eslint-config-nodemailer": "1.2.0",
|
|
38
38
|
"eslint-config-prettier": "9.1.0",
|
|
@@ -40,7 +40,7 @@
|
|
|
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.
|
|
43
|
+
"imapflow-jsdoc-template": "3.4.0-imapflow.2",
|
|
44
44
|
"jsdoc": "3.6.11",
|
|
45
45
|
"st": "3.0.1",
|
|
46
46
|
"tsd-jsdoc": "2.5.0"
|
|
@@ -49,9 +49,9 @@
|
|
|
49
49
|
"encoding-japanese": "2.2.0",
|
|
50
50
|
"iconv-lite": "0.6.3",
|
|
51
51
|
"libbase64": "1.3.0",
|
|
52
|
-
"libmime": "5.3.
|
|
53
|
-
"libqp": "2.1.
|
|
54
|
-
"mailsplit": "5.4.
|
|
52
|
+
"libmime": "5.3.6",
|
|
53
|
+
"libqp": "2.1.1",
|
|
54
|
+
"mailsplit": "5.4.2",
|
|
55
55
|
"nodemailer": "6.9.16",
|
|
56
56
|
"pino": "9.5.0",
|
|
57
57
|
"socks": "2.8.3"
|