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.
Files changed (121) hide show
  1. package/README.md +187 -67
  2. package/bin/help.txt +20 -5
  3. package/bin/imapkit.js +64 -24
  4. package/dist/cjs/commands/append.js +1 -1
  5. package/dist/cjs/commands/create.js +2 -2
  6. package/dist/cjs/commands/delete.js +2 -14
  7. package/dist/cjs/commands/handlers/fetch.js +2 -1
  8. package/dist/cjs/commands/handlers/flags.d.ts +3 -3
  9. package/dist/cjs/commands/handlers/flags.js +3 -3
  10. package/dist/cjs/commands/handlers/store.js +7 -7
  11. package/dist/cjs/commands/list.js +17 -3
  12. package/dist/cjs/commands/rename.js +6 -46
  13. package/dist/cjs/commands/select.js +1 -0
  14. package/dist/cjs/commands/subscribe.js +7 -8
  15. package/dist/cjs/commands/unsubscribe.js +3 -9
  16. package/dist/cjs/control.d.ts +425 -0
  17. package/dist/cjs/control.js +637 -0
  18. package/dist/cjs/dates.d.ts +9 -1
  19. package/dist/cjs/dates.js +20 -0
  20. package/dist/cjs/envelope.d.ts +1 -1
  21. package/dist/cjs/envelope.js +26 -1
  22. package/dist/cjs/index.d.ts +9 -1
  23. package/dist/cjs/index.js +9 -2
  24. package/dist/cjs/load-plugins.d.ts +3 -1
  25. package/dist/cjs/load-plugins.js +28 -4
  26. package/dist/cjs/plugins/acl.js +109 -42
  27. package/dist/cjs/plugins/binary.js +2 -1
  28. package/dist/cjs/plugins/condstore.js +7 -0
  29. package/dist/cjs/plugins/context-search.js +2 -2
  30. package/dist/cjs/plugins/create-special-use.d.ts +0 -5
  31. package/dist/cjs/plugins/create-special-use.js +11 -1
  32. package/dist/cjs/plugins/imap4rev2.js +5 -0
  33. package/dist/cjs/plugins/index.js +1 -3
  34. package/dist/cjs/plugins/metadata-server.d.ts +6 -2
  35. package/dist/cjs/plugins/metadata-server.js +3 -1
  36. package/dist/cjs/plugins/metadata.d.ts +5 -1
  37. package/dist/cjs/plugins/metadata.js +143 -57
  38. package/dist/cjs/plugins/objectid.js +10 -0
  39. package/dist/cjs/plugins/quota.js +50 -2
  40. package/dist/cjs/plugins/savedate.js +1 -1
  41. package/dist/cjs/plugins/special-use.d.ts +0 -8
  42. package/dist/cjs/plugins/special-use.js +23 -0
  43. package/dist/cjs/quirks.d.ts +28 -0
  44. package/dist/cjs/quirks.js +125 -0
  45. package/dist/cjs/random.d.ts +10 -0
  46. package/dist/cjs/random.js +22 -0
  47. package/dist/cjs/rest.d.ts +54 -0
  48. package/dist/cjs/rest.js +567 -0
  49. package/dist/cjs/script.d.ts +33 -4
  50. package/dist/cjs/script.js +64 -4
  51. package/dist/cjs/server.d.ts +199 -9
  52. package/dist/cjs/server.js +603 -227
  53. package/dist/cjs/smtp-listener.d.ts +3 -2
  54. package/dist/cjs/smtp-listener.js +10 -6
  55. package/dist/cjs/storage-schema.d.ts +139 -0
  56. package/dist/cjs/storage-schema.js +222 -0
  57. package/dist/cjs/store-operations.d.ts +171 -0
  58. package/dist/cjs/store-operations.js +425 -0
  59. package/dist/cjs/types.d.ts +26 -1
  60. package/dist/esm/commands/append.js +1 -1
  61. package/dist/esm/commands/create.js +2 -2
  62. package/dist/esm/commands/delete.js +2 -14
  63. package/dist/esm/commands/handlers/fetch.js +2 -1
  64. package/dist/esm/commands/handlers/flags.d.ts +3 -3
  65. package/dist/esm/commands/handlers/flags.js +3 -3
  66. package/dist/esm/commands/handlers/store.js +7 -7
  67. package/dist/esm/commands/list.js +17 -3
  68. package/dist/esm/commands/rename.js +6 -46
  69. package/dist/esm/commands/select.js +1 -0
  70. package/dist/esm/commands/subscribe.js +7 -8
  71. package/dist/esm/commands/unsubscribe.js +3 -9
  72. package/dist/esm/control.d.ts +425 -0
  73. package/dist/esm/control.js +630 -0
  74. package/dist/esm/dates.d.ts +9 -1
  75. package/dist/esm/dates.js +20 -1
  76. package/dist/esm/envelope.d.ts +1 -1
  77. package/dist/esm/envelope.js +26 -1
  78. package/dist/esm/index.d.ts +9 -1
  79. package/dist/esm/index.js +5 -2
  80. package/dist/esm/load-plugins.d.ts +3 -1
  81. package/dist/esm/load-plugins.js +28 -4
  82. package/dist/esm/plugins/acl.js +109 -42
  83. package/dist/esm/plugins/binary.js +2 -1
  84. package/dist/esm/plugins/condstore.js +7 -0
  85. package/dist/esm/plugins/context-search.js +2 -2
  86. package/dist/esm/plugins/create-special-use.d.ts +0 -5
  87. package/dist/esm/plugins/create-special-use.js +8 -1
  88. package/dist/esm/plugins/imap4rev2.js +5 -0
  89. package/dist/esm/plugins/index.js +1 -3
  90. package/dist/esm/plugins/metadata-server.d.ts +6 -2
  91. package/dist/esm/plugins/metadata-server.js +3 -1
  92. package/dist/esm/plugins/metadata.d.ts +5 -1
  93. package/dist/esm/plugins/metadata.js +143 -57
  94. package/dist/esm/plugins/objectid.js +10 -0
  95. package/dist/esm/plugins/quota.js +50 -2
  96. package/dist/esm/plugins/savedate.js +1 -1
  97. package/dist/esm/plugins/special-use.d.ts +0 -8
  98. package/dist/esm/plugins/special-use.js +23 -0
  99. package/dist/esm/quirks.d.ts +28 -0
  100. package/dist/esm/quirks.js +121 -0
  101. package/dist/esm/random.d.ts +10 -0
  102. package/dist/esm/random.js +19 -0
  103. package/dist/esm/rest.d.ts +54 -0
  104. package/dist/esm/rest.js +559 -0
  105. package/dist/esm/script.d.ts +33 -4
  106. package/dist/esm/script.js +63 -4
  107. package/dist/esm/server.d.ts +199 -9
  108. package/dist/esm/server.js +606 -230
  109. package/dist/esm/smtp-listener.d.ts +3 -2
  110. package/dist/esm/smtp-listener.js +10 -6
  111. package/dist/esm/storage-schema.d.ts +139 -0
  112. package/dist/esm/storage-schema.js +218 -0
  113. package/dist/esm/store-operations.d.ts +171 -0
  114. package/dist/esm/store-operations.js +410 -0
  115. package/dist/esm/types.d.ts +26 -1
  116. package/dist/plugin-help.json +3 -16
  117. package/package.json +11 -3
  118. package/dist/cjs/plugins/xtoybird.d.ts +0 -2
  119. package/dist/cjs/plugins/xtoybird.js +0 -236
  120. package/dist/esm/plugins/xtoybird.d.ts +0 -2
  121. package/dist/esm/plugins/xtoybird.js +0 -218
@@ -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 EVENTS = ['greeting', 'command', 'input', 'response', 'continuation'];
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
- command: ['command', 'input', 'response', 'continuation'],
14
- tag: ['command', 'input', 'response', 'continuation'],
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: EVENTS,
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)
@@ -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
- processNotifications(data?: CommandContext | null): void;
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} [ignoreSelf] If set to true, does not send EXISTS notice to itself
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} [ignoreSelf] If set to true, does not send EXISTS notice to itself
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
  */