imapkit 4.3.1 → 5.0.1
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/README.md +187 -67
- package/bin/help.txt +20 -5
- package/bin/imapkit.js +64 -24
- package/dist/cjs/commands/append.js +1 -1
- package/dist/cjs/commands/create.js +2 -2
- package/dist/cjs/commands/delete.js +2 -14
- package/dist/cjs/commands/handlers/fetch.js +2 -1
- package/dist/cjs/commands/handlers/flags.d.ts +3 -3
- package/dist/cjs/commands/handlers/flags.js +3 -3
- package/dist/cjs/commands/handlers/store.js +7 -7
- package/dist/cjs/commands/list.js +17 -3
- package/dist/cjs/commands/rename.js +6 -46
- package/dist/cjs/commands/select.js +1 -0
- package/dist/cjs/commands/subscribe.js +7 -8
- package/dist/cjs/commands/unsubscribe.js +3 -9
- package/dist/cjs/control.d.ts +425 -0
- package/dist/cjs/control.js +637 -0
- package/dist/cjs/dates.d.ts +9 -1
- package/dist/cjs/dates.js +20 -0
- package/dist/cjs/envelope.d.ts +1 -1
- package/dist/cjs/envelope.js +26 -1
- package/dist/cjs/index.d.ts +9 -1
- package/dist/cjs/index.js +9 -2
- package/dist/cjs/load-plugins.d.ts +3 -1
- package/dist/cjs/load-plugins.js +28 -4
- package/dist/cjs/plugins/acl.js +109 -42
- package/dist/cjs/plugins/binary.js +2 -1
- package/dist/cjs/plugins/condstore.js +7 -0
- package/dist/cjs/plugins/context-search.js +2 -2
- package/dist/cjs/plugins/create-special-use.d.ts +0 -5
- package/dist/cjs/plugins/create-special-use.js +11 -1
- package/dist/cjs/plugins/imap4rev2.js +5 -0
- package/dist/cjs/plugins/index.js +1 -3
- package/dist/cjs/plugins/metadata-server.d.ts +6 -2
- package/dist/cjs/plugins/metadata-server.js +3 -1
- package/dist/cjs/plugins/metadata.d.ts +5 -1
- package/dist/cjs/plugins/metadata.js +143 -57
- package/dist/cjs/plugins/objectid.js +10 -0
- package/dist/cjs/plugins/quota.js +50 -2
- package/dist/cjs/plugins/savedate.js +1 -1
- package/dist/cjs/plugins/special-use.d.ts +0 -8
- package/dist/cjs/plugins/special-use.js +23 -0
- package/dist/cjs/quirks.d.ts +28 -0
- package/dist/cjs/quirks.js +125 -0
- package/dist/cjs/random.d.ts +10 -0
- package/dist/cjs/random.js +22 -0
- package/dist/cjs/rest.d.ts +54 -0
- package/dist/cjs/rest.js +567 -0
- package/dist/cjs/script.d.ts +33 -4
- package/dist/cjs/script.js +64 -4
- package/dist/cjs/server.d.ts +199 -9
- package/dist/cjs/server.js +603 -227
- package/dist/cjs/smtp-listener.d.ts +3 -2
- package/dist/cjs/smtp-listener.js +10 -6
- package/dist/cjs/storage-schema.d.ts +139 -0
- package/dist/cjs/storage-schema.js +222 -0
- package/dist/cjs/store-operations.d.ts +171 -0
- package/dist/cjs/store-operations.js +425 -0
- package/dist/cjs/types.d.ts +26 -1
- package/dist/esm/commands/append.js +1 -1
- package/dist/esm/commands/create.js +2 -2
- package/dist/esm/commands/delete.js +2 -14
- package/dist/esm/commands/handlers/fetch.js +2 -1
- package/dist/esm/commands/handlers/flags.d.ts +3 -3
- package/dist/esm/commands/handlers/flags.js +3 -3
- package/dist/esm/commands/handlers/store.js +7 -7
- package/dist/esm/commands/list.js +17 -3
- package/dist/esm/commands/rename.js +6 -46
- package/dist/esm/commands/select.js +1 -0
- package/dist/esm/commands/subscribe.js +7 -8
- package/dist/esm/commands/unsubscribe.js +3 -9
- package/dist/esm/control.d.ts +425 -0
- package/dist/esm/control.js +630 -0
- package/dist/esm/dates.d.ts +9 -1
- package/dist/esm/dates.js +20 -1
- package/dist/esm/envelope.d.ts +1 -1
- package/dist/esm/envelope.js +26 -1
- package/dist/esm/index.d.ts +9 -1
- package/dist/esm/index.js +5 -2
- package/dist/esm/load-plugins.d.ts +3 -1
- package/dist/esm/load-plugins.js +28 -4
- package/dist/esm/plugins/acl.js +109 -42
- package/dist/esm/plugins/binary.js +2 -1
- package/dist/esm/plugins/condstore.js +7 -0
- package/dist/esm/plugins/context-search.js +2 -2
- package/dist/esm/plugins/create-special-use.d.ts +0 -5
- package/dist/esm/plugins/create-special-use.js +8 -1
- package/dist/esm/plugins/imap4rev2.js +5 -0
- package/dist/esm/plugins/index.js +1 -3
- package/dist/esm/plugins/metadata-server.d.ts +6 -2
- package/dist/esm/plugins/metadata-server.js +3 -1
- package/dist/esm/plugins/metadata.d.ts +5 -1
- package/dist/esm/plugins/metadata.js +143 -57
- package/dist/esm/plugins/objectid.js +10 -0
- package/dist/esm/plugins/quota.js +50 -2
- package/dist/esm/plugins/savedate.js +1 -1
- package/dist/esm/plugins/special-use.d.ts +0 -8
- package/dist/esm/plugins/special-use.js +23 -0
- package/dist/esm/quirks.d.ts +28 -0
- package/dist/esm/quirks.js +121 -0
- package/dist/esm/random.d.ts +10 -0
- package/dist/esm/random.js +19 -0
- package/dist/esm/rest.d.ts +54 -0
- package/dist/esm/rest.js +559 -0
- package/dist/esm/script.d.ts +33 -4
- package/dist/esm/script.js +63 -4
- package/dist/esm/server.d.ts +199 -9
- package/dist/esm/server.js +606 -230
- package/dist/esm/smtp-listener.d.ts +3 -2
- package/dist/esm/smtp-listener.js +10 -6
- package/dist/esm/storage-schema.d.ts +139 -0
- package/dist/esm/storage-schema.js +218 -0
- package/dist/esm/store-operations.d.ts +171 -0
- package/dist/esm/store-operations.js +410 -0
- package/dist/esm/types.d.ts +26 -1
- package/dist/plugin-help.json +3 -16
- package/package.json +11 -3
- package/dist/cjs/plugins/xtoybird.d.ts +0 -2
- package/dist/cjs/plugins/xtoybird.js +0 -236
- package/dist/esm/plugins/xtoybird.d.ts +0 -2
- package/dist/esm/plugins/xtoybird.js +0 -218
|
@@ -7,7 +7,8 @@ import type { IMAPServer } from './types.js';
|
|
|
7
7
|
*
|
|
8
8
|
* @param {Number} smtpPort - port to listen on for SMTP commands
|
|
9
9
|
* @param {Object} imapServer - the ImapKit IMAP server
|
|
10
|
-
* @param {Function} [callback] - function executed when SMTP server is listening
|
|
10
|
+
* @param {Function} [callback] - function executed when SMTP server is listening, with the port
|
|
11
|
+
* @param {String} [host] - address to listen on, all addresses if not set
|
|
11
12
|
* @return {SMTPServer} the SMTP server instance, use `close()` to stop it
|
|
12
13
|
*/
|
|
13
|
-
export declare function startSMTPServer(smtpPort: number, imapServer: IMAPServer, callback?: (() => void) | null): SMTPServer;
|
|
14
|
+
export declare function startSMTPServer(smtpPort: number, imapServer: IMAPServer, callback?: ((port: number) => void) | null, host?: string): SMTPServer;
|
|
@@ -6,10 +6,11 @@ import { SMTPServer } from 'smtp-server';
|
|
|
6
6
|
*
|
|
7
7
|
* @param {Number} smtpPort - port to listen on for SMTP commands
|
|
8
8
|
* @param {Object} imapServer - the ImapKit IMAP server
|
|
9
|
-
* @param {Function} [callback] - function executed when SMTP server is listening
|
|
9
|
+
* @param {Function} [callback] - function executed when SMTP server is listening, with the port
|
|
10
|
+
* @param {String} [host] - address to listen on, all addresses if not set
|
|
10
11
|
* @return {SMTPServer} the SMTP server instance, use `close()` to stop it
|
|
11
12
|
*/
|
|
12
|
-
export function startSMTPServer(smtpPort, imapServer, callback) {
|
|
13
|
+
export function startSMTPServer(smtpPort, imapServer, callback, host) {
|
|
13
14
|
const credentials = imapServer.getCredentials();
|
|
14
15
|
const server = new SMTPServer({
|
|
15
16
|
banner: 'ImapKit',
|
|
@@ -36,9 +37,12 @@ export function startSMTPServer(smtpPort, imapServer, callback) {
|
|
|
36
37
|
console.error('SMTP server error: %s', err.message);
|
|
37
38
|
}
|
|
38
39
|
});
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
40
|
+
const onListening = () => callback?.(server.server.address().port);
|
|
41
|
+
if (host) {
|
|
42
|
+
server.listen(smtpPort, host, onListening);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
server.listen(smtpPort, onListening);
|
|
46
|
+
}
|
|
43
47
|
return server;
|
|
44
48
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape of the `storage` option, which is also the shape of `server.control.snapshot()`: a JSON Schema for
|
|
3
|
+
* editors and other tools, and the check the server runs on the option, so that a typo in a fixture ("message" for
|
|
4
|
+
* "messages") fails with a clear error instead of an empty mailbox. Plugins keep their own data on mailboxes and
|
|
5
|
+
* messages (acl, metadata, MODSEQ ...), so other keys are allowed, only the types of the known keys are checked
|
|
6
|
+
*/
|
|
7
|
+
/** JSON Schema (draft 2020-12) of the `storage` option */
|
|
8
|
+
export declare const storageSchema: {
|
|
9
|
+
readonly $schema: "https://json-schema.org/draft/2020-12/schema";
|
|
10
|
+
readonly title: "ImapKit storage";
|
|
11
|
+
readonly description: "Namespaces by prefix (\"INBOX\", \"\", \"INBOX.\", \"#shared/\" ...), INBOX is a mailbox of its own";
|
|
12
|
+
readonly type: "object";
|
|
13
|
+
readonly properties: {
|
|
14
|
+
readonly INBOX: {
|
|
15
|
+
readonly $ref: "#/$defs/mailbox";
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
readonly additionalProperties: {
|
|
19
|
+
readonly $ref: "#/$defs/namespace";
|
|
20
|
+
};
|
|
21
|
+
readonly $defs: {
|
|
22
|
+
readonly message: {
|
|
23
|
+
readonly oneOf: readonly [{
|
|
24
|
+
readonly type: "string";
|
|
25
|
+
readonly description: "the message source";
|
|
26
|
+
}, {
|
|
27
|
+
readonly type: "object";
|
|
28
|
+
readonly properties: {
|
|
29
|
+
readonly raw: {
|
|
30
|
+
readonly type: "string";
|
|
31
|
+
readonly description: "the message source (a Buffer also works in JavaScript)";
|
|
32
|
+
};
|
|
33
|
+
readonly uid: {
|
|
34
|
+
type: string;
|
|
35
|
+
minimum: number;
|
|
36
|
+
maximum: number;
|
|
37
|
+
};
|
|
38
|
+
readonly flags: {
|
|
39
|
+
readonly oneOf: readonly [{
|
|
40
|
+
readonly type: "string";
|
|
41
|
+
}, {
|
|
42
|
+
type: string;
|
|
43
|
+
items: {
|
|
44
|
+
type: string;
|
|
45
|
+
};
|
|
46
|
+
}];
|
|
47
|
+
};
|
|
48
|
+
readonly internaldate: {
|
|
49
|
+
readonly type: "string";
|
|
50
|
+
readonly description: "RFC 3501 date-time, e.g. \"14-Sep-2013 21:22:28 -0300\" (a Date also works in JavaScript)";
|
|
51
|
+
};
|
|
52
|
+
readonly recent: {
|
|
53
|
+
readonly type: "boolean";
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
}];
|
|
57
|
+
};
|
|
58
|
+
readonly mailbox: {
|
|
59
|
+
readonly type: "object";
|
|
60
|
+
readonly properties: {
|
|
61
|
+
readonly uid: {
|
|
62
|
+
readonly type: "integer";
|
|
63
|
+
};
|
|
64
|
+
readonly uidvalidity: {
|
|
65
|
+
type: string;
|
|
66
|
+
minimum: number;
|
|
67
|
+
maximum: number;
|
|
68
|
+
};
|
|
69
|
+
readonly uidnext: {
|
|
70
|
+
type: string;
|
|
71
|
+
minimum: number;
|
|
72
|
+
maximum: number;
|
|
73
|
+
};
|
|
74
|
+
readonly flags: {
|
|
75
|
+
type: string;
|
|
76
|
+
items: {
|
|
77
|
+
type: string;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
readonly permanentFlags: {
|
|
81
|
+
type: string;
|
|
82
|
+
items: {
|
|
83
|
+
type: string;
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
readonly allowPermanentFlags: {
|
|
87
|
+
readonly type: "boolean";
|
|
88
|
+
};
|
|
89
|
+
readonly subscribed: {
|
|
90
|
+
readonly type: "boolean";
|
|
91
|
+
};
|
|
92
|
+
readonly knownFlags: {
|
|
93
|
+
type: string;
|
|
94
|
+
items: {
|
|
95
|
+
type: string;
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
readonly messages: {
|
|
99
|
+
readonly type: "array";
|
|
100
|
+
readonly items: {
|
|
101
|
+
readonly $ref: "#/$defs/message";
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
readonly folders: {
|
|
105
|
+
readonly type: "object";
|
|
106
|
+
readonly additionalProperties: {
|
|
107
|
+
readonly $ref: "#/$defs/mailbox";
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
readonly namespace: {
|
|
113
|
+
readonly type: "object";
|
|
114
|
+
readonly properties: {
|
|
115
|
+
readonly separator: {
|
|
116
|
+
readonly type: "string";
|
|
117
|
+
readonly minLength: 1;
|
|
118
|
+
readonly maxLength: 1;
|
|
119
|
+
};
|
|
120
|
+
readonly type: {
|
|
121
|
+
readonly enum: readonly ["personal", "user", "shared"];
|
|
122
|
+
};
|
|
123
|
+
readonly folders: {
|
|
124
|
+
readonly type: "object";
|
|
125
|
+
readonly additionalProperties: {
|
|
126
|
+
readonly $ref: "#/$defs/mailbox";
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Checks the `storage` option
|
|
135
|
+
*
|
|
136
|
+
* @param {Object} storage Storage
|
|
137
|
+
* @throws {Error} "Invalid storage at <path>: <problem>"
|
|
138
|
+
*/
|
|
139
|
+
export declare function validateStorage(storage: unknown): void;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape of the `storage` option, which is also the shape of `server.control.snapshot()`: a JSON Schema for
|
|
3
|
+
* editors and other tools, and the check the server runs on the option, so that a typo in a fixture ("message" for
|
|
4
|
+
* "messages") fails with a clear error instead of an empty mailbox. Plugins keep their own data on mailboxes and
|
|
5
|
+
* messages (acl, metadata, MODSEQ ...), so other keys are allowed, only the types of the known keys are checked
|
|
6
|
+
*/
|
|
7
|
+
import { MAX_NUMBER } from './numbers.js';
|
|
8
|
+
import { isDateTime } from './dates.js';
|
|
9
|
+
// keys of plugins, an unknown key that looks like a typo of a known key is refused unless a plugin uses it
|
|
10
|
+
const PLUGIN_KEYS = [
|
|
11
|
+
'acl',
|
|
12
|
+
'metadata',
|
|
13
|
+
'special-use',
|
|
14
|
+
'MAILBOXID',
|
|
15
|
+
'HIGHESTMODSEQ',
|
|
16
|
+
'qresyncExpunged',
|
|
17
|
+
'appendLimit',
|
|
18
|
+
'SAVEDATE',
|
|
19
|
+
'MODSEQ',
|
|
20
|
+
'EMAILID',
|
|
21
|
+
'THREADID',
|
|
22
|
+
'X-GM-LABELS',
|
|
23
|
+
'X-GM-MSGID',
|
|
24
|
+
'X-GM-THRID'
|
|
25
|
+
];
|
|
26
|
+
const flagList = { type: 'array', items: { type: 'string' } };
|
|
27
|
+
const nzNumber = { type: 'integer', minimum: 1, maximum: MAX_NUMBER };
|
|
28
|
+
/** JSON Schema (draft 2020-12) of the `storage` option */
|
|
29
|
+
export const storageSchema = {
|
|
30
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
31
|
+
title: 'ImapKit storage',
|
|
32
|
+
description: 'Namespaces by prefix ("INBOX", "", "INBOX.", "#shared/" ...), INBOX is a mailbox of its own',
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: { INBOX: { $ref: '#/$defs/mailbox' } },
|
|
35
|
+
additionalProperties: { $ref: '#/$defs/namespace' },
|
|
36
|
+
$defs: {
|
|
37
|
+
message: {
|
|
38
|
+
oneOf: [
|
|
39
|
+
{ type: 'string', description: 'the message source' },
|
|
40
|
+
{
|
|
41
|
+
type: 'object',
|
|
42
|
+
properties: {
|
|
43
|
+
raw: { type: 'string', description: 'the message source (a Buffer also works in JavaScript)' },
|
|
44
|
+
uid: nzNumber,
|
|
45
|
+
flags: { oneOf: [{ type: 'string' }, flagList] },
|
|
46
|
+
internaldate: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
description: 'RFC 3501 date-time, e.g. "14-Sep-2013 21:22:28 -0300" (a Date also works in JavaScript)'
|
|
49
|
+
},
|
|
50
|
+
recent: { type: 'boolean' }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
mailbox: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
uid: { type: 'integer' },
|
|
59
|
+
uidvalidity: nzNumber,
|
|
60
|
+
uidnext: nzNumber,
|
|
61
|
+
flags: flagList,
|
|
62
|
+
permanentFlags: flagList,
|
|
63
|
+
allowPermanentFlags: { type: 'boolean' },
|
|
64
|
+
subscribed: { type: 'boolean' },
|
|
65
|
+
knownFlags: flagList,
|
|
66
|
+
messages: { type: 'array', items: { $ref: '#/$defs/message' } },
|
|
67
|
+
folders: { type: 'object', additionalProperties: { $ref: '#/$defs/mailbox' } }
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
namespace: {
|
|
71
|
+
type: 'object',
|
|
72
|
+
properties: {
|
|
73
|
+
separator: { type: 'string', minLength: 1, maxLength: 1 },
|
|
74
|
+
type: { enum: ['personal', 'user', 'shared'] },
|
|
75
|
+
folders: { type: 'object', additionalProperties: { $ref: '#/$defs/mailbox' } }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
// the keys the schema knows, the typo check compares unknown keys with them
|
|
81
|
+
const MAILBOX_KEYS = Object.keys(storageSchema.$defs.mailbox.properties);
|
|
82
|
+
const NAMESPACE_KEYS = ['separator', 'type'];
|
|
83
|
+
const MESSAGE_KEYS = Object.keys(storageSchema.$defs.message.oneOf[1].properties);
|
|
84
|
+
/**
|
|
85
|
+
* Finds the known key that an unknown key looks like a typo of: the same key in another case, or one edit away
|
|
86
|
+
*
|
|
87
|
+
* @param {String} key Unknown key
|
|
88
|
+
* @param {Array} known Known keys
|
|
89
|
+
* @return {String|null} the known key, or null
|
|
90
|
+
*/
|
|
91
|
+
function typoOf(key, known) {
|
|
92
|
+
if (known.includes(key) || PLUGIN_KEYS.includes(key)) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return known.find(name => name.toLowerCase() === key.toLowerCase() || (key.length > 3 && editDistance(name, key) === 1)) || null;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Levenshtein distance, only used for short keys
|
|
99
|
+
*/
|
|
100
|
+
function editDistance(a, b) {
|
|
101
|
+
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
102
|
+
for (let i = 1; i <= a.length; i++) {
|
|
103
|
+
let previous = row[0];
|
|
104
|
+
row[0] = i;
|
|
105
|
+
for (let j = 1; j <= b.length; j++) {
|
|
106
|
+
const current = row[j];
|
|
107
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
108
|
+
previous = current;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return row[b.length];
|
|
112
|
+
}
|
|
113
|
+
const isObject = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
114
|
+
const isFlagList = (value) => Array.isArray(value) && value.every(flag => typeof flag === 'string');
|
|
115
|
+
const isNz = (value) => Number.isSafeInteger(value) && value >= 1 && value <= MAX_NUMBER;
|
|
116
|
+
/**
|
|
117
|
+
* Checks the `storage` option
|
|
118
|
+
*
|
|
119
|
+
* @param {Object} storage Storage
|
|
120
|
+
* @throws {Error} "Invalid storage at <path>: <problem>"
|
|
121
|
+
*/
|
|
122
|
+
export function validateStorage(storage) {
|
|
123
|
+
const fail = (path, problem) => {
|
|
124
|
+
throw new Error('Invalid storage at ' + path + ': ' + problem);
|
|
125
|
+
};
|
|
126
|
+
const checkKeys = (value, path, known) => {
|
|
127
|
+
Object.keys(value).forEach(key => {
|
|
128
|
+
const typo = typoOf(key, known);
|
|
129
|
+
if (typo) {
|
|
130
|
+
fail(path, 'unknown key ' + JSON.stringify(key) + ', did you mean ' + JSON.stringify(typo) + '?');
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
const checkMessage = (message, path) => {
|
|
135
|
+
if (typeof message === 'string' || message instanceof Uint8Array) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (!isObject(message)) {
|
|
139
|
+
return fail(path, 'a message is a string or an object');
|
|
140
|
+
}
|
|
141
|
+
checkKeys(message, path, MESSAGE_KEYS);
|
|
142
|
+
if (message.raw !== undefined && typeof message.raw !== 'string' && !(message.raw instanceof Uint8Array)) {
|
|
143
|
+
fail(path + '.raw', 'must be a string');
|
|
144
|
+
}
|
|
145
|
+
if (message.uid !== undefined && !isNz(message.uid)) {
|
|
146
|
+
fail(path + '.uid', 'must be an integer from 1 to ' + MAX_NUMBER);
|
|
147
|
+
}
|
|
148
|
+
if (message.flags !== undefined && typeof message.flags !== 'string' && !isFlagList(message.flags)) {
|
|
149
|
+
fail(path + '.flags', 'must be a flag or a list of flags');
|
|
150
|
+
}
|
|
151
|
+
// FETCH sends the internal date as it is and SORT ARRIVAL reads it, so it must be a date-time of RFC 3501
|
|
152
|
+
// section 9 (month names in any case, they are sent as "Jan", "Feb", ...) or a valid Date. The save date
|
|
153
|
+
// of the SAVEDATE plugin is a date-time too (RFC 8514 section 4.2)
|
|
154
|
+
for (const key of ['internaldate', 'SAVEDATE']) {
|
|
155
|
+
const value = message[key];
|
|
156
|
+
if (value === undefined || value === false || (key === 'SAVEDATE' && value === null)) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (value instanceof Date ? isNaN(value.getTime()) : !isDateTime(value)) {
|
|
160
|
+
fail(path + '.' + key, 'must be a date-time string like "14-Sep-2013 21:22:28 -0300" or a Date, not ' + JSON.stringify(value));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (message.recent !== undefined && typeof message.recent !== 'boolean') {
|
|
164
|
+
fail(path + '.recent', 'must be true or false');
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
const checkMailbox = (mailbox, path, known) => {
|
|
168
|
+
if (!isObject(mailbox)) {
|
|
169
|
+
return fail(path, 'a mailbox is an object');
|
|
170
|
+
}
|
|
171
|
+
checkKeys(mailbox, path, known);
|
|
172
|
+
for (const key of ['uidvalidity', 'uidnext']) {
|
|
173
|
+
if (mailbox[key] !== undefined && !isNz(mailbox[key])) {
|
|
174
|
+
fail(path + '.' + key, 'must be an integer from 1 to ' + MAX_NUMBER);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
for (const key of ['flags', 'permanentFlags', 'knownFlags']) {
|
|
178
|
+
if (mailbox[key] !== undefined && !isFlagList(mailbox[key])) {
|
|
179
|
+
fail(path + '.' + key, 'must be a list of strings');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
for (const key of ['allowPermanentFlags', 'subscribed']) {
|
|
183
|
+
if (mailbox[key] !== undefined && typeof mailbox[key] !== 'boolean') {
|
|
184
|
+
fail(path + '.' + key, 'must be true or false');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (mailbox.messages !== undefined) {
|
|
188
|
+
if (!Array.isArray(mailbox.messages)) {
|
|
189
|
+
fail(path + '.messages', 'must be a list of messages');
|
|
190
|
+
}
|
|
191
|
+
mailbox.messages.forEach((message, i) => checkMessage(message, path + '.messages[' + i + ']'));
|
|
192
|
+
}
|
|
193
|
+
if (mailbox.folders !== undefined) {
|
|
194
|
+
if (!isObject(mailbox.folders)) {
|
|
195
|
+
fail(path + '.folders', 'must be an object of mailboxes by name');
|
|
196
|
+
}
|
|
197
|
+
Object.keys(mailbox.folders).forEach(name => checkMailbox(mailbox.folders[name], path + '.folders[' + JSON.stringify(name) + ']', MAILBOX_KEYS));
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
if (!isObject(storage)) {
|
|
201
|
+
fail('the top level', 'storage is an object of namespaces');
|
|
202
|
+
}
|
|
203
|
+
Object.keys(storage).forEach(key => {
|
|
204
|
+
const value = storage[key];
|
|
205
|
+
const path = JSON.stringify(key);
|
|
206
|
+
if (key === 'INBOX') {
|
|
207
|
+
return checkMailbox(value, path, MAILBOX_KEYS.concat('separator'));
|
|
208
|
+
}
|
|
209
|
+
checkMailbox(value, path, MAILBOX_KEYS.concat(NAMESPACE_KEYS));
|
|
210
|
+
const namespace = value;
|
|
211
|
+
if (namespace.separator !== undefined && (typeof namespace.separator !== 'string' || namespace.separator.length !== 1)) {
|
|
212
|
+
fail(path + '.separator', 'must be a single character');
|
|
213
|
+
}
|
|
214
|
+
if (namespace.type !== undefined && !['personal', 'user', 'shared'].includes(namespace.type)) {
|
|
215
|
+
fail(path + '.type', 'must be "personal", "user" or "shared"');
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Changes of the message store that IMAP commands and the control API share: expunge, flag changes, and the
|
|
3
|
+
* mailbox operations of DELETE, RENAME, SUBSCRIBE and UNSUBSCRIBE. Each one tells the sessions about the change
|
|
4
|
+
* the way another session's command would (EXISTS, EXPUNGE, unsolicited FETCH, BYE, `mailbox` events). The
|
|
5
|
+
* session that caused a change is `server.activeConnection` (null for the control API), the commands keep only
|
|
6
|
+
* what is specific to a session: argument checks, the selected mailbox, read-only state and the responses.
|
|
7
|
+
*/
|
|
8
|
+
import type { IMAPConnection, IMAPServer, Mailbox, Message } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* the deprecated XOAUTH2 session timeout of the default user, and of a user the control API adds without one. It is
|
|
11
|
+
* stored and listed but never used, access tokens do not expire
|
|
12
|
+
*/
|
|
13
|
+
declare const DEFAULT_SESSION_TIMEOUT: number;
|
|
14
|
+
/** An error of a failed store operation or control API call, `code` is a RFC 5530 response code or INVALID */
|
|
15
|
+
declare class ImapKitError extends Error {
|
|
16
|
+
code: string;
|
|
17
|
+
constructor(message: string, code: string);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Creates an error for a failed operation, with a RFC 5530 response code
|
|
21
|
+
*
|
|
22
|
+
* @param {String} message Error message
|
|
23
|
+
* @param {String} code Response code, e.g. "NONEXISTENT"
|
|
24
|
+
* @return {Error} Error object
|
|
25
|
+
*/
|
|
26
|
+
declare function storeError(message: string, code: string): ImapKitError;
|
|
27
|
+
interface ExpungeOptions {
|
|
28
|
+
/** the session that caused the expunge, for the `expunge` event (QRESYNC, NOTIFY), defaults to the active session */
|
|
29
|
+
origin?: IMAPConnection | null | undefined;
|
|
30
|
+
/** a session that gets no EXPUNGE and no EXISTS responses (CLOSE) */
|
|
31
|
+
skip?: IMAPConnection | false | null | undefined;
|
|
32
|
+
/** a session that gets the EXPUNGE responses but no EXISTS response (EXPUNGE, MOVE) */
|
|
33
|
+
skipExists?: IMAPConnection | false | null | undefined;
|
|
34
|
+
/** the EXPUNGE responses go from the highest UID to the lowest (MESSAGELIMIT, RFC 9738 section 3.1) */
|
|
35
|
+
highestFirst?: boolean | undefined;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Removes messages from a mailbox and tells the sessions that have it selected with EXPUNGE responses
|
|
39
|
+
* (VANISHED with QRESYNC) and a new EXISTS. The `expunge` event goes out before the notifications
|
|
40
|
+
*
|
|
41
|
+
* @param {Object} server IMAP server
|
|
42
|
+
* @param {Object} mailbox Mailbox of the messages
|
|
43
|
+
* @param {Array|Function} messagesOrFilterFunc Messages to remove, or a function that returns true for them
|
|
44
|
+
* @param {Object} [options] `{ origin, skip, skipExists, highestFirst }`
|
|
45
|
+
* @return {Array} the removed messages
|
|
46
|
+
*/
|
|
47
|
+
declare function expungeMessages(server: IMAPServer, mailbox: Mailbox, messagesOrFilterFunc: Message[] | ((message: Message) => unknown), options?: ExpungeOptions): Message[];
|
|
48
|
+
/**
|
|
49
|
+
* Tells the sessions that have a mailbox selected about changed flags, they get an untagged FETCH with the
|
|
50
|
+
* new flags (RFC 3501 section 5.2)
|
|
51
|
+
*
|
|
52
|
+
* @param {Object} server IMAP server
|
|
53
|
+
* @param {Object} mailbox Mailbox of the messages
|
|
54
|
+
* @param {Array} messages Messages with changed flags
|
|
55
|
+
* @param {Object} [ignoreConnection] A session that is not told, e.g. the one whose STORE changed the flags
|
|
56
|
+
*/
|
|
57
|
+
declare function notifyFlagChanges(server: IMAPServer, mailbox: Mailbox, messages: Message[], ignoreConnection?: IMAPConnection | null): void;
|
|
58
|
+
type FlagMode = 'set' | 'add' | 'remove';
|
|
59
|
+
/**
|
|
60
|
+
* Checks flags the way STORE does: system flags the server knows (never \Recent), valid keywords, and only
|
|
61
|
+
* permanent flags in a mailbox that does not allow new keywords
|
|
62
|
+
*
|
|
63
|
+
* @param {Object} server IMAP server
|
|
64
|
+
* @param {Object} mailbox Mailbox the flags are for
|
|
65
|
+
* @param {Array} flags Flags to check
|
|
66
|
+
* @param {Boolean} stored true if the flags are stored, so they must be permanent flags of the mailbox
|
|
67
|
+
* @return {Array} the normalized flags without duplicates
|
|
68
|
+
* @throws {Error} INVALID for a flag that can not be used
|
|
69
|
+
*/
|
|
70
|
+
declare function checkFlags(server: IMAPServer, mailbox: Mailbox, flags: unknown, stored: boolean): string[];
|
|
71
|
+
/**
|
|
72
|
+
* Changes the flags of messages outside of a STORE command. The flags follow the rules of STORE: system flags
|
|
73
|
+
* the server knows (never \Recent), valid keywords, and only permanent flags in a mailbox that does not allow
|
|
74
|
+
* new keywords. Plugins learn about the changed messages from the `flags` event `(mailbox, messages, origin)`
|
|
75
|
+
* before the sessions are told (CONDSTORE gives them new mod-sequences there)
|
|
76
|
+
*
|
|
77
|
+
* @param {Object} server IMAP server
|
|
78
|
+
* @param {Object} mailbox Mailbox of the messages
|
|
79
|
+
* @param {Array} messages Messages to change
|
|
80
|
+
* @param {Array} flags Flags to set, add or remove
|
|
81
|
+
* @param {String} mode "set", "add" or "remove"
|
|
82
|
+
* @return {Array} the messages whose flags changed
|
|
83
|
+
*/
|
|
84
|
+
declare function changeFlags(server: IMAPServer, mailbox: Mailbox, messages: Message[], flags: string[], mode: FlagMode): Message[];
|
|
85
|
+
/**
|
|
86
|
+
* Creates a mailbox (CREATE), with any missing superior hierarchy levels
|
|
87
|
+
*
|
|
88
|
+
* @param {Object} server IMAP server
|
|
89
|
+
* @param {String} path Storage name of the mailbox
|
|
90
|
+
* @return {Object} the created mailbox
|
|
91
|
+
* @throws {Error} with a RFC 5530 `code` (ALREADYEXISTS, CANNOT ...)
|
|
92
|
+
*/
|
|
93
|
+
declare function createMailbox(server: IMAPServer, path: string): Mailbox;
|
|
94
|
+
/**
|
|
95
|
+
* Deletes a mailbox (DELETE). Other sessions that have it selected are disconnected with an untagged BYE
|
|
96
|
+
* (RFC 2180 section 3.3, RFC 2683 section 3.1.2), as they can not be told about the deletion in any other way
|
|
97
|
+
*
|
|
98
|
+
* @param {Object} server IMAP server
|
|
99
|
+
* @param {String} path Storage name of the mailbox
|
|
100
|
+
* @throws {Error} with a response `code` (NONEXISTENT, CANNOT, HASCHILDREN ...)
|
|
101
|
+
*/
|
|
102
|
+
declare function deleteMailbox(server: IMAPServer, path: string): void;
|
|
103
|
+
/**
|
|
104
|
+
* Renames a mailbox (RENAME). Renaming INBOX moves its messages to the new mailbox and leaves INBOX
|
|
105
|
+
* empty (RFC 3501 section 6.3.5). Everything that can be checked is checked up front, so that a failed
|
|
106
|
+
* rename never loses the source
|
|
107
|
+
*
|
|
108
|
+
* @param {Object} server IMAP server
|
|
109
|
+
* @param {String} source Storage name of the mailbox
|
|
110
|
+
* @param {String} destination New storage name
|
|
111
|
+
* @return {Object} `{ path, mailbox }`: the new name, and the source mailbox object, which is INBOX itself for
|
|
112
|
+
* a rename of INBOX
|
|
113
|
+
* @throws {Error} with a RFC 5530 `code` (NONEXISTENT, ALREADYEXISTS, CANNOT ...)
|
|
114
|
+
*/
|
|
115
|
+
declare function renameMailbox(server: IMAPServer, source: string, destination: string): {
|
|
116
|
+
path: string;
|
|
117
|
+
mailbox: Mailbox;
|
|
118
|
+
};
|
|
119
|
+
type UidMode = 'keep' | 'renumber' | 'shuffle' | 'offset';
|
|
120
|
+
interface UidValidityOptions {
|
|
121
|
+
/** the new UIDVALIDITY, it must be greater than the current one. Default: one above every value in use */
|
|
122
|
+
uidvalidity?: number | undefined;
|
|
123
|
+
/** what happens to the UIDs: "keep" (default), "renumber" (1 to n in the current order), "shuffle" (1 to n in a
|
|
124
|
+
* random order) or "offset" (every UID moves above the old UIDNEXT, plus `offset`) */
|
|
125
|
+
uids?: UidMode | undefined;
|
|
126
|
+
/** extra gap for "offset" */
|
|
127
|
+
offset?: number | undefined;
|
|
128
|
+
/** seed of the "shuffle" order, for repeatable tests */
|
|
129
|
+
seed?: number | undefined;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Gives a mailbox a new UIDVALIDITY, optionally with new UIDs. A UID never changes during a session (RFC 9051
|
|
133
|
+
* section 2.3.1.1), so the sessions that have the mailbox selected are disconnected with BYE, and the new value
|
|
134
|
+
* is greater than every earlier one, as UIDs that do not persist require. A client that keeps its cached UIDs
|
|
135
|
+
* without checking UIDVALIDITY gets other messages ("shuffle") or none ("offset") for them
|
|
136
|
+
*
|
|
137
|
+
* @param {Object} server IMAP server
|
|
138
|
+
* @param {Object} mailbox Mailbox, not \Noselect
|
|
139
|
+
* @param {Object} [options] `{ uidvalidity, uids, offset, seed }`
|
|
140
|
+
* @return {Object} `{ uidvalidity, uidnext, uids }`, `uids` maps every old UID to its new one
|
|
141
|
+
* @throws {Error} INVALID for a UIDVALIDITY that is not greater, or invalid options
|
|
142
|
+
*/
|
|
143
|
+
declare function resetUidValidity(server: IMAPServer, mailbox: Mailbox, options?: UidValidityOptions): {
|
|
144
|
+
uidvalidity: number;
|
|
145
|
+
uidnext: number;
|
|
146
|
+
uids: {
|
|
147
|
+
uid: number;
|
|
148
|
+
newUid: number;
|
|
149
|
+
}[];
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Subscribes a mailbox (SUBSCRIBE), only an existing mailbox can be subscribed
|
|
153
|
+
*
|
|
154
|
+
* @param {Object} server IMAP server
|
|
155
|
+
* @param {String} path Storage name of the mailbox
|
|
156
|
+
* @return {Boolean} true if the subscription changed
|
|
157
|
+
* @throws {Error} NONEXISTENT for a name that is not a mailbox
|
|
158
|
+
*/
|
|
159
|
+
declare function subscribeMailbox(server: IMAPServer, path: string): boolean;
|
|
160
|
+
/**
|
|
161
|
+
* Unsubscribes a name (UNSUBSCRIBE). The subscription list holds names, so a name stays removable after its
|
|
162
|
+
* mailbox is deleted (RFC 3501 section 6.3.6). Removing a name that is not subscribed is not an error
|
|
163
|
+
* (RFC 9051 section 6.3.8)
|
|
164
|
+
*
|
|
165
|
+
* @param {Object} server IMAP server
|
|
166
|
+
* @param {String} path Storage name
|
|
167
|
+
* @return {Boolean} true if the subscription changed
|
|
168
|
+
*/
|
|
169
|
+
declare function unsubscribeMailbox(server: IMAPServer, path: string): boolean;
|
|
170
|
+
export { DEFAULT_SESSION_TIMEOUT, ImapKitError, storeError, expungeMessages, notifyFlagChanges, checkFlags, changeFlags, createMailbox, deleteMailbox, renameMailbox, resetUidValidity, subscribeMailbox, unsubscribeMailbox };
|
|
171
|
+
export type { ExpungeOptions, FlagMode, UidMode, UidValidityOptions };
|