imapkit 4.1.1 → 4.3.0

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 CHANGED
@@ -65,7 +65,7 @@ ImapKit is a single user, multiple connection IMAP server. Changes made over IMA
65
65
 
66
66
  Several clients can connect to the server simultaneously but all the clients share the same user account, even if login credentials are different. The ACL plugin can limit what users other than the owner can do (see [ACL](#acl)).
67
67
 
68
- ImapKit is extendable: any command can be overridden and plugins can be added (see [Creating custom plugins](#creating-custom-plugins), and `src/commands` and `src/plugins` for the built-in commands and plugins).
68
+ ImapKit is extendable: any command can be overridden and plugins can be added (see [Creating custom plugins](#creating-custom-plugins), and `src/commands` and `src/plugins` for the built-in commands and plugins). To test a client against a broken or unusual server, [script rules](#scripted-faults) make the server misbehave at chosen points.
69
69
 
70
70
  ## Strict by design
71
71
 
@@ -395,6 +395,89 @@ describe('IMAP tests', () => {
395
395
  });
396
396
  ```
397
397
 
398
+ ## Scripted faults
399
+
400
+ ImapKit is strict and correct by default. To test how a client copes with a server that is not, script rules make the server deviate from the protocol at chosen points: answer a command with a canned response, send a literal where a quoted string is expected, cut a response in the middle of a literal, delay or split output, or drop the connection. Rules come from the `script` server option (a rule or a list of rules), or are added at runtime with `server.script.add()`:
401
+
402
+ ```javascript
403
+ const server = imapkit({
404
+ plugins: ['IDLE'],
405
+ script: [
406
+ // the first SELECT gets NO, the next ones run as usual
407
+ { on: 'command', command: 'SELECT', times: 1, send: '$TAG NO [UNAVAILABLE] Try again later\r\n' },
408
+ // the body of message 1 is cut short and the connection dropped
409
+ { on: 'response', command: 'FETCH', match: /^\* 1 FETCH .*BODY\[\]/, truncate: 40 }
410
+ ]
411
+ });
412
+
413
+ // rules added later return handles
414
+ const [literals, late] = server.script.add([
415
+ // every string the grammar allows is sent as a literal, valid IMAP that a client must handle
416
+ { on: 'response', untagged: true, literals: true },
417
+ // the FETCH response of UID 2 arrives after the tagged OK, like some servers do now and then
418
+ { on: 'response', command: 'UID FETCH', untagged: true, match: /UID 2\b/, times: 1, defer: 'tagged' }
419
+ ]);
420
+ // ... run the client
421
+ assert.strictEqual(late.hits, 1);
422
+ literals.remove();
423
+ ```
424
+
425
+ Every rule watches one event (`on`):
426
+
427
+ - `greeting`: the `* OK` greeting of a new connection
428
+ - `command`: a complete command line (with its literals) from the client. The rule acts instead of the parser and the command handler, so it also matches lines that do not parse and commands that do not exist. The rule is chosen when the line arrives (matchers like `state` see the session at that moment), and acts in the order of the commands, so pipelined responses stay in order
429
+ - `input`: a line read by a command that takes over the input, like `DONE` of IDLE or a SASL response of AUTHENTICATE
430
+ - `response`: every response the server sends with `connection.send()`, tagged and untagged, as the exact bytes that are about to go out, after every plugin and the core changed the response
431
+ - `continuation`: a `+` continuation request (literals, IDLE, AUTHENTICATE)
432
+
433
+ The matchers of a rule all have to match. Rules are checked in the order they were added, the first rule that matches and is not used up handles the event, so a later rule can handle what an earlier one leaves alone.
434
+
435
+ | Matcher | Events | Matches |
436
+ | -------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
437
+ | `command` | all but greeting | the command name, or a list of names, case-insensitive (`'UID FETCH'`). An unsolicited response belongs to the command that runs, or that reads input (IDLE) |
438
+ | `tag` | all but greeting | the command tag, a string or a RegExp |
439
+ | `description` | response, continuation | the description passed to `connection.send()`, or a list of them. Continuation requests are `LITERAL`, `IDLE`, `AUTHENTICATE PLAIN` and `AUTHENTICATE OAUTHBEARER` |
440
+ | `untagged` | response | `true` for untagged responses only, `false` for tagged ones |
441
+ | `session` | all | the number of the connection, or a list of numbers, 1 for the first connection the server accepted |
442
+ | `state`, `user`, `mailbox` | all | the session state (`'Not Authenticated'`, `'Authenticated'`, `'Selected'`), the authenticated user, the path of the selected mailbox |
443
+ | `match` | all | a RegExp, or a string with a regular expression, tested against the command line or the output bytes (a binary string) |
444
+ | `when` | all | a function that gets the event context and returns true to match |
445
+ | `nth` | all | the rule fires from the nth matching event on (default 1) |
446
+ | `times` | all | the rule fires this many times at most, then lets later rules handle the event |
447
+
448
+ The actions say what happens instead of the usual behavior. Strings are sent as they are (binary strings, one character per octet, or UTF-8 when they have characters above U+00FF) without an added CRLF, and `$TAG` in a string is replaced with the tag of the command. A Buffer is sent as it is, a function gets the event context and returns a string or a Buffer.
449
+
450
+ | Action | Events | Effect |
451
+ | --------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
452
+ | `send` | all | output events: bytes sent instead of the output. command and input: bytes sent instead of processing the line |
453
+ | `run` | command, input | process the line as usual after `send` (to add output before the real response) |
454
+ | `drop` | all | output events: send nothing. command and input: ignore the line, the client gets no answer |
455
+ | `mutate` | response, continuation | `(response, context)` gets a copy of the response object before it is compiled and changes it, or returns another one. Continuation requests have a response object only when a plugin sends them with `connection.send()` (the error challenges of XOAUTH2 and OAUTHBEARER), other events ignore `mutate`. A tagged response that does not compile then is sent as `NO [SERVERBUG]`, an untagged one is dropped. Use `send` for output that is not valid IMAP |
456
+ | `literals` | response | sends every string of the response that the grammar allows (string, nstring, astring) as a literal. The positions that take only a quoted string stay quoted: the hierarchy delimiter of LIST, LSUB and NAMESPACE, CHILDINFO values, INTERNALDATE and SAVEDATE, and the media types `"TEXT"` and `"MESSAGE" "RFC822"` in a body structure (RFC 9051 section 9, RFC 5258 section 6). Atoms, response codes and text stay as they are. Works together with `mutate`, after it |
457
+ | `defer` | response | holds an untagged response back: `'tagged'` sends it right after the tagged response of its command, `'next'` with the answer to the next command, before its first response. Needs `untagged: true`. `send`, `before` and `after` change the held output, `drop`, `delay`, `chunk`, `truncate` and `close` can not be combined with it. A response that does not belong to a command (one that arrives in IDLE belongs to IDLE) is held for the next tagged response or command. Held responses are dropped when the connection closes |
458
+ | `before`, `after` | output events | bytes sent before or after the output, e.g. an unsolicited response |
459
+ | `delay` | all but input | milliseconds to wait before the output goes out, all later output waits behind it. For a command, the wait before the rule acts or the command runs, later commands wait too |
460
+ | `chunk`, `chunkDelay` | all | write the bytes in pieces of `chunk` octets, `chunkDelay` milliseconds apart (default 10) |
461
+ | `truncate` | all | send only this many octets of the bytes, then close the connection |
462
+ | `close` | all | close the connection after the bytes are sent, `'reset'` destroys the socket instead (a TCP RST where the runtime supports it), 20 ms after the bytes so that the RST does not overtake them. Input that arrives meanwhile is not processed |
463
+
464
+ A rule needs at least one action, and for command and input events `chunk` and `truncate` need `send`. A rule with only `delay` (and `run`) delays the line and then processes it as usual. Rules are checked when they are added: an unknown option, an option that does not apply to the event, or an invalid value throws a `TypeError`, so a typo can not turn into a rule that never fires.
465
+
466
+ The event context, which `when`, `mutate` and `send` functions get, has `event`, `connection`, `session`, `state`, `user`, `mailbox`, `tag`, `command`, `data` (the command line or the output bytes, as a binary string), and for responses `description` and `response`.
467
+
468
+ `server.script.add(rule)` returns a handle with `rule`, `matched` (events that matched the rule, also before `nth`), `hits` (events the rule handled) and `remove()`, `server.script.add([rules])` returns a list of handles. `server.script.rules` lists the handles in order, `server.script.clear()` removes every rule. The server emits a `script` event `{ rule, event, session, tag, command }` every time a rule fires.
469
+
470
+ The `imapkit` command takes the rules as JSON with `--script=<path>` (or `IMAPKIT_SCRIPT`), or as `script` in the `--config` file. JSON rules use strings for `match` and `send`, functions (`when`, `mutate`, function values of `send`) work only from JavaScript:
471
+
472
+ ```json
473
+ [
474
+ { "on": "greeting", "send": "* BYE Too many connections\r\n", "close": true, "times": 1 },
475
+ { "on": "response", "command": "FETCH", "untagged": true, "send": "* 1 FETCH (BODY[] {100}\r\nshort", "close": true }
476
+ ]
477
+ ```
478
+
479
+ Faults change only the output and the handling of the lines a rule matches, the state of the server stays consistent: a LOGIN answered by a rule with `OK` does not log the session in, and a dropped EXPUNGE response still removes the message. COMPRESS works with delayed output, as the output keeps the compression layer it was sent with. Script rules are for tests only, a rule can send anything.
480
+
398
481
  ## Creating custom plugins
399
482
 
400
483
  A plugin can be a string as a pointer to a built in plugin or a function. Plugin function is run when the server is created and gets server instance object as an argument.
@@ -589,6 +672,8 @@ server.resetHandlers.push(function (connection) {
589
672
 
590
673
  Any response sent to the client can be overridden or cancelled by other handlers. You should append your handler to `server.outputHandlers` array. If something is being sent to the client, the response object is passed through all handlers in this array.
591
674
 
675
+ Output handlers are meant for extensions that change valid responses. They run before the core finishes the response (response codes like `EXPUNGEISSUED`, mailbox names, the 7-bit status text) and before the compiler, which refuses output that is not valid IMAP. To make the server send something wrong on purpose, use [script rules](#scripted-faults) instead, they see the final bytes.
676
+
592
677
  server.outputHandlers.push(function(connection, /* arguments from connection.send */){})
593
678
 
594
679
  `response` arguments from `connection.send` is an object and thus any modifications will be passed on. If `skipResponse` property is added to the response object, the data is not sent to the client.
package/bin/help.txt CHANGED
@@ -16,6 +16,9 @@ Usage: imapkit [OPTS]
16
16
  -d, --debug=true Writes IMAP traffic to console
17
17
  --storage=<path> Path to JSON file with the directory tree
18
18
  --config=<config> Path to config JSON file
19
+ --script=<path> Path to a JSON file with script rules that
20
+ make the server misbehave on purpose (see
21
+ "Scripted faults" below)
19
22
  --plugin=<plugin> Enables a plugin for the server. See below for
20
23
  available plugins.
21
24
  --smtpPort=<port> Port number for incoming SMTP server. If not set
@@ -23,7 +26,7 @@ Usage: imapkit [OPTS]
23
26
 
24
27
  The options can also be set with environment variables: IMAPKIT_PORT,
25
28
  IMAPKIT_SECURE, IMAPKIT_DEBUG, IMAPKIT_STORAGE, IMAPKIT_CONFIG,
26
- IMAPKIT_PLUGINS (comma separated) and IMAPKIT_SMTPPORT.
29
+ IMAPKIT_SCRIPT, IMAPKIT_PLUGINS (comma separated) and IMAPKIT_SMTPPORT.
27
30
 
28
31
  NB! If port or smtpPort values are below 1024 you most probably need to
29
32
  use sudo or run the command with administrator rights.
@@ -61,6 +64,29 @@ Configuration file takes the following structure
61
64
  }
62
65
  }
63
66
 
67
+ Scripted faults
68
+ ---------------
69
+
70
+ A script file holds a list of rules. Each rule watches one event
71
+ ("greeting", "command", "input", "response" or "continuation") and
72
+ changes what the server does when its matchers fit. For example, this
73
+ script answers the first FETCH with a literal that is too short and
74
+ then drops the connection:
75
+
76
+ [
77
+ {
78
+ "on": "response",
79
+ "command": "FETCH",
80
+ "untagged": true,
81
+ "times": 1,
82
+ "send": "* 1 FETCH (BODY[] {100}\r\nshort",
83
+ "close": true
84
+ }
85
+ ]
86
+
87
+ Strings are sent as they are, "$TAG" stands for the command tag and
88
+ "match" is a regular expression. See the README for every option.
89
+
64
90
  Storage file is a tree like structure starting with namespace values.
65
91
  INBOX has its own namespace.
66
92
 
package/bin/imapkit.js CHANGED
@@ -19,6 +19,7 @@ const { values: argv } = parseArgs({
19
19
  help: { type: 'boolean', short: 'h' },
20
20
  config: { type: 'string' },
21
21
  storage: { type: 'string' },
22
+ script: { type: 'string' },
22
23
  plugin: { type: 'string', multiple: true },
23
24
  smtpPort: { type: 'string' }
24
25
  }
@@ -28,6 +29,7 @@ const isTrue = value => (value || '').toString().trim().toLowerCase() === 'true'
28
29
 
29
30
  const configLocation = argv.config || process.env.IMAPKIT_CONFIG;
30
31
  const storageLocation = argv.storage || process.env.IMAPKIT_STORAGE;
32
+ const scriptLocation = argv.script || process.env.IMAPKIT_SCRIPT;
31
33
  const smtpPort = argv.smtpPort || process.env.IMAPKIT_SMTPPORT;
32
34
  const pluginsList = []
33
35
  .concat(argv.plugin || process.env.IMAPKIT_PLUGINS || [])
@@ -51,6 +53,10 @@ if (storageLocation) {
51
53
  config.storage = JSON.parse(fs.readFileSync(storageLocation, 'utf-8'));
52
54
  }
53
55
 
56
+ if (scriptLocation) {
57
+ config.script = JSON.parse(fs.readFileSync(scriptLocation, 'utf-8'));
58
+ }
59
+
54
60
  if (pluginsList.length) {
55
61
  config.plugins = pluginsList;
56
62
  }
@@ -1,6 +1,7 @@
1
1
  import createServer, { TAG_REGEX, IMAPServer, IMAPConnection } from './server.js';
2
2
  export { TAG_REGEX, IMAPServer, IMAPConnection };
3
3
  export type { Attribute, ParsedCommand, IMAPResponse, Notification, Callback, CommandHandler, CommandOptions, Plugin, IMAPError, Message, Mailbox, StorageNamespace, UserData, IMAPServerOptions } from './types.js';
4
+ export type { ScriptRule, ScriptContext, ScriptEvent, ScriptBytes, ScriptHandle } from './script.js';
4
5
  declare const imapkit: typeof createServer & {
5
6
  TAG_REGEX: RegExp;
6
7
  IMAPServer: typeof IMAPServer;
@@ -57,7 +57,7 @@ function authPlainPlugin(server) {
57
57
  authenticate(connection, parsed, data, str);
58
58
  };
59
59
  // Send an empty continuation request to the client
60
- connection.write('+ \r\n');
60
+ connection.sendContinuation('', 'AUTHENTICATE PLAIN');
61
61
  }
62
62
  else if (parsed.attributes.length === 1 &&
63
63
  // second argument must be Base64 string as ATOM
@@ -66,7 +66,7 @@ function idlePlugin(server) {
66
66
  }, 'INVALID IDLE', parsed, data);
67
67
  }
68
68
  };
69
- connection.write('+ idling\r\n');
69
+ connection.sendContinuation('idling', 'IDLE');
70
70
  connection.processNotifications();
71
71
  return callback();
72
72
  }, { states: command_states_js_1.states.AUTHENTICATED, noArguments: true });
@@ -169,7 +169,7 @@ function oauthbearerPlugin(server) {
169
169
  if (!args.length) {
170
170
  // without an initial response the client sends its response after an empty challenge
171
171
  readResponse(connection, parsed, data, decoded => authenticate(connection, parsed, data, decoded));
172
- connection.write('+ \r\n');
172
+ connection.sendContinuation('', 'AUTHENTICATE OAUTHBEARER');
173
173
  return callback();
174
174
  }
175
175
  if (args.length !== 1 || !args[0] || args[0].type !== 'ATOM') {
@@ -0,0 +1,216 @@
1
+ import type { IMAPConnection, IMAPServer } from './server.js';
2
+ import type { IMAPResponse, Transport } from './types.js';
3
+ /**
4
+ * Scripted faults: rules that make the server deviate from the protocol on purpose, to test how a client
5
+ * copes with a broken or unusual server. Every rule watches one kind of event, the first rule that matches
6
+ * an event handles it. Output rules see the exact bytes that are about to go out, after every plugin and the
7
+ * core changed the response, so they can send anything at all, also what the strict server never would.
8
+ */
9
+ /** The events a rule can watch */
10
+ export type ScriptEvent = 'greeting' | 'command' | 'input' | 'response' | 'continuation';
11
+ /** Bytes a rule sends, a function gets the context of the event */
12
+ export type ScriptBytes = string | Buffer | ((context: ScriptContext) => string | Buffer);
13
+ /** The event a rule is checked against */
14
+ export interface ScriptContext {
15
+ event: ScriptEvent;
16
+ connection: IMAPConnection;
17
+ /** the number of the connection, 1 for the first one the server accepted */
18
+ session: number;
19
+ state: string;
20
+ /** the authenticated user, or null */
21
+ user: string | null;
22
+ /** path of the selected mailbox, or null */
23
+ mailbox: string | null;
24
+ /** tag of the command the event belongs to, or null (greeting, unsolicited responses) */
25
+ tag: string | null;
26
+ /** name of the command the event belongs to in upper case (`"UID FETCH"`), or null */
27
+ command: string | null;
28
+ /** the received line (command, input) or the bytes about to be sent (output events), as a binary string */
29
+ data: string;
30
+ /** the description of the response, see `connection.send()` (response and continuation events) */
31
+ description?: string | null | undefined;
32
+ /** the response object (response and continuation events sent with `connection.send()`) */
33
+ response?: IMAPResponse | undefined;
34
+ }
35
+ /** A rule as it is given to `server.script.add()` or the `script` option */
36
+ export interface ScriptRule {
37
+ on: ScriptEvent;
38
+ /** command name or names, case-insensitive (`"FETCH"`, `"UID FETCH"`) */
39
+ command?: string | string[] | undefined;
40
+ /** command tag, a string matches exactly */
41
+ tag?: string | RegExp | undefined;
42
+ /** response description or descriptions (response and continuation events) */
43
+ description?: string | string[] | undefined;
44
+ /** connection number or numbers, 1 for the first connection */
45
+ session?: number | number[] | undefined;
46
+ user?: string | undefined;
47
+ state?: string | string[] | undefined;
48
+ /** path of the selected mailbox */
49
+ mailbox?: string | undefined;
50
+ /** only untagged (true) or only tagged (false) responses (response events) */
51
+ untagged?: boolean | undefined;
52
+ /** tested against `context.data`, a string is a regular expression source */
53
+ match?: string | RegExp | undefined;
54
+ when?: ((context: ScriptContext) => boolean) | undefined;
55
+ /** the rule fires from the nth matching event on (default 1) */
56
+ nth?: number | undefined;
57
+ /** the rule fires this many times at most (default unlimited) */
58
+ times?: number | undefined;
59
+ /** sends every string of the response that the grammar allows as a literal (response events) */
60
+ literals?: boolean | undefined;
61
+ /**
62
+ * holds an untagged response back: `"tagged"` sends it after the tagged response of its command, `"next"` with the
63
+ * first response that belongs to another command (response events)
64
+ */
65
+ defer?: 'tagged' | 'next' | undefined;
66
+ /** changes or replaces the response object before it is compiled (response events) */
67
+ mutate?: ((response: IMAPResponse, context: ScriptContext) => IMAPResponse | void) | undefined;
68
+ /** bytes to send instead of the output, or instead of answering the command or input line. `$TAG` in a string is the tag */
69
+ send?: ScriptBytes | undefined;
70
+ /** bytes to send before the output (output events) */
71
+ before?: ScriptBytes | undefined;
72
+ /** bytes to send after the output (output events) */
73
+ after?: ScriptBytes | undefined;
74
+ /** process the command or input line as usual after `send` (command and input events) */
75
+ run?: boolean | undefined;
76
+ /** send nothing (output events), or ignore the command or input line */
77
+ drop?: boolean | undefined;
78
+ /** milliseconds to wait before the output goes out, or before the command is handled. Later output waits too */
79
+ delay?: number | undefined;
80
+ /** write the bytes in pieces of this many octets */
81
+ chunk?: number | undefined;
82
+ /** milliseconds between the pieces of `chunk` (default 10) */
83
+ chunkDelay?: number | undefined;
84
+ /** send only this many octets of the bytes, then close the connection */
85
+ truncate?: number | undefined;
86
+ /** close the connection after the bytes are sent, `"reset"` destroys the socket instead of ending it */
87
+ close?: boolean | 'reset' | undefined;
88
+ }
89
+ /** A rule that was added, `server.script.add()` returns it */
90
+ export interface ScriptHandle {
91
+ readonly rule: ScriptRule;
92
+ /** events that matched the rule, also before `nth` */
93
+ readonly matched: number;
94
+ /** events the rule handled */
95
+ readonly hits: number;
96
+ remove(): void;
97
+ }
98
+ /** Output that waits in the output queue of a connection, see `IMAPConnection#queueOutput` */
99
+ export interface OutputOperation {
100
+ data?: Buffer | undefined;
101
+ delay?: number | undefined;
102
+ chunk?: number | undefined;
103
+ chunkDelay?: number | undefined;
104
+ close?: boolean | 'reset' | undefined;
105
+ /** the transport layer of the connection when the output was queued */
106
+ transport?: Transport | null | undefined;
107
+ }
108
+ declare const SET_MATCHERS: readonly ["command", "description", "session", "state", "user", "mailbox"];
109
+ type SetMatcher = (typeof SET_MATCHERS)[number];
110
+ interface Entry extends ScriptHandle {
111
+ matched: number;
112
+ hits: number;
113
+ match: RegExp | null;
114
+ sets: Partial<Record<SetMatcher, Set<string | number>>>;
115
+ }
116
+ /**
117
+ * The script of a server, `server.script`
118
+ */
119
+ export declare class ServerScript {
120
+ server: IMAPServer;
121
+ entries: Entry[];
122
+ /** the events that rules watch, so that an event nobody watches costs one lookup */
123
+ watched: Set<ScriptEvent>;
124
+ constructor(server: IMAPServer, rules?: ScriptRule | ScriptRule[] | undefined);
125
+ /** the rules that were added, in the order they are checked */
126
+ get rules(): ScriptHandle[];
127
+ /**
128
+ * Adds a rule, or a list of rules, after the rules added earlier
129
+ *
130
+ * @param {Object|Array} rules Rule or rules
131
+ * @return {Object|Array} handle or handles with `hits`, `matched` and `remove()`
132
+ */
133
+ add(rule: ScriptRule): ScriptHandle;
134
+ add(rules: ScriptRule[]): ScriptHandle[];
135
+ add(rules: ScriptRule | ScriptRule[]): ScriptHandle | ScriptHandle[];
136
+ /** removes every rule */
137
+ clear(): void;
138
+ /**
139
+ * Checks if any rule watches an event, so that the event can skip building a context
140
+ *
141
+ * @param {String} event Event name
142
+ * @return {Boolean} true if a rule watches the event
143
+ */
144
+ watches(event: ScriptEvent): boolean;
145
+ /**
146
+ * Finds the rule that handles an event of a connection
147
+ *
148
+ * @param {Object} connection IMAP connection
149
+ * @param {String} event Event name
150
+ * @param {Object} fields `data`, and `tag`, `command`, `description`, `response` where they are known
151
+ * @return {Object|null} `{ rule, context }`, or null if no rule handles the event
152
+ */
153
+ check(connection: IMAPConnection, event: ScriptEvent, fields: Partial<ScriptContext> & {
154
+ data: string;
155
+ }): {
156
+ rule: ScriptRule;
157
+ context: ScriptContext;
158
+ } | null;
159
+ private setEntries;
160
+ private addEntry;
161
+ /**
162
+ * Finds the rule that handles an event and counts it
163
+ *
164
+ * @param {Object} context Event context
165
+ * @return {Object|null} the rule, or null if no rule handles the event
166
+ */
167
+ find(context: ScriptContext): ScriptRule | null;
168
+ private matches;
169
+ }
170
+ /**
171
+ * Sends output through the rule that handles it: the output itself (unless dropped or replaced with `send`)
172
+ * between `before` and `after`, cut by `truncate`, with the timing of `delay` and `chunk`
173
+ *
174
+ * @param {Object} connection IMAP connection
175
+ * @param {Object} rule Rule that handles the output
176
+ * @param {Object} context Event context
177
+ * @param {Buffer} output The output
178
+ */
179
+ export declare function sendOutput(connection: IMAPConnection, rule: ScriptRule, context: ScriptContext, output: Buffer): void;
180
+ /** Output held back by `defer`, see releaseDeferred */
181
+ export interface DeferredOutput {
182
+ mode: 'tagged' | 'next';
183
+ /** tag of the command the response belongs to, null for an unsolicited response */
184
+ tag: string | null;
185
+ data: Buffer;
186
+ }
187
+ /**
188
+ * Sends the held back responses whose turn has come (`defer`). Before a response of another command go the ones held
189
+ * with `"next"`, after the tagged response of their command the ones held with `"tagged"`. A response held without a
190
+ * command goes with the next command, or after the next tagged response
191
+ *
192
+ * @param {Object} connection IMAP connection
193
+ * @param {String|null} tag Tag of the command the response that is sent belongs to
194
+ * @param {Boolean} tagged false before the response is sent, true after a tagged response was sent
195
+ */
196
+ export declare function releaseDeferred(connection: IMAPConnection, tag: string | null, tagged: boolean): void;
197
+ /**
198
+ * Turns the strings of a response into literals wherever the grammar allows a string (RFC 9051 section 9: string,
199
+ * nstring and astring). Positions that take only a quoted string stay quoted: the hierarchy delimiter of LIST, LSUB
200
+ * and NAMESPACE, CHILDINFO values (RFC 5258 section 6), date-time values and the quoted media types of a body structure
201
+ *
202
+ * @param {Object} response Response object
203
+ * @return {Object} a copy of the response with literals, the response is not changed
204
+ */
205
+ export declare function literalResponse(response: IMAPResponse): IMAPResponse;
206
+ /**
207
+ * Handles a command or input line with the rule that matched it: sends the bytes of `send`, closes the connection,
208
+ * and processes the line as usual with `run` (or when the rule only delays it)
209
+ *
210
+ * @param {Object} connection IMAP connection
211
+ * @param {Object} rule Rule that matched
212
+ * @param {Object} context Event context
213
+ * @param {Function} run Processes the line as usual
214
+ */
215
+ export declare function handleLine(connection: IMAPConnection, rule: ScriptRule, context: ScriptContext, run: () => void): void;
216
+ export {};