imapkit 4.3.1 → 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.
Files changed (93) hide show
  1. package/README.md +167 -51
  2. package/bin/help.txt +17 -3
  3. package/bin/imapkit.js +53 -23
  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/flags.d.ts +3 -3
  8. package/dist/cjs/commands/handlers/flags.js +3 -3
  9. package/dist/cjs/commands/handlers/store.js +3 -3
  10. package/dist/cjs/commands/rename.js +6 -46
  11. package/dist/cjs/commands/select.js +1 -0
  12. package/dist/cjs/commands/subscribe.js +7 -8
  13. package/dist/cjs/commands/unsubscribe.js +3 -9
  14. package/dist/cjs/control.d.ts +430 -0
  15. package/dist/cjs/control.js +637 -0
  16. package/dist/cjs/index.d.ts +8 -1
  17. package/dist/cjs/index.js +9 -2
  18. package/dist/cjs/load-plugins.d.ts +1 -1
  19. package/dist/cjs/load-plugins.js +7 -2
  20. package/dist/cjs/plugins/acl.js +109 -42
  21. package/dist/cjs/plugins/condstore.js +7 -0
  22. package/dist/cjs/plugins/index.js +1 -3
  23. package/dist/cjs/plugins/metadata.js +140 -57
  24. package/dist/cjs/plugins/objectid.js +10 -0
  25. package/dist/cjs/plugins/quota.js +50 -2
  26. package/dist/cjs/plugins/savedate.js +1 -1
  27. package/dist/cjs/plugins/special-use.d.ts +0 -8
  28. package/dist/cjs/plugins/special-use.js +23 -0
  29. package/dist/cjs/quirks.d.ts +28 -0
  30. package/dist/cjs/quirks.js +124 -0
  31. package/dist/cjs/random.d.ts +10 -0
  32. package/dist/cjs/random.js +22 -0
  33. package/dist/cjs/rest.d.ts +50 -0
  34. package/dist/cjs/rest.js +561 -0
  35. package/dist/cjs/script.d.ts +33 -4
  36. package/dist/cjs/script.js +64 -4
  37. package/dist/cjs/server.d.ts +147 -8
  38. package/dist/cjs/server.js +398 -144
  39. package/dist/cjs/smtp-listener.d.ts +3 -2
  40. package/dist/cjs/smtp-listener.js +10 -6
  41. package/dist/cjs/storage-schema.d.ts +139 -0
  42. package/dist/cjs/storage-schema.js +215 -0
  43. package/dist/cjs/store-operations.d.ts +168 -0
  44. package/dist/cjs/store-operations.js +422 -0
  45. package/dist/cjs/types.d.ts +22 -1
  46. package/dist/esm/commands/append.js +1 -1
  47. package/dist/esm/commands/create.js +2 -2
  48. package/dist/esm/commands/delete.js +2 -14
  49. package/dist/esm/commands/handlers/flags.d.ts +3 -3
  50. package/dist/esm/commands/handlers/flags.js +3 -3
  51. package/dist/esm/commands/handlers/store.js +3 -3
  52. package/dist/esm/commands/rename.js +6 -46
  53. package/dist/esm/commands/select.js +1 -0
  54. package/dist/esm/commands/subscribe.js +7 -8
  55. package/dist/esm/commands/unsubscribe.js +3 -9
  56. package/dist/esm/control.d.ts +430 -0
  57. package/dist/esm/control.js +630 -0
  58. package/dist/esm/index.d.ts +8 -1
  59. package/dist/esm/index.js +5 -2
  60. package/dist/esm/load-plugins.d.ts +1 -1
  61. package/dist/esm/load-plugins.js +7 -2
  62. package/dist/esm/plugins/acl.js +109 -42
  63. package/dist/esm/plugins/condstore.js +7 -0
  64. package/dist/esm/plugins/index.js +1 -3
  65. package/dist/esm/plugins/metadata.js +140 -57
  66. package/dist/esm/plugins/objectid.js +10 -0
  67. package/dist/esm/plugins/quota.js +50 -2
  68. package/dist/esm/plugins/savedate.js +1 -1
  69. package/dist/esm/plugins/special-use.d.ts +0 -8
  70. package/dist/esm/plugins/special-use.js +23 -0
  71. package/dist/esm/quirks.d.ts +28 -0
  72. package/dist/esm/quirks.js +120 -0
  73. package/dist/esm/random.d.ts +10 -0
  74. package/dist/esm/random.js +19 -0
  75. package/dist/esm/rest.d.ts +50 -0
  76. package/dist/esm/rest.js +553 -0
  77. package/dist/esm/script.d.ts +33 -4
  78. package/dist/esm/script.js +63 -4
  79. package/dist/esm/server.d.ts +147 -8
  80. package/dist/esm/server.js +399 -145
  81. package/dist/esm/smtp-listener.d.ts +3 -2
  82. package/dist/esm/smtp-listener.js +10 -6
  83. package/dist/esm/storage-schema.d.ts +139 -0
  84. package/dist/esm/storage-schema.js +211 -0
  85. package/dist/esm/store-operations.d.ts +168 -0
  86. package/dist/esm/store-operations.js +407 -0
  87. package/dist/esm/types.d.ts +22 -1
  88. package/dist/plugin-help.json +0 -14
  89. package/package.json +11 -3
  90. package/dist/cjs/plugins/xtoybird.d.ts +0 -2
  91. package/dist/cjs/plugins/xtoybird.js +0 -236
  92. package/dist/esm/plugins/xtoybird.d.ts +0 -2
  93. package/dist/esm/plugins/xtoybird.js +0 -218
