imapflow 1.2.8 → 1.2.9
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/.ncurc.js +1 -1
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +7 -0
- package/README.md +3 -3
- package/lib/charsets.js +15 -0
- package/lib/commands/append.js +62 -54
- package/lib/commands/authenticate.js +99 -52
- package/lib/commands/capability.js +12 -2
- package/lib/commands/close.js +11 -1
- package/lib/commands/compress.js +10 -1
- package/lib/commands/copy.js +18 -1
- package/lib/commands/create.js +15 -2
- package/lib/commands/delete.js +10 -1
- package/lib/commands/enable.js +12 -1
- package/lib/commands/expunge.js +18 -2
- package/lib/commands/fetch.js +39 -4
- package/lib/commands/id.js +22 -3
- package/lib/commands/idle.js +39 -4
- package/lib/commands/list.js +86 -48
- package/lib/commands/login.js +12 -1
- package/lib/commands/logout.js +11 -2
- package/lib/commands/move.js +17 -1
- package/lib/commands/namespace.js +32 -2
- package/lib/commands/noop.js +6 -1
- package/lib/commands/quota.js +33 -14
- package/lib/commands/rename.js +13 -1
- package/lib/commands/search.js +16 -1
- package/lib/commands/select.js +76 -33
- package/lib/commands/starttls.js +6 -1
- package/lib/commands/status.js +64 -52
- package/lib/commands/store.js +27 -4
- package/lib/commands/subscribe.js +7 -1
- package/lib/commands/unsubscribe.js +7 -1
- package/lib/handler/imap-compiler.js +44 -2
- package/lib/handler/imap-formal-syntax.js +51 -3
- package/lib/handler/imap-handler.js +8 -0
- package/lib/handler/imap-parser.js +23 -2
- package/lib/handler/imap-stream.js +84 -31
- package/lib/handler/parser-instance.js +61 -1
- package/lib/handler/token-parser.js +66 -9
- package/lib/imap-commands.js +11 -0
- package/lib/imap-flow.d.ts +6 -0
- package/lib/imap-flow.js +164 -42
- package/lib/jp-decoder.js +10 -0
- package/lib/limited-passthrough.js +12 -5
- package/lib/proxy-connection.js +18 -12
- package/lib/search-compiler.js +3 -11
- package/lib/special-use.js +23 -16
- package/lib/tools.js +218 -13
- package/package.json +4 -11
- package/test/commands-integration-test.js +33 -0
- package/test/special-use-test.js +32 -0
- package/assets/favicon.ico +0 -0
- package/jsdoc.json +0 -28
|
@@ -2,26 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
const { Transform } = require('stream');
|
|
4
4
|
|
|
5
|
+
// A Transform stream that passes through data up to a maximum byte limit,
|
|
6
|
+
// then silently discards all subsequent chunks. Used to enforce download
|
|
7
|
+
// size limits when fetching message content from the IMAP server.
|
|
5
8
|
class LimitedPassthrough extends Transform {
|
|
6
9
|
constructor(options) {
|
|
7
10
|
super();
|
|
8
11
|
this.options = options || {};
|
|
9
12
|
this.maxBytes = this.options.maxBytes || Infinity;
|
|
10
13
|
this.processed = 0;
|
|
14
|
+
// Once set to true, all subsequent chunks are dropped without error
|
|
11
15
|
this.limited = false;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
_transform(chunk, encoding, done) {
|
|
19
|
+
// If the limit was already reached, discard the chunk immediately
|
|
15
20
|
if (this.limited) {
|
|
16
21
|
return done();
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
const remainingBytes = this.maxBytes - this.processed;
|
|
25
|
+
if (remainingBytes < 1) {
|
|
26
|
+
return done();
|
|
27
|
+
}
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
// Slice the chunk to fit within the remaining byte budget
|
|
30
|
+
if (chunk.length > remainingBytes) {
|
|
31
|
+
chunk = chunk.slice(0, remainingBytes);
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
this.processed += chunk.length;
|
package/lib/proxy-connection.js
CHANGED
|
@@ -7,11 +7,22 @@ const httpProxyClientAsync = util.promisify(httpProxyClient);
|
|
|
7
7
|
const dns = require('dns').promises;
|
|
8
8
|
const net = require('net');
|
|
9
9
|
|
|
10
|
+
// Redacts the password from a parsed URL object before it is logged,
|
|
11
|
+
// preventing credentials from appearing in log output.
|
|
12
|
+
const hidePassword = proxyUrl => {
|
|
13
|
+
if (proxyUrl.password) {
|
|
14
|
+
proxyUrl.password = '(hidden)';
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
10
18
|
const proxyConnection = async (logger, connectionUrl, host, port) => {
|
|
11
19
|
let proxyUrl = new URL(connectionUrl);
|
|
12
20
|
|
|
13
21
|
let protocol = proxyUrl.protocol.replace(/:$/, '').toLowerCase();
|
|
14
22
|
|
|
23
|
+
// Pre-resolve the IMAP server hostname to an IP address before passing it to the proxy.
|
|
24
|
+
// Some proxy implementations (especially SOCKS4) do not support hostname resolution,
|
|
25
|
+
// so we resolve DNS on the client side to ensure compatibility.
|
|
15
26
|
if (!net.isIP(host)) {
|
|
16
27
|
let resolveResult = await dns.resolve(host);
|
|
17
28
|
if (resolveResult && resolveResult.length) {
|
|
@@ -26,9 +37,7 @@ const proxyConnection = async (logger, connectionUrl, host, port) => {
|
|
|
26
37
|
try {
|
|
27
38
|
let socket = await httpProxyClientAsync(proxyUrl.href, port, host);
|
|
28
39
|
if (socket) {
|
|
29
|
-
|
|
30
|
-
proxyUrl.password = '(hidden)';
|
|
31
|
-
}
|
|
40
|
+
hidePassword(proxyUrl);
|
|
32
41
|
logger.info({
|
|
33
42
|
msg: 'Established a socket via HTTP proxy',
|
|
34
43
|
proxyUrl: proxyUrl.href,
|
|
@@ -38,9 +47,7 @@ const proxyConnection = async (logger, connectionUrl, host, port) => {
|
|
|
38
47
|
}
|
|
39
48
|
return socket;
|
|
40
49
|
} catch (err) {
|
|
41
|
-
|
|
42
|
-
proxyUrl.password = '(hidden)';
|
|
43
|
-
}
|
|
50
|
+
hidePassword(proxyUrl);
|
|
44
51
|
logger.error({
|
|
45
52
|
msg: 'Failed to establish a socket via HTTP proxy',
|
|
46
53
|
proxyUrl: proxyUrl.href,
|
|
@@ -59,6 +66,9 @@ const proxyConnection = async (logger, connectionUrl, host, port) => {
|
|
|
59
66
|
case 'socks4a': {
|
|
60
67
|
let proxyType = Number(protocol.replace(/\D/g, '')) || 5;
|
|
61
68
|
|
|
69
|
+
// targetHost here is the SOCKS proxy server's hostname (not the final IMAP destination).
|
|
70
|
+
// The SOCKS library needs a resolved IP for the proxy host it connects to.
|
|
71
|
+
// The final IMAP destination (host/port) is passed separately as 'destination'.
|
|
62
72
|
let targetHost = proxyUrl.hostname;
|
|
63
73
|
if (!net.isIP(targetHost)) {
|
|
64
74
|
let resolveResult = await dns.resolve(targetHost);
|
|
@@ -89,9 +99,7 @@ const proxyConnection = async (logger, connectionUrl, host, port) => {
|
|
|
89
99
|
try {
|
|
90
100
|
const info = await SocksClient.createConnection(connectionOpts);
|
|
91
101
|
if (info && info.socket) {
|
|
92
|
-
|
|
93
|
-
proxyUrl.password = '(hidden)';
|
|
94
|
-
}
|
|
102
|
+
hidePassword(proxyUrl);
|
|
95
103
|
logger.info({
|
|
96
104
|
msg: 'Established a socket via SOCKS proxy',
|
|
97
105
|
proxyUrl: proxyUrl.href,
|
|
@@ -101,9 +109,7 @@ const proxyConnection = async (logger, connectionUrl, host, port) => {
|
|
|
101
109
|
}
|
|
102
110
|
return info.socket;
|
|
103
111
|
} catch (err) {
|
|
104
|
-
|
|
105
|
-
proxyUrl.password = '(hidden)';
|
|
106
|
-
}
|
|
112
|
+
hidePassword(proxyUrl);
|
|
107
113
|
logger.error({
|
|
108
114
|
msg: 'Failed to establish a socket via SOCKS proxy',
|
|
109
115
|
proxyUrl: proxyUrl.href,
|
package/lib/search-compiler.js
CHANGED
|
@@ -105,8 +105,8 @@ let isUnicodeString = str => {
|
|
|
105
105
|
* Supports standard IMAP search criteria and extensions like OBJECTID and Gmail extensions.
|
|
106
106
|
*
|
|
107
107
|
* @param {Object} connection - IMAP connection object
|
|
108
|
-
* @param {
|
|
109
|
-
* @param {
|
|
108
|
+
* @param {Map} connection.capabilities - Map of server capabilities
|
|
109
|
+
* @param {Set} connection.enabled - Set of enabled extensions
|
|
110
110
|
* @param {Object} connection.mailbox - Current mailbox information
|
|
111
111
|
* @param {Set} connection.mailbox.flags - Available flags in the mailbox
|
|
112
112
|
* @param {Object} query - Search query object
|
|
@@ -259,15 +259,7 @@ module.exports.searchCompiler = (connection, query) => {
|
|
|
259
259
|
// Convert to seconds ago from now
|
|
260
260
|
const now = Date.now();
|
|
261
261
|
const withinSeconds = Math.round(Math.max(0, now - params[term].getTime()) / 1000);
|
|
262
|
-
|
|
263
|
-
switch (term.toUpperCase()) {
|
|
264
|
-
case 'BEFORE':
|
|
265
|
-
withinKeyword = 'OLDER';
|
|
266
|
-
break;
|
|
267
|
-
case 'SINCE':
|
|
268
|
-
withinKeyword = 'YOUNGER';
|
|
269
|
-
break;
|
|
270
|
-
}
|
|
262
|
+
const withinKeyword = term.toUpperCase() === 'BEFORE' ? 'OLDER' : 'YOUNGER';
|
|
271
263
|
setOpt(attributes, withinKeyword, withinSeconds.toString());
|
|
272
264
|
break;
|
|
273
265
|
}
|
package/lib/special-use.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// Localized folder name mappings for detecting special-use mailboxes.
|
|
4
|
+
// When the server does not advertise the SPECIAL-USE extension (RFC 6154),
|
|
5
|
+
// we fall back to matching folder names against these lists of known
|
|
6
|
+
// translations in various languages (including non-Latin scripts).
|
|
3
7
|
module.exports = {
|
|
4
8
|
flags: ['\\All', '\\Archive', '\\Drafts', '\\Flagged', '\\Junk', '\\Sent', '\\Trash'],
|
|
5
9
|
names: {
|
|
@@ -281,27 +285,30 @@ module.exports = {
|
|
|
281
285
|
},
|
|
282
286
|
|
|
283
287
|
specialUse(hasSpecialUseExtension, folder) {
|
|
284
|
-
|
|
285
|
-
|
|
288
|
+
// If the server supports SPECIAL-USE (RFC 6154), check for special-use flags first.
|
|
289
|
+
// Extension-provided flags take precedence over name-based detection because they
|
|
290
|
+
// are authoritative -- the server explicitly marks the folder's role.
|
|
286
291
|
if (hasSpecialUseExtension) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
source: 'extension'
|
|
290
|
-
}
|
|
292
|
+
const flag = module.exports.flags.find(flag => folder.flags.has(flag));
|
|
293
|
+
if (flag) {
|
|
294
|
+
return { flag, source: 'extension' };
|
|
295
|
+
}
|
|
291
296
|
}
|
|
292
297
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
+
// Fallback: match folder name against known localized names.
|
|
299
|
+
// Remove U+200E (LEFT-TO-RIGHT MARK) which some mail clients (especially for
|
|
300
|
+
// RTL languages like Arabic, Hebrew) insert into folder names for display purposes.
|
|
301
|
+
// These invisible marks would otherwise prevent exact string matching.
|
|
302
|
+
let name = folder.name
|
|
303
|
+
.toLowerCase()
|
|
304
|
+
.replace(/\u200e/g, '')
|
|
305
|
+
.trim();
|
|
298
306
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
};
|
|
307
|
+
const flag = Object.keys(module.exports.names).find(flag => module.exports.names[flag].includes(name));
|
|
308
|
+
if (flag) {
|
|
309
|
+
return { flag, source: 'name' };
|
|
303
310
|
}
|
|
304
311
|
|
|
305
|
-
return
|
|
312
|
+
return { flag: null };
|
|
306
313
|
}
|
|
307
314
|
};
|