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
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The control API, `server.control`: inspects and changes the server state from a test, without an IMAP session.
|
|
3
|
+
* Every change reaches the connected sessions as if another session had made it (EXISTS, EXPUNGE or VANISHED,
|
|
4
|
+
* unsolicited FETCH, BYE, NOTIFY events, CONDSTORE mod-sequences), with no session as its origin. The API is the
|
|
5
|
+
* operator: ACL does not apply, but every argument is checked. Mailboxes are addressed by their storage name
|
|
6
|
+
* (modified UTF-7), messages by mailbox and UID. Methods return plain data and throw an ImapKitError with a `code`:
|
|
7
|
+
* NONEXISTENT, ALREADYEXISTS, INVALID, or the response code of a failed mailbox operation (CANNOT, HASCHILDREN ...)
|
|
8
|
+
*/
|
|
9
|
+
import validateMailboxName from './mailbox-name.js';
|
|
10
|
+
import { DEFAULT_SESSION_TIMEOUT, ImapKitError, expungeMessages, checkFlags, changeFlags, createMailbox, deleteMailbox, renameMailbox, resetUidValidity, subscribeMailbox, unsubscribeMailbox } from './store-operations.js';
|
|
11
|
+
/**
|
|
12
|
+
* Copies a value of a plugin into the snapshot as JSON data, values that JSON can not hold are left out
|
|
13
|
+
*
|
|
14
|
+
* @param {*} value Any value
|
|
15
|
+
* @return {*} a JSON copy, or undefined
|
|
16
|
+
*/
|
|
17
|
+
function jsonCopy(value) {
|
|
18
|
+
if (value === undefined || typeof value === 'function') {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
// strings, numbers and booleans are immutable, the message source does not need a round trip
|
|
22
|
+
return value === null || typeof value !== 'object' ? value : JSON.parse(JSON.stringify(value));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Copies the properties of an object as JSON data, except the listed keys and values JSON can not hold
|
|
26
|
+
*
|
|
27
|
+
* @param {Object} source Object to copy
|
|
28
|
+
* @param {Set} skip Keys to leave out
|
|
29
|
+
* @return {Object} copy
|
|
30
|
+
*/
|
|
31
|
+
function copyProperties(source, skip) {
|
|
32
|
+
const result = {};
|
|
33
|
+
Object.keys(source).forEach(key => {
|
|
34
|
+
const value = skip.has(key) ? undefined : jsonCopy(source[key]);
|
|
35
|
+
if (value !== undefined) {
|
|
36
|
+
result[key] = value;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
const MESSAGE_SKIP = new Set(['ghost']);
|
|
42
|
+
const NAMESPACE_SKIP = new Set(['folders']);
|
|
43
|
+
// properties the server derives when it loads a mailbox, a snapshot leaves them out
|
|
44
|
+
const DERIVED_MAILBOX_KEYS = new Set(['path', 'namespace', 'messages', 'folders', 'subscribed', 'flags']);
|
|
45
|
+
const DERIVED_FLAGS = new Set(['\\HasChildren', '\\HasNoChildren']);
|
|
46
|
+
class Control {
|
|
47
|
+
constructor(server) {
|
|
48
|
+
this.server = server;
|
|
49
|
+
this.routes = [];
|
|
50
|
+
this.snapshotHandlers = [];
|
|
51
|
+
this.mailboxInfoHandlers = [];
|
|
52
|
+
this.messageInfoHandlers = [];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Adds an operation of a plugin, e.g. `server.control.setAcl()` of ACL, so that a plugin that is not loaded
|
|
56
|
+
* leaves no trace. The operation runs with no session as its origin, like the built-in ones, and throws
|
|
57
|
+
* ImapKitErrors (see storeError() in store-operations.ts)
|
|
58
|
+
*
|
|
59
|
+
* @param {String} name Method name on server.control
|
|
60
|
+
* @param {Function} fn Operation
|
|
61
|
+
* @param {Array} [routes] REST routes of the operation
|
|
62
|
+
*/
|
|
63
|
+
register(name, fn, routes) {
|
|
64
|
+
if (name in this) {
|
|
65
|
+
throw new Error('Control operation ' + name + ' exists already');
|
|
66
|
+
}
|
|
67
|
+
this[name] = (...args) => this.run(() => fn(...args));
|
|
68
|
+
this.routes.push(...(routes || []));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Runs a change with no session as its origin, also when a listener of a command event calls the API
|
|
72
|
+
*
|
|
73
|
+
* @param {Function} fn Change
|
|
74
|
+
* @return {*} the result of fn
|
|
75
|
+
*/
|
|
76
|
+
run(fn) {
|
|
77
|
+
return this.server.withOrigin(null, fn);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Finds a mailbox by its storage name
|
|
81
|
+
*
|
|
82
|
+
* @param {String} path Storage name, INBOX in any case
|
|
83
|
+
* @param {Boolean} [selectable] true if the mailbox must hold messages (not \Noselect)
|
|
84
|
+
* @return {Object} mailbox
|
|
85
|
+
* @throws {ImapKitError} NONEXISTENT
|
|
86
|
+
*/
|
|
87
|
+
requireMailbox(path, selectable) {
|
|
88
|
+
const mailbox = typeof path === 'string' && path ? this.server.getMailbox(path) : undefined;
|
|
89
|
+
if (!mailbox || (selectable && mailbox.flags.indexOf('\\Noselect') >= 0)) {
|
|
90
|
+
throw new ImapKitError('Mailbox ' + JSON.stringify(path) + ' does not exist', 'NONEXISTENT');
|
|
91
|
+
}
|
|
92
|
+
return mailbox;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Checks a new mailbox name
|
|
96
|
+
*
|
|
97
|
+
* @param {String} path Storage name
|
|
98
|
+
* @throws {ImapKitError} INVALID for a name that is not valid modified UTF-7
|
|
99
|
+
*/
|
|
100
|
+
checkName(path) {
|
|
101
|
+
this.checkPath(path);
|
|
102
|
+
const problem = validateMailboxName(path);
|
|
103
|
+
if (problem) {
|
|
104
|
+
throw new ImapKitError(problem, 'INVALID');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Checks that a mailbox argument is a name, the store operations check the rest
|
|
109
|
+
*
|
|
110
|
+
* @param {String} path Storage name
|
|
111
|
+
* @throws {ImapKitError} INVALID
|
|
112
|
+
*/
|
|
113
|
+
checkPath(path) {
|
|
114
|
+
if (typeof path !== 'string' || !path) {
|
|
115
|
+
throw new ImapKitError('Mailbox name must be a non-empty string', 'INVALID');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Finds messages by UID
|
|
120
|
+
*
|
|
121
|
+
* @param {Object} mailbox Mailbox
|
|
122
|
+
* @param {Array} uids UIDs of the messages
|
|
123
|
+
* @return {Array} messages in the order of the UIDs, without duplicates
|
|
124
|
+
* @throws {ImapKitError} INVALID for a list that is not UIDs, NONEXISTENT for a UID that is not in the mailbox
|
|
125
|
+
*/
|
|
126
|
+
requireMessages(mailbox, uids) {
|
|
127
|
+
if (!Array.isArray(uids) || uids.some(uid => !Number.isSafeInteger(uid) || uid < 1)) {
|
|
128
|
+
throw new ImapKitError('UIDs must be an array of positive integers', 'INVALID');
|
|
129
|
+
}
|
|
130
|
+
const byUid = new Map(mailbox.messages.map(message => [message.uid, message]));
|
|
131
|
+
const missing = uids.filter(uid => !byUid.has(uid));
|
|
132
|
+
if (missing.length) {
|
|
133
|
+
throw new ImapKitError('No message with UID ' + missing.join(', ') + ' in ' + mailbox.path, 'NONEXISTENT');
|
|
134
|
+
}
|
|
135
|
+
return [...new Set(uids)].map(uid => byUid.get(uid));
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Finds the sessions a filter selects
|
|
139
|
+
*
|
|
140
|
+
* @param {Number|Object} filter A session number, or `{ session, user }`
|
|
141
|
+
* @return {Array} connections
|
|
142
|
+
*/
|
|
143
|
+
findSessions(filter) {
|
|
144
|
+
const query = typeof filter === 'number' ? { session: filter } : filter || {};
|
|
145
|
+
if (query.session === undefined && query.user === undefined) {
|
|
146
|
+
throw new ImapKitError('Select sessions by number or user', 'INVALID');
|
|
147
|
+
}
|
|
148
|
+
// a session that is closing already (BYE) is left out
|
|
149
|
+
return [...this.server.connections].filter(connection => connection.isOpen() &&
|
|
150
|
+
(query.session === undefined || connection.sessionNumber === query.session) &&
|
|
151
|
+
(query.user === undefined || connection.username === query.user));
|
|
152
|
+
}
|
|
153
|
+
// Inspection
|
|
154
|
+
/**
|
|
155
|
+
* Returns the storage as JSON data in the shape of the `storage` option, so that `imapkit({ storage })` with it
|
|
156
|
+
* starts from the same mailboxes, messages, UIDs, flags and subscriptions. Plugin data on mailboxes and messages
|
|
157
|
+
* is included as far as it is JSON data. Subscriptions of names that are not mailboxes are not part of it
|
|
158
|
+
*
|
|
159
|
+
* @return {Object} storage
|
|
160
|
+
*/
|
|
161
|
+
snapshot() {
|
|
162
|
+
const server = this.server;
|
|
163
|
+
const copyMailbox = (mailbox) => {
|
|
164
|
+
const result = copyProperties(mailbox, DERIVED_MAILBOX_KEYS);
|
|
165
|
+
this.snapshotHandlers.forEach(handler => handler(mailbox, result));
|
|
166
|
+
result.flags = mailbox.flags.filter(flag => !DERIVED_FLAGS.has(flag));
|
|
167
|
+
result.subscribed = !!mailbox.subscribed;
|
|
168
|
+
result.messages = mailbox.messages.map(message => copyProperties(message, MESSAGE_SKIP));
|
|
169
|
+
if (mailbox.folders) {
|
|
170
|
+
result.folders = copyFolders(mailbox.folders);
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
};
|
|
174
|
+
const copyFolders = (folders) => {
|
|
175
|
+
const result = {};
|
|
176
|
+
Object.keys(folders).forEach(name => {
|
|
177
|
+
result[name] = copyMailbox(folders[name]);
|
|
178
|
+
});
|
|
179
|
+
return result;
|
|
180
|
+
};
|
|
181
|
+
const storage = {};
|
|
182
|
+
Object.keys(server.storage).forEach(key => {
|
|
183
|
+
if (key === 'INBOX') {
|
|
184
|
+
storage.INBOX = copyMailbox(server.storage.INBOX);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const namespace = server.storage[key];
|
|
188
|
+
storage[key] = Object.assign(copyProperties(namespace, NAMESPACE_SKIP), { folders: copyFolders(namespace.folders || {}) });
|
|
189
|
+
});
|
|
190
|
+
return storage;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Describes every mailbox, including \Noselect hierarchy levels
|
|
194
|
+
*
|
|
195
|
+
* @return {Array} mailboxes ordered by name
|
|
196
|
+
*/
|
|
197
|
+
listMailboxes() {
|
|
198
|
+
return Object.keys(this.server.folderCache)
|
|
199
|
+
.sort()
|
|
200
|
+
.map(path => this.describeMailbox(this.server.folderCache[path]));
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Describes a mailbox
|
|
204
|
+
*
|
|
205
|
+
* @param {String} path Storage name
|
|
206
|
+
* @return {Object} mailbox info
|
|
207
|
+
*/
|
|
208
|
+
getMailbox(path) {
|
|
209
|
+
return this.describeMailbox(this.requireMailbox(path));
|
|
210
|
+
}
|
|
211
|
+
describeMailbox(mailbox) {
|
|
212
|
+
const status = this.server.getStatus(mailbox);
|
|
213
|
+
const info = {
|
|
214
|
+
path: mailbox.path,
|
|
215
|
+
delimiter: this.server.getSeparator(mailbox),
|
|
216
|
+
flags: mailbox.flags.slice(),
|
|
217
|
+
selectable: mailbox.flags.indexOf('\\Noselect') < 0,
|
|
218
|
+
subscribed: !!mailbox.subscribed,
|
|
219
|
+
messages: mailbox.messages.length,
|
|
220
|
+
unseen: status ? status.unseen : 0,
|
|
221
|
+
uidnext: mailbox.uidnext,
|
|
222
|
+
uidvalidity: mailbox.uidvalidity,
|
|
223
|
+
permanentFlags: status ? status.permanentFlags.slice() : []
|
|
224
|
+
};
|
|
225
|
+
if (this.server.condstore && typeof mailbox.HIGHESTMODSEQ === 'number') {
|
|
226
|
+
info.highestModseq = mailbox.HIGHESTMODSEQ;
|
|
227
|
+
}
|
|
228
|
+
this.mailboxInfoHandlers.forEach(handler => handler(mailbox, info));
|
|
229
|
+
return info;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Describes the messages of a mailbox
|
|
233
|
+
*
|
|
234
|
+
* @param {String} path Storage name
|
|
235
|
+
* @param {Object} [options] `{ uids, raw }`: only these UIDs, include the message source
|
|
236
|
+
* @return {Array} messages ordered by UID
|
|
237
|
+
*/
|
|
238
|
+
listMessages(path, options = {}) {
|
|
239
|
+
const mailbox = this.requireMailbox(path, true);
|
|
240
|
+
const messages = options.uids ? this.requireMessages(mailbox, options.uids).sort((a, b) => a.uid - b.uid) : mailbox.messages;
|
|
241
|
+
return messages.map(message => this.describeMessage(message, !!options.raw));
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Describes a message
|
|
245
|
+
*
|
|
246
|
+
* @param {String} path Storage name
|
|
247
|
+
* @param {Number} uid UID of the message
|
|
248
|
+
* @param {Object} [options] `{ raw }`: include the message source, true by default
|
|
249
|
+
* @return {Object} message info
|
|
250
|
+
*/
|
|
251
|
+
getMessage(path, uid, options = {}) {
|
|
252
|
+
const mailbox = this.requireMailbox(path, true);
|
|
253
|
+
return this.describeMessage(this.requireMessages(mailbox, [uid])[0], options.raw !== false);
|
|
254
|
+
}
|
|
255
|
+
describeMessage(message, raw) {
|
|
256
|
+
const info = {
|
|
257
|
+
uid: message.uid,
|
|
258
|
+
flags: message.flags.slice(),
|
|
259
|
+
internaldate: message.internaldate,
|
|
260
|
+
size: message.raw.length
|
|
261
|
+
};
|
|
262
|
+
if (this.server.condstore && typeof message.MODSEQ === 'number') {
|
|
263
|
+
info.modseq = message.MODSEQ;
|
|
264
|
+
}
|
|
265
|
+
if (raw) {
|
|
266
|
+
info.raw = Buffer.from(message.raw, 'binary');
|
|
267
|
+
}
|
|
268
|
+
this.messageInfoHandlers.forEach(handler => handler(message, info));
|
|
269
|
+
return info;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Describes the connected sessions
|
|
273
|
+
*
|
|
274
|
+
* @return {Array} sessions ordered by number
|
|
275
|
+
*/
|
|
276
|
+
sessions() {
|
|
277
|
+
return [...this.server.connections].map(connection => connection.describe()).sort((a, b) => a.session - b.session);
|
|
278
|
+
}
|
|
279
|
+
// Messages
|
|
280
|
+
/**
|
|
281
|
+
* Adds a message to a mailbox, like a delivery from outside: sessions that have the mailbox selected get EXISTS,
|
|
282
|
+
* the first read-write one sees it as \Recent
|
|
283
|
+
*
|
|
284
|
+
* @param {String} path Storage name
|
|
285
|
+
* @param {Object} message `{ raw, flags, internaldate }`
|
|
286
|
+
* @param {Object} [options] `{ checks }`: true runs the checks APPEND runs (QUOTA, APPENDLIMIT ...), a failed
|
|
287
|
+
* one throws an ImapKitError with its response code, e.g. OVERQUOTA
|
|
288
|
+
* @return {Object} `{ uid, uidvalidity }`
|
|
289
|
+
*/
|
|
290
|
+
addMessage(path, message, options = {}) {
|
|
291
|
+
const mailbox = this.requireMailbox(path, true);
|
|
292
|
+
const { raw, flags, internaldate } = message || {};
|
|
293
|
+
let source;
|
|
294
|
+
if (typeof raw === 'string') {
|
|
295
|
+
source = Buffer.from(raw, 'utf-8').toString('binary');
|
|
296
|
+
}
|
|
297
|
+
else if (raw instanceof Uint8Array) {
|
|
298
|
+
source = Buffer.from(raw).toString('binary');
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
throw new ImapKitError('Message source (raw) must be a string or a Buffer', 'INVALID');
|
|
302
|
+
}
|
|
303
|
+
if (!source.length) {
|
|
304
|
+
throw new ImapKitError('Message source (raw) can not be empty', 'INVALID');
|
|
305
|
+
}
|
|
306
|
+
const list = checkFlags(this.server, mailbox, flags || [], true);
|
|
307
|
+
if (internaldate !== undefined &&
|
|
308
|
+
!(internaldate instanceof Date && !isNaN(internaldate.getTime())) &&
|
|
309
|
+
!this.server.validateInternalDate(internaldate)) {
|
|
310
|
+
throw new ImapKitError('Internal date must be a Date or a date-time string like "17-Jul-1996 02:44:25 -0700"', 'INVALID');
|
|
311
|
+
}
|
|
312
|
+
if (options.checks) {
|
|
313
|
+
const failure = this.server.appendChecks
|
|
314
|
+
.map(check => check(null, mailbox, [{ raw: source, flags: list, internaldate }], { command: 'APPEND' }))
|
|
315
|
+
.find(result => result && !result.soft);
|
|
316
|
+
if (failure) {
|
|
317
|
+
const code = Array.isArray(failure.code) ? failure.code[0] : failure.code;
|
|
318
|
+
throw new ImapKitError(failure.text, typeof code === 'string' ? code : 'CANNOT');
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const added = this.run(() => this.server.appendMessage(mailbox, list, internaldate || null, source));
|
|
322
|
+
return { uid: added.message.uid, uidvalidity: mailbox.uidvalidity };
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Changes the flags of messages. Sessions that have the mailbox selected get unsolicited FETCH responses, with
|
|
326
|
+
* CONDSTORE the messages get new mod-sequences
|
|
327
|
+
*
|
|
328
|
+
* @param {String} path Storage name
|
|
329
|
+
* @param {Array} uids UIDs of the messages
|
|
330
|
+
* @param {Array} flags Flags
|
|
331
|
+
* @param {String} [mode] "set" (default), "add" or "remove"
|
|
332
|
+
* @return {Array} `{ uid, flags }` of every message
|
|
333
|
+
*/
|
|
334
|
+
setFlags(path, uids, flags, mode = 'set') {
|
|
335
|
+
const mailbox = this.requireMailbox(path, true);
|
|
336
|
+
const messages = this.requireMessages(mailbox, uids);
|
|
337
|
+
this.run(() => changeFlags(this.server, mailbox, messages, flags, mode));
|
|
338
|
+
return messages.map(message => ({ uid: message.uid, flags: message.flags.slice() }));
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Removes messages. Sessions that have the mailbox selected get EXPUNGE (VANISHED with QRESYNC), following the
|
|
342
|
+
* RFC 2180 rules a session's EXPUNGE would
|
|
343
|
+
*
|
|
344
|
+
* @param {String} path Storage name
|
|
345
|
+
* @param {Array} uids UIDs of the messages
|
|
346
|
+
* @return {Array} the removed UIDs
|
|
347
|
+
*/
|
|
348
|
+
expungeMessages(path, uids) {
|
|
349
|
+
const mailbox = this.requireMailbox(path, true);
|
|
350
|
+
const messages = this.requireMessages(mailbox, uids);
|
|
351
|
+
return this.run(() => expungeMessages(this.server, mailbox, messages)).map(message => message.uid);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Copies messages to another mailbox, the copies keep flags and internal date and get new UIDs
|
|
355
|
+
*
|
|
356
|
+
* @param {String} path Storage name of the source
|
|
357
|
+
* @param {Array} uids UIDs of the messages
|
|
358
|
+
* @param {String} target Storage name of the target
|
|
359
|
+
* @return {Object} `{ uidvalidity, uids }`: UIDVALIDITY of the target, `{ uid, targetUid }` for every message
|
|
360
|
+
*/
|
|
361
|
+
copyMessages(path, uids, target) {
|
|
362
|
+
return this.transfer(path, uids, target, false);
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Moves messages to another mailbox: copies them and expunges them from the source
|
|
366
|
+
*
|
|
367
|
+
* @param {String} path Storage name of the source
|
|
368
|
+
* @param {Array} uids UIDs of the messages
|
|
369
|
+
* @param {String} target Storage name of the target
|
|
370
|
+
* @return {Object} `{ uidvalidity, uids }`: UIDVALIDITY of the target, `{ uid, targetUid }` for every message
|
|
371
|
+
*/
|
|
372
|
+
moveMessages(path, uids, target) {
|
|
373
|
+
return this.transfer(path, uids, target, true);
|
|
374
|
+
}
|
|
375
|
+
transfer(path, uids, target, move) {
|
|
376
|
+
const mailbox = this.requireMailbox(path, true);
|
|
377
|
+
const targetMailbox = this.requireMailbox(target, true);
|
|
378
|
+
// in UID order, like COPY and MOVE
|
|
379
|
+
const messages = this.requireMessages(mailbox, uids).sort((a, b) => a.uid - b.uid);
|
|
380
|
+
return this.run(() => {
|
|
381
|
+
const result = messages.map(message => ({ uid: message.uid, targetUid: this.server.copyMessage(targetMailbox, message).message.uid }));
|
|
382
|
+
if (move) {
|
|
383
|
+
expungeMessages(this.server, mailbox, messages);
|
|
384
|
+
}
|
|
385
|
+
return { uidvalidity: targetMailbox.uidvalidity, uids: result };
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Replaces a message. The content of a UID never changes (RFC 9051 section 2.3.1.1), so this adds the new
|
|
390
|
+
* message and expunges the old one, the new message gets a new UID. Flags and internal date of the old
|
|
391
|
+
* message are kept unless given
|
|
392
|
+
*
|
|
393
|
+
* @param {String} path Storage name
|
|
394
|
+
* @param {Number} uid UID of the message to replace
|
|
395
|
+
* @param {Object} message `{ raw, flags, internaldate }`
|
|
396
|
+
* @return {Object} `{ uid, uidvalidity }` of the new message
|
|
397
|
+
*/
|
|
398
|
+
replaceMessage(path, uid, message) {
|
|
399
|
+
const mailbox = this.requireMailbox(path, true);
|
|
400
|
+
const old = this.requireMessages(mailbox, [uid])[0];
|
|
401
|
+
const added = this.addMessage(path, {
|
|
402
|
+
raw: message && message.raw,
|
|
403
|
+
flags: message && message.flags ? message.flags : old.flags.slice(),
|
|
404
|
+
internaldate: message && message.internaldate ? message.internaldate : old.internaldate
|
|
405
|
+
});
|
|
406
|
+
this.run(() => expungeMessages(this.server, mailbox, [old]));
|
|
407
|
+
return added;
|
|
408
|
+
}
|
|
409
|
+
// Mailboxes
|
|
410
|
+
/**
|
|
411
|
+
* Creates a mailbox, with any missing superior levels
|
|
412
|
+
*
|
|
413
|
+
* @param {String} path Storage name
|
|
414
|
+
* @param {Object} [options] `{ subscribed }`
|
|
415
|
+
* @return {Object} mailbox info
|
|
416
|
+
*/
|
|
417
|
+
createMailbox(path, options = {}) {
|
|
418
|
+
this.checkName(path);
|
|
419
|
+
const mailbox = this.run(() => {
|
|
420
|
+
const created = createMailbox(this.server, path);
|
|
421
|
+
if (options.subscribed) {
|
|
422
|
+
subscribeMailbox(this.server, created.path);
|
|
423
|
+
}
|
|
424
|
+
return created;
|
|
425
|
+
});
|
|
426
|
+
return this.describeMailbox(mailbox);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Deletes a mailbox. Sessions that have it selected are disconnected with BYE, like after DELETE in another session
|
|
430
|
+
*
|
|
431
|
+
* @param {String} path Storage name
|
|
432
|
+
*/
|
|
433
|
+
deleteMailbox(path) {
|
|
434
|
+
this.checkPath(path);
|
|
435
|
+
this.run(() => deleteMailbox(this.server, path));
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Renames a mailbox, with the RENAME rules (renaming INBOX moves its messages)
|
|
439
|
+
*
|
|
440
|
+
* @param {String} path Storage name
|
|
441
|
+
* @param {String} newPath New storage name
|
|
442
|
+
* @return {Object} info of the renamed mailbox
|
|
443
|
+
*/
|
|
444
|
+
renameMailbox(path, newPath) {
|
|
445
|
+
this.checkPath(path);
|
|
446
|
+
this.checkName(newPath);
|
|
447
|
+
const result = this.run(() => renameMailbox(this.server, path, newPath));
|
|
448
|
+
return this.getMailbox(result.path);
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Gives a mailbox a new UIDVALIDITY, optionally with new UIDs ("keep", "renumber", "shuffle" or "offset"). Sessions
|
|
452
|
+
* that have the mailbox selected are disconnected with BYE, as a UID must not change during a session
|
|
453
|
+
*
|
|
454
|
+
* @param {String} path Storage name
|
|
455
|
+
* @param {Object} [options] `{ uidvalidity, uids, offset, seed }`
|
|
456
|
+
* @return {Object} `{ uidvalidity, uidnext, uids }`, `uids` lists `{ uid, newUid }` for every message
|
|
457
|
+
*/
|
|
458
|
+
resetUidValidity(path, options = {}) {
|
|
459
|
+
const mailbox = this.requireMailbox(path, true);
|
|
460
|
+
return this.run(() => resetUidValidity(this.server, mailbox, options || {}));
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Subscribes a mailbox
|
|
464
|
+
*
|
|
465
|
+
* @param {String} path Storage name
|
|
466
|
+
* @return {Boolean} true if the subscription changed
|
|
467
|
+
*/
|
|
468
|
+
subscribe(path) {
|
|
469
|
+
this.checkPath(path);
|
|
470
|
+
return this.run(() => subscribeMailbox(this.server, path));
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Unsubscribes a name, it does not have to be a mailbox
|
|
474
|
+
*
|
|
475
|
+
* @param {String} path Storage name
|
|
476
|
+
* @return {Boolean} true if the subscription changed
|
|
477
|
+
*/
|
|
478
|
+
unsubscribe(path) {
|
|
479
|
+
this.checkPath(path);
|
|
480
|
+
return this.run(() => unsubscribeMailbox(this.server, path));
|
|
481
|
+
}
|
|
482
|
+
// Users
|
|
483
|
+
/**
|
|
484
|
+
* Lists the users, without credentials
|
|
485
|
+
*
|
|
486
|
+
* @return {Array} `{ name, xoauth2 }`, xoauth2 is true for a user with an access token
|
|
487
|
+
*/
|
|
488
|
+
listUsers() {
|
|
489
|
+
return Object.keys(this.server.users)
|
|
490
|
+
.sort()
|
|
491
|
+
.map(name => ({ name, xoauth2: !!(this.server.users[name].xoauth2 && this.server.users[name].xoauth2.accessToken) }));
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Adds a user
|
|
495
|
+
*
|
|
496
|
+
* @param {String} name User name
|
|
497
|
+
* @param {Object} options `{ password, xoauth2: { accessToken, sessionTimeout } }`
|
|
498
|
+
*/
|
|
499
|
+
addUser(name, options) {
|
|
500
|
+
if (typeof name !== 'string' || !name) {
|
|
501
|
+
throw new ImapKitError('User name must be a non-empty string', 'INVALID');
|
|
502
|
+
}
|
|
503
|
+
if (Object.hasOwn(this.server.users, name)) {
|
|
504
|
+
throw new ImapKitError('User ' + JSON.stringify(name) + ' already exists', 'ALREADYEXISTS');
|
|
505
|
+
}
|
|
506
|
+
this.server.users[name] = this.userData({}, options);
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Changes the password or the XOAUTH2 token of a user
|
|
510
|
+
*
|
|
511
|
+
* @param {String} name User name
|
|
512
|
+
* @param {Object} options `{ password, xoauth2 }`, null xoauth2 removes the token
|
|
513
|
+
*/
|
|
514
|
+
updateUser(name, options) {
|
|
515
|
+
this.server.users[name] = this.userData(this.requireUser(name), options);
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Deletes a user, its sessions are disconnected with BYE unless `disconnect` is false
|
|
519
|
+
*
|
|
520
|
+
* @param {String} name User name
|
|
521
|
+
* @param {Object} [options] `{ disconnect }`
|
|
522
|
+
*/
|
|
523
|
+
deleteUser(name, options = {}) {
|
|
524
|
+
this.requireUser(name);
|
|
525
|
+
delete this.server.users[name];
|
|
526
|
+
if (options.disconnect !== false) {
|
|
527
|
+
this.findSessions({ user: name }).forEach(connection => connection.bye('User was deleted', 'USER DELETED'));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
requireUser(name) {
|
|
531
|
+
if (typeof name !== 'string' || !Object.hasOwn(this.server.users, name)) {
|
|
532
|
+
throw new ImapKitError('User ' + JSON.stringify(name) + ' does not exist', 'NONEXISTENT');
|
|
533
|
+
}
|
|
534
|
+
return this.server.users[name];
|
|
535
|
+
}
|
|
536
|
+
userData(user, options) {
|
|
537
|
+
const { password, xoauth2 } = options || {};
|
|
538
|
+
const result = Object.assign({}, user);
|
|
539
|
+
if (password !== undefined) {
|
|
540
|
+
if (typeof password !== 'string') {
|
|
541
|
+
throw new ImapKitError('Password must be a string', 'INVALID');
|
|
542
|
+
}
|
|
543
|
+
result.password = password;
|
|
544
|
+
}
|
|
545
|
+
if (xoauth2 === null) {
|
|
546
|
+
delete result.xoauth2;
|
|
547
|
+
}
|
|
548
|
+
else if (xoauth2 !== undefined) {
|
|
549
|
+
if (typeof xoauth2 !== 'object' || typeof xoauth2.accessToken !== 'string') {
|
|
550
|
+
throw new ImapKitError('xoauth2 must be { accessToken, sessionTimeout }', 'INVALID');
|
|
551
|
+
}
|
|
552
|
+
result.xoauth2 = { accessToken: xoauth2.accessToken, sessionTimeout: Number(xoauth2.sessionTimeout) || DEFAULT_SESSION_TIMEOUT };
|
|
553
|
+
}
|
|
554
|
+
return result;
|
|
555
|
+
}
|
|
556
|
+
// Server
|
|
557
|
+
/**
|
|
558
|
+
* Disconnects sessions
|
|
559
|
+
*
|
|
560
|
+
* @param {Number|Object} filter A session number, or `{ session, user }`
|
|
561
|
+
* @param {Object} [options] `{ text, reset }`: the text of the untagged BYE, or reset the TCP connection without BYE
|
|
562
|
+
* @return {Number} how many sessions were disconnected
|
|
563
|
+
*/
|
|
564
|
+
disconnect(filter, options = {}) {
|
|
565
|
+
const sessions = this.findSessions(filter);
|
|
566
|
+
sessions.forEach(connection => {
|
|
567
|
+
if (options.reset) {
|
|
568
|
+
connection.closeNow('reset');
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
connection.bye(options.text || 'Disconnected by the server', 'DISCONNECT');
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
return sessions.length;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Writes bytes to a session as they are, e.g. an untagged response between commands (ALERT, BYE, EXISTS).
|
|
578
|
+
* Nothing checks that they are valid IMAP
|
|
579
|
+
*
|
|
580
|
+
* @param {Number} session Session number
|
|
581
|
+
* @param {String|Buffer} data Bytes to send, a string is encoded as UTF-8
|
|
582
|
+
*/
|
|
583
|
+
inject(session, data) {
|
|
584
|
+
const connection = this.findSessions({ session })[0];
|
|
585
|
+
if (!connection) {
|
|
586
|
+
throw new ImapKitError('Session ' + session + ' does not exist', 'NONEXISTENT');
|
|
587
|
+
}
|
|
588
|
+
if (typeof data !== 'string' && !(data instanceof Uint8Array)) {
|
|
589
|
+
throw new ImapKitError('Data must be a string or a Buffer', 'INVALID');
|
|
590
|
+
}
|
|
591
|
+
connection.write(Buffer.from(data));
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Restores the mailboxes and users of the server options and disconnects every session with BYE, so that a
|
|
595
|
+
* long running server (the imapkit command) can be reused between tests. Plugins restore their own state on
|
|
596
|
+
* the `reset` event (QUOTA limits, server annotations). Script rules stay, `server.script.clear()` removes them
|
|
597
|
+
*/
|
|
598
|
+
reset() {
|
|
599
|
+
this.server.connections.forEach(connection => {
|
|
600
|
+
if (connection.isOpen()) {
|
|
601
|
+
connection.bye('Server reset', 'RESET');
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
this.run(() => {
|
|
605
|
+
this.server.loadUsers();
|
|
606
|
+
this.server.loadStorage();
|
|
607
|
+
this.server.emit('reset');
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Stops the server. A graceful shutdown stops accepting connections and waits until the clients disconnect,
|
|
612
|
+
* otherwise the sessions are closed right away
|
|
613
|
+
*
|
|
614
|
+
* @param {Object} [options] `{ graceful }`, true by default
|
|
615
|
+
* @return {Promise} resolves when the server is closed
|
|
616
|
+
*/
|
|
617
|
+
shutdown(options = {}) {
|
|
618
|
+
return new Promise((resolve, reject) => {
|
|
619
|
+
const done = (err) => (err && err.code !== 'ERR_SERVER_NOT_RUNNING' ? reject(err) : resolve());
|
|
620
|
+
if (options.graceful === false) {
|
|
621
|
+
this.server.close(done);
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
this.server.closeListeners(false);
|
|
625
|
+
this.server.server.close(done);
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
export { Control, ImapKitError };
|
package/dist/esm/dates.d.ts
CHANGED
|
@@ -36,6 +36,14 @@ declare function isRealDate(day: number | string | undefined, month: number, yea
|
|
|
36
36
|
* @return {String|Boolean} the date, or false for an impossible date
|
|
37
37
|
*/
|
|
38
38
|
declare function dateKey(day: number | string, month: number, year: number | string): string | false;
|
|
39
|
+
/**
|
|
40
|
+
* Checks a date-time string of RFC 3501 section 9, like an INTERNALDATE ("14-Sep-2013 21:22:28 -0300"):
|
|
41
|
+
* date-time = DQUOTE date-day-fixed "-" date-month "-" date-year SP time SP zone DQUOTE
|
|
42
|
+
*
|
|
43
|
+
* @param {String} value Value to check
|
|
44
|
+
* @return {Boolean} true if the value is a date-time string of a real date and time
|
|
45
|
+
*/
|
|
46
|
+
declare function isDateTime(value: unknown): boolean;
|
|
39
47
|
/**
|
|
40
48
|
* Parses a date-time value of the RFC 3501 section 9 form, like an INTERNALDATE ("14-Sep-2013 21:22:28 -0300").
|
|
41
49
|
* A value with only the date part is accepted as well, its time fields are left undefined
|
|
@@ -61,4 +69,4 @@ declare function parseHeaderDate(header: string | undefined): DateParts | null;
|
|
|
61
69
|
* @return {Number} timestamp
|
|
62
70
|
*/
|
|
63
71
|
declare function toTimestamp(date: DateParts): number;
|
|
64
|
-
export { MONTHS, monthIndex, isRealDate, dateKey, parseDateTime, parseHeaderDate, toTimestamp };
|
|
72
|
+
export { MONTHS, monthIndex, isRealDate, isDateTime, dateKey, parseDateTime, parseHeaderDate, toTimestamp };
|
package/dist/esm/dates.js
CHANGED
|
@@ -40,6 +40,25 @@ function dateKey(day, month, year) {
|
|
|
40
40
|
}
|
|
41
41
|
return String(year).padStart(4, '0') + '-' + String(month + 1).padStart(2, '0') + '-' + String(day).padStart(2, '0');
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Checks a date-time string of RFC 3501 section 9, like an INTERNALDATE ("14-Sep-2013 21:22:28 -0300"):
|
|
45
|
+
* date-time = DQUOTE date-day-fixed "-" date-month "-" date-year SP time SP zone DQUOTE
|
|
46
|
+
*
|
|
47
|
+
* @param {String} value Value to check
|
|
48
|
+
* @return {Boolean} true if the value is a date-time string of a real date and time
|
|
49
|
+
*/
|
|
50
|
+
function isDateTime(value) {
|
|
51
|
+
if (!value || typeof value !== 'string') {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
// month names are case-insensitive like all ABNF strings
|
|
55
|
+
const match = value.match(/^( \d|\d\d)-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\d{4}) (\d{2}):(\d{2}):(\d{2}) [-+](\d{2})(\d{2})$/i);
|
|
56
|
+
if (!match) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
// the values must also make a real date and time
|
|
60
|
+
return (isRealDate(match[1], monthIndex(match[2]), match[3]) && Number(match[4]) < 24 && Number(match[5]) < 60 && Number(match[6]) < 61 && Number(match[8]) < 60);
|
|
61
|
+
}
|
|
43
62
|
/**
|
|
44
63
|
* Parses a date-time value of the RFC 3501 section 9 form, like an INTERNALDATE ("14-Sep-2013 21:22:28 -0300").
|
|
45
64
|
* A value with only the date part is accepted as well, its time fields are left undefined
|
|
@@ -112,4 +131,4 @@ function toTimestamp(date) {
|
|
|
112
131
|
}
|
|
113
132
|
return Date.UTC(date.year, date.month, date.day, date.hours || 0, date.minutes || 0, date.seconds || 0) - offset * 60 * 1000;
|
|
114
133
|
}
|
|
115
|
-
export { MONTHS, monthIndex, isRealDate, dateKey, parseDateTime, parseHeaderDate, toTimestamp };
|
|
134
|
+
export { MONTHS, monthIndex, isRealDate, isDateTime, dateKey, parseDateTime, parseHeaderDate, toTimestamp };
|
package/dist/esm/envelope.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ParsedAddress } from './addressparser.js';
|
|
2
2
|
import type { ParsedHeader } from './mimeparser.js';
|
|
3
3
|
/** An address of the ENVELOPE (RFC 3501 section 9): name, source route, mailbox and host, all NIL for a group end */
|
|
4
|
-
export type EnvelopeAddress = [name: string | null, adl: null, mailbox: string | null, host: string | null];
|
|
4
|
+
export type EnvelopeAddress = [name: string | null, adl: string | null, mailbox: string | null, host: string | null];
|
|
5
5
|
/** The fields of the ENVELOPE (RFC 3501 section 7.4.2), in order */
|
|
6
6
|
export type Envelope = [
|
|
7
7
|
date: string | null,
|