imapkit 0.0.0-stage → 4.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 (114) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +608 -2
  3. package/bin/help.txt +98 -0
  4. package/bin/imapkit.js +108 -0
  5. package/cert/server.crt +20 -0
  6. package/cert/server.key +28 -0
  7. package/lib/addressparser.js +283 -0
  8. package/lib/arguments.js +112 -0
  9. package/lib/bodystructure.js +149 -0
  10. package/lib/command-states.js +109 -0
  11. package/lib/commands/append.js +313 -0
  12. package/lib/commands/capability.js +47 -0
  13. package/lib/commands/check.js +21 -0
  14. package/lib/commands/close.js +30 -0
  15. package/lib/commands/copy.js +115 -0
  16. package/lib/commands/create.js +52 -0
  17. package/lib/commands/delete.js +64 -0
  18. package/lib/commands/examine.js +7 -0
  19. package/lib/commands/expunge.js +27 -0
  20. package/lib/commands/fetch.js +229 -0
  21. package/lib/commands/handlers/fetch.js +209 -0
  22. package/lib/commands/handlers/flags.js +42 -0
  23. package/lib/commands/handlers/search.js +519 -0
  24. package/lib/commands/handlers/status.js +85 -0
  25. package/lib/commands/handlers/store.js +127 -0
  26. package/lib/commands/list.js +100 -0
  27. package/lib/commands/login.js +67 -0
  28. package/lib/commands/logout.js +41 -0
  29. package/lib/commands/lsub.js +87 -0
  30. package/lib/commands/noop.js +21 -0
  31. package/lib/commands/rename.js +102 -0
  32. package/lib/commands/search.js +76 -0
  33. package/lib/commands/select.js +289 -0
  34. package/lib/commands/status.js +63 -0
  35. package/lib/commands/store.js +151 -0
  36. package/lib/commands/subscribe.js +53 -0
  37. package/lib/commands/uid copy.js +7 -0
  38. package/lib/commands/uid fetch.js +5 -0
  39. package/lib/commands/uid search.js +5 -0
  40. package/lib/commands/uid store.js +5 -0
  41. package/lib/commands/unsubscribe.js +50 -0
  42. package/lib/dates.js +123 -0
  43. package/lib/deflate-layer.js +232 -0
  44. package/lib/envelope.js +82 -0
  45. package/lib/esearch.js +208 -0
  46. package/lib/framing.js +102 -0
  47. package/lib/list-extensions.js +36 -0
  48. package/lib/load-plugins.js +109 -0
  49. package/lib/mailbox-name.js +133 -0
  50. package/lib/mimeparser.js +778 -0
  51. package/lib/mock-client.js +233 -0
  52. package/lib/numbers.js +52 -0
  53. package/lib/plugins/acl.js +964 -0
  54. package/lib/plugins/appendlimit.js +83 -0
  55. package/lib/plugins/auth-plain.js +94 -0
  56. package/lib/plugins/binary.js +256 -0
  57. package/lib/plugins/catenate.js +253 -0
  58. package/lib/plugins/compress.js +76 -0
  59. package/lib/plugins/condstore.js +563 -0
  60. package/lib/plugins/context-search.js +321 -0
  61. package/lib/plugins/context-sort.js +19 -0
  62. package/lib/plugins/create-special-use.js +108 -0
  63. package/lib/plugins/enable.js +155 -0
  64. package/lib/plugins/esearch.js +156 -0
  65. package/lib/plugins/esort.js +60 -0
  66. package/lib/plugins/id.js +138 -0
  67. package/lib/plugins/idle.js +105 -0
  68. package/lib/plugins/imap4rev2.js +202 -0
  69. package/lib/plugins/list-extended.js +258 -0
  70. package/lib/plugins/list-status.js +31 -0
  71. package/lib/plugins/literalminus.js +20 -0
  72. package/lib/plugins/literalplus.js +18 -0
  73. package/lib/plugins/logindisabled.js +50 -0
  74. package/lib/plugins/messagelimit.js +234 -0
  75. package/lib/plugins/metadata-server.js +13 -0
  76. package/lib/plugins/metadata.js +475 -0
  77. package/lib/plugins/move.js +110 -0
  78. package/lib/plugins/multiappend.js +26 -0
  79. package/lib/plugins/multisearch.js +269 -0
  80. package/lib/plugins/namespace.js +67 -0
  81. package/lib/plugins/notify.js +654 -0
  82. package/lib/plugins/oauthbearer.js +217 -0
  83. package/lib/plugins/objectid.js +243 -0
  84. package/lib/plugins/partial.js +68 -0
  85. package/lib/plugins/preview.js +400 -0
  86. package/lib/plugins/qresync.js +525 -0
  87. package/lib/plugins/quota.js +285 -0
  88. package/lib/plugins/replace.js +145 -0
  89. package/lib/plugins/sasl-ir.js +12 -0
  90. package/lib/plugins/savedate.js +59 -0
  91. package/lib/plugins/savelimit.js +18 -0
  92. package/lib/plugins/searchres.js +82 -0
  93. package/lib/plugins/sort-display.js +23 -0
  94. package/lib/plugins/sort.js +132 -0
  95. package/lib/plugins/special-use.js +95 -0
  96. package/lib/plugins/starttls.js +57 -0
  97. package/lib/plugins/status-size.js +19 -0
  98. package/lib/plugins/thread-orderedsubject.js +16 -0
  99. package/lib/plugins/thread-references.js +16 -0
  100. package/lib/plugins/uidonly.js +135 -0
  101. package/lib/plugins/uidplus.js +124 -0
  102. package/lib/plugins/unauthenticate.js +28 -0
  103. package/lib/plugins/unselect.js +36 -0
  104. package/lib/plugins/utf8-accept.js +68 -0
  105. package/lib/plugins/x-gm-ext-1.js +456 -0
  106. package/lib/plugins/xoauth2.js +188 -0
  107. package/lib/plugins/xtoybird.js +282 -0
  108. package/lib/server.js +2880 -0
  109. package/lib/smtp-listener.js +51 -0
  110. package/lib/sorting.js +373 -0
  111. package/lib/threading.js +357 -0
  112. package/lib/utf8-session.js +123 -0
  113. package/lib/vanished.js +57 -0
  114. package/package.json +61 -5
@@ -0,0 +1,289 @@
1
+ 'use strict';
2
+
3
+ module.exports = function (connection, parsed, data, callback) {
4
+ return selectMailbox(connection, parsed, data, callback, false);
5
+ };
6
+
7
+ /**
8
+ * Starts a SELECT or EXAMINE, also one that is going to fail: ENABLE is not allowed once a mailbox has been selected
9
+ * (RFC 5161 section 3.1), and the selected mailbox gets closed. CLOSED marks where the responses for the new mailbox
10
+ * start (RFC 7162 section 3.2.11, RFC 9051 sections 6.3.2 and 7.1)
11
+ *
12
+ * @param {Object} connection IMAP connection
13
+ * @param {Object} parsed Parsed command
14
+ * @param {String} data Raw command
15
+ */
16
+ function beginSelect(connection, parsed, data) {
17
+ connection.everSelected = true;
18
+
19
+ if (connection.state === 'Selected' && connection.server.closedChecks.some(check => check(connection))) {
20
+ connection.send(
21
+ {
22
+ tag: '*',
23
+ command: 'OK',
24
+ attributes: [
25
+ { type: 'SECTION', section: [{ type: 'ATOM', value: 'CLOSED' }] },
26
+ { type: 'TEXT', value: 'Previous mailbox closed' }
27
+ ]
28
+ },
29
+ 'CLOSED',
30
+ parsed,
31
+ data
32
+ );
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Shared implementation of SELECT and EXAMINE
38
+ *
39
+ * @param {Boolean} readOnly If true, the mailbox is opened read-only (EXAMINE)
40
+ */
41
+ function selectMailbox(connection, parsed, data, callback, readOnly) {
42
+ const command = readOnly ? 'EXAMINE' : 'SELECT';
43
+
44
+ if (!parsed.attributes || parsed.attributes.length !== 1 || !parsed.attributes[0] || ['STRING', 'LITERAL', 'ATOM'].indexOf(parsed.attributes[0].type) < 0) {
45
+ connection.send(
46
+ {
47
+ tag: parsed.tag,
48
+ command: 'BAD',
49
+ attributes: [
50
+ {
51
+ type: 'TEXT',
52
+ value: command + ' expects 1 mailbox argument'
53
+ }
54
+ ]
55
+ },
56
+ 'INVALID COMMAND',
57
+ parsed,
58
+ data
59
+ );
60
+ return callback();
61
+ }
62
+
63
+ const path = parsed.attributes[0].value;
64
+
65
+ beginSelect(connection, parsed, data);
66
+
67
+ const mailbox = connection.server.getMailbox(path);
68
+
69
+ if (!mailbox || mailbox.flags.indexOf('\\Noselect') >= 0) {
70
+ // a failed SELECT leaves no mailbox selected (RFC 3501 section 6.3.1)
71
+ connection.closeMailbox();
72
+
73
+ connection.sendStatus(parsed, data, 'NO', 'Mailbox does not exist', 'NONEXISTENT', command + ' FAILED');
74
+ return callback();
75
+ }
76
+
77
+ connection.state = 'Selected';
78
+ connection.selectedMailbox = mailbox;
79
+ connection.readOnly = readOnly;
80
+
81
+ connection.notificationQueue = [];
82
+
83
+ // \Recent is a session flag: a read-write session takes over the messages
84
+ // that no other session has seen yet (RFC 3501 section 2.3.2)
85
+ connection.recent = new Set();
86
+ mailbox.messages.forEach(message => {
87
+ if (message.recent) {
88
+ connection.recent.add(message);
89
+ if (!readOnly) {
90
+ delete message.recent;
91
+ }
92
+ }
93
+ });
94
+
95
+ const status = connection.server.getStatus(mailbox);
96
+ const permanentFlags = status.permanentFlags.map(flag => {
97
+ return {
98
+ type: 'ATOM',
99
+ value: flag
100
+ };
101
+ });
102
+
103
+ connection.send(
104
+ {
105
+ tag: '*',
106
+ command: 'FLAGS',
107
+ attributes: [permanentFlags]
108
+ },
109
+ command + ' FLAGS',
110
+ parsed,
111
+ data
112
+ );
113
+
114
+ if (mailbox.allowPermanentFlags) {
115
+ permanentFlags.push({
116
+ type: 'TEXT',
117
+ value: '\\*'
118
+ });
119
+ }
120
+
121
+ connection.send(
122
+ {
123
+ tag: '*',
124
+ command: 'OK',
125
+ attributes: [
126
+ {
127
+ type: 'SECTION',
128
+ section: [
129
+ {
130
+ type: 'ATOM',
131
+ value: 'PERMANENTFLAGS'
132
+ },
133
+ // nothing can be changed in a read-only mailbox
134
+ readOnly ? [] : permanentFlags
135
+ ]
136
+ },
137
+ {
138
+ type: 'TEXT',
139
+ value: readOnly ? 'No permanent flags permitted' : 'Flags permitted'
140
+ }
141
+ ]
142
+ },
143
+ command + ' PERMANENTFLAGS',
144
+ parsed,
145
+ data
146
+ );
147
+
148
+ connection.send(
149
+ {
150
+ tag: '*',
151
+ attributes: [
152
+ mailbox.messages.length,
153
+ {
154
+ type: 'ATOM',
155
+ value: 'EXISTS'
156
+ }
157
+ ]
158
+ },
159
+ command + ' EXISTS',
160
+ parsed,
161
+ data
162
+ );
163
+
164
+ connection.send(
165
+ {
166
+ tag: '*',
167
+ attributes: [
168
+ connection.recent.size,
169
+ {
170
+ type: 'ATOM',
171
+ value: 'RECENT'
172
+ }
173
+ ]
174
+ },
175
+ command + ' RECENT',
176
+ parsed,
177
+ data
178
+ );
179
+
180
+ const firstUnseen = mailbox.messages.findIndex(message => message.flags.indexOf('\\Seen') < 0);
181
+ if (firstUnseen >= 0) {
182
+ connection.send(
183
+ {
184
+ tag: '*',
185
+ command: 'OK',
186
+ attributes: [
187
+ {
188
+ type: 'SECTION',
189
+ section: [
190
+ {
191
+ type: 'ATOM',
192
+ value: 'UNSEEN'
193
+ },
194
+ firstUnseen + 1
195
+ ]
196
+ },
197
+ {
198
+ type: 'TEXT',
199
+ value: 'First unseen message'
200
+ }
201
+ ]
202
+ },
203
+ command + ' UNSEEN',
204
+ parsed,
205
+ data
206
+ );
207
+ }
208
+
209
+ connection.send(
210
+ {
211
+ tag: '*',
212
+ command: 'OK',
213
+ attributes: [
214
+ {
215
+ type: 'SECTION',
216
+ section: [
217
+ {
218
+ type: 'ATOM',
219
+ value: 'UIDVALIDITY'
220
+ },
221
+ mailbox.uidvalidity
222
+ ]
223
+ },
224
+ {
225
+ type: 'TEXT',
226
+ value: 'UIDs valid'
227
+ }
228
+ ]
229
+ },
230
+ command + ' UIDVALIDITY',
231
+ parsed,
232
+ data
233
+ );
234
+
235
+ connection.send(
236
+ {
237
+ tag: '*',
238
+ command: 'OK',
239
+ attributes: [
240
+ {
241
+ type: 'SECTION',
242
+ section: [
243
+ {
244
+ type: 'ATOM',
245
+ value: 'UIDNEXT'
246
+ },
247
+ mailbox.uidnext
248
+ ]
249
+ },
250
+ {
251
+ type: 'TEXT',
252
+ value: 'Predicted next UID'
253
+ }
254
+ ]
255
+ },
256
+ command + ' UIDNEXT',
257
+ parsed,
258
+ data
259
+ );
260
+
261
+ connection.send(
262
+ {
263
+ tag: parsed.tag,
264
+ command: 'OK',
265
+ attributes: [
266
+ {
267
+ type: 'SECTION',
268
+ section: [
269
+ {
270
+ type: 'ATOM',
271
+ value: readOnly ? 'READ-ONLY' : 'READ-WRITE'
272
+ }
273
+ ]
274
+ },
275
+ {
276
+ type: 'TEXT',
277
+ value: 'Completed'
278
+ }
279
+ ]
280
+ },
281
+ command,
282
+ parsed,
283
+ data
284
+ );
285
+ return callback();
286
+ }
287
+
288
+ module.exports.selectMailbox = selectMailbox;
289
+ module.exports.beginSelect = beginSelect;
@@ -0,0 +1,63 @@
1
+ 'use strict';
2
+
3
+ const { parseStatusItems, sendStatus } = require('./handlers/status');
4
+
5
+ module.exports = function (connection, parsed, data, callback) {
6
+ if (
7
+ !parsed.attributes ||
8
+ parsed.attributes.length !== 2 ||
9
+ !parsed.attributes[0] ||
10
+ ['STRING', 'LITERAL', 'ATOM'].indexOf(parsed.attributes[0].type) < 0 ||
11
+ !Array.isArray(parsed.attributes[1]) ||
12
+ !parsed.attributes[1].length
13
+ ) {
14
+ connection.send(
15
+ {
16
+ tag: parsed.tag,
17
+ command: 'BAD',
18
+ attributes: [
19
+ {
20
+ type: 'TEXT',
21
+ value: 'STATUS expects mailbox argument and a list of status items'
22
+ }
23
+ ]
24
+ },
25
+ 'INVALID COMMAND',
26
+ parsed,
27
+ data
28
+ );
29
+ return callback();
30
+ }
31
+
32
+ const path = parsed.attributes[0].value;
33
+
34
+ // invalid arguments are BAD even for a mailbox that does not exist
35
+ let items;
36
+ try {
37
+ items = parseStatusItems(connection.server, parsed.attributes[1], connection);
38
+ } catch (err) {
39
+ connection.sendStatus(parsed, data, 'BAD', err.message, false, 'STATUS FAILED');
40
+ return callback();
41
+ }
42
+
43
+ const mailbox = connection.server.getMailbox(path);
44
+
45
+ if (!mailbox || mailbox.flags.indexOf('\\Noselect') >= 0) {
46
+ connection.sendStatus(parsed, data, 'NO', 'Mailbox does not exist', 'NONEXISTENT', 'STATUS FAILED');
47
+ return callback();
48
+ }
49
+
50
+ sendStatus(connection, path, mailbox, items, parsed, data);
51
+
52
+ // RFC 3501 section 6.3.10 and RFC 9051 section 6.3.11: STATUS SHOULD NOT be used on the selected mailbox. It
53
+ // works, but the client gets the CLIENTBUG response code (RFC 5530 section 3) like RFC 9051 section 7.1 shows
54
+ const clientBug = connection.state === 'Selected' && connection.selectedMailbox === mailbox;
55
+ connection.sendStatus(
56
+ parsed,
57
+ data,
58
+ 'OK',
59
+ clientBug ? 'Status completed, STATUS SHOULD NOT be used on the selected mailbox' : 'Status completed',
60
+ clientBug && 'CLIENTBUG'
61
+ );
62
+ return callback();
63
+ };
@@ -0,0 +1,151 @@
1
+ 'use strict';
2
+
3
+ const storeHandlers = require('./handlers/store');
4
+ const { restoreNilAtoms, isAtom } = require('../arguments');
5
+
6
+ /**
7
+ * Returns the handler of a STORE data item, plugins first
8
+ *
9
+ * @param {Object} connection IMAP connection
10
+ * @param {String} name Data item name, e.g. "+FLAGS.SILENT"
11
+ * @return {Function|undefined} handler
12
+ */
13
+ function getStoreHandler(connection, name) {
14
+ name = String(name).toUpperCase();
15
+ return connection.server.storeHandlers[name] || storeHandlers[name];
16
+ }
17
+
18
+ /**
19
+ * Shared implementation of STORE and UID STORE
20
+ *
21
+ * @param {Boolean} isUid If true, the sequence set lists UIDs
22
+ */
23
+ function processStore(isUid, connection, parsed, data, callback) {
24
+ const command = isUid ? 'UID STORE' : 'STORE';
25
+
26
+ // a store handler with `astringValues` takes astrings, like the labels of X-GM-LABELS: these can be literals,
27
+ // and NIL is a name there. Flags are atoms (RFC 3501 section 9: flag-list)
28
+ const itemArgument = parsed.attributes && parsed.attributes[1];
29
+ const itemHandler = isAtom(itemArgument) && getStoreHandler(connection, itemArgument.value);
30
+ const astringValues = !!(itemHandler && itemHandler.astringValues);
31
+ const valueTypes = astringValues ? ['ATOM', 'STRING', 'LITERAL'] : ['ATOM', 'STRING'];
32
+ if (astringValues) {
33
+ restoreNilAtoms(parsed, data, path => path[0] === 2);
34
+ }
35
+
36
+ if (
37
+ !parsed.attributes ||
38
+ parsed.attributes.length !== 3 ||
39
+ !parsed.attributes[0] ||
40
+ ['ATOM', 'SEQUENCE'].indexOf(parsed.attributes[0].type) < 0 ||
41
+ !parsed.attributes[1] ||
42
+ ['ATOM'].indexOf(parsed.attributes[1].type) < 0 ||
43
+ !parsed.attributes[2] ||
44
+ !(valueTypes.indexOf(parsed.attributes[2].type) >= 0 || Array.isArray(parsed.attributes[2]))
45
+ ) {
46
+ connection.send(
47
+ {
48
+ tag: parsed.tag,
49
+ command: 'BAD',
50
+ attributes: [
51
+ {
52
+ type: 'TEXT',
53
+ value: command + ' expects sequence set, item name and item value'
54
+ }
55
+ ]
56
+ },
57
+ 'INVALID COMMAND',
58
+ parsed,
59
+ data
60
+ );
61
+ return callback();
62
+ }
63
+
64
+ // A mailbox opened with EXAMINE is read-only (RFC 3501 6.3.2), so its flags can not be changed
65
+ if (connection.refuseReadOnly(parsed, data, command + ' FAILED')) {
66
+ return callback();
67
+ }
68
+
69
+ const range = connection.limitRange(parsed, connection.getCommandRange(parsed.attributes[0].value, isUid));
70
+ const itemName = (parsed.attributes[1].value || '').toUpperCase();
71
+ const itemValue = [].concat(parsed.attributes[2] || []);
72
+ const affected = [];
73
+ // if the sequence set includes a message that another session expunged without this session being told yet
74
+ let hasExpunged = false;
75
+
76
+ try {
77
+ itemValue.forEach((item, i) => {
78
+ if (!item || valueTypes.indexOf(item.type) < 0) {
79
+ throw new Error('Invalid item value #' + (i + 1));
80
+ }
81
+ });
82
+
83
+ const handler = itemHandler || getStoreHandler(connection, itemName);
84
+ if (!handler) {
85
+ throw new Error('Invalid STORE argument ' + itemName);
86
+ }
87
+
88
+ range.forEach(rangeMessage => {
89
+ if (rangeMessage[1].ghost) {
90
+ // RFC 2180 section 4.2: the flags of an expunged message are not stored
91
+ hasExpunged = true;
92
+ return;
93
+ }
94
+
95
+ for (let i = 0, len = connection.server.storeFilters.length; i < len; i++) {
96
+ if (!connection.server.storeFilters[i](connection, rangeMessage[1], parsed, rangeMessage[0])) {
97
+ return;
98
+ }
99
+ }
100
+
101
+ handler(connection, rangeMessage[1], itemValue, rangeMessage[0], parsed, data);
102
+
103
+ affected.push(rangeMessage[1]);
104
+ });
105
+ } catch (E) {
106
+ connection.send(
107
+ {
108
+ tag: parsed.tag,
109
+ command: 'BAD',
110
+ attributes: [
111
+ {
112
+ type: 'TEXT',
113
+ value: E.message
114
+ }
115
+ ]
116
+ },
117
+ command + ' FAILED',
118
+ parsed,
119
+ data
120
+ );
121
+ return callback();
122
+ }
123
+
124
+ // RFC 2180 sections 4.2.2 and 4.2.3: when the set includes expunged messages, the others are stored and get
125
+ // their FETCH responses, and the tagged NO tells the client to issue NOOP. With .SILENT the result is OK as
126
+ // long as the other messages were stored (section 4.2.1)
127
+ const failed = hasExpunged && !/\.SILENT$/.test(itemName);
128
+
129
+ connection.send(
130
+ {
131
+ tag: parsed.tag,
132
+ command: failed ? 'NO' : 'OK',
133
+ attributes: [].concat(failed ? { type: 'SECTION', section: [{ type: 'ATOM', value: 'EXPUNGEISSUED' }] } : [], {
134
+ type: 'TEXT',
135
+ value: failed ? 'Some of the messages no longer exist' : command + ' completed'
136
+ })
137
+ },
138
+ command + (failed ? ' FAILED' : ' COMPLETE'),
139
+ parsed,
140
+ data,
141
+ affected
142
+ );
143
+
144
+ // other sessions that have the mailbox selected learn about the new flags (RFC 3501 section 5.2)
145
+ connection.notifyFlagChanges(affected);
146
+
147
+ callback();
148
+ }
149
+
150
+ module.exports = (connection, parsed, data, callback) => processStore(false, connection, parsed, data, callback);
151
+ module.exports.processStore = processStore;
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ module.exports = function (connection, parsed, data, callback) {
4
+ if (!parsed.attributes || parsed.attributes.length !== 1 || !parsed.attributes[0] || ['STRING', 'LITERAL', 'ATOM'].indexOf(parsed.attributes[0].type) < 0) {
5
+ connection.send(
6
+ {
7
+ tag: parsed.tag,
8
+ command: 'BAD',
9
+ attributes: [
10
+ {
11
+ type: 'TEXT',
12
+ value: 'SUBSCRIBE expects mailbox name'
13
+ }
14
+ ]
15
+ },
16
+ 'INVALID COMMAND',
17
+ parsed,
18
+ data
19
+ );
20
+ return callback();
21
+ }
22
+
23
+ const path = parsed.attributes[0].value;
24
+
25
+ const mailbox = connection.server.getMailbox(path);
26
+
27
+ if (!mailbox || mailbox.flags.indexOf('\\Noselect') >= 0) {
28
+ connection.sendStatus(parsed, data, 'NO', 'Mailbox does not exist', 'NONEXISTENT', 'SUBSCRIBE FAILED');
29
+ return callback();
30
+ }
31
+
32
+ if (!mailbox.subscribed) {
33
+ mailbox.subscribed = true;
34
+ connection.server.mailboxChanged('subscribe', mailbox.path);
35
+ }
36
+
37
+ connection.send(
38
+ {
39
+ tag: parsed.tag,
40
+ command: 'OK',
41
+ attributes: [
42
+ {
43
+ type: 'TEXT',
44
+ value: 'SUBSCRIBE completed'
45
+ }
46
+ ]
47
+ },
48
+ 'SUBSCRIBE',
49
+ parsed,
50
+ data
51
+ );
52
+ return callback();
53
+ };
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ const { copyMessages } = require('./copy');
4
+
5
+ module.exports = function (connection, parsed, data, callback) {
6
+ return copyMessages(connection, parsed, data, callback, true);
7
+ };
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ const { processFetch } = require('./fetch');
4
+
5
+ module.exports = (connection, parsed, data, callback) => processFetch(true, connection, parsed, data, callback);
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ const { processSearch } = require('./search');
4
+
5
+ module.exports = (connection, parsed, data, callback) => processSearch(true, connection, parsed, data, callback);
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ const { processStore } = require('./store');
4
+
5
+ module.exports = (connection, parsed, data, callback) => processStore(true, connection, parsed, data, callback);
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ module.exports = function (connection, parsed, data, callback) {
4
+ if (!parsed.attributes || parsed.attributes.length !== 1 || !parsed.attributes[0] || ['STRING', 'LITERAL', 'ATOM'].indexOf(parsed.attributes[0].type) < 0) {
5
+ connection.send(
6
+ {
7
+ tag: parsed.tag,
8
+ command: 'BAD',
9
+ attributes: [
10
+ {
11
+ type: 'TEXT',
12
+ value: 'UNSUBSCRIBE expects mailbox name'
13
+ }
14
+ ]
15
+ },
16
+ 'INVALID COMMAND',
17
+ parsed,
18
+ data
19
+ );
20
+ return callback();
21
+ }
22
+
23
+ let path = parsed.attributes[0].value;
24
+ if (path.toUpperCase() === 'INBOX') {
25
+ path = 'INBOX';
26
+ }
27
+
28
+ // The subscription list holds names, so a name stays removable after its mailbox is deleted
29
+ // (RFC 3501 section 6.3.6). Removing a name that is not subscribed is not an error (RFC 9051 section 6.3.8)
30
+ if (connection.server.subscriptions.delete(path)) {
31
+ connection.server.mailboxChanged('unsubscribe', path);
32
+ }
33
+
34
+ connection.send(
35
+ {
36
+ tag: parsed.tag,
37
+ command: 'OK',
38
+ attributes: [
39
+ {
40
+ type: 'TEXT',
41
+ value: 'UNSUBSCRIBE completed'
42
+ }
43
+ ]
44
+ },
45
+ 'UNSUBSCRIBE',
46
+ parsed,
47
+ data
48
+ );
49
+ return callback();
50
+ };