@@ -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,120 @@
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
+ const FETCH_COMMANDS = ['FETCH', 'UID FETCH'];
9
+ /**
10
+ * The part a body section belongs to: "2.MIME", "2.HEADER" and "2" all belong to part 2, "HEADER" and "TEXT" to the
11
+ * message itself
12
+ *
13
+ * @param {Array} section Section of a BODY item, its first entry is the section spec
14
+ * @return {String} part path, "" for the message
15
+ */
16
+ function partPath(section) {
17
+ const spec = String((section[0] && section[0].value) || '').toUpperCase();
18
+ return spec.replace(/(^|\.)(MIME|HEADER(\.FIELDS(\.NOT)?)?|TEXT)$/, '');
19
+ }
20
+ /**
21
+ * Changes the BODY[...] values of an untagged FETCH response
22
+ *
23
+ * @param {Object} response Untagged FETCH response
24
+ * @param {Function} change `(name, value, index)` returns the new value
25
+ * @return {Object|undefined} the changed response, nothing for other responses
26
+ */
27
+ function changeBodyValues(response, change) {
28
+ const items = response.attributes && response.attributes[2];
29
+ if (!Array.isArray(items) || !response.attributes[1] || response.attributes[1].value !== 'FETCH') {
30
+ return undefined;
31
+ }
32
+ const changed = items.slice();
33
+ for (let i = 0; i < changed.length - 1; i += 2) {
34
+ const name = changed[i];
35
+ if (name && name.type === 'ATOM' && String(name.value).toUpperCase() === 'BODY' && Array.isArray(name.section)) {
36
+ changed[i + 1] = change(name, changed[i + 1]);
37
+ }
38
+ }
39
+ return Object.assign({}, response, { attributes: [response.attributes[0], response.attributes[1], changed] });
40
+ }
41
+ export const quirks = {
42
+ 'james-fetchgroup': {
43
+ description: 'Apache James FetchGroup: only the first section asked for a part in one FETCH is answered, later ones for the same ' +
44
+ 'part come back empty. BODY[2.MIME] BODY[2] gives a zero-length body, the reverse order loses the headers',
45
+ rules: [
46
+ {
47
+ on: 'response',
48
+ command: FETCH_COMMANDS,
49
+ untagged: true,
50
+ mutate: response => {
51
+ const seen = new Set();
52
+ return changeBodyValues(response, (name, value) => {
53
+ const path = partPath(name.section);
54
+ if (seen.has(path)) {
55
+ return { type: 'LITERAL', value: '' };
56
+ }
57
+ seen.add(path);
58
+ return value;
59
+ });
60
+ }
61
+ }
62
+ ]
63
+ },
64
+ 'james-late-fetch': {
65
+ description: 'Apache James: now and then (1 in 4) a FETCH response comes after the tagged OK of its command',
66
+ rules: [{ on: 'response', command: FETCH_COMMANDS, untagged: true, chance: 0.25, defer: 'tagged' }]
67
+ },
68
+ 'yahoo-quoted-sections': {
69
+ description: 'Yahoo: short body sections (up to 100 octets without line breaks) are quoted strings instead of literals',
70
+ rules: [
71
+ {
72
+ on: 'response',
73
+ command: FETCH_COMMANDS,
74
+ untagged: true,
75
+ mutate: response => changeBodyValues(response, (name, value) => value && value.type === 'LITERAL' && typeof value.value === 'string' && value.value.length <= 100 && /^[\x20-\x7e]*$/.test(value.value)
76
+ ? { type: 'STRING', value: value.value }
77
+ : value)
78
+ }
79
+ ]
80
+ },
81
+ 'm365-throttle': {
82
+ description: 'Microsoft 365: 1 in 10 commands (not LOGOUT) is refused with "BAD Request is throttled. Suggested Backoff Time: 1000 milliseconds"',
83
+ rules: [
84
+ {
85
+ on: 'command',
86
+ chance: 0.1,
87
+ when: context => context.command !== 'LOGOUT',
88
+ send: '$TAG BAD Request is throttled. Suggested Backoff Time: 1000 milliseconds\r\n'
89
+ }
90
+ ]
91
+ },
92
+ 'no-uidplus': {
93
+ description: 'A server without UIDPLUS: no APPENDUID, COPYUID or UID EXPUNGE, even when the plugins list it',
94
+ removePlugins: ['UIDPLUS']
95
+ },
96
+ 'no-move': {
97
+ description: 'A server without MOVE, clients have to COPY, STORE \\Deleted and EXPUNGE',
98
+ removePlugins: ['MOVE']
99
+ }
100
+ };
101
+ /**
102
+ * Resolves the `quirks` option
103
+ *
104
+ * @param {Array|String} names Quirk names
105
+ * @return {Object} `{ rules, removePlugins }` of all of them
106
+ * @throws {Error} for an unknown name
107
+ */
108
+ export function resolveQuirks(names) {
109
+ const rules = [];
110
+ const removePlugins = new Set();
111
+ [].concat(names || []).forEach(name => {
112
+ const quirk = Object.hasOwn(quirks, String(name).toLowerCase()) ? quirks[String(name).toLowerCase()] : null;
113
+ if (!quirk) {
114
+ throw new Error('Unknown quirk "' + name + '". Available quirks: ' + Object.keys(quirks).join(', '));
115
+ }
116
+ rules.push(...(quirk.rules || []));
117
+ (quirk.removePlugins || []).forEach(plugin => removePlugins.add(plugin));
118
+ });
119
+ return { rules, removePlugins };
120
+ }
@@ -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,19 @@
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 function seededRandom(seed) {
11
+ let state = seed >>> 0;
12
+ return () => {
13
+ state = (state + 0x6d2b79f5) >>> 0;
14
+ let t = state;
15
+ t = Math.imul(t ^ (t >>> 15), t | 1);
16
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
17
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
18
+ };
19
+ }
@@ -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 };