imapkit 4.3.0 → 5.0.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 +167 -51
- package/bin/help.txt +17 -3
- package/bin/imapkit.js +53 -23
- 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/flags.d.ts +3 -3
- package/dist/cjs/commands/handlers/flags.js +3 -3
- package/dist/cjs/commands/handlers/store.js +3 -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 +430 -0
- package/dist/cjs/control.js +637 -0
- package/dist/cjs/index.d.ts +8 -1
- package/dist/cjs/index.js +9 -2
- package/dist/cjs/load-plugins.d.ts +1 -1
- package/dist/cjs/load-plugins.js +7 -2
- package/dist/cjs/plugins/acl.js +109 -42
- package/dist/cjs/plugins/condstore.js +7 -0
- package/dist/cjs/plugins/index.js +1 -3
- package/dist/cjs/plugins/metadata.js +140 -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 +124 -0
- package/dist/cjs/random.d.ts +10 -0
- package/dist/cjs/random.js +22 -0
- package/dist/cjs/rest.d.ts +50 -0
- package/dist/cjs/rest.js +561 -0
- package/dist/cjs/script.d.ts +33 -4
- package/dist/cjs/script.js +64 -4
- package/dist/cjs/server.d.ts +149 -8
- package/dist/cjs/server.js +429 -152
- 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 +215 -0
- package/dist/cjs/store-operations.d.ts +168 -0
- package/dist/cjs/store-operations.js +422 -0
- package/dist/cjs/types.d.ts +22 -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/flags.d.ts +3 -3
- package/dist/esm/commands/handlers/flags.js +3 -3
- package/dist/esm/commands/handlers/store.js +3 -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 +430 -0
- package/dist/esm/control.js +630 -0
- package/dist/esm/index.d.ts +8 -1
- package/dist/esm/index.js +5 -2
- package/dist/esm/load-plugins.d.ts +1 -1
- package/dist/esm/load-plugins.js +7 -2
- package/dist/esm/plugins/acl.js +109 -42
- package/dist/esm/plugins/condstore.js +7 -0
- package/dist/esm/plugins/index.js +1 -3
- package/dist/esm/plugins/metadata.js +140 -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 +120 -0
- package/dist/esm/random.d.ts +10 -0
- package/dist/esm/random.js +19 -0
- package/dist/esm/rest.d.ts +50 -0
- package/dist/esm/rest.js +553 -0
- package/dist/esm/script.d.ts +33 -4
- package/dist/esm/script.js +63 -4
- package/dist/esm/server.d.ts +149 -8
- package/dist/esm/server.js +429 -152
- 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 +211 -0
- package/dist/esm/store-operations.d.ts +168 -0
- package/dist/esm/store-operations.js +407 -0
- package/dist/esm/types.d.ts +22 -1
- package/dist/plugin-help.json +0 -14
- 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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = specialUsePlugin;
|
|
4
4
|
const list_extensions_js_1 = require("../list-extensions.js");
|
|
5
|
+
const store_operations_js_1 = require("../store-operations.js");
|
|
5
6
|
/**
|
|
6
7
|
* @help Enables SPECIAL-USE [RFC6154] capability
|
|
7
8
|
* @help Mailboxes need to have a "special-use"
|
|
@@ -10,6 +11,8 @@ const list_extensions_js_1 = require("../list-extensions.js");
|
|
|
10
11
|
* @help With LIST-EXTENDED the SPECIAL-USE selection and
|
|
11
12
|
* @help return options work with the other LIST options
|
|
12
13
|
*/
|
|
14
|
+
// RFC 6154 section 2 and RFC 8457 (\\Important)
|
|
15
|
+
const SPECIAL_USES = new Set(['\\All', '\\Archive', '\\Drafts', '\\Flagged', '\\Junk', '\\Sent', '\\Trash', '\\Important']);
|
|
13
16
|
function specialUsePlugin(server) {
|
|
14
17
|
// Register capability
|
|
15
18
|
server.registerCapability('SPECIAL-USE');
|
|
@@ -80,6 +83,26 @@ function specialUsePlugin(server) {
|
|
|
80
83
|
}
|
|
81
84
|
}
|
|
82
85
|
});
|
|
86
|
+
// Control API (README "Control API"): the special-use attributes of a mailbox (RFC 6154 section 2)
|
|
87
|
+
server.control.mailboxInfoHandlers.push((mailbox, info) => {
|
|
88
|
+
info.specialUse = getSpecialUse(mailbox);
|
|
89
|
+
});
|
|
90
|
+
const setSpecialUseOperation = (path, uses) => {
|
|
91
|
+
const mailbox = server.control.requireMailbox(path, true);
|
|
92
|
+
if (!Array.isArray(uses) || uses.some(use => typeof use !== 'string' || !SPECIAL_USES.has(use))) {
|
|
93
|
+
throw (0, store_operations_js_1.storeError)('Special-use attributes must be a list of ' + [...SPECIAL_USES].join(', '), 'INVALID');
|
|
94
|
+
}
|
|
95
|
+
mailbox['special-use'] = [...new Set(uses)];
|
|
96
|
+
return server.control.getMailbox(mailbox.path);
|
|
97
|
+
};
|
|
98
|
+
server.control.register('setSpecialUse', setSpecialUseOperation, [
|
|
99
|
+
{
|
|
100
|
+
method: 'PUT',
|
|
101
|
+
path: '/v1/mailboxes/{path}/special-use',
|
|
102
|
+
summary: 'Sets the special-use attributes of a mailbox ({ specialUse: ["\\Sent"] }, SPECIAL-USE plugin)',
|
|
103
|
+
handler: ({ params, body }) => server.control.setSpecialUse(params.path, body.specialUse)
|
|
104
|
+
}
|
|
105
|
+
]);
|
|
83
106
|
}
|
|
84
107
|
{
|
|
85
108
|
const defaultExport = exports.default;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quirk presets (#87): named sets of script rules (and plugins to leave out) that make the server behave like a known
|
|
3
|
+
* real server, so a client test reproduces the bug of that server in every run, without the server itself. The
|
|
4
|
+
* `quirks` option turns them on, the rules come after the rules of the `script` option. The probabilistic ones use
|
|
5
|
+
* the random numbers of the `scriptSeed` option, so a run can be repeated. The presets are exported as data, copy
|
|
6
|
+
* and adjust one as script rules when it does not fit
|
|
7
|
+
*/
|
|
8
|
+
import type { ScriptRule } from './script.js';
|
|
9
|
+
/** A quirk preset */
|
|
10
|
+
export interface Quirk {
|
|
11
|
+
description: string;
|
|
12
|
+
/** script rules, see README "Scripted faults" */
|
|
13
|
+
rules?: ScriptRule[] | undefined;
|
|
14
|
+
/** plugins that are not loaded even when the `plugins` option lists them */
|
|
15
|
+
removePlugins?: string[] | undefined;
|
|
16
|
+
}
|
|
17
|
+
export declare const quirks: Record<string, Quirk>;
|
|
18
|
+
/**
|
|
19
|
+
* Resolves the `quirks` option
|
|
20
|
+
*
|
|
21
|
+
* @param {Array|String} names Quirk names
|
|
22
|
+
* @return {Object} `{ rules, removePlugins }` of all of them
|
|
23
|
+
* @throws {Error} for an unknown name
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveQuirks(names: string[] | string | null | undefined): {
|
|
26
|
+
rules: ScriptRule[];
|
|
27
|
+
removePlugins: Set<string>;
|
|
28
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Quirk presets (#87): named sets of script rules (and plugins to leave out) that make the server behave like a known
|
|
4
|
+
* real server, so a client test reproduces the bug of that server in every run, without the server itself. The
|
|
5
|
+
* `quirks` option turns them on, the rules come after the rules of the `script` option. The probabilistic ones use
|
|
6
|
+
* the random numbers of the `scriptSeed` option, so a run can be repeated. The presets are exported as data, copy
|
|
7
|
+
* and adjust one as script rules when it does not fit
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.quirks = void 0;
|
|
11
|
+
exports.resolveQuirks = resolveQuirks;
|
|
12
|
+
const FETCH_COMMANDS = ['FETCH', 'UID FETCH'];
|
|
13
|
+
/**
|
|
14
|
+
* The part a body section belongs to: "2.MIME", "2.HEADER" and "2" all belong to part 2, "HEADER" and "TEXT" to the
|
|
15
|
+
* message itself
|
|
16
|
+
*
|
|
17
|
+
* @param {Array} section Section of a BODY item, its first entry is the section spec
|
|
18
|
+
* @return {String} part path, "" for the message
|
|
19
|
+
*/
|
|
20
|
+
function partPath(section) {
|
|
21
|
+
const spec = String((section[0] && section[0].value) || '').toUpperCase();
|
|
22
|
+
return spec.replace(/(^|\.)(MIME|HEADER(\.FIELDS(\.NOT)?)?|TEXT)$/, '');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Changes the BODY[...] values of an untagged FETCH response
|
|
26
|
+
*
|
|
27
|
+
* @param {Object} response Untagged FETCH response
|
|
28
|
+
* @param {Function} change `(name, value, index)` returns the new value
|
|
29
|
+
* @return {Object|undefined} the changed response, nothing for other responses
|
|
30
|
+
*/
|
|
31
|
+
function changeBodyValues(response, change) {
|
|
32
|
+
const items = response.attributes && response.attributes[2];
|
|
33
|
+
if (!Array.isArray(items) || !response.attributes[1] || response.attributes[1].value !== 'FETCH') {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
const changed = items.slice();
|
|
37
|
+
for (let i = 0; i < changed.length - 1; i += 2) {
|
|
38
|
+
const name = changed[i];
|
|
39
|
+
if (name && name.type === 'ATOM' && String(name.value).toUpperCase() === 'BODY' && Array.isArray(name.section)) {
|
|
40
|
+
changed[i + 1] = change(name, changed[i + 1]);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return Object.assign({}, response, { attributes: [response.attributes[0], response.attributes[1], changed] });
|
|
44
|
+
}
|
|
45
|
+
exports.quirks = {
|
|
46
|
+
'james-fetchgroup': {
|
|
47
|
+
description: 'Apache James FetchGroup: only the first section asked for a part in one FETCH is answered, later ones for the same ' +
|
|
48
|
+
'part come back empty. BODY[2.MIME] BODY[2] gives a zero-length body, the reverse order loses the headers',
|
|
49
|
+
rules: [
|
|
50
|
+
{
|
|
51
|
+
on: 'response',
|
|
52
|
+
command: FETCH_COMMANDS,
|
|
53
|
+
untagged: true,
|
|
54
|
+
mutate: response => {
|
|
55
|
+
const seen = new Set();
|
|
56
|
+
return changeBodyValues(response, (name, value) => {
|
|
57
|
+
const path = partPath(name.section);
|
|
58
|
+
if (seen.has(path)) {
|
|
59
|
+
return { type: 'LITERAL', value: '' };
|
|
60
|
+
}
|
|
61
|
+
seen.add(path);
|
|
62
|
+
return value;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
'james-late-fetch': {
|
|
69
|
+
description: 'Apache James: now and then (1 in 4) a FETCH response comes after the tagged OK of its command',
|
|
70
|
+
rules: [{ on: 'response', command: FETCH_COMMANDS, untagged: true, chance: 0.25, defer: 'tagged' }]
|
|
71
|
+
},
|
|
72
|
+
'yahoo-quoted-sections': {
|
|
73
|
+
description: 'Yahoo: short body sections (up to 100 octets without line breaks) are quoted strings instead of literals',
|
|
74
|
+
rules: [
|
|
75
|
+
{
|
|
76
|
+
on: 'response',
|
|
77
|
+
command: FETCH_COMMANDS,
|
|
78
|
+
untagged: true,
|
|
79
|
+
mutate: response => changeBodyValues(response, (name, value) => value && value.type === 'LITERAL' && typeof value.value === 'string' && value.value.length <= 100 && /^[\x20-\x7e]*$/.test(value.value)
|
|
80
|
+
? { type: 'STRING', value: value.value }
|
|
81
|
+
: value)
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
'm365-throttle': {
|
|
86
|
+
description: 'Microsoft 365: 1 in 10 commands (not LOGOUT) is refused with "BAD Request is throttled. Suggested Backoff Time: 1000 milliseconds"',
|
|
87
|
+
rules: [
|
|
88
|
+
{
|
|
89
|
+
on: 'command',
|
|
90
|
+
chance: 0.1,
|
|
91
|
+
when: context => context.command !== 'LOGOUT',
|
|
92
|
+
send: '$TAG BAD Request is throttled. Suggested Backoff Time: 1000 milliseconds\r\n'
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
'no-uidplus': {
|
|
97
|
+
description: 'A server without UIDPLUS: no APPENDUID, COPYUID or UID EXPUNGE, even when the plugins list it',
|
|
98
|
+
removePlugins: ['UIDPLUS']
|
|
99
|
+
},
|
|
100
|
+
'no-move': {
|
|
101
|
+
description: 'A server without MOVE, clients have to COPY, STORE \\Deleted and EXPUNGE',
|
|
102
|
+
removePlugins: ['MOVE']
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Resolves the `quirks` option
|
|
107
|
+
*
|
|
108
|
+
* @param {Array|String} names Quirk names
|
|
109
|
+
* @return {Object} `{ rules, removePlugins }` of all of them
|
|
110
|
+
* @throws {Error} for an unknown name
|
|
111
|
+
*/
|
|
112
|
+
function resolveQuirks(names) {
|
|
113
|
+
const rules = [];
|
|
114
|
+
const removePlugins = new Set();
|
|
115
|
+
[].concat(names || []).forEach(name => {
|
|
116
|
+
const quirk = Object.hasOwn(exports.quirks, String(name).toLowerCase()) ? exports.quirks[String(name).toLowerCase()] : null;
|
|
117
|
+
if (!quirk) {
|
|
118
|
+
throw new Error('Unknown quirk "' + name + '". Available quirks: ' + Object.keys(exports.quirks).join(', '));
|
|
119
|
+
}
|
|
120
|
+
rules.push(...(quirk.rules || []));
|
|
121
|
+
(quirk.removePlugins || []).forEach(plugin => removePlugins.add(plugin));
|
|
122
|
+
});
|
|
123
|
+
return { rules, removePlugins };
|
|
124
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seeded random numbers, so that randomized test behavior (UID shuffles, script rule chances) can be repeated
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* A small seeded random number generator (mulberry32), for repeatable "shuffle" orders
|
|
6
|
+
*
|
|
7
|
+
* @param {Number} seed Seed
|
|
8
|
+
* @return {Function} returns numbers from 0 (inclusive) to 1 (exclusive)
|
|
9
|
+
*/
|
|
10
|
+
export declare function seededRandom(seed: number): () => number;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Seeded random numbers, so that randomized test behavior (UID shuffles, script rule chances) can be repeated
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.seededRandom = seededRandom;
|
|
7
|
+
/**
|
|
8
|
+
* A small seeded random number generator (mulberry32), for repeatable "shuffle" orders
|
|
9
|
+
*
|
|
10
|
+
* @param {Number} seed Seed
|
|
11
|
+
* @return {Function} returns numbers from 0 (inclusive) to 1 (exclusive)
|
|
12
|
+
*/
|
|
13
|
+
function seededRandom(seed) {
|
|
14
|
+
let state = seed >>> 0;
|
|
15
|
+
return () => {
|
|
16
|
+
state = (state + 0x6d2b79f5) >>> 0;
|
|
17
|
+
let t = state;
|
|
18
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
19
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
20
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The REST API: the control API over HTTP, for tests written in any language. Off unless the `rest` option (or
|
|
3
|
+
* `--rest-port`) turns it on. It is full control over the server, including the mail of every user, so it listens
|
|
4
|
+
* on 127.0.0.1 by default, needs a bearer token on any other address, sends no CORS headers, takes JSON bodies
|
|
5
|
+
* only (a browser can not send them cross-origin without a CORS preflight) and, without a token, only answers
|
|
6
|
+
* requests for a loopback host name (DNS rebinding). Built on node:http only.
|
|
7
|
+
*/
|
|
8
|
+
import http from 'node:http';
|
|
9
|
+
import type { IMAPServer } from './types.js';
|
|
10
|
+
import type { ControlRoute as Route } from './control.js';
|
|
11
|
+
/** Options of the REST API, the `rest` server option */
|
|
12
|
+
interface RestOptions {
|
|
13
|
+
port?: number | undefined;
|
|
14
|
+
/** address to listen on, 127.0.0.1 by default. Any other than a loopback address needs a token */
|
|
15
|
+
host?: string | undefined;
|
|
16
|
+
/** bearer token that every request must send in `Authorization: Bearer <token>` */
|
|
17
|
+
token?: string | undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Checks if a host name or address is a loopback one
|
|
21
|
+
*
|
|
22
|
+
* @param {String} host Host name or address, can be in brackets
|
|
23
|
+
* @return {Boolean} true for localhost, 127.0.0.0/8 and ::1
|
|
24
|
+
*/
|
|
25
|
+
declare function isLoopback(host: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Builds the route table of a server
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} server IMAP server
|
|
30
|
+
* @return {Array} routes
|
|
31
|
+
*/
|
|
32
|
+
declare function getRoutes(server: IMAPServer): Route[];
|
|
33
|
+
/**
|
|
34
|
+
* Describes the routes as an OpenAPI 3.1 document
|
|
35
|
+
*
|
|
36
|
+
* @param {Array} routes Route table
|
|
37
|
+
* @return {Object} OpenAPI document
|
|
38
|
+
*/
|
|
39
|
+
declare function openApi(routes: Route[]): Record<string, unknown>;
|
|
40
|
+
/**
|
|
41
|
+
* Creates the HTTP server of the REST API, it does not listen yet
|
|
42
|
+
*
|
|
43
|
+
* @param {Object} server IMAP server
|
|
44
|
+
* @param {Object} options `{ host, token }`
|
|
45
|
+
* @return {Object} HTTP server
|
|
46
|
+
* @throws {Error} for a host that is not a loopback address without a token
|
|
47
|
+
*/
|
|
48
|
+
declare function createRestServer(server: IMAPServer, options: RestOptions): http.Server;
|
|
49
|
+
export { createRestServer, isLoopback, getRoutes, openApi };
|
|
50
|
+
export type { RestOptions };
|