imapflow 1.2.8 → 1.2.10
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 +14 -0
- package/README.md +36 -58
- package/eslint.config.js +18 -16
- 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 +175 -46
- 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 +5 -17
- package/test/commands-integration-test.js +33 -0
- package/test/connection-edge-cases-test.js +105 -0
- package/test/special-use-test.js +32 -0
- package/.babelrc +0 -6
- package/.eslintrc +0 -16
- package/assets/favicon.ico +0 -0
- package/jsdoc.json +0 -28
package/.ncurc.js
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.10](https://github.com/postalsys/imapflow/compare/v1.2.9...v1.2.10) (2026-02-19)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* add null guards in compress() to prevent crash on TLS teardown ([84cd3ff](https://github.com/postalsys/imapflow/commit/84cd3ff8815d8ba37a37c1e7a09c244e80b61948))
|
|
9
|
+
|
|
10
|
+
## [1.2.9](https://github.com/postalsys/imapflow/compare/v1.2.8...v1.2.9) (2026-02-06)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* trigger build ([fd7a4c2](https://github.com/postalsys/imapflow/commit/fd7a4c2012d0b7742c45a2aced2d05da8401aa9a))
|
|
16
|
+
|
|
3
17
|
## [1.2.8](https://github.com/postalsys/imapflow/compare/v1.2.7...v1.2.8) (2026-01-28)
|
|
4
18
|
|
|
5
19
|
|
package/README.md
CHANGED
|
@@ -1,109 +1,87 @@
|
|
|
1
1
|
# ImapFlow
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Modern and easy-to-use IMAP client library for Node.js.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
The focus for ImapFlow is to provide easy to use API over IMAP. Using ImapFlow does not expect knowledge about specific IMAP details. A general understanding is good enough.
|
|
9
|
-
|
|
10
|
-
IMAP extensions are handled in the background, so, for example, you can always request `labels` value from a {@link FetchQueryObject|fetch()} call, but if the IMAP server does not support `X-GM-EXT-1` extension, then `labels` value is not included in the response.
|
|
5
|
+
[](https://www.npmjs.com/package/imapflow)
|
|
6
|
+
[](https://github.com/postalsys/imapflow/blob/master/LICENSE)
|
|
11
7
|
|
|
12
|
-
|
|
8
|
+
ImapFlow provides a clean, promise-based API for working with IMAP, so you don't need in-depth knowledge of the protocol. IMAP extensions are detected and handled automatically. You write the same code regardless of server capabilities, and ImapFlow adapts behind the scenes.
|
|
13
9
|
|
|
14
|
-
|
|
10
|
+
## Features
|
|
15
11
|
|
|
16
|
-
|
|
12
|
+
- **Async/await API** - all methods return Promises
|
|
13
|
+
- **Automatic IMAP extension handling** - CONDSTORE, QRESYNC, IDLE, COMPRESS, and [more](https://imapflow.com/docs/)
|
|
14
|
+
- **Message streaming** - async iterators for efficient processing
|
|
15
|
+
- **Mailbox locking** - built-in locking mechanism for safe concurrent access
|
|
16
|
+
- **TypeScript support** - type definitions included
|
|
17
|
+
- **Proxy support** - SOCKS and HTTP CONNECT proxies
|
|
18
|
+
- **Gmail support** - labels, raw search via X-GM-EXT-1
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
## Installation
|
|
19
21
|
|
|
20
|
-
```
|
|
22
|
+
```bash
|
|
21
23
|
npm install imapflow
|
|
22
24
|
```
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
## Quick Example
|
|
25
27
|
|
|
26
28
|
```js
|
|
27
29
|
const { ImapFlow } = require('imapflow');
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### Promises
|
|
31
30
|
|
|
32
|
-
All ImapFlow methods use Promises, so you need to wait using `await` or wait for the `then()` method to fire until you get the response.
|
|
33
|
-
|
|
34
|
-
```js
|
|
35
|
-
const { ImapFlow } = require('imapflow');
|
|
36
31
|
const client = new ImapFlow({
|
|
37
|
-
host: '
|
|
32
|
+
host: 'imap.example.com',
|
|
38
33
|
port: 993,
|
|
39
34
|
secure: true,
|
|
40
35
|
auth: {
|
|
41
|
-
user: '
|
|
42
|
-
pass: '
|
|
36
|
+
user: 'user@example.com',
|
|
37
|
+
pass: 'password'
|
|
43
38
|
}
|
|
44
39
|
});
|
|
45
40
|
|
|
46
41
|
const main = async () => {
|
|
47
|
-
// Wait until client connects and authorizes
|
|
48
42
|
await client.connect();
|
|
49
43
|
|
|
50
|
-
// Select and lock a mailbox. Throws if mailbox does not exist
|
|
51
44
|
let lock = await client.getMailboxLock('INBOX');
|
|
52
45
|
try {
|
|
53
|
-
// fetch latest message
|
|
54
|
-
// client.mailbox includes information about currently selected mailbox
|
|
55
|
-
// "exists" value is also the largest sequence number available in the mailbox
|
|
46
|
+
// fetch latest message
|
|
56
47
|
let message = await client.fetchOne(client.mailbox.exists, { source: true });
|
|
57
48
|
console.log(message.source.toString());
|
|
58
49
|
|
|
59
50
|
// list subjects for all messages
|
|
60
|
-
// uid value is always included in FETCH response, envelope strings are in unicode.
|
|
61
51
|
for await (let message of client.fetch('1:*', { envelope: true })) {
|
|
62
52
|
console.log(`${message.uid}: ${message.envelope.subject}`);
|
|
63
53
|
}
|
|
64
54
|
} finally {
|
|
65
|
-
//
|
|
55
|
+
// always release the lock
|
|
66
56
|
lock.release();
|
|
67
57
|
}
|
|
68
58
|
|
|
69
|
-
// log out and close connection
|
|
70
59
|
await client.logout();
|
|
71
60
|
};
|
|
72
61
|
|
|
73
|
-
main().catch(
|
|
62
|
+
main().catch(console.error);
|
|
74
63
|
```
|
|
75
64
|
|
|
76
|
-
|
|
65
|
+
See the [Quick Start guide](https://imapflow.com/docs/getting-started/quick-start) for more examples, including Gmail, Outlook, and Yahoo configuration.
|
|
77
66
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
```js
|
|
81
|
-
const { ImapFlow } = require('imapflow');
|
|
82
|
-
const client = new ImapFlow({
|
|
83
|
-
host: 'mail.example.com',
|
|
84
|
-
port: 993,
|
|
85
|
-
secure: true,
|
|
86
|
-
auth: {
|
|
87
|
-
user: 'admin@example.com', // Admin credentials (authentication identity)
|
|
88
|
-
pass: 'adminpassword',
|
|
89
|
-
authzid: 'user@example.com', // User to impersonate (authorization identity)
|
|
90
|
-
loginMethod: 'AUTH=PLAIN' // Must use PLAIN mechanism for authzid
|
|
91
|
-
}
|
|
92
|
-
});
|
|
67
|
+
## Documentation
|
|
93
68
|
|
|
94
|
-
|
|
95
|
-
await client.connect();
|
|
96
|
-
// Now operating on user@example.com's mailbox as admin
|
|
97
|
-
```
|
|
69
|
+
Full documentation is available at **[imapflow.com](https://imapflow.com/docs/)**.
|
|
98
70
|
|
|
99
|
-
|
|
71
|
+
- [Installation](https://imapflow.com/docs/getting-started/installation) - requirements and setup
|
|
72
|
+
- [Quick Start](https://imapflow.com/docs/getting-started/quick-start) - your first ImapFlow application
|
|
73
|
+
- [Basic Usage](https://imapflow.com/docs/guides/basic-usage) - core concepts and patterns
|
|
74
|
+
- [Configuration](https://imapflow.com/docs/guides/configuration) - connection options and settings
|
|
75
|
+
- [Fetching Messages](https://imapflow.com/docs/guides/fetching-messages) - reading email data
|
|
76
|
+
- [Searching](https://imapflow.com/docs/guides/searching) - finding messages with search queries
|
|
77
|
+
- [Mailbox Management](https://imapflow.com/docs/guides/mailbox-management) - creating, renaming, and deleting mailboxes
|
|
78
|
+
- [API Reference](https://imapflow.com/docs/api/imapflow-client) - complete method and event documentation
|
|
100
79
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
[API reference](https://imapflow.com/module-imapflow-ImapFlow.html).
|
|
80
|
+
> [!NOTE]
|
|
81
|
+
> If you are looking for a complete email integration solution, ImapFlow was built for [EmailEngine](https://emailengine.app/), a self-hosted email gateway that provides REST API access to IMAP and SMTP accounts.
|
|
104
82
|
|
|
105
83
|
## License
|
|
106
84
|
|
|
107
|
-
|
|
85
|
+
Copyright (c) 2020-2025 Postal Systems OU
|
|
108
86
|
|
|
109
|
-
Licensed under
|
|
87
|
+
Licensed under the MIT license.
|
package/eslint.config.js
CHANGED
|
@@ -1,38 +1,40 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { FlatCompat } = require('@eslint/eslintrc');
|
|
4
3
|
const js = require('@eslint/js');
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
recommendedConfig: js.configs.recommended
|
|
9
|
-
});
|
|
4
|
+
const globals = require('globals');
|
|
5
|
+
const prettierConfig = require('eslint-config-prettier/flat');
|
|
6
|
+
const nodemailerConfig = require('eslint-config-nodemailer');
|
|
10
7
|
|
|
11
8
|
module.exports = [
|
|
12
9
|
{
|
|
13
10
|
ignores: ['node_modules/**', 'examples/**', 'docs/**']
|
|
14
11
|
},
|
|
15
|
-
|
|
12
|
+
js.configs.recommended,
|
|
16
13
|
{
|
|
17
14
|
languageOptions: {
|
|
18
|
-
ecmaVersion:
|
|
15
|
+
ecmaVersion: 2022,
|
|
19
16
|
sourceType: 'script',
|
|
20
|
-
globals: {
|
|
21
|
-
BigInt: 'readonly'
|
|
22
|
-
},
|
|
23
|
-
parser: require('@babel/eslint-parser'),
|
|
24
17
|
parserOptions: {
|
|
25
|
-
|
|
18
|
+
ecmaFeatures: {
|
|
19
|
+
globalReturn: true
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
globals: {
|
|
23
|
+
...globals.node,
|
|
24
|
+
...globals.es2021,
|
|
25
|
+
it: 'readonly',
|
|
26
|
+
describe: 'readonly',
|
|
27
|
+
beforeEach: 'readonly',
|
|
28
|
+
afterEach: 'readonly'
|
|
26
29
|
}
|
|
27
30
|
},
|
|
28
|
-
plugins: {
|
|
29
|
-
'@babel': require('@babel/eslint-plugin')
|
|
30
|
-
},
|
|
31
31
|
rules: {
|
|
32
|
+
...nodemailerConfig.rules,
|
|
32
33
|
'no-await-in-loop': 0,
|
|
33
34
|
'require-atomic-updates': 0
|
|
34
35
|
}
|
|
35
36
|
},
|
|
37
|
+
prettierConfig,
|
|
36
38
|
{
|
|
37
39
|
files: ['eslint.config.js', '.prettierrc.js', '.ncurc.js'],
|
|
38
40
|
rules: {
|
package/lib/charsets.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// Subset of the IANA Character Sets registry (https://www.iana.org/assignments/character-sets/).
|
|
4
|
+
// Used to validate and resolve charset names found in MIME Content-Type parameters.
|
|
5
|
+
// This list covers the most commonly encountered charsets in email messages.
|
|
3
6
|
const CHARACTER_SETS = [
|
|
4
7
|
'US-ASCII',
|
|
5
8
|
'ISO-8859-1',
|
|
@@ -262,6 +265,14 @@ const CHARACTER_SETS = [
|
|
|
262
265
|
|
|
263
266
|
const CHARSET_MAP = new Map();
|
|
264
267
|
|
|
268
|
+
// Build a lookup map with normalized keys for fuzzy charset resolution.
|
|
269
|
+
// Normalization strategy:
|
|
270
|
+
// 1. Strip all underscores, hyphens, and spaces, then lowercase (e.g., "ISO-8859-1" -> "iso88591")
|
|
271
|
+
// 2. Create additional aliases for common alternative prefixes:
|
|
272
|
+
// - "windows" -> "win" (e.g., "windows1252" also matches as "win1252")
|
|
273
|
+
// - "usascii" -> "ascii" (common shorthand)
|
|
274
|
+
// - "iso8859" -> "latin" (e.g., "iso88591" also matches as "latin1")
|
|
275
|
+
// This handles the many variant spellings found in real-world email headers.
|
|
265
276
|
CHARACTER_SETS.forEach(entry => {
|
|
266
277
|
let key = entry.replace(/[_-\s]/g, '').toLowerCase();
|
|
267
278
|
let modifiedKey = key
|
|
@@ -274,6 +285,10 @@ CHARACTER_SETS.forEach(entry => {
|
|
|
274
285
|
}
|
|
275
286
|
});
|
|
276
287
|
|
|
288
|
+
// Resolves a charset name to its canonical IANA form using case-insensitive,
|
|
289
|
+
// symbol-stripping normalization. For example, "WIN-1252", "windows_1252",
|
|
290
|
+
// and "WINDOWS-1252" all resolve to "windows-1252".
|
|
291
|
+
// Returns null if the charset is not recognized.
|
|
277
292
|
module.exports.resolveCharset = charset => {
|
|
278
293
|
let key = charset.replace(/[_-\s]/g, '').toLowerCase();
|
|
279
294
|
if (CHARSET_MAP.has(key)) {
|
package/lib/commands/append.js
CHANGED
|
@@ -2,7 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
const { formatFlag, canUseFlag, formatDateTime, normalizePath, encodePath, comparePaths, enhanceCommandError } = require('../tools.js');
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Appends a message to a mailbox.
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} connection - IMAP connection instance
|
|
9
|
+
* @param {string} destination - Destination mailbox path
|
|
10
|
+
* @param {Buffer|string} content - Message content (RFC 822 format)
|
|
11
|
+
* @param {string|string[]} [flags] - Message flags to set on the appended message
|
|
12
|
+
* @param {Date|string} [idate] - Internal date to set for the message
|
|
13
|
+
* @returns {Promise<{destination: string, path?: string, uid?: number, uidValidity?: BigInt, seq?: number}|undefined>} Append result with UID info if available, or undefined if preconditions not met
|
|
14
|
+
* @throws {Error} If the APPEND command fails or message exceeds APPENDLIMIT
|
|
15
|
+
*/
|
|
6
16
|
module.exports = async (connection, destination, content, flags, idate) => {
|
|
7
17
|
if (![connection.states.AUTHENTICATED, connection.states.SELECTED].includes(connection.state) || !destination) {
|
|
8
18
|
// nothing to do here
|
|
@@ -13,6 +23,8 @@ module.exports = async (connection, destination, content, flags, idate) => {
|
|
|
13
23
|
content = Buffer.from(content);
|
|
14
24
|
}
|
|
15
25
|
|
|
26
|
+
// APPENDLIMIT capability (RFC 7889): server may advertise the maximum message
|
|
27
|
+
// size it accepts. Check before sending to avoid a wasted round-trip.
|
|
16
28
|
if (connection.capabilities.has('APPENDLIMIT')) {
|
|
17
29
|
let appendLimit = connection.capabilities.get('APPENDLIMIT');
|
|
18
30
|
if (typeof appendLimit === 'number' && appendLimit < content.length) {
|
|
@@ -24,28 +36,36 @@ module.exports = async (connection, destination, content, flags, idate) => {
|
|
|
24
36
|
|
|
25
37
|
destination = normalizePath(connection, destination);
|
|
26
38
|
|
|
39
|
+
// If appending to the currently selected mailbox, we can listen for the
|
|
40
|
+
// untagged EXISTS response to capture the new message's sequence number.
|
|
27
41
|
let expectExists = comparePaths(connection, connection.mailbox.path, destination);
|
|
28
42
|
|
|
43
|
+
// Validate and format flags. Only flags allowed by the mailbox's permanentFlags are included.
|
|
29
44
|
flags = (Array.isArray(flags) ? flags : [].concat(flags || []))
|
|
30
45
|
.map(flag => flag && formatFlag(flag.toString()))
|
|
31
46
|
.filter(flag => flag && canUseFlag(connection.mailbox, flag));
|
|
32
47
|
|
|
48
|
+
// APPEND command format: APPEND <mailbox> [<flags>] [<date-time>] <literal>
|
|
33
49
|
let attributes = [{ type: 'ATOM', value: encodePath(connection, destination) }];
|
|
34
50
|
|
|
51
|
+
// Internal date: the date the server should record for this message.
|
|
52
|
+
// Must be quoted (STRING type) per the IMAP date-time grammar.
|
|
35
53
|
idate = idate ? formatDateTime(idate) : false;
|
|
36
54
|
|
|
55
|
+
// Flags and date are optional; flags must come before date if both are present
|
|
37
56
|
if (flags.length || idate) {
|
|
38
57
|
attributes.push(flags.map(flag => ({ type: 'ATOM', value: flag })));
|
|
39
58
|
}
|
|
40
59
|
|
|
41
60
|
if (idate) {
|
|
42
|
-
attributes.push({ type: 'STRING', value: idate });
|
|
61
|
+
attributes.push({ type: 'STRING', value: idate });
|
|
43
62
|
}
|
|
44
63
|
|
|
64
|
+
// BINARY extension (RFC 3516): if the message content contains NUL bytes,
|
|
65
|
+
// use literal8 syntax (~{size}\r\n) instead of regular literal ({size}\r\n).
|
|
66
|
+
// Regular literals cannot contain NUL bytes per the IMAP grammar.
|
|
45
67
|
let isLiteral8 = false;
|
|
46
68
|
if (connection.capabilities.has('BINARY') && !connection.disableBinary) {
|
|
47
|
-
// Value is literal8 if it contains NULL bytes. The server must support the BINARY extension
|
|
48
|
-
// and if it does not then send the value as a regular literal and hope for the best
|
|
49
69
|
isLiteral8 = content.indexOf(Buffer.from([0])) >= 0;
|
|
50
70
|
}
|
|
51
71
|
|
|
@@ -56,72 +76,58 @@ module.exports = async (connection, destination, content, flags, idate) => {
|
|
|
56
76
|
map.path = connection.mailbox.path;
|
|
57
77
|
}
|
|
58
78
|
|
|
79
|
+
// Handler for untagged EXISTS: captures the new message count which gives
|
|
80
|
+
// us the sequence number of the appended message (it's the latest message).
|
|
81
|
+
const handleExistsUpdate = untagged => {
|
|
82
|
+
map.seq = Number(untagged.command);
|
|
83
|
+
|
|
84
|
+
// Update the connection's mailbox state and emit 'exists' event if the
|
|
85
|
+
// count changed (notifies listeners about the new message).
|
|
86
|
+
if (expectExists) {
|
|
87
|
+
let prevCount = connection.mailbox.exists;
|
|
88
|
+
if (map.seq !== prevCount) {
|
|
89
|
+
connection.mailbox.exists = map.seq;
|
|
90
|
+
connection.emit('exists', {
|
|
91
|
+
path: connection.mailbox.path,
|
|
92
|
+
count: map.seq,
|
|
93
|
+
prevCount
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
59
99
|
let response;
|
|
60
100
|
try {
|
|
61
101
|
response = await connection.exec('APPEND', attributes, {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
EXISTS: async untagged => {
|
|
65
|
-
map.seq = Number(untagged.command);
|
|
66
|
-
|
|
67
|
-
if (expectExists) {
|
|
68
|
-
let prevCount = connection.mailbox.exists;
|
|
69
|
-
if (map.seq !== prevCount) {
|
|
70
|
-
connection.mailbox.exists = map.seq;
|
|
71
|
-
connection.emit('exists', {
|
|
72
|
-
path: connection.mailbox.path,
|
|
73
|
-
count: map.seq,
|
|
74
|
-
prevCount
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
: false
|
|
102
|
+
// Only listen for EXISTS if we're appending to the currently selected mailbox
|
|
103
|
+
untagged: expectExists ? { EXISTS: handleExistsUpdate } : false
|
|
81
104
|
});
|
|
82
105
|
|
|
106
|
+
// UIDPLUS (RFC 4315): the server may include APPENDUID response code in
|
|
107
|
+
// the tagged OK. Format: [APPENDUID <uidValidity> <uid>]
|
|
83
108
|
let section = response.response.attributes && response.response.attributes[0] && response.response.attributes[0].section;
|
|
84
109
|
if (section && section.length) {
|
|
85
110
|
let responseCode = section[0] && typeof section[0].value === 'string' ? section[0].value : '';
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
map.uid = uid;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
break;
|
|
111
|
+
if (responseCode.toUpperCase() === 'APPENDUID') {
|
|
112
|
+
let uidValidity = section[1] && typeof section[1].value === 'string' && !isNaN(section[1].value) ? BigInt(section[1].value) : false;
|
|
113
|
+
let uid = section[2] && typeof section[2].value === 'string' && !isNaN(section[2].value) ? Number(section[2].value) : false;
|
|
114
|
+
if (uidValidity) {
|
|
115
|
+
map.uidValidity = uidValidity;
|
|
116
|
+
}
|
|
117
|
+
if (uid) {
|
|
118
|
+
map.uid = uid;
|
|
119
|
+
}
|
|
99
120
|
}
|
|
100
121
|
}
|
|
101
122
|
|
|
102
123
|
response.next();
|
|
103
124
|
|
|
125
|
+
// If we didn't get an EXISTS during APPEND (some servers don't send it
|
|
126
|
+
// until the next command), issue a NOOP to flush pending notifications.
|
|
104
127
|
if (expectExists && !map.seq) {
|
|
105
|
-
// try to use NOOP to get the new sequence number
|
|
106
128
|
try {
|
|
107
129
|
response = await connection.exec('NOOP', false, {
|
|
108
|
-
untagged: {
|
|
109
|
-
EXISTS: async untagged => {
|
|
110
|
-
map.seq = Number(untagged.command);
|
|
111
|
-
|
|
112
|
-
if (expectExists) {
|
|
113
|
-
let prevCount = connection.mailbox.exists;
|
|
114
|
-
if (map.seq !== prevCount) {
|
|
115
|
-
connection.mailbox.exists = map.seq;
|
|
116
|
-
connection.emit('exists', {
|
|
117
|
-
path: connection.mailbox.path,
|
|
118
|
-
count: map.seq,
|
|
119
|
-
prevCount
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
},
|
|
130
|
+
untagged: { EXISTS: handleExistsUpdate },
|
|
125
131
|
comment: 'Sequence not found from APPEND output'
|
|
126
132
|
});
|
|
127
133
|
response.next();
|
|
@@ -130,6 +136,8 @@ module.exports = async (connection, destination, content, flags, idate) => {
|
|
|
130
136
|
}
|
|
131
137
|
}
|
|
132
138
|
|
|
139
|
+
// If we have a sequence number but no UID (server doesn't support UIDPLUS),
|
|
140
|
+
// look up the UID via SEARCH to provide a consistent result to the caller.
|
|
133
141
|
if (map.seq && !map.uid) {
|
|
134
142
|
let list = await connection.search({ seq: map.seq }, { uid: true });
|
|
135
143
|
if (list && list.length) {
|