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
package/dist/cjs/script.js
CHANGED
|
@@ -4,14 +4,18 @@ exports.ServerScript = void 0;
|
|
|
4
4
|
exports.sendOutput = sendOutput;
|
|
5
5
|
exports.releaseDeferred = releaseDeferred;
|
|
6
6
|
exports.literalResponse = literalResponse;
|
|
7
|
+
exports.handleQuiet = handleQuiet;
|
|
7
8
|
exports.handleLine = handleLine;
|
|
8
|
-
const
|
|
9
|
+
const random_js_1 = require("./random.js");
|
|
10
|
+
const numbers_js_1 = require("./numbers.js");
|
|
11
|
+
const EVENTS = ['greeting', 'command', 'input', 'response', 'continuation', 'quiet'];
|
|
9
12
|
const OUTPUT_EVENTS = ['greeting', 'response', 'continuation'];
|
|
10
13
|
// the events where a key is valid, every other key is a typo and refused
|
|
11
14
|
const KEYS = {
|
|
12
15
|
on: EVENTS,
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
// a quiet event belongs to the command that waits for input (IDLE), if there is one
|
|
17
|
+
command: ['command', 'input', 'response', 'continuation', 'quiet'],
|
|
18
|
+
tag: ['command', 'input', 'response', 'continuation', 'quiet'],
|
|
15
19
|
description: ['response', 'continuation'],
|
|
16
20
|
session: EVENTS,
|
|
17
21
|
user: EVENTS,
|
|
@@ -22,6 +26,8 @@ const KEYS = {
|
|
|
22
26
|
when: EVENTS,
|
|
23
27
|
nth: EVENTS,
|
|
24
28
|
times: EVENTS,
|
|
29
|
+
chance: EVENTS,
|
|
30
|
+
quietFor: ['quiet'],
|
|
25
31
|
mutate: ['response', 'continuation'],
|
|
26
32
|
literals: ['response'],
|
|
27
33
|
defer: ['response'],
|
|
@@ -29,7 +35,7 @@ const KEYS = {
|
|
|
29
35
|
before: OUTPUT_EVENTS,
|
|
30
36
|
after: OUTPUT_EVENTS,
|
|
31
37
|
run: ['command', 'input'],
|
|
32
|
-
drop:
|
|
38
|
+
drop: ['greeting', 'command', 'input', 'response', 'continuation'],
|
|
33
39
|
delay: ['greeting', 'command', 'response', 'continuation'],
|
|
34
40
|
chunk: EVENTS,
|
|
35
41
|
chunkDelay: EVENTS,
|
|
@@ -77,6 +83,12 @@ function validateRule(rule) {
|
|
|
77
83
|
throw new TypeError('Script rule option "' + key + '" can not be used with "on": "' + rule.on + '"');
|
|
78
84
|
}
|
|
79
85
|
}
|
|
86
|
+
if (rule.on === 'quiet' && !(Number.isInteger(rule.quietFor) && rule.quietFor > 0)) {
|
|
87
|
+
throw new TypeError('A quiet rule needs "quietFor", a positive number of milliseconds');
|
|
88
|
+
}
|
|
89
|
+
if (rule.chance !== undefined && !(typeof rule.chance === 'number' && rule.chance >= 0 && rule.chance <= 1)) {
|
|
90
|
+
throw new TypeError('Script rule option "chance" must be a number from 0 to 1');
|
|
91
|
+
}
|
|
80
92
|
if (!ACTIONS.some(key => rule[key] !== undefined && rule[key] !== false)) {
|
|
81
93
|
throw new TypeError('A script rule needs an action (' + ACTIONS.join(', ') + ')');
|
|
82
94
|
}
|
|
@@ -99,6 +111,9 @@ function validateRule(rule) {
|
|
|
99
111
|
}
|
|
100
112
|
}
|
|
101
113
|
for (const key of ['delay', 'chunkDelay', 'truncate']) {
|
|
114
|
+
if (key === 'chunkDelay' && rule[key] === 'tick') {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
102
117
|
if (rule[key] !== undefined && !(Number.isInteger(rule[key]) && rule[key] >= 0)) {
|
|
103
118
|
throw new TypeError('Script rule option "' + key + '" must be a non-negative integer');
|
|
104
119
|
}
|
|
@@ -157,6 +172,14 @@ class ServerScript {
|
|
|
157
172
|
constructor(server, rules) {
|
|
158
173
|
this.server = server;
|
|
159
174
|
this.entries = [];
|
|
175
|
+
this.ruleCounter = 0;
|
|
176
|
+
const seed = server.options.scriptSeed;
|
|
177
|
+
const seedRandom = () => {
|
|
178
|
+
this.random = (0, random_js_1.seededRandom)(typeof seed === 'number' ? seed : Math.floor(Math.random() * numbers_js_1.MAX_NUMBER));
|
|
179
|
+
};
|
|
180
|
+
seedRandom();
|
|
181
|
+
// control.reset() starts the same random sequence again
|
|
182
|
+
server.on('reset', seedRandom);
|
|
160
183
|
this.watched = new Set();
|
|
161
184
|
if (rules) {
|
|
162
185
|
this.add(rules);
|
|
@@ -206,6 +229,26 @@ class ServerScript {
|
|
|
206
229
|
setEntries(entries) {
|
|
207
230
|
this.entries = entries;
|
|
208
231
|
this.watched = new Set(entries.map(entry => entry.rule.on));
|
|
232
|
+
if (this.watched.has('quiet')) {
|
|
233
|
+
// sessions that are quiet already start counting now
|
|
234
|
+
this.server.connections?.forEach(connection => connection.watchQuiet());
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* The next quiet time a rule waits for, after the quiet time the rules were checked for already
|
|
239
|
+
*
|
|
240
|
+
* @param {Number} checked Milliseconds of quiet the rules were checked for
|
|
241
|
+
* @return {Number|null} the smallest `quietFor` above it, null if there is none
|
|
242
|
+
*/
|
|
243
|
+
nextQuiet(checked) {
|
|
244
|
+
let next = null;
|
|
245
|
+
for (const entry of this.entries) {
|
|
246
|
+
const quietFor = entry.rule.quietFor;
|
|
247
|
+
if (entry.rule.on === 'quiet' && quietFor !== undefined && quietFor > checked && (next === null || quietFor < next)) {
|
|
248
|
+
next = quietFor;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return next;
|
|
209
252
|
}
|
|
210
253
|
addEntry(rule, match) {
|
|
211
254
|
// a copy, so that changing the caller's object later does not change the rule
|
|
@@ -219,6 +262,7 @@ class ServerScript {
|
|
|
219
262
|
}
|
|
220
263
|
}
|
|
221
264
|
const entry = {
|
|
265
|
+
id: ++this.ruleCounter,
|
|
222
266
|
rule: copy,
|
|
223
267
|
match,
|
|
224
268
|
sets,
|
|
@@ -245,6 +289,9 @@ class ServerScript {
|
|
|
245
289
|
if (entry.matched < (rule.nth || 1) || (rule.times !== undefined && entry.hits >= rule.times)) {
|
|
246
290
|
continue;
|
|
247
291
|
}
|
|
292
|
+
if (rule.chance !== undefined && this.random() >= rule.chance) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
248
295
|
entry.hits++;
|
|
249
296
|
this.server.emit('script', { rule, event: context.event, session: context.session, tag: context.tag, command: context.command });
|
|
250
297
|
return rule;
|
|
@@ -266,6 +313,9 @@ class ServerScript {
|
|
|
266
313
|
if (rule.untagged !== undefined && rule.untagged !== (!context.response || context.response.tag === '*')) {
|
|
267
314
|
return false;
|
|
268
315
|
}
|
|
316
|
+
if (rule.quietFor !== undefined && (context.quiet || 0) < rule.quietFor) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
269
319
|
if (entry.match && !entry.match.test(context.data)) {
|
|
270
320
|
return false;
|
|
271
321
|
}
|
|
@@ -460,6 +510,16 @@ function sendBytes(connection, rule, data, delay) {
|
|
|
460
510
|
close: rule.close || (rule.truncate === undefined ? undefined : true)
|
|
461
511
|
});
|
|
462
512
|
}
|
|
513
|
+
/**
|
|
514
|
+
* Handles a quiet event with the rule that matched it: sends the bytes of `send` and closes the connection with `close`
|
|
515
|
+
*
|
|
516
|
+
* @param {Object} connection IMAP connection
|
|
517
|
+
* @param {Object} rule Rule that matched
|
|
518
|
+
* @param {Object} context Event context
|
|
519
|
+
*/
|
|
520
|
+
function handleQuiet(connection, rule, context) {
|
|
521
|
+
sendBytes(connection, rule, resolveBytes(rule.send, context) || Buffer.alloc(0));
|
|
522
|
+
}
|
|
463
523
|
/**
|
|
464
524
|
* Handles a command or input line with the rule that matched it: sends the bytes of `send`, closes the connection,
|
|
465
525
|
* and processes the line as usual with `run` (or when the rule only delays it)
|
package/dist/cjs/server.d.ts
CHANGED
|
@@ -3,10 +3,26 @@ import net from 'node:net';
|
|
|
3
3
|
import tls from 'node:tls';
|
|
4
4
|
import type { CompilerOptions, ParserOptions } from 'imap-handler';
|
|
5
5
|
import type { ResolvedCommandOptions } from './command-states.js';
|
|
6
|
+
import { Control } from './control.js';
|
|
7
|
+
import type { RestServer } from './rest.js';
|
|
8
|
+
import type { SessionInfo } from './control.js';
|
|
6
9
|
import { ServerScript } from './script.js';
|
|
7
10
|
import type { DeferredOutput, OutputOperation, ScriptContext, ScriptEvent, ScriptRule } from './script.js';
|
|
8
11
|
import type { AppendCheck, AppendCheckOptions, AppendDataHandler, AppendMessage, CapabilityCheck, CheckResult, ClosedCheck, CommandCheck, CommandContext, CommandOptions, CommandHandler, ConnectionHandler, ConnectionState, CopyHandler, FetchHandler, IMAPResponse, IMAPServerOptions, ListedMailbox, LiteralFilter, Mailbox, MailboxHandler, MailboxStatus, Message, MessageFilter, MessageHandler, MessageRange, Namespace, Notification, NotifyEvent, NotifyFilter, OutputHandler, ParsedCommand, RangeLimit, Refusal, SearchAccessCheck, SearchHandler, SearchLimit, ServerStorage, StatusHandler, StorageMailbox, StorageMessage, StoreHandler, SubscriptionStandIn, Transport, UrlAccessCheck, UserData } from './types.js';
|
|
9
12
|
declare const TAG_REGEX: RegExp;
|
|
13
|
+
/** The parts of a smtp-server SMTPServer that the server uses, smtp-server is an optional dependency */
|
|
14
|
+
interface SMTPListener {
|
|
15
|
+
close(callback: () => void): void;
|
|
16
|
+
once(event: 'error', listener: (err: Error) => void): unknown;
|
|
17
|
+
removeListener(event: 'error', listener: (err: Error) => void): unknown;
|
|
18
|
+
server: net.Server;
|
|
19
|
+
}
|
|
20
|
+
/** The command whose tagged response is pending, and the session state before it ran */
|
|
21
|
+
interface CommandStart {
|
|
22
|
+
tag: string;
|
|
23
|
+
command: string;
|
|
24
|
+
state: ConnectionState;
|
|
25
|
+
}
|
|
10
26
|
/**
|
|
11
27
|
* Creates a new IMAP server, call `listen()` on it to start accepting connections
|
|
12
28
|
*
|
|
@@ -64,7 +80,58 @@ declare class IMAPServer extends Stream {
|
|
|
64
80
|
script: ServerScript;
|
|
65
81
|
/** connections accepted so far, the number of a connection is `connection.sessionNumber` */
|
|
66
82
|
sessionCounter: number;
|
|
83
|
+
/** the control API, see src/control.ts */
|
|
84
|
+
control: Control;
|
|
85
|
+
/** the SMTP server that `start()` runs for the `smtp` option */
|
|
86
|
+
smtpServer: SMTPListener | null;
|
|
87
|
+
/** the HTTP server of the REST API that `start()` runs for the `rest` option */
|
|
88
|
+
restServer: RestServer | null;
|
|
67
89
|
constructor(options?: IMAPServerOptions);
|
|
90
|
+
/**
|
|
91
|
+
* Sets the users from the `users` option, or the default user. The users are deep copied, so that runtime changes
|
|
92
|
+
* never leak into the caller's objects or into other servers built from the same fixture. Without a prototype,
|
|
93
|
+
* user names like "__proto__" or "toString" are plain keys
|
|
94
|
+
*/
|
|
95
|
+
loadUsers(): void;
|
|
96
|
+
/**
|
|
97
|
+
* Builds the mailboxes from the `storage` option, a deep copy of it. Message and mailbox handlers of plugins run
|
|
98
|
+
* on every message and mailbox
|
|
99
|
+
*/
|
|
100
|
+
loadStorage(): void;
|
|
101
|
+
/**
|
|
102
|
+
* Starts accepting connections
|
|
103
|
+
*
|
|
104
|
+
* @param {Number} [port] Port to listen on, a free port if not set
|
|
105
|
+
* @param {String} [host] Address to listen on, all addresses if not set
|
|
106
|
+
* @return {Promise<Number>} resolves with the port the server listens on
|
|
107
|
+
*/
|
|
108
|
+
start(port?: number, host?: string): Promise<number>;
|
|
109
|
+
/**
|
|
110
|
+
* Starts the REST API of the `rest` option, see src/rest.ts
|
|
111
|
+
*
|
|
112
|
+
* @return {Promise<Number>} resolves with the HTTP port
|
|
113
|
+
*/
|
|
114
|
+
startRest(): Promise<number>;
|
|
115
|
+
/**
|
|
116
|
+
* Starts the SMTP server of the `smtp` option. smtp-server is an optional dependency, loaded only here
|
|
117
|
+
*
|
|
118
|
+
* @return {Promise<Number>} resolves with the SMTP port
|
|
119
|
+
*/
|
|
120
|
+
startSmtp(): Promise<number>;
|
|
121
|
+
/**
|
|
122
|
+
* Loads the SMTP listener, which needs the smtp-server package
|
|
123
|
+
*
|
|
124
|
+
* @return {Promise<Object>} the smtp-listener module
|
|
125
|
+
*/
|
|
126
|
+
loadSmtpListener(): Promise<{
|
|
127
|
+
startSMTPServer(port: number, server: IMAPServer, callback: (port: number) => void, host?: string): SMTPListener;
|
|
128
|
+
}>;
|
|
129
|
+
/**
|
|
130
|
+
* Stops the server and closes every connection
|
|
131
|
+
*
|
|
132
|
+
* @return {Promise} resolves when the server is closed
|
|
133
|
+
*/
|
|
134
|
+
stop(): Promise<void>;
|
|
68
135
|
/**
|
|
69
136
|
* Starts accepting connections, takes the arguments of `net.Server#listen()`
|
|
70
137
|
*/
|
|
@@ -72,6 +139,13 @@ declare class IMAPServer extends Stream {
|
|
|
72
139
|
listen(port: number, hostname: string, listeningListener?: () => void): void;
|
|
73
140
|
listen(options: net.ListenOptions, listeningListener?: () => void): void;
|
|
74
141
|
close(callback?: (err?: Error) => void): void;
|
|
142
|
+
/**
|
|
143
|
+
* Stops the SMTP and REST listeners of `start()`
|
|
144
|
+
*
|
|
145
|
+
* @param {Boolean} force true closes the open REST connections too, otherwise only the idle ones (a request that
|
|
146
|
+
* is answered, like POST /v1/shutdown, finishes first)
|
|
147
|
+
*/
|
|
148
|
+
closeListeners(force: boolean): void;
|
|
75
149
|
/**
|
|
76
150
|
* Returns TLS key and certificate. Without credentials in the options the
|
|
77
151
|
* bundled self-signed certificate for localhost is used.
|
|
@@ -144,13 +218,23 @@ declare class IMAPServer extends Stream {
|
|
|
144
218
|
*
|
|
145
219
|
* @param {String} type Kind of change
|
|
146
220
|
* @param {String} path Storage name of the mailbox
|
|
147
|
-
* @param {Object} [details] `{ oldPath, mailbox }`: the earlier name of a renamed mailbox, the mailbox
|
|
148
|
-
* object that a DELETE removed
|
|
221
|
+
* @param {Object} [details] `{ oldPath, mailbox, created }`: the earlier name of a renamed mailbox, the mailbox
|
|
222
|
+
* object that a DELETE removed, the mailboxes a CREATE made (superior levels first)
|
|
149
223
|
*/
|
|
150
224
|
mailboxChanged(type: string, path: string, details?: {
|
|
151
225
|
oldPath?: string | undefined;
|
|
152
226
|
mailbox?: Mailbox | undefined;
|
|
227
|
+
created?: string[] | undefined;
|
|
153
228
|
}): void;
|
|
229
|
+
/**
|
|
230
|
+
* Runs a change with `origin` as the session that caused it, the `origin` of the notifications and events it
|
|
231
|
+
* makes. Commands run with their session, the control API with null
|
|
232
|
+
*
|
|
233
|
+
* @param {Object|null} origin Session, or null for a change from outside
|
|
234
|
+
* @param {Function} fn Change
|
|
235
|
+
* @return {*} the result of fn
|
|
236
|
+
*/
|
|
237
|
+
withOrigin<T>(origin: IMAPConnection | null, fn: () => T): T;
|
|
154
238
|
/**
|
|
155
239
|
* Retrieves the handler of an IMAP command
|
|
156
240
|
*
|
|
@@ -286,6 +370,32 @@ declare class IMAPServer extends Stream {
|
|
|
286
370
|
* @param {Array} flags Flags of a message
|
|
287
371
|
*/
|
|
288
372
|
rememberFlags(mailbox: Mailbox, flags: string[]): void;
|
|
373
|
+
/**
|
|
374
|
+
* Checks if a flag can be stored on messages of a mailbox: any flag when the mailbox allows new keywords
|
|
375
|
+
* (PERMANENTFLAGS has `\*`), otherwise only a flag in the PERMANENTFLAGS list that getStatus() builds, the
|
|
376
|
+
* `permanentFlags` of the mailbox and every flag its messages had. RFC 3501 section 7.1 (RFC 9051 section 7.1):
|
|
377
|
+
* PERMANENTFLAGS "indicates which of the known flags the client can change permanently", STORE ignores the others
|
|
378
|
+
*
|
|
379
|
+
* @param {Object} mailbox Mailbox object
|
|
380
|
+
* @param {String} flag Normalized flag
|
|
381
|
+
* @return {Boolean} true if the flag is a permanent flag of the mailbox
|
|
382
|
+
*/
|
|
383
|
+
isPermanentFlag(mailbox: Mailbox, flag: string): boolean;
|
|
384
|
+
/**
|
|
385
|
+
* The flags of the PERMANENTFLAGS list (without `\*`): the `permanentFlags` of the mailbox and every flag its
|
|
386
|
+
* messages have or had (`knownFlags`, see rememberFlags)
|
|
387
|
+
*
|
|
388
|
+
* @param {Object} mailbox Mailbox object
|
|
389
|
+
* @return {Array} flags, a new list
|
|
390
|
+
*/
|
|
391
|
+
permanentFlagList(mailbox: Mailbox): string[];
|
|
392
|
+
/**
|
|
393
|
+
* The current time for dates the server sets itself (INTERNALDATE of a message without one, SAVEDATE). The `now`
|
|
394
|
+
* option fixes it for repeatable tests: a Date, a timestamp, or a function that returns one
|
|
395
|
+
*
|
|
396
|
+
* @return {Date} current time
|
|
397
|
+
*/
|
|
398
|
+
now(): Date;
|
|
289
399
|
/**
|
|
290
400
|
* Converts a date-time value from storage or a client to the form it is sent in
|
|
291
401
|
*
|
|
@@ -457,6 +567,8 @@ interface QueuedCommand {
|
|
|
457
567
|
rule: ScriptRule;
|
|
458
568
|
context: ScriptContext;
|
|
459
569
|
} | undefined;
|
|
570
|
+
/** handler for a command that has no registered one (AUTHENTICATE with an unknown mechanism) */
|
|
571
|
+
handler?: CommandHandler | undefined;
|
|
460
572
|
}
|
|
461
573
|
declare class IMAPConnection {
|
|
462
574
|
[key: string]: any;
|
|
@@ -510,11 +622,18 @@ declare class IMAPConnection {
|
|
|
510
622
|
} | undefined;
|
|
511
623
|
/** output that waits behind a delay of a script rule, null when output is written right away */
|
|
512
624
|
_outputQueue: OutputOperation[] | null;
|
|
513
|
-
_outputTimer: ReturnType<typeof setTimeout> | null;
|
|
625
|
+
_outputTimer: ReturnType<typeof setTimeout> | ReturnType<typeof setImmediate> | null;
|
|
626
|
+
/** a piece of a chunked write with chunkDelay 0 or "tick" waits until it was handed to the system */
|
|
627
|
+
_outputWaiting: boolean | undefined;
|
|
514
628
|
/** untagged responses that `defer` rules hold back, see releaseDeferred in src/script.ts */
|
|
515
629
|
_deferredOutput: DeferredOutput[] | null;
|
|
516
630
|
/** a script rule closes the connection with a RST, see closeNow */
|
|
517
631
|
_resetting: boolean | undefined;
|
|
632
|
+
/** time of the last input or output, and the timer of the quiet events of script rules, see watchQuiet */
|
|
633
|
+
_lastActivity: number | undefined;
|
|
634
|
+
_quietTimer: ReturnType<typeof setTimeout> | null | undefined;
|
|
635
|
+
/** the command whose tagged response is pending and the session before it ran, see commandCompleted */
|
|
636
|
+
_commandStart: CommandStart | null | undefined;
|
|
518
637
|
constructor(server: IMAPServer, socket: net.Socket);
|
|
519
638
|
/**
|
|
520
639
|
* Writes protocol output to the client, through the transport layer if there is one
|
|
@@ -542,8 +661,9 @@ declare class IMAPConnection {
|
|
|
542
661
|
*
|
|
543
662
|
* @param {Buffer} data Output
|
|
544
663
|
* @param {Object|null} transport Transport layer
|
|
664
|
+
* @param {Function} [callback] Called once the data was handed to the system (a transport: on the next turn)
|
|
545
665
|
*/
|
|
546
|
-
writeLayer(data: Buffer, transport: Transport | null): void;
|
|
666
|
+
writeLayer(data: Buffer, transport: Transport | null, callback?: () => void): void;
|
|
547
667
|
/**
|
|
548
668
|
* Sends output through the script rule that handles its event, if there is one
|
|
549
669
|
*
|
|
@@ -566,7 +686,19 @@ declare class IMAPConnection {
|
|
|
566
686
|
*
|
|
567
687
|
* @param {Buffer} data Data to send
|
|
568
688
|
*/
|
|
569
|
-
writeRaw(data: Buffer | string): void;
|
|
689
|
+
writeRaw(data: Buffer | string, callback?: () => void): void;
|
|
690
|
+
/**
|
|
691
|
+
* Notes input or output for the quiet events of script rules (#88)
|
|
692
|
+
*/
|
|
693
|
+
touch(): void;
|
|
694
|
+
/**
|
|
695
|
+
* Starts waiting for the next quiet event, if a rule waits for one and the session is not waiting already. When
|
|
696
|
+
* the time is up, the first rule that matches handles the event (`send`, `close`), the output it sends starts
|
|
697
|
+
* the next quiet time
|
|
698
|
+
*
|
|
699
|
+
* @param {Number} [checked] Quiet time the rules were checked for already, only longer ones are waited for
|
|
700
|
+
*/
|
|
701
|
+
watchQuiet(checked?: number): void;
|
|
570
702
|
/**
|
|
571
703
|
* Handles data from the socket, through the transport layer if there is one
|
|
572
704
|
*
|
|
@@ -810,7 +942,36 @@ declare class IMAPConnection {
|
|
|
810
942
|
* @return {Array} Notifications to send
|
|
811
943
|
*/
|
|
812
944
|
prepareNotifications(queue: Notification[]): Notification[];
|
|
813
|
-
|
|
945
|
+
/**
|
|
946
|
+
* Checks if EXPUNGE responses must wait while a command runs: during FETCH, STORE and SEARCH (RFC 3501 section
|
|
947
|
+
* 7.4.1), during the commands that extensions add to this list (see the noExpunge command option), and during UID
|
|
948
|
+
* SEARCH with message numbers in the search criteria (RFC 7162 section 3.2.10.2 for VANISHED, EXPUNGE may wait as
|
|
949
|
+
* well, RFC 3501 only allows it during UID commands)
|
|
950
|
+
*
|
|
951
|
+
* @param {Object} data Parsed command
|
|
952
|
+
* @return {Boolean} true if the command holds back EXPUNGE responses
|
|
953
|
+
*/
|
|
954
|
+
holdsExpunge(data: CommandContext): boolean;
|
|
955
|
+
/**
|
|
956
|
+
* Sends the queued notifications. During a command that holds back EXPUNGE responses (see holdsExpunge), or with
|
|
957
|
+
* `beforeCommand` before a command that refers to messages by sequence number, only the notifications queued before
|
|
958
|
+
* the first pending EXPUNGE go out: new messages (EXISTS) and flag changes. RFC 3501 section 5.2: "A server MUST send
|
|
959
|
+
* mailbox size updates automatically if a mailbox size change is observed during the processing of a command",
|
|
960
|
+
* section 7.4.1 forbids only EXPUNGE during FETCH, STORE and SEARCH. What was queued after the EXPUNGE waits with it,
|
|
961
|
+
* an EXISTS sent before it would describe a list that the client can not know yet
|
|
962
|
+
*
|
|
963
|
+
* @param {Object} [data] Parsed command that runs, or null between commands
|
|
964
|
+
* @param {Boolean} [beforeCommand] true when the command has not run yet, then a command with message sequence
|
|
965
|
+
* numbers (COPY, MOVE) also waits with the EXPUNGE responses, its numbers refer to the messages before them
|
|
966
|
+
*/
|
|
967
|
+
processNotifications(data?: CommandContext | null, beforeCommand?: boolean): void;
|
|
968
|
+
/**
|
|
969
|
+
* Sends an untagged RECENT response with the number of \Recent messages among the first `count` messages
|
|
970
|
+
*
|
|
971
|
+
* @param {Number} count Number of messages the client was told about
|
|
972
|
+
* @param {Map} sequence Message to the sequence number this session knows it by
|
|
973
|
+
*/
|
|
974
|
+
sendRecent(count: number, sequence: Map<Message, number>): void;
|
|
814
975
|
/**
|
|
815
976
|
* Compile a command object to a response string and write it to socket.
|
|
816
977
|
* If the command object has a skipResponse property, the command is
|
|
@@ -879,6 +1040,7 @@ declare class IMAPConnection {
|
|
|
879
1040
|
* @return {Boolean} true if the command uses message sequence numbers
|
|
880
1041
|
*/
|
|
881
1042
|
usesSequenceNumbers(parsed: CommandContext): boolean;
|
|
1043
|
+
findSequenceNumbers(parsed: CommandContext): boolean;
|
|
882
1044
|
/**
|
|
883
1045
|
* Decodes a SASL client response. It must be valid base64 by the RFC 3501 section 9 grammar,
|
|
884
1046
|
* "=" stands for an empty initial response (RFC 4959 section 3).
|
|
@@ -949,6 +1111,34 @@ declare class IMAPConnection {
|
|
|
949
1111
|
* @param {Boolean} [scripted] The line comes from a script rule with `run`, it is next in the queue
|
|
950
1112
|
*/
|
|
951
1113
|
scheduleCommand(data: string, scripted?: boolean): void;
|
|
1114
|
+
/**
|
|
1115
|
+
* Emits the `command` event when the tagged response of a command goes out, and the `session` events "login"
|
|
1116
|
+
* (the command authenticated the session) and "logout" (back to Not Authenticated, UNAUTHENTICATE). Every command
|
|
1117
|
+
* that changes these does so before its tagged response. "select" and "unselect" come from SELECT and
|
|
1118
|
+
* closeMailbox(). Tests wait for these instead of polling
|
|
1119
|
+
*
|
|
1120
|
+
* @param {String} status OK, NO or BAD
|
|
1121
|
+
*/
|
|
1122
|
+
commandCompleted(status: string): void;
|
|
1123
|
+
/**
|
|
1124
|
+
* Emits a `session` event, `{ type, session, ...fields }`
|
|
1125
|
+
*
|
|
1126
|
+
* @param {String} type Event type
|
|
1127
|
+
* @param {Object} [fields] More fields of the event
|
|
1128
|
+
*/
|
|
1129
|
+
emitSession(type: string, fields?: Record<string, unknown>): void;
|
|
1130
|
+
/**
|
|
1131
|
+
* Describes the session without credentials or sockets, for the control API and the `session` events
|
|
1132
|
+
*
|
|
1133
|
+
* @return {Object} `{ session, user, state, mailbox, readOnly, enabled, secure, compressed, remoteAddress }`
|
|
1134
|
+
*/
|
|
1135
|
+
describe(): SessionInfo;
|
|
1136
|
+
/**
|
|
1137
|
+
* Checks if output can still be sent: the socket is open and the connection is not closing (BYE, LOGOUT)
|
|
1138
|
+
*
|
|
1139
|
+
* @return {Boolean} true for a live session
|
|
1140
|
+
*/
|
|
1141
|
+
isOpen(): boolean;
|
|
952
1142
|
/**
|
|
953
1143
|
* Handles a command line with the script rule that matched it, after the rule's delay. With `run` the line
|
|
954
1144
|
* goes through the parser and the command handler as usual afterwards
|
|
@@ -963,19 +1153,19 @@ declare class IMAPConnection {
|
|
|
963
1153
|
*
|
|
964
1154
|
* @param {Object} mailbox Mailbox to check for
|
|
965
1155
|
* @param {Boolean} [ignoreSelf] If set to true, does not send any notices to itself
|
|
966
|
-
* @param {Boolean} [
|
|
1156
|
+
* @param {Boolean} [ignoreExists] If set to true, does not send EXISTS notice to itself
|
|
967
1157
|
*/
|
|
968
1158
|
expungeDeleted(mailbox: Mailbox, ignoreSelf?: boolean, ignoreExists?: boolean): void;
|
|
969
1159
|
/**
|
|
970
1160
|
* Given a set of messages in a mailbox (possibly via getMessageRange), remove
|
|
971
|
-
* them from the mailbox and generate EXPUNGE notifications.
|
|
1161
|
+
* them from the mailbox and generate EXPUNGE notifications, see expungeMessages() in store-operations.ts
|
|
972
1162
|
*
|
|
973
1163
|
* @param {Object} mailbox Mailbox to check for
|
|
974
1164
|
* @param {Function|Array} messagesOrFilterFunc An Array of messages in the
|
|
975
1165
|
* folder that should be removed or a filtering function that indicates
|
|
976
1166
|
* messages to be removed by returning true.
|
|
977
1167
|
* @param {Boolean} [ignoreSelf] If set to true, does not send any notices to itself
|
|
978
|
-
* @param {Boolean} [
|
|
1168
|
+
* @param {Boolean} [ignoreExists] If set to true, does not send EXISTS notice to itself
|
|
979
1169
|
* @param {Boolean} [highestFirst] If set to true, the EXPUNGE responses go from the highest UID to the lowest
|
|
980
1170
|
* (MESSAGELIMIT, RFC 9738 section 3.1), otherwise from the lowest
|
|
981
1171
|
*/
|