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.
- package/LICENSE +16 -0
- package/README.md +608 -2
- package/bin/help.txt +98 -0
- package/bin/imapkit.js +108 -0
- package/cert/server.crt +20 -0
- package/cert/server.key +28 -0
- package/lib/addressparser.js +283 -0
- package/lib/arguments.js +112 -0
- package/lib/bodystructure.js +149 -0
- package/lib/command-states.js +109 -0
- package/lib/commands/append.js +313 -0
- package/lib/commands/capability.js +47 -0
- package/lib/commands/check.js +21 -0
- package/lib/commands/close.js +30 -0
- package/lib/commands/copy.js +115 -0
- package/lib/commands/create.js +52 -0
- package/lib/commands/delete.js +64 -0
- package/lib/commands/examine.js +7 -0
- package/lib/commands/expunge.js +27 -0
- package/lib/commands/fetch.js +229 -0
- package/lib/commands/handlers/fetch.js +209 -0
- package/lib/commands/handlers/flags.js +42 -0
- package/lib/commands/handlers/search.js +519 -0
- package/lib/commands/handlers/status.js +85 -0
- package/lib/commands/handlers/store.js +127 -0
- package/lib/commands/list.js +100 -0
- package/lib/commands/login.js +67 -0
- package/lib/commands/logout.js +41 -0
- package/lib/commands/lsub.js +87 -0
- package/lib/commands/noop.js +21 -0
- package/lib/commands/rename.js +102 -0
- package/lib/commands/search.js +76 -0
- package/lib/commands/select.js +289 -0
- package/lib/commands/status.js +63 -0
- package/lib/commands/store.js +151 -0
- package/lib/commands/subscribe.js +53 -0
- package/lib/commands/uid copy.js +7 -0
- package/lib/commands/uid fetch.js +5 -0
- package/lib/commands/uid search.js +5 -0
- package/lib/commands/uid store.js +5 -0
- package/lib/commands/unsubscribe.js +50 -0
- package/lib/dates.js +123 -0
- package/lib/deflate-layer.js +232 -0
- package/lib/envelope.js +82 -0
- package/lib/esearch.js +208 -0
- package/lib/framing.js +102 -0
- package/lib/list-extensions.js +36 -0
- package/lib/load-plugins.js +109 -0
- package/lib/mailbox-name.js +133 -0
- package/lib/mimeparser.js +778 -0
- package/lib/mock-client.js +233 -0
- package/lib/numbers.js +52 -0
- package/lib/plugins/acl.js +964 -0
- package/lib/plugins/appendlimit.js +83 -0
- package/lib/plugins/auth-plain.js +94 -0
- package/lib/plugins/binary.js +256 -0
- package/lib/plugins/catenate.js +253 -0
- package/lib/plugins/compress.js +76 -0
- package/lib/plugins/condstore.js +563 -0
- package/lib/plugins/context-search.js +321 -0
- package/lib/plugins/context-sort.js +19 -0
- package/lib/plugins/create-special-use.js +108 -0
- package/lib/plugins/enable.js +155 -0
- package/lib/plugins/esearch.js +156 -0
- package/lib/plugins/esort.js +60 -0
- package/lib/plugins/id.js +138 -0
- package/lib/plugins/idle.js +105 -0
- package/lib/plugins/imap4rev2.js +202 -0
- package/lib/plugins/list-extended.js +258 -0
- package/lib/plugins/list-status.js +31 -0
- package/lib/plugins/literalminus.js +20 -0
- package/lib/plugins/literalplus.js +18 -0
- package/lib/plugins/logindisabled.js +50 -0
- package/lib/plugins/messagelimit.js +234 -0
- package/lib/plugins/metadata-server.js +13 -0
- package/lib/plugins/metadata.js +475 -0
- package/lib/plugins/move.js +110 -0
- package/lib/plugins/multiappend.js +26 -0
- package/lib/plugins/multisearch.js +269 -0
- package/lib/plugins/namespace.js +67 -0
- package/lib/plugins/notify.js +654 -0
- package/lib/plugins/oauthbearer.js +217 -0
- package/lib/plugins/objectid.js +243 -0
- package/lib/plugins/partial.js +68 -0
- package/lib/plugins/preview.js +400 -0
- package/lib/plugins/qresync.js +525 -0
- package/lib/plugins/quota.js +285 -0
- package/lib/plugins/replace.js +145 -0
- package/lib/plugins/sasl-ir.js +12 -0
- package/lib/plugins/savedate.js +59 -0
- package/lib/plugins/savelimit.js +18 -0
- package/lib/plugins/searchres.js +82 -0
- package/lib/plugins/sort-display.js +23 -0
- package/lib/plugins/sort.js +132 -0
- package/lib/plugins/special-use.js +95 -0
- package/lib/plugins/starttls.js +57 -0
- package/lib/plugins/status-size.js +19 -0
- package/lib/plugins/thread-orderedsubject.js +16 -0
- package/lib/plugins/thread-references.js +16 -0
- package/lib/plugins/uidonly.js +135 -0
- package/lib/plugins/uidplus.js +124 -0
- package/lib/plugins/unauthenticate.js +28 -0
- package/lib/plugins/unselect.js +36 -0
- package/lib/plugins/utf8-accept.js +68 -0
- package/lib/plugins/x-gm-ext-1.js +456 -0
- package/lib/plugins/xoauth2.js +188 -0
- package/lib/plugins/xtoybird.js +282 -0
- package/lib/server.js +2880 -0
- package/lib/smtp-listener.js +51 -0
- package/lib/sorting.js +373 -0
- package/lib/threading.js +357 -0
- package/lib/utf8-session.js +123 -0
- package/lib/vanished.js +57 -0
- 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,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
|
+
};
|