iobroker.imap 0.2.2 → 0.3.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 +68 -60
- package/admin/blockly.js +1 -1
- package/admin/i18n/de/translations.json +4 -4
- package/admin/i18n/en/translations.json +4 -4
- package/admin/jsonConfig.json +17 -1
- package/io-package.json +14 -14
- package/lib/helper.js +40 -6
- package/lib/imap_event.js +154 -58
- package/lib/translator.js +78 -0
- package/main.js +235 -214
- package/package.json +6 -8
- package/admin/index_m.html +0 -40
- package/lib/Connection.js +0 -1583
package/lib/Connection.js
DELETED
|
@@ -1,1583 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
//This file is from the node-imap module
|
|
4
|
-
const tls = require("tls");
|
|
5
|
-
const crypto = require("crypto");
|
|
6
|
-
const Socket = require("net").Socket;
|
|
7
|
-
const EventEmitter = require("events").EventEmitter;
|
|
8
|
-
const inspect = require("util").inspect;
|
|
9
|
-
const isDate = require("util").isDate;
|
|
10
|
-
const utf7 = require("utf7").imap;
|
|
11
|
-
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
12
|
-
// @ts-ignore
|
|
13
|
-
const Parser = require("./../../node-imap/lib/Parser").Parser;
|
|
14
|
-
// @ts-ignore
|
|
15
|
-
const parseExpr = require("./../../node-imap/lib/Parser").parseExpr;
|
|
16
|
-
// @ts-ignore
|
|
17
|
-
const parseHeader = require("./../../node-imap/lib/Parser").parseHeader;
|
|
18
|
-
// @ts-ignore
|
|
19
|
-
const validateUIDList = require("./../../node-imap/lib/utils").validateUIDList;
|
|
20
|
-
// @ts-ignore
|
|
21
|
-
const deepEqual = require("./../../node-imap/lib/utils")._deepEqual;
|
|
22
|
-
// @ts-ignore
|
|
23
|
-
const escape = require("./../../node-imap/lib/utils").escape;
|
|
24
|
-
// @ts-ignore
|
|
25
|
-
const buildSearchQuery = require("./../../node-imap/lib/utils").buildSearchQuery;
|
|
26
|
-
const MAX_INT = Number.MAX_SAFE_INTEGER;
|
|
27
|
-
const KEEPALIVE_INTERVAL = 10000;
|
|
28
|
-
const MAX_IDLE_WAIT = 300000; // 5 minutes
|
|
29
|
-
|
|
30
|
-
const FETCH_ATTR_MAP = {
|
|
31
|
-
"RFC822.SIZE": "size",
|
|
32
|
-
BODY: "struct",
|
|
33
|
-
BODYSTRUCTURE: "struct",
|
|
34
|
-
ENVELOPE: "envelope",
|
|
35
|
-
INTERNALDATE: "date",
|
|
36
|
-
};
|
|
37
|
-
const SPECIAL_USE_ATTRIBUTES = [
|
|
38
|
-
"\\All",
|
|
39
|
-
"\\Archive",
|
|
40
|
-
"\\Drafts",
|
|
41
|
-
"\\Flagged",
|
|
42
|
-
"\\Important",
|
|
43
|
-
"\\Junk",
|
|
44
|
-
"\\Sent",
|
|
45
|
-
"\\Trash",
|
|
46
|
-
];
|
|
47
|
-
const CRLF = "\r\n";
|
|
48
|
-
const RE_CMD = /^([^ ]+)(?: |$)/;
|
|
49
|
-
const RE_UIDCMD_HASRESULTS = /^UID (?:FETCH|SEARCH|SORT)/;
|
|
50
|
-
const RE_IDLENOOPRES = /^(IDLE|NOOP) /;
|
|
51
|
-
const RE_OPENBOX = /^EXAMINE|^SELECT$/;
|
|
52
|
-
const RE_BODYPART = /^BODY\[/;
|
|
53
|
-
// eslint-disable-next-line no-control-regex, no-useless-escape
|
|
54
|
-
const RE_INVALID_KW_CHARS = /[\(\)\{\\\"\]\%\*\x00-\x20\x7F]/;
|
|
55
|
-
const RE_ESCAPE = /\\\\/g;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Connection
|
|
59
|
-
* @param {*} config
|
|
60
|
-
*/
|
|
61
|
-
class Connection extends EventEmitter {
|
|
62
|
-
constructor(config) {
|
|
63
|
-
super();
|
|
64
|
-
if (!(this instanceof Connection)) return new Connection(config);
|
|
65
|
-
// EventEmitter.call(this);
|
|
66
|
-
config || (config = {});
|
|
67
|
-
this._config = {
|
|
68
|
-
localAddress: config.localAddress,
|
|
69
|
-
socket: config.socket,
|
|
70
|
-
socketTimeout: config.socketTimeout || 0,
|
|
71
|
-
host: config.host || "localhost",
|
|
72
|
-
servername: config.servername || "localhost",
|
|
73
|
-
port: config.port || 143,
|
|
74
|
-
tls: config.tls,
|
|
75
|
-
tlsOptions: config.tlsOptions,
|
|
76
|
-
autotls: config.autotls,
|
|
77
|
-
user: config.user,
|
|
78
|
-
password: config.password,
|
|
79
|
-
xoauth: config.xoauth,
|
|
80
|
-
xoauth2: config.xoauth2,
|
|
81
|
-
id: config.id || null,
|
|
82
|
-
connTimeout: config.connTimeout || 10000,
|
|
83
|
-
authTimeout: config.authTimeout || 5000,
|
|
84
|
-
keepalive: config.keepalive === undefined || config.keepalive === null ? true : config.keepalive,
|
|
85
|
-
};
|
|
86
|
-
this._sock = config.socket || undefined;
|
|
87
|
-
this._tagcount = 0;
|
|
88
|
-
this._tmrConn = undefined;
|
|
89
|
-
this._tmrKeepalive = undefined;
|
|
90
|
-
this._tmrAuth = undefined;
|
|
91
|
-
this._queue = [];
|
|
92
|
-
this._box = undefined;
|
|
93
|
-
this._idle = { started: undefined, enabled: false };
|
|
94
|
-
this._parser = undefined;
|
|
95
|
-
this._curReq = undefined;
|
|
96
|
-
this.delimiter = undefined;
|
|
97
|
-
this.namespaces = undefined;
|
|
98
|
-
this.state = "disconnected";
|
|
99
|
-
this.debug = config.debug;
|
|
100
|
-
this.client = config.client;
|
|
101
|
-
this.inbox = config.inbox;
|
|
102
|
-
}
|
|
103
|
-
connect() {
|
|
104
|
-
let config = {};
|
|
105
|
-
config = this._config || {};
|
|
106
|
-
const self = this;
|
|
107
|
-
let parser;
|
|
108
|
-
const tlsOptions = {};
|
|
109
|
-
const socket = config.socket || new Socket();
|
|
110
|
-
socket.setKeepAlive(true);
|
|
111
|
-
this._sock = undefined;
|
|
112
|
-
this._tagcount = 0;
|
|
113
|
-
this._tmrConn = undefined;
|
|
114
|
-
this._tmrKeepalive = undefined;
|
|
115
|
-
this._tmrAuth = undefined;
|
|
116
|
-
this._queue = [];
|
|
117
|
-
this._box = undefined;
|
|
118
|
-
this._idle = { started: undefined, enabled: false };
|
|
119
|
-
this._parser = undefined;
|
|
120
|
-
this._curReq = undefined;
|
|
121
|
-
this.delimiter = undefined;
|
|
122
|
-
this.namespaces = undefined;
|
|
123
|
-
this.state = "disconnected";
|
|
124
|
-
if (config.tls) {
|
|
125
|
-
tlsOptions["host"] = config.host;
|
|
126
|
-
tlsOptions["servername"] = config.servername || config.host;
|
|
127
|
-
// Host name may be overridden the tlsOptions
|
|
128
|
-
for (const k in config.tlsOptions) tlsOptions[k] = config.tlsOptions[k];
|
|
129
|
-
tlsOptions["socket"] = socket;
|
|
130
|
-
}
|
|
131
|
-
if (config.tls) {
|
|
132
|
-
this._sock = tls.connect(tlsOptions, onconnect);
|
|
133
|
-
this._isTsl = true;
|
|
134
|
-
} else {
|
|
135
|
-
socket.once("connect", onconnect);
|
|
136
|
-
this._sock = socket;
|
|
137
|
-
}
|
|
138
|
-
function onconnect() {
|
|
139
|
-
clearTimeout(self._tmrConn);
|
|
140
|
-
self.state = "connected";
|
|
141
|
-
self.debug && self.debug("[connection] Connected to host");
|
|
142
|
-
self._tmrAuth = setTimeout(function () {
|
|
143
|
-
const err = new Error("Timed out while authenticating with server");
|
|
144
|
-
err["source"] = "timeout-auth";
|
|
145
|
-
self.emit("error", err, self.client, "onconnect");
|
|
146
|
-
socket.destroy();
|
|
147
|
-
}, config.authTimeout);
|
|
148
|
-
}
|
|
149
|
-
const additionalClose = !config.socket;
|
|
150
|
-
this._onError = function (err) {
|
|
151
|
-
clearTimeout(self._tmrConn);
|
|
152
|
-
clearTimeout(self._tmrAuth);
|
|
153
|
-
self.debug && self.debug("[connection] Error: " + err);
|
|
154
|
-
err["source"] = "socket";
|
|
155
|
-
if (additionalClose && self._isTsl) socket.destroy();
|
|
156
|
-
self.emit("error", err, self.client, "_onError");
|
|
157
|
-
};
|
|
158
|
-
this._sock.on("error", this._onError);
|
|
159
|
-
this._onSocketTimeout = function () {
|
|
160
|
-
clearTimeout(self._tmrConn);
|
|
161
|
-
clearTimeout(self._tmrAuth);
|
|
162
|
-
clearTimeout(self._tmrKeepalive);
|
|
163
|
-
self.state = "disconnected";
|
|
164
|
-
self.debug && self.debug("[connection] Socket timeout");
|
|
165
|
-
const err = new Error("Socket timed out while talking to server");
|
|
166
|
-
err["source"] = "socket-timeout";
|
|
167
|
-
self.emit("error", err, self.client, "socket-timeout");
|
|
168
|
-
socket.destroy();
|
|
169
|
-
};
|
|
170
|
-
this._sock.on("timeout", this._onSocketTimeout);
|
|
171
|
-
socket.setTimeout(config.socketTimeout);
|
|
172
|
-
socket.once("close", function (had_err) {
|
|
173
|
-
clearTimeout(self._tmrConn);
|
|
174
|
-
clearTimeout(self._tmrAuth);
|
|
175
|
-
clearTimeout(self._tmrKeepalive);
|
|
176
|
-
self.state = "disconnected";
|
|
177
|
-
self.debug && self.debug("[connection] Closed");
|
|
178
|
-
self.emit("close", had_err, self.client);
|
|
179
|
-
});
|
|
180
|
-
socket.once("end", function () {
|
|
181
|
-
clearTimeout(self._tmrConn);
|
|
182
|
-
clearTimeout(self._tmrAuth);
|
|
183
|
-
clearTimeout(self._tmrKeepalive);
|
|
184
|
-
self.state = "disconnected";
|
|
185
|
-
self.debug && self.debug("[connection] Ended");
|
|
186
|
-
self.emit("end", self.client);
|
|
187
|
-
});
|
|
188
|
-
this._parser = parser = new Parser(this._sock, this.debug);
|
|
189
|
-
parser.on("untagged", function (info) {
|
|
190
|
-
self._resUntagged(info);
|
|
191
|
-
});
|
|
192
|
-
parser.on("tagged", function (info) {
|
|
193
|
-
self._resTagged(info);
|
|
194
|
-
});
|
|
195
|
-
parser.on("body", function (stream, info) {
|
|
196
|
-
let msg = self._curReq.fetchCache[info.seqno];
|
|
197
|
-
if (msg === undefined) {
|
|
198
|
-
msg = self._curReq.fetchCache[info.seqno] = {
|
|
199
|
-
msgEmitter: new EventEmitter(),
|
|
200
|
-
toget: self._curReq.fetching.slice(0),
|
|
201
|
-
attrs: {},
|
|
202
|
-
ended: false,
|
|
203
|
-
};
|
|
204
|
-
self._curReq.bodyEmitter.emit("message", msg.msgEmitter, info.seqno);
|
|
205
|
-
}
|
|
206
|
-
const toget = msg.toget;
|
|
207
|
-
// here we compare the parsed version of the expression inside BODY[]
|
|
208
|
-
// because 'HEADER.FIELDS (TO FROM)' really is equivalent to
|
|
209
|
-
// 'HEADER.FIELDS ("TO" "FROM")' and some servers will actually send the
|
|
210
|
-
// quoted form even if the client did not use quotes
|
|
211
|
-
|
|
212
|
-
const thisbody = info.which != null ? parseExpr(info.which.toUpperCase()) : "";
|
|
213
|
-
for (let i = 0, len = toget.length; i < len; ++i) {
|
|
214
|
-
if (deepEqual(thisbody, toget[i])) {
|
|
215
|
-
toget.splice(i, 1);
|
|
216
|
-
msg.msgEmitter.emit("body", stream, info);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
stream.resume(); // a body we didn't ask for?
|
|
221
|
-
});
|
|
222
|
-
parser.on("continue", function (info) {
|
|
223
|
-
if (self._curReq) {
|
|
224
|
-
const type = self._curReq.type;
|
|
225
|
-
if (type === "IDLE") {
|
|
226
|
-
if (
|
|
227
|
-
self._queue != undefined &&
|
|
228
|
-
self._idle != undefined &&
|
|
229
|
-
self._queue.length &&
|
|
230
|
-
self._idle.started === 0 &&
|
|
231
|
-
self._curReq &&
|
|
232
|
-
self._curReq.type === "IDLE" &&
|
|
233
|
-
self._sock &&
|
|
234
|
-
self._sock.writable &&
|
|
235
|
-
!self._idle.enabled
|
|
236
|
-
) {
|
|
237
|
-
self.debug && self.debug("=> DONE");
|
|
238
|
-
self._sock.write("DONE" + CRLF);
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
// now idling
|
|
242
|
-
// @ts-ignore
|
|
243
|
-
self._idle.started = Date.now();
|
|
244
|
-
} else if (/^AUTHENTICATE XOAUTH/.test(self._curReq.fullcmd)) {
|
|
245
|
-
self._curReq.oauthError = Buffer.from(info.text, "base64").toString("utf8");
|
|
246
|
-
self.debug && self.debug("=> " + inspect(CRLF));
|
|
247
|
-
self._sock.write(CRLF);
|
|
248
|
-
} else if (/^AUTHENTICATE CRAM-MD5/.test(self._curReq.fullcmd)) {
|
|
249
|
-
self._authCRAMMD5(info.text);
|
|
250
|
-
} else if (type === "APPEND") {
|
|
251
|
-
self._sockWriteAppendData(self._curReq.appendData);
|
|
252
|
-
} else if (self._curReq.lines && self._curReq.lines.length) {
|
|
253
|
-
const line = self._curReq.lines.shift() + "\r\n";
|
|
254
|
-
self.debug && self.debug("=> " + inspect(line));
|
|
255
|
-
self._sock.write(line, "binary");
|
|
256
|
-
}
|
|
257
|
-
} else {
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
parser.on("other", function (line) {
|
|
262
|
-
let m;
|
|
263
|
-
if (
|
|
264
|
-
(m = RE_IDLENOOPRES.exec(line)) &&
|
|
265
|
-
self._idle !== undefined &&
|
|
266
|
-
self._queue !== undefined &&
|
|
267
|
-
self._config !== undefined
|
|
268
|
-
) {
|
|
269
|
-
// no longer idling
|
|
270
|
-
self._idle.enabled = false;
|
|
271
|
-
self._idle.started = undefined;
|
|
272
|
-
clearTimeout(self._tmrKeepalive);
|
|
273
|
-
self._curReq = undefined;
|
|
274
|
-
if (
|
|
275
|
-
self._queue.length === 0 &&
|
|
276
|
-
self._config.keepalive &&
|
|
277
|
-
self.state === "authenticated" &&
|
|
278
|
-
!self._idle.enabled
|
|
279
|
-
) {
|
|
280
|
-
self._idle.enabled = true;
|
|
281
|
-
if (m[1] === "NOOP") self._doKeepaliveTimer();
|
|
282
|
-
else self._doKeepaliveTimer(true);
|
|
283
|
-
}
|
|
284
|
-
self._processQueue();
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
this._tmrConn = setTimeout(function () {
|
|
288
|
-
const err = new Error("Timed out while connecting to server");
|
|
289
|
-
err["source"] = "timeout";
|
|
290
|
-
self.emit("error", err, self.client, "_tmrConn");
|
|
291
|
-
socket.destroy();
|
|
292
|
-
}, config.connTimeout);
|
|
293
|
-
socket.connect({
|
|
294
|
-
port: config.port,
|
|
295
|
-
host: config.host,
|
|
296
|
-
localAddress: config.localAddress,
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
serverSupports(cap) {
|
|
300
|
-
return this._caps && this._caps.indexOf(cap) > -1;
|
|
301
|
-
}
|
|
302
|
-
serverAllSupport() {
|
|
303
|
-
return this._caps;
|
|
304
|
-
}
|
|
305
|
-
destroy() {
|
|
306
|
-
this._queue = [];
|
|
307
|
-
this._curReq = undefined;
|
|
308
|
-
this._sock && this._sock.end();
|
|
309
|
-
}
|
|
310
|
-
end() {
|
|
311
|
-
const self = this;
|
|
312
|
-
this._enqueue("LOGOUT", function () {
|
|
313
|
-
self._queue = [];
|
|
314
|
-
self._curReq = undefined;
|
|
315
|
-
self._sock.end();
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
append(data, options, cb) {
|
|
319
|
-
const literal = this.serverSupports("LITERAL+");
|
|
320
|
-
if (typeof options === "function") {
|
|
321
|
-
cb = options;
|
|
322
|
-
options = undefined;
|
|
323
|
-
}
|
|
324
|
-
options = options || {};
|
|
325
|
-
if (!options.mailbox) {
|
|
326
|
-
if (!this._box) throw new Error("No mailbox specified or currently selected");
|
|
327
|
-
else options.mailbox = this._box.name;
|
|
328
|
-
}
|
|
329
|
-
let cmd = 'APPEND "' + escape(utf7.encode("" + options.mailbox)) + '"';
|
|
330
|
-
if (options.flags) {
|
|
331
|
-
if (!Array.isArray(options.flags)) options.flags = [options.flags];
|
|
332
|
-
if (options.flags.length > 0) {
|
|
333
|
-
for (let i = 0, len = options.flags.length; i < len; ++i) {
|
|
334
|
-
if (options.flags[i][0] !== "$" && options.flags[i][0] !== "\\")
|
|
335
|
-
options.flags[i] = "\\" + options.flags[i];
|
|
336
|
-
}
|
|
337
|
-
cmd += " (" + options.flags.join(" ") + ")";
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
if (options.date) {
|
|
341
|
-
if (!isDate(options.date)) throw new Error("`date` is not a Date object");
|
|
342
|
-
cmd += ' "';
|
|
343
|
-
cmd += ("0" + options.date.getDate()).slice(-2);
|
|
344
|
-
cmd += "-";
|
|
345
|
-
cmd += MONTHS[options.date.getMonth()];
|
|
346
|
-
cmd += "-";
|
|
347
|
-
cmd += options.date.getFullYear();
|
|
348
|
-
cmd += " ";
|
|
349
|
-
cmd += ("0" + options.date.getHours()).slice(-2);
|
|
350
|
-
cmd += ":";
|
|
351
|
-
cmd += ("0" + options.date.getMinutes()).slice(-2);
|
|
352
|
-
cmd += ":";
|
|
353
|
-
cmd += ("0" + options.date.getSeconds()).slice(-2);
|
|
354
|
-
cmd += options.date.getTimezoneOffset() > 0 ? " -" : " +";
|
|
355
|
-
cmd += ("0" + -options.date.getTimezoneOffset() / 60).slice(-2);
|
|
356
|
-
cmd += ("0" + (-options.date.getTimezoneOffset() % 60)).slice(-2);
|
|
357
|
-
cmd += '"';
|
|
358
|
-
}
|
|
359
|
-
cmd += " {";
|
|
360
|
-
cmd += Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data);
|
|
361
|
-
cmd += (literal ? "+" : "") + "}";
|
|
362
|
-
this._enqueue(cmd, cb);
|
|
363
|
-
if (literal && this._queue !== undefined) this._queue[this._queue.length - 1].literalAppendData = data;
|
|
364
|
-
else if (this._queue !== undefined) this._queue[this._queue.length - 1].appendData = data;
|
|
365
|
-
}
|
|
366
|
-
getSpecialUseBoxes(cb) {
|
|
367
|
-
this._enqueue('XLIST "" "*"', cb);
|
|
368
|
-
}
|
|
369
|
-
getBoxes(namespace, cb) {
|
|
370
|
-
if (typeof namespace === "function") {
|
|
371
|
-
cb = namespace;
|
|
372
|
-
namespace = "";
|
|
373
|
-
}
|
|
374
|
-
namespace = escape(utf7.encode("" + namespace));
|
|
375
|
-
this._enqueue('LIST "' + namespace + '" "*"', cb);
|
|
376
|
-
}
|
|
377
|
-
id(identification, cb) {
|
|
378
|
-
if (!this.serverSupports("ID")) throw new Error("Server does not support ID");
|
|
379
|
-
let cmd = "ID";
|
|
380
|
-
if (identification === null || Object.keys(identification).length === 0) cmd += " NIL";
|
|
381
|
-
else {
|
|
382
|
-
if (Object.keys(identification).length > 30) throw new Error("Max allowed number of keys is 30");
|
|
383
|
-
const kv = [];
|
|
384
|
-
for (const k in identification) {
|
|
385
|
-
if (Buffer.byteLength(k) > 30) throw new Error("Max allowed key length is 30");
|
|
386
|
-
if (Buffer.byteLength(identification[k]) > 1024) throw new Error("Max allowed value length is 1024");
|
|
387
|
-
kv.push('"' + escape(k) + '"');
|
|
388
|
-
kv.push('"' + escape(identification[k]) + '"');
|
|
389
|
-
}
|
|
390
|
-
cmd += " (" + kv.join(" ") + ")";
|
|
391
|
-
}
|
|
392
|
-
this._enqueue(cmd, cb);
|
|
393
|
-
}
|
|
394
|
-
openBox(name, readOnly, cb) {
|
|
395
|
-
if (this.state !== "authenticated") throw new Error("Not authenticated");
|
|
396
|
-
if (typeof readOnly === "function") {
|
|
397
|
-
cb = readOnly;
|
|
398
|
-
readOnly = false;
|
|
399
|
-
}
|
|
400
|
-
name = "" + name;
|
|
401
|
-
const encname = escape(utf7.encode(name));
|
|
402
|
-
let cmd = readOnly ? "EXAMINE" : "SELECT";
|
|
403
|
-
const self = this;
|
|
404
|
-
cmd += ' "' + encname + '"';
|
|
405
|
-
if (this.serverSupports("CONDSTORE")) cmd += " (CONDSTORE)";
|
|
406
|
-
this._enqueue(cmd, function (err) {
|
|
407
|
-
if (err) {
|
|
408
|
-
self._box = undefined;
|
|
409
|
-
cb(err);
|
|
410
|
-
} else {
|
|
411
|
-
if (self._box !== undefined) self._box.name = name;
|
|
412
|
-
cb(err, self._box);
|
|
413
|
-
}
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
closeBox(shouldExpunge, cb) {
|
|
417
|
-
if (this._box === undefined) throw new Error("No mailbox is currently selected");
|
|
418
|
-
const self = this;
|
|
419
|
-
if (typeof shouldExpunge === "function") {
|
|
420
|
-
cb = shouldExpunge;
|
|
421
|
-
shouldExpunge = true;
|
|
422
|
-
}
|
|
423
|
-
if (shouldExpunge) {
|
|
424
|
-
this._enqueue("CLOSE", function (err) {
|
|
425
|
-
if (!err) self._box = undefined;
|
|
426
|
-
cb(err);
|
|
427
|
-
});
|
|
428
|
-
} else {
|
|
429
|
-
if (this.serverSupports("UNSELECT")) {
|
|
430
|
-
// use UNSELECT if available, as it claims to be "cleaner" than the
|
|
431
|
-
// alternative "hack"
|
|
432
|
-
this._enqueue("UNSELECT", function (err) {
|
|
433
|
-
if (!err) self._box = undefined;
|
|
434
|
-
cb(err);
|
|
435
|
-
});
|
|
436
|
-
} else {
|
|
437
|
-
// "HACK": close the box without expunging by attempting to SELECT a
|
|
438
|
-
// non-existent mailbox
|
|
439
|
-
const badbox = "NODEJSIMAPCLOSINGBOX" + Date.now();
|
|
440
|
-
this._enqueue('SELECT "' + badbox + '"', function (err) {
|
|
441
|
-
self._box = undefined;
|
|
442
|
-
if (err) return cb(err);
|
|
443
|
-
cb();
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
addBox(name, cb) {
|
|
449
|
-
this._enqueue('CREATE "' + escape(utf7.encode("" + name)) + '"', cb);
|
|
450
|
-
}
|
|
451
|
-
delBox(name, cb) {
|
|
452
|
-
this._enqueue('DELETE "' + escape(utf7.encode("" + name)) + '"', cb);
|
|
453
|
-
}
|
|
454
|
-
renameBox(oldname, newname, cb) {
|
|
455
|
-
const encoldname = escape(utf7.encode("" + oldname));
|
|
456
|
-
const encnewname = escape(utf7.encode("" + newname));
|
|
457
|
-
const self = this;
|
|
458
|
-
this._enqueue('RENAME "' + encoldname + '" "' + encnewname + '"', function (err) {
|
|
459
|
-
if (err) return cb(err);
|
|
460
|
-
if (self._box && self._box.name === oldname && oldname.toUpperCase() !== "INBOX") {
|
|
461
|
-
self._box.name = newname;
|
|
462
|
-
cb(err, self._box);
|
|
463
|
-
} else cb();
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
subscribeBox(name, cb) {
|
|
467
|
-
this._enqueue('SUBSCRIBE "' + escape(utf7.encode("" + name)) + '"', cb);
|
|
468
|
-
}
|
|
469
|
-
unsubscribeBox(name, cb) {
|
|
470
|
-
this._enqueue('UNSUBSCRIBE "' + escape(utf7.encode("" + name)) + '"', cb);
|
|
471
|
-
}
|
|
472
|
-
getSubscribedBoxes(namespace, cb) {
|
|
473
|
-
if (typeof namespace === "function") {
|
|
474
|
-
cb = namespace;
|
|
475
|
-
namespace = "";
|
|
476
|
-
}
|
|
477
|
-
namespace = escape(utf7.encode("" + namespace));
|
|
478
|
-
this._enqueue('LSUB "' + namespace + '" "*"', cb);
|
|
479
|
-
}
|
|
480
|
-
status(boxName, cb) {
|
|
481
|
-
if (this._box && this._box.name === boxName)
|
|
482
|
-
throw new Error("Cannot call status on currently selected mailbox");
|
|
483
|
-
boxName = escape(utf7.encode("" + boxName));
|
|
484
|
-
let info = ["MESSAGES", "RECENT", "UNSEEN", "UIDVALIDITY", "UIDNEXT"];
|
|
485
|
-
if (this.serverSupports("CONDSTORE")) info.push("HIGHESTMODSEQ");
|
|
486
|
-
// @ts-ignore
|
|
487
|
-
info = info.join(" ");
|
|
488
|
-
this._enqueue('STATUS "' + boxName + '" (' + info + ")", cb);
|
|
489
|
-
}
|
|
490
|
-
expunge(uids, cb) {
|
|
491
|
-
if (typeof uids === "function") {
|
|
492
|
-
cb = uids;
|
|
493
|
-
uids = undefined;
|
|
494
|
-
}
|
|
495
|
-
if (uids !== undefined) {
|
|
496
|
-
if (!Array.isArray(uids)) uids = [uids];
|
|
497
|
-
validateUIDList(uids);
|
|
498
|
-
if (uids.length === 0) throw new Error("Empty uid list");
|
|
499
|
-
uids = uids.join(",");
|
|
500
|
-
if (!this.serverSupports("UIDPLUS")) throw new Error("Server does not support this feature (UIDPLUS)");
|
|
501
|
-
this._enqueue("UID EXPUNGE " + uids, cb);
|
|
502
|
-
} else this._enqueue("EXPUNGE", cb);
|
|
503
|
-
}
|
|
504
|
-
search(criteria, cb) {
|
|
505
|
-
this._search("UID ", criteria, cb);
|
|
506
|
-
}
|
|
507
|
-
_search(which, criteria, cb) {
|
|
508
|
-
if (this._box === undefined) throw new Error("No mailbox is currently selected");
|
|
509
|
-
else if (!Array.isArray(criteria)) throw new Error("Expected array for search criteria");
|
|
510
|
-
let cmd = which + "SEARCH";
|
|
511
|
-
const info = { hasUTF8: false /*output*/ };
|
|
512
|
-
let query = buildSearchQuery(criteria, this._caps, info);
|
|
513
|
-
let lines;
|
|
514
|
-
if (info.hasUTF8) {
|
|
515
|
-
cmd += " CHARSET UTF-8";
|
|
516
|
-
lines = query.split(CRLF);
|
|
517
|
-
query = lines.shift();
|
|
518
|
-
}
|
|
519
|
-
cmd += query;
|
|
520
|
-
this._enqueue(cmd, cb);
|
|
521
|
-
if (info.hasUTF8) {
|
|
522
|
-
const req = this._queue !== undefined ? this._queue[this._queue.length - 1] : 0;
|
|
523
|
-
req.lines = lines;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
addFlags(uids, flags, cb) {
|
|
527
|
-
this._store("UID ", uids, { mode: "+", flags: flags }, cb);
|
|
528
|
-
}
|
|
529
|
-
delFlags(uids, flags, cb) {
|
|
530
|
-
this._store("UID ", uids, { mode: "-", flags: flags }, cb);
|
|
531
|
-
}
|
|
532
|
-
setFlags(uids, flags, cb) {
|
|
533
|
-
this._store("UID ", uids, { mode: "", flags: flags }, cb);
|
|
534
|
-
}
|
|
535
|
-
addKeywords(uids, keywords, cb) {
|
|
536
|
-
this._store("UID ", uids, { mode: "+", keywords: keywords }, cb);
|
|
537
|
-
}
|
|
538
|
-
delKeywords(uids, keywords, cb) {
|
|
539
|
-
this._store("UID ", uids, { mode: "-", keywords: keywords }, cb);
|
|
540
|
-
}
|
|
541
|
-
setKeywords(uids, keywords, cb) {
|
|
542
|
-
this._store("UID ", uids, { mode: "", keywords: keywords }, cb);
|
|
543
|
-
}
|
|
544
|
-
_store(which, uids, cfg, cb) {
|
|
545
|
-
const mode = cfg.mode;
|
|
546
|
-
const isFlags = cfg.flags !== undefined;
|
|
547
|
-
let items = isFlags ? cfg.flags : cfg.keywords;
|
|
548
|
-
if (this._box === undefined) throw new Error("No mailbox is currently selected");
|
|
549
|
-
else if (uids === undefined) throw new Error("No messages specified");
|
|
550
|
-
if (!Array.isArray(uids)) uids = [uids];
|
|
551
|
-
validateUIDList(uids);
|
|
552
|
-
if (uids.length === 0) {
|
|
553
|
-
throw new Error("Empty " + (which === "" ? "sequence number" : "uid") + "list");
|
|
554
|
-
}
|
|
555
|
-
if ((!Array.isArray(items) && typeof items !== "string") || (Array.isArray(items) && items.length === 0))
|
|
556
|
-
throw new Error((isFlags ? "Flags" : "Keywords") + " argument must be a string or a non-empty Array");
|
|
557
|
-
if (!Array.isArray(items)) items = [items];
|
|
558
|
-
for (let i = 0, len = items.length; i < len; ++i) {
|
|
559
|
-
if (isFlags) {
|
|
560
|
-
if (items[i][0] !== "\\") items[i] = "\\" + items[i];
|
|
561
|
-
} else {
|
|
562
|
-
// keyword contains any char except control characters (%x00-1F and %x7F)
|
|
563
|
-
// and: '(', ')', '{', ' ', '%', '*', '\', '"', ']'
|
|
564
|
-
if (RE_INVALID_KW_CHARS.test(items[i])) {
|
|
565
|
-
throw new Error('The keyword "' + items[i] + '" contains invalid characters');
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
items = items.join(" ");
|
|
570
|
-
uids = uids.join(",");
|
|
571
|
-
let modifiers = "";
|
|
572
|
-
if (cfg.modseq !== undefined && !this._box.nomodseq) modifiers += "UNCHANGEDSINCE " + cfg.modseq + " ";
|
|
573
|
-
this._enqueue(which + "STORE " + uids + " " + modifiers + mode + "FLAGS.SILENT (" + items + ")", cb);
|
|
574
|
-
}
|
|
575
|
-
copy(uids, boxTo, cb) {
|
|
576
|
-
this._copy("UID ", uids, boxTo, cb);
|
|
577
|
-
}
|
|
578
|
-
_copy(which, uids, boxTo, cb) {
|
|
579
|
-
if (this._box === undefined) throw new Error("No mailbox is currently selected");
|
|
580
|
-
if (!Array.isArray(uids)) uids = [uids];
|
|
581
|
-
validateUIDList(uids);
|
|
582
|
-
if (uids.length === 0) {
|
|
583
|
-
throw new Error("Empty " + (which === "" ? "sequence number" : "uid") + "list");
|
|
584
|
-
}
|
|
585
|
-
boxTo = escape(utf7.encode("" + boxTo));
|
|
586
|
-
this._enqueue(which + "COPY " + uids.join(",") + ' "' + boxTo + '"', cb);
|
|
587
|
-
}
|
|
588
|
-
move(uids, boxTo, cb) {
|
|
589
|
-
this._move("UID ", uids, boxTo, cb);
|
|
590
|
-
}
|
|
591
|
-
_move(which, uids, boxTo, cb) {
|
|
592
|
-
if (this._box === undefined) throw new Error("No mailbox is currently selected");
|
|
593
|
-
if (this.serverSupports("MOVE")) {
|
|
594
|
-
if (!Array.isArray(uids)) uids = [uids];
|
|
595
|
-
validateUIDList(uids);
|
|
596
|
-
if (uids.length === 0) {
|
|
597
|
-
throw new Error("Empty " + (which === "" ? "sequence number" : "uid") + "list");
|
|
598
|
-
}
|
|
599
|
-
uids = uids.join(",");
|
|
600
|
-
boxTo = escape(utf7.encode("" + boxTo));
|
|
601
|
-
this._enqueue(which + "MOVE " + uids + ' "' + boxTo + '"', cb);
|
|
602
|
-
// @ts-ignore
|
|
603
|
-
} else if (this._box.permFlags.indexOf("\\Deleted") === -1 && this._box.flags.indexOf("\\Deleted") === -1) {
|
|
604
|
-
throw new Error("Cannot move message: " + "server does not allow deletion of messages");
|
|
605
|
-
} else {
|
|
606
|
-
let deletedUIDs;
|
|
607
|
-
let task = 0;
|
|
608
|
-
const self = this;
|
|
609
|
-
this._copy(which, uids, boxTo, function ccb(err, info) {
|
|
610
|
-
if (err) return cb(err, info);
|
|
611
|
-
if (task === 0 && which && self.serverSupports("UIDPLUS")) {
|
|
612
|
-
// UIDPLUS gives us a 'UID EXPUNGE n' command to expunge a subset of
|
|
613
|
-
// messages with the \Deleted flag set. This allows us to skip some
|
|
614
|
-
// actions.
|
|
615
|
-
task = 2;
|
|
616
|
-
}
|
|
617
|
-
// Make sure we don't expunge any messages marked as Deleted except the
|
|
618
|
-
// one we are moving
|
|
619
|
-
if (task === 0) {
|
|
620
|
-
self.search(["DELETED"], function (e, result) {
|
|
621
|
-
++task;
|
|
622
|
-
deletedUIDs = result;
|
|
623
|
-
ccb(e, info);
|
|
624
|
-
});
|
|
625
|
-
} else if (task === 1) {
|
|
626
|
-
if (deletedUIDs.length) {
|
|
627
|
-
self.delFlags(deletedUIDs, "\\Deleted", function (e) {
|
|
628
|
-
++task;
|
|
629
|
-
ccb(e, info);
|
|
630
|
-
});
|
|
631
|
-
} else {
|
|
632
|
-
++task;
|
|
633
|
-
ccb(err, info);
|
|
634
|
-
}
|
|
635
|
-
} else if (task === 2) {
|
|
636
|
-
const cbMarkDel = function (e) {
|
|
637
|
-
++task;
|
|
638
|
-
ccb(e, info);
|
|
639
|
-
};
|
|
640
|
-
if (which) self.addFlags(uids, "\\Deleted", cbMarkDel);
|
|
641
|
-
else self.seq.addFlags(uids, "\\Deleted", cbMarkDel);
|
|
642
|
-
} else if (task === 3) {
|
|
643
|
-
if (which && self.serverSupports("UIDPLUS")) {
|
|
644
|
-
self.expunge(uids, function (e) {
|
|
645
|
-
cb(e, info);
|
|
646
|
-
});
|
|
647
|
-
} else {
|
|
648
|
-
self.expunge(function (e) {
|
|
649
|
-
++task;
|
|
650
|
-
ccb(e, info);
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
} else if (task === 4) {
|
|
654
|
-
if (deletedUIDs.length) {
|
|
655
|
-
self.addFlags(deletedUIDs, "\\Deleted", function (e) {
|
|
656
|
-
cb(e, info);
|
|
657
|
-
});
|
|
658
|
-
} else cb(err, info);
|
|
659
|
-
}
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
fetch(uids, options) {
|
|
664
|
-
return this._fetch("UID ", uids, options);
|
|
665
|
-
}
|
|
666
|
-
_fetch(which, uids, options) {
|
|
667
|
-
if (uids === undefined || uids === null || (Array.isArray(uids) && uids.length === 0))
|
|
668
|
-
throw new Error("Nothing to fetch");
|
|
669
|
-
if (!Array.isArray(uids)) uids = [uids];
|
|
670
|
-
validateUIDList(uids);
|
|
671
|
-
if (uids.length === 0) {
|
|
672
|
-
throw new Error("Empty " + (which === "" ? "sequence number" : "uid") + "list");
|
|
673
|
-
}
|
|
674
|
-
uids = uids.join(",");
|
|
675
|
-
let cmd = which + "FETCH " + uids + " (";
|
|
676
|
-
const fetching = [];
|
|
677
|
-
let i;
|
|
678
|
-
let len;
|
|
679
|
-
let key;
|
|
680
|
-
if (this.serverSupports("X-GM-EXT-1")) {
|
|
681
|
-
fetching.push("X-GM-THRID");
|
|
682
|
-
fetching.push("X-GM-MSGID");
|
|
683
|
-
fetching.push("X-GM-LABELS");
|
|
684
|
-
}
|
|
685
|
-
if (this.serverSupports("CONDSTORE") && this._box !== undefined && !this._box.nomodseq) fetching.push("MODSEQ");
|
|
686
|
-
fetching.push("UID");
|
|
687
|
-
fetching.push("FLAGS");
|
|
688
|
-
fetching.push("INTERNALDATE");
|
|
689
|
-
let modifiers;
|
|
690
|
-
if (options) {
|
|
691
|
-
modifiers = options.modifiers;
|
|
692
|
-
if (options.envelope) fetching.push("ENVELOPE");
|
|
693
|
-
if (options.struct) fetching.push("BODYSTRUCTURE");
|
|
694
|
-
if (options.size) fetching.push("RFC822.SIZE");
|
|
695
|
-
if (Array.isArray(options.extensions)) {
|
|
696
|
-
options.extensions.forEach(function (extension) {
|
|
697
|
-
fetching.push(extension.toUpperCase());
|
|
698
|
-
});
|
|
699
|
-
}
|
|
700
|
-
cmd += fetching.join(" ");
|
|
701
|
-
if (options.bodies !== undefined) {
|
|
702
|
-
let bodies = options.bodies;
|
|
703
|
-
const prefix = options.markSeen ? "" : ".PEEK";
|
|
704
|
-
if (!Array.isArray(bodies)) bodies = [bodies];
|
|
705
|
-
for (i = 0, len = bodies.length; i < len; ++i) {
|
|
706
|
-
fetching.push(parseExpr("" + bodies[i]));
|
|
707
|
-
cmd += " BODY" + prefix + "[" + bodies[i] + "]";
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
} else cmd += fetching.join(" ");
|
|
711
|
-
cmd += ")";
|
|
712
|
-
const modkeys = typeof modifiers === "object" ? Object.keys(modifiers) : [];
|
|
713
|
-
let modstr = " (";
|
|
714
|
-
for (i = 0, len = modkeys.length; i < len; ++i) {
|
|
715
|
-
key = modkeys[i].toUpperCase();
|
|
716
|
-
if (
|
|
717
|
-
key === "CHANGEDSINCE" &&
|
|
718
|
-
this.serverSupports("CONDSTORE") &&
|
|
719
|
-
this._box !== undefined &&
|
|
720
|
-
!this._box.nomodseq
|
|
721
|
-
)
|
|
722
|
-
modstr += key + " " + modifiers[modkeys[i]] + " ";
|
|
723
|
-
}
|
|
724
|
-
if (modstr.length > 2) {
|
|
725
|
-
cmd += modstr.substring(0, modstr.length - 1);
|
|
726
|
-
cmd += ")";
|
|
727
|
-
}
|
|
728
|
-
this._enqueue(cmd);
|
|
729
|
-
const req = this._queue !== undefined ? this._queue[this._queue.length - 1] : 0;
|
|
730
|
-
req.fetchCache = {};
|
|
731
|
-
req.fetching = fetching;
|
|
732
|
-
this.debug && this.debug("FETCH: " + JSON.stringify(fetching));
|
|
733
|
-
this.debug && this.debug("CMD: " + cmd);
|
|
734
|
-
return (req.bodyEmitter = new EventEmitter());
|
|
735
|
-
}
|
|
736
|
-
// Extension methods ===========================================================
|
|
737
|
-
setLabels(uids, labels, cb) {
|
|
738
|
-
this._storeLabels("UID ", uids, labels, "", cb);
|
|
739
|
-
}
|
|
740
|
-
addLabels(uids, labels, cb) {
|
|
741
|
-
this._storeLabels("UID ", uids, labels, "+", cb);
|
|
742
|
-
}
|
|
743
|
-
delLabels(uids, labels, cb) {
|
|
744
|
-
this._storeLabels("UID ", uids, labels, "-", cb);
|
|
745
|
-
}
|
|
746
|
-
_storeLabels(which, uids, labels, mode, cb) {
|
|
747
|
-
if (!this.serverSupports("X-GM-EXT-1")) throw new Error("Server must support X-GM-EXT-1 capability");
|
|
748
|
-
else if (this._box === undefined) throw new Error("No mailbox is currently selected");
|
|
749
|
-
else if (uids === undefined) throw new Error("No messages specified");
|
|
750
|
-
if (!Array.isArray(uids)) uids = [uids];
|
|
751
|
-
validateUIDList(uids);
|
|
752
|
-
if (uids.length === 0) {
|
|
753
|
-
throw new Error("Empty " + (which === "" ? "sequence number" : "uid") + "list");
|
|
754
|
-
}
|
|
755
|
-
if ((!Array.isArray(labels) && typeof labels !== "string") || (Array.isArray(labels) && labels.length === 0))
|
|
756
|
-
throw new Error("labels argument must be a string or a non-empty Array");
|
|
757
|
-
if (!Array.isArray(labels)) labels = [labels];
|
|
758
|
-
labels = labels
|
|
759
|
-
.map(function (v) {
|
|
760
|
-
return '"' + escape(utf7.encode("" + v)) + '"';
|
|
761
|
-
})
|
|
762
|
-
.join(" ");
|
|
763
|
-
uids = uids.join(",");
|
|
764
|
-
this._enqueue(which + "STORE " + uids + " " + mode + "X-GM-LABELS.SILENT (" + labels + ")", cb);
|
|
765
|
-
}
|
|
766
|
-
sort(sorts, criteria, cb) {
|
|
767
|
-
this._sort("UID ", sorts, criteria, cb);
|
|
768
|
-
}
|
|
769
|
-
_sort(which, sorts, criteria, cb) {
|
|
770
|
-
let displaySupported = false;
|
|
771
|
-
if (this._box === undefined) throw new Error("No mailbox is currently selected");
|
|
772
|
-
else if (!Array.isArray(sorts) || !sorts.length)
|
|
773
|
-
throw new Error("Expected array with at least one sort criteria");
|
|
774
|
-
else if (!Array.isArray(criteria)) throw new Error("Expected array for search criteria");
|
|
775
|
-
else if (!this.serverSupports("SORT")) throw new Error("Sort is not supported on the server");
|
|
776
|
-
|
|
777
|
-
if (this.serverSupports("SORT=DISPLAY")) {
|
|
778
|
-
displaySupported = true;
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
sorts = sorts.map(function (c) {
|
|
782
|
-
if (typeof c !== "string")
|
|
783
|
-
throw new Error("Unexpected sort criteria data type. " + "Expected string. Got: " + typeof criteria);
|
|
784
|
-
let modifier = "";
|
|
785
|
-
if (c[0] === "-") {
|
|
786
|
-
modifier = "REVERSE ";
|
|
787
|
-
c = c.substring(1);
|
|
788
|
-
}
|
|
789
|
-
switch (c.toUpperCase()) {
|
|
790
|
-
case "ARRIVAL":
|
|
791
|
-
case "CC":
|
|
792
|
-
case "DATE":
|
|
793
|
-
case "FROM":
|
|
794
|
-
case "SIZE":
|
|
795
|
-
case "SUBJECT":
|
|
796
|
-
case "TO":
|
|
797
|
-
break;
|
|
798
|
-
case "DISPLAYFROM":
|
|
799
|
-
case "DISPLAYTO":
|
|
800
|
-
if (!displaySupported) {
|
|
801
|
-
throw new Error("Unexpected sort criteria: " + c);
|
|
802
|
-
}
|
|
803
|
-
break;
|
|
804
|
-
default:
|
|
805
|
-
throw new Error("Unexpected sort criteria: " + c);
|
|
806
|
-
}
|
|
807
|
-
return modifier + c;
|
|
808
|
-
});
|
|
809
|
-
sorts = sorts.join(" ");
|
|
810
|
-
const info = { hasUTF8: false /*output*/ };
|
|
811
|
-
let query = buildSearchQuery(criteria, this._caps, info);
|
|
812
|
-
let charset = "US-ASCII";
|
|
813
|
-
let lines;
|
|
814
|
-
if (info.hasUTF8) {
|
|
815
|
-
charset = "UTF-8";
|
|
816
|
-
lines = query.split(CRLF);
|
|
817
|
-
query = lines.shift();
|
|
818
|
-
}
|
|
819
|
-
this._enqueue(which + "SORT (" + sorts + ") " + charset + query, cb);
|
|
820
|
-
if (info.hasUTF8) {
|
|
821
|
-
const req = this._queue !== undefined ? this._queue[this._queue.length - 1] : 0;
|
|
822
|
-
req.lines = lines;
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
esearch(criteria, options, cb) {
|
|
826
|
-
this._esearch("UID ", criteria, options, cb);
|
|
827
|
-
}
|
|
828
|
-
_esearch(which, criteria, options, cb) {
|
|
829
|
-
if (this._box === undefined) throw new Error("No mailbox is currently selected");
|
|
830
|
-
else if (!Array.isArray(criteria)) throw new Error("Expected array for search options");
|
|
831
|
-
const info = { hasUTF8: false /*output*/ };
|
|
832
|
-
let query = buildSearchQuery(criteria, this._caps, info);
|
|
833
|
-
let charset = "";
|
|
834
|
-
let lines;
|
|
835
|
-
if (info.hasUTF8) {
|
|
836
|
-
charset = " CHARSET UTF-8";
|
|
837
|
-
lines = query.split(CRLF);
|
|
838
|
-
query = lines.shift();
|
|
839
|
-
}
|
|
840
|
-
if (typeof options === "function") {
|
|
841
|
-
cb = options;
|
|
842
|
-
options = "";
|
|
843
|
-
} else if (!options) options = "";
|
|
844
|
-
if (Array.isArray(options)) options = options.join(" ");
|
|
845
|
-
this._enqueue(which + "SEARCH RETURN (" + options + ")" + charset + query, cb);
|
|
846
|
-
if (info.hasUTF8) {
|
|
847
|
-
const req = this._queue !== undefined ? this._queue[this._queue.length - 1] : 0;
|
|
848
|
-
req.lines = lines;
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
setQuota(quotaRoot, limits, cb) {
|
|
852
|
-
if (typeof limits === "function") {
|
|
853
|
-
cb = limits;
|
|
854
|
-
limits = {};
|
|
855
|
-
}
|
|
856
|
-
let triplets = "";
|
|
857
|
-
for (const l in limits) {
|
|
858
|
-
if (triplets) triplets += " ";
|
|
859
|
-
triplets += l + " " + limits[l];
|
|
860
|
-
}
|
|
861
|
-
quotaRoot = escape(utf7.encode("" + quotaRoot));
|
|
862
|
-
this._enqueue('SETQUOTA "' + quotaRoot + '" (' + triplets + ")", function (err, quotalist) {
|
|
863
|
-
if (err) return cb(err);
|
|
864
|
-
cb(err, quotalist ? quotalist[0] : limits);
|
|
865
|
-
});
|
|
866
|
-
}
|
|
867
|
-
getQuota(quotaRoot, cb) {
|
|
868
|
-
quotaRoot = escape(utf7.encode("" + quotaRoot));
|
|
869
|
-
this._enqueue('GETQUOTA "' + quotaRoot + '"', function (err, quotalist) {
|
|
870
|
-
if (err) return cb(err);
|
|
871
|
-
cb(err, quotalist[0]);
|
|
872
|
-
});
|
|
873
|
-
}
|
|
874
|
-
getQuotaRoot(boxName, cb) {
|
|
875
|
-
boxName = escape(utf7.encode("" + boxName));
|
|
876
|
-
this._enqueue('GETQUOTAROOT "' + boxName + '"', function (err, quotalist) {
|
|
877
|
-
if (err) return cb(err);
|
|
878
|
-
const quotas = {};
|
|
879
|
-
if (quotalist) {
|
|
880
|
-
for (let i = 0, len = quotalist.length; i < len; ++i) {
|
|
881
|
-
if (quotalist[i].root === "") {
|
|
882
|
-
quotas["data"] = quotalist[i].resources;
|
|
883
|
-
} else {
|
|
884
|
-
quotas[quotalist[i].root] = quotalist[i].resources;
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
cb(err, quotas);
|
|
889
|
-
});
|
|
890
|
-
}
|
|
891
|
-
thread(algorithm, criteria, cb) {
|
|
892
|
-
this._thread("UID ", algorithm, criteria, cb);
|
|
893
|
-
}
|
|
894
|
-
_thread(which, algorithm, criteria, cb) {
|
|
895
|
-
algorithm = algorithm.toUpperCase();
|
|
896
|
-
if (!this.serverSupports("THREAD=" + algorithm))
|
|
897
|
-
throw new Error("Server does not support that threading algorithm");
|
|
898
|
-
const info = { hasUTF8: false /*output*/ };
|
|
899
|
-
let query = buildSearchQuery(criteria, this._caps, info);
|
|
900
|
-
let charset = "US-ASCII";
|
|
901
|
-
let lines;
|
|
902
|
-
if (info.hasUTF8) {
|
|
903
|
-
charset = "UTF-8";
|
|
904
|
-
lines = query.split(CRLF);
|
|
905
|
-
query = lines.shift();
|
|
906
|
-
}
|
|
907
|
-
this._enqueue(which + "THREAD " + algorithm + " " + charset + query, cb);
|
|
908
|
-
if (info.hasUTF8) {
|
|
909
|
-
const req = this._queue !== undefined ? this._queue[this._queue.length - 1] : 0;
|
|
910
|
-
req.lines = lines;
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
addFlagsSince(uids, flags, modseq, cb) {
|
|
914
|
-
this._store("UID ", uids, { mode: "+", flags: flags, modseq: modseq }, cb);
|
|
915
|
-
}
|
|
916
|
-
delFlagsSince(uids, flags, modseq, cb) {
|
|
917
|
-
this._store("UID ", uids, { mode: "-", flags: flags, modseq: modseq }, cb);
|
|
918
|
-
}
|
|
919
|
-
setFlagsSince(uids, flags, modseq, cb) {
|
|
920
|
-
this._store("UID ", uids, { mode: "", flags: flags, modseq: modseq }, cb);
|
|
921
|
-
}
|
|
922
|
-
addKeywordsSince(uids, keywords, modseq, cb) {
|
|
923
|
-
this._store("UID ", uids, { mode: "+", keywords: keywords, modseq: modseq }, cb);
|
|
924
|
-
}
|
|
925
|
-
delKeywordsSince(uids, keywords, modseq, cb) {
|
|
926
|
-
this._store("UID ", uids, { mode: "-", keywords: keywords, modseq: modseq }, cb);
|
|
927
|
-
}
|
|
928
|
-
setKeywordsSince(uids, keywords, modseq, cb) {
|
|
929
|
-
this._store("UID ", uids, { mode: "", keywords: keywords, modseq: modseq }, cb);
|
|
930
|
-
}
|
|
931
|
-
_resUntagged(info) {
|
|
932
|
-
const type = info.type;
|
|
933
|
-
let i;
|
|
934
|
-
let len;
|
|
935
|
-
let box;
|
|
936
|
-
let attrs;
|
|
937
|
-
let key;
|
|
938
|
-
if (type === "bye") this._sock.end();
|
|
939
|
-
else if (type === "namespace") this.namespaces = info.text;
|
|
940
|
-
else if (type === "id") this._curReq.cbargs.push(info.text);
|
|
941
|
-
else if (type === "capability")
|
|
942
|
-
this._caps = info.text.map(function (v) {
|
|
943
|
-
return v.toUpperCase();
|
|
944
|
-
});
|
|
945
|
-
else if (type === "preauth") this.state = "authenticated";
|
|
946
|
-
else if (type === "sort" || type === "thread" || type === "esearch") this._curReq.cbargs.push(info.text);
|
|
947
|
-
else if (type === "search") {
|
|
948
|
-
if (info.text.results !== undefined) {
|
|
949
|
-
// CONDSTORE-modified search results
|
|
950
|
-
this._curReq.cbargs.push(info.text.results);
|
|
951
|
-
this._curReq.cbargs.push(info.text.modseq);
|
|
952
|
-
} else this._curReq.cbargs.push(info.text);
|
|
953
|
-
} else if (type === "quota") {
|
|
954
|
-
const cbargs = this._curReq.cbargs;
|
|
955
|
-
if (!cbargs.length) cbargs.push([]);
|
|
956
|
-
cbargs[0].push(info.text);
|
|
957
|
-
} else if (type === "recent") {
|
|
958
|
-
if (!this._box && RE_OPENBOX.test(this._curReq.type)) this._createCurrentBox();
|
|
959
|
-
if (this._box) {
|
|
960
|
-
this._box.messages.new = info.num;
|
|
961
|
-
if (info.num > 0 && this.state === "authenticated") {
|
|
962
|
-
this._box.seq = info.num;
|
|
963
|
-
this._box.time = Date.now();
|
|
964
|
-
this._box.reason = "recent";
|
|
965
|
-
this.emit("new", this._box, info, this.state, this.client);
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
} else if (type === "flags") {
|
|
969
|
-
if (!this._box && RE_OPENBOX.test(this._curReq.type)) this._createCurrentBox();
|
|
970
|
-
if (this._box) this._box.flags = info.text;
|
|
971
|
-
} else if (type === "bad" || type === "no") {
|
|
972
|
-
if (info.textCode && info.textCode.key && info.textCode.key === "WEBALERT") {
|
|
973
|
-
this.emit("webalert", {
|
|
974
|
-
url: info.textCode.val,
|
|
975
|
-
message: info.text,
|
|
976
|
-
client: this.client,
|
|
977
|
-
});
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
if (this.state === "connected" && !this._curReq) {
|
|
981
|
-
clearTimeout(this._tmrConn);
|
|
982
|
-
clearTimeout(this._tmrAuth);
|
|
983
|
-
const err = new Error("Received negative welcome: " + info.text);
|
|
984
|
-
err["source"] = "protocol";
|
|
985
|
-
this.emit("error", err, this.client, "protocol");
|
|
986
|
-
this._sock.end();
|
|
987
|
-
}
|
|
988
|
-
} else if (type === "exists") {
|
|
989
|
-
if (!this._box && RE_OPENBOX.test(this._curReq.type)) this._createCurrentBox();
|
|
990
|
-
if (this._box) {
|
|
991
|
-
let prev = this._box.messages.total;
|
|
992
|
-
const now = info.num;
|
|
993
|
-
this._box.messages.total = now;
|
|
994
|
-
if (this._box.name === "") prev = now;
|
|
995
|
-
if (now > prev && this.state === "authenticated") {
|
|
996
|
-
this._box.messages.new = now - prev;
|
|
997
|
-
if (this._box.uidnext != null) this._box.uidnext = this._box.uidnext + this._box.messages.new;
|
|
998
|
-
this._box.seq = info.num;
|
|
999
|
-
this._box.reason = "new";
|
|
1000
|
-
this._box.time = Date.now();
|
|
1001
|
-
this.emit("mail", this._box.messages.new, this._box, info, this.client);
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
} else if (type === "expunge") {
|
|
1005
|
-
if (this._box) {
|
|
1006
|
-
if (this._box.messages.total > 0) --this._box.messages.total;
|
|
1007
|
-
this.emit("expunge", info.num, this.client);
|
|
1008
|
-
}
|
|
1009
|
-
} else if (type === "ok") {
|
|
1010
|
-
if (this.state === "connected" && !this._curReq) this._login();
|
|
1011
|
-
else if (typeof info.textCode === "string" && info.textCode.toUpperCase() === "ALERT")
|
|
1012
|
-
this.emit("alert", info.text, this.client);
|
|
1013
|
-
else if (this._curReq && info.textCode && RE_OPENBOX.test(this._curReq.type)) {
|
|
1014
|
-
// we're opening a mailbox
|
|
1015
|
-
if (!this._box) this._createCurrentBox();
|
|
1016
|
-
if (info.textCode.key) key = info.textCode.key.toUpperCase();
|
|
1017
|
-
else key = info.textCode;
|
|
1018
|
-
if (this._box === undefined) return;
|
|
1019
|
-
if (key === "UIDVALIDITY") this._box.uidvalidity = info.textCode.val;
|
|
1020
|
-
else if (key === "UIDNEXT") this._box.uidnext = info.textCode.val;
|
|
1021
|
-
else if (key === "HIGHESTMODSEQ") this._box.highestmodseq = "" + info.textCode.val;
|
|
1022
|
-
else if (key === "PERMANENTFLAGS") {
|
|
1023
|
-
let idx;
|
|
1024
|
-
let permFlags;
|
|
1025
|
-
let keywords;
|
|
1026
|
-
this._box.permFlags = permFlags = info.textCode.val;
|
|
1027
|
-
// @ts-ignore
|
|
1028
|
-
if ((idx = this._box.permFlags.indexOf("\\*")) > -1) {
|
|
1029
|
-
this._box.newKeywords = true;
|
|
1030
|
-
permFlags.splice(idx, 1);
|
|
1031
|
-
}
|
|
1032
|
-
this._box.keywords = keywords = permFlags.filter(function (f) {
|
|
1033
|
-
return f[0] !== "\\";
|
|
1034
|
-
});
|
|
1035
|
-
for (i = 0, len = keywords.length; i < len; ++i)
|
|
1036
|
-
permFlags.splice(permFlags.indexOf(keywords[i]), 1);
|
|
1037
|
-
} else if (key === "UIDNOTSTICKY") this._box.persistentUIDs = false;
|
|
1038
|
-
else if (key === "NOMODSEQ") this._box.nomodseq = true;
|
|
1039
|
-
} else if (typeof info.textCode === "string" && info.textCode.toUpperCase() === "UIDVALIDITY")
|
|
1040
|
-
this.emit("uidvalidity", info.text, this.client);
|
|
1041
|
-
} else if (type === "list" || type === "lsub" || type === "xlist") {
|
|
1042
|
-
if (this.delimiter === undefined) this.delimiter = info.text.delimiter;
|
|
1043
|
-
else {
|
|
1044
|
-
if (this._curReq.cbargs.length === 0) this._curReq.cbargs.push({});
|
|
1045
|
-
box = {
|
|
1046
|
-
attribs: info.text.flags,
|
|
1047
|
-
delimiter: info.text.delimiter,
|
|
1048
|
-
children: null,
|
|
1049
|
-
parent: null,
|
|
1050
|
-
};
|
|
1051
|
-
for (i = 0, len = SPECIAL_USE_ATTRIBUTES.length; i < len; ++i)
|
|
1052
|
-
if (box.attribs.indexOf(SPECIAL_USE_ATTRIBUTES[i]) > -1)
|
|
1053
|
-
box["special_use_attrib"] = SPECIAL_USE_ATTRIBUTES[i];
|
|
1054
|
-
let name = info.text.name;
|
|
1055
|
-
let curChildren = this._curReq.cbargs[0];
|
|
1056
|
-
if (box.delimiter) {
|
|
1057
|
-
const path = name.split(box.delimiter);
|
|
1058
|
-
let parent = null;
|
|
1059
|
-
name = path.pop();
|
|
1060
|
-
for (i = 0, len = path.length; i < len; ++i) {
|
|
1061
|
-
if (!curChildren[path[i]]) curChildren[path[i]] = {};
|
|
1062
|
-
if (!curChildren[path[i]].children) curChildren[path[i]].children = {};
|
|
1063
|
-
parent = curChildren[path[i]];
|
|
1064
|
-
curChildren = curChildren[path[i]].children;
|
|
1065
|
-
}
|
|
1066
|
-
box.parent = parent;
|
|
1067
|
-
}
|
|
1068
|
-
if (curChildren[name]) box.children = curChildren[name].children;
|
|
1069
|
-
curChildren[name] = box;
|
|
1070
|
-
}
|
|
1071
|
-
} else if (type === "status") {
|
|
1072
|
-
box = {
|
|
1073
|
-
name: info.text.name,
|
|
1074
|
-
uidnext: 0,
|
|
1075
|
-
uidvalidity: 0,
|
|
1076
|
-
messages: {
|
|
1077
|
-
total: 0,
|
|
1078
|
-
new: 0,
|
|
1079
|
-
unseen: 0,
|
|
1080
|
-
},
|
|
1081
|
-
};
|
|
1082
|
-
attrs = info.text.attrs;
|
|
1083
|
-
if (attrs) {
|
|
1084
|
-
if (attrs.recent !== undefined) box.messages.new = attrs.recent;
|
|
1085
|
-
if (attrs.unseen !== undefined) box.messages.unseen = attrs.unseen;
|
|
1086
|
-
if (attrs.messages !== undefined) box.messages.total = attrs.messages;
|
|
1087
|
-
if (attrs.uidnext !== undefined) box.uidnext = attrs.uidnext;
|
|
1088
|
-
if (attrs.uidvalidity !== undefined) box.uidvalidity = attrs.uidvalidity;
|
|
1089
|
-
if (attrs.highestmodseq !== undefined)
|
|
1090
|
-
// CONDSTORE
|
|
1091
|
-
box["highestmodseq"] = "" + attrs.highestmodseq;
|
|
1092
|
-
}
|
|
1093
|
-
this._curReq.cbargs.push(box);
|
|
1094
|
-
} else if (type === "fetch") {
|
|
1095
|
-
if (this._curReq && this._curReq.fullcmd) {
|
|
1096
|
-
this.debug && this.debug("this._curReq.fullcmd: " + this._curReq.fullcmd);
|
|
1097
|
-
}
|
|
1098
|
-
this.debug && this.debug("infos: " + JSON.stringify(info));
|
|
1099
|
-
if (this._curReq && this._curReq.fullcmd != null && /^(?:UID )?FETCH/.test(this._curReq.fullcmd)) {
|
|
1100
|
-
this.debug && this.debug("this._curReq.fullcmd: " + this._curReq.fullcmd);
|
|
1101
|
-
this.debug &&
|
|
1102
|
-
this.debug(
|
|
1103
|
-
"this._curReq.fetchCache[info.num]: " + JSON.stringify(this._curReq.fetchCache[info.num]),
|
|
1104
|
-
);
|
|
1105
|
-
// FETCH response sent as result of FETCH request
|
|
1106
|
-
const msg = this._curReq.fetchCache[info.num];
|
|
1107
|
-
const keys = Object.keys(info.text);
|
|
1108
|
-
const keyslen = keys.length;
|
|
1109
|
-
let toget;
|
|
1110
|
-
let msgEmitter;
|
|
1111
|
-
let j;
|
|
1112
|
-
if (msg === undefined) {
|
|
1113
|
-
// simple case -- no bodies were streamed
|
|
1114
|
-
toget = this._curReq.fetching.slice(0);
|
|
1115
|
-
if (toget.length === 0) return;
|
|
1116
|
-
msgEmitter = new EventEmitter();
|
|
1117
|
-
attrs = {};
|
|
1118
|
-
this._curReq.bodyEmitter.emit("message", msgEmitter, info.num);
|
|
1119
|
-
} else {
|
|
1120
|
-
toget = msg.toget;
|
|
1121
|
-
msgEmitter = msg.msgEmitter;
|
|
1122
|
-
attrs = msg.attrs;
|
|
1123
|
-
}
|
|
1124
|
-
i = toget.length;
|
|
1125
|
-
if (i === 0) {
|
|
1126
|
-
if (msg && !msg.ended) {
|
|
1127
|
-
msg.ended = true;
|
|
1128
|
-
process.nextTick(function () {
|
|
1129
|
-
msgEmitter.emit("end");
|
|
1130
|
-
});
|
|
1131
|
-
}
|
|
1132
|
-
return;
|
|
1133
|
-
}
|
|
1134
|
-
if (keyslen > 0) {
|
|
1135
|
-
while (--i >= 0) {
|
|
1136
|
-
j = keyslen;
|
|
1137
|
-
while (--j >= 0) {
|
|
1138
|
-
if (keys[j].toUpperCase() === toget[i]) {
|
|
1139
|
-
if (!RE_BODYPART.test(toget[i])) {
|
|
1140
|
-
if (toget[i] === "X-GM-LABELS") {
|
|
1141
|
-
const labels = info.text[keys[j]];
|
|
1142
|
-
for (let k = 0, lenk = labels.length; k < lenk; ++k)
|
|
1143
|
-
labels[k] = utf7.decode(("" + labels[k]).replace(RE_ESCAPE, "\\"));
|
|
1144
|
-
}
|
|
1145
|
-
key = FETCH_ATTR_MAP[toget[i]];
|
|
1146
|
-
if (!key) key = toget[i].toLowerCase();
|
|
1147
|
-
attrs[key] = info.text[keys[j]];
|
|
1148
|
-
}
|
|
1149
|
-
toget.splice(i, 1);
|
|
1150
|
-
break;
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
if (toget.length === 0) {
|
|
1156
|
-
if (msg) msg.ended = true;
|
|
1157
|
-
process.nextTick(function () {
|
|
1158
|
-
msgEmitter.emit("attributes", attrs);
|
|
1159
|
-
msgEmitter.emit("end");
|
|
1160
|
-
});
|
|
1161
|
-
} else if (msg === undefined) {
|
|
1162
|
-
this._curReq.fetchCache[info.num] = {
|
|
1163
|
-
msgEmitter: msgEmitter,
|
|
1164
|
-
toget: toget,
|
|
1165
|
-
attrs: attrs,
|
|
1166
|
-
ended: false,
|
|
1167
|
-
};
|
|
1168
|
-
}
|
|
1169
|
-
} else {
|
|
1170
|
-
// FETCH response sent as result of STORE request or sent unilaterally,
|
|
1171
|
-
// treat them as the same for now for simplicity
|
|
1172
|
-
if (!this._box && RE_OPENBOX.test(this._curReq.type)) this._createCurrentBox();
|
|
1173
|
-
if (this._box) {
|
|
1174
|
-
if (this._box.seq === info.num && this._box.reason === "new") {
|
|
1175
|
-
if (Date.now() - this._box.time < 10) {
|
|
1176
|
-
this._box.seq = info.num;
|
|
1177
|
-
this._box.time = Date.now();
|
|
1178
|
-
this._box.reason = "update";
|
|
1179
|
-
return;
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
this._box.seq = info.num;
|
|
1183
|
-
this._box.time = Date.now();
|
|
1184
|
-
this._box.reason = "update";
|
|
1185
|
-
}
|
|
1186
|
-
this.emit("update", info.num, info, this.client);
|
|
1187
|
-
}
|
|
1188
|
-
} else if (type !== "") {
|
|
1189
|
-
this.emit("type", this._box, info, this._curReq, this.client);
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
_resTagged(info) {
|
|
1193
|
-
const req = this._curReq;
|
|
1194
|
-
let err;
|
|
1195
|
-
if (!req) return;
|
|
1196
|
-
this._curReq = undefined;
|
|
1197
|
-
if (info.type === "no" || info.type === "bad") {
|
|
1198
|
-
let errtext;
|
|
1199
|
-
if (info.text) errtext = info.text;
|
|
1200
|
-
else errtext = req.oauthError;
|
|
1201
|
-
err = new Error(errtext);
|
|
1202
|
-
err["type"] = info.type;
|
|
1203
|
-
err["textCode"] = info.textCode;
|
|
1204
|
-
err["source"] = "protocol";
|
|
1205
|
-
} else if (this._box) {
|
|
1206
|
-
if (req.type === "EXAMINE" || req.type === "SELECT") {
|
|
1207
|
-
this._box.readOnly = typeof info.textCode === "string" && info.textCode.toUpperCase() === "READ-ONLY";
|
|
1208
|
-
}
|
|
1209
|
-
// According to RFC 3501, UID commands do not give errors for
|
|
1210
|
-
// non-existant user-supplied UIDs, so give the callback empty results
|
|
1211
|
-
// if we unexpectedly received no untagged responses.
|
|
1212
|
-
if (RE_UIDCMD_HASRESULTS.test(req.fullcmd) && req.cbargs.length === 0) req.cbargs.push([]);
|
|
1213
|
-
}
|
|
1214
|
-
if (req.bodyEmitter) {
|
|
1215
|
-
const bodyEmitter = req.bodyEmitter;
|
|
1216
|
-
if (err) {
|
|
1217
|
-
bodyEmitter.emit("error", err);
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
process.nextTick(function () {
|
|
1221
|
-
bodyEmitter.emit("end");
|
|
1222
|
-
});
|
|
1223
|
-
} else {
|
|
1224
|
-
req.cbargs.unshift(err);
|
|
1225
|
-
if (info.textCode && info.textCode.key) {
|
|
1226
|
-
const key = info.textCode.key.toUpperCase();
|
|
1227
|
-
if (key === "APPENDUID")
|
|
1228
|
-
// [uidvalidity, newUID]
|
|
1229
|
-
req.cbargs.push(info.textCode.val[1]);
|
|
1230
|
-
else if (key === "COPYUID")
|
|
1231
|
-
// [uidvalidity, sourceUIDs, destUIDs]
|
|
1232
|
-
req.cbargs.push(info.textCode.val[2]);
|
|
1233
|
-
}
|
|
1234
|
-
req.cb && req.cb.apply(this, req.cbargs);
|
|
1235
|
-
}
|
|
1236
|
-
if (
|
|
1237
|
-
this._queue !== undefined &&
|
|
1238
|
-
this._config !== undefined &&
|
|
1239
|
-
this._idle !== undefined &&
|
|
1240
|
-
this._queue.length === 0 &&
|
|
1241
|
-
this._config.keepalive &&
|
|
1242
|
-
this.state === "authenticated" &&
|
|
1243
|
-
!this._idle.enabled
|
|
1244
|
-
) {
|
|
1245
|
-
this._idle.enabled = true;
|
|
1246
|
-
this._doKeepaliveTimer(true);
|
|
1247
|
-
}
|
|
1248
|
-
this._processQueue();
|
|
1249
|
-
}
|
|
1250
|
-
_createCurrentBox() {
|
|
1251
|
-
this._box = {
|
|
1252
|
-
name: "",
|
|
1253
|
-
flags: [],
|
|
1254
|
-
readOnly: false,
|
|
1255
|
-
uidvalidity: 0,
|
|
1256
|
-
uidnext: 0,
|
|
1257
|
-
permFlags: [],
|
|
1258
|
-
keywords: [],
|
|
1259
|
-
newKeywords: false,
|
|
1260
|
-
persistentUIDs: true,
|
|
1261
|
-
nomodseq: false,
|
|
1262
|
-
seq: 0,
|
|
1263
|
-
time: 0,
|
|
1264
|
-
reason: "",
|
|
1265
|
-
user: this.client,
|
|
1266
|
-
messages: {
|
|
1267
|
-
total: 0,
|
|
1268
|
-
new: 0,
|
|
1269
|
-
},
|
|
1270
|
-
};
|
|
1271
|
-
}
|
|
1272
|
-
_doKeepaliveTimer(immediate) {
|
|
1273
|
-
const self = this;
|
|
1274
|
-
if (this._config === undefined) return;
|
|
1275
|
-
const interval = this._config.keepalive.interval || KEEPALIVE_INTERVAL;
|
|
1276
|
-
const idleWait = this._config.keepalive.idleInterval || MAX_IDLE_WAIT;
|
|
1277
|
-
const forceNoop = this._config.keepalive.forceNoop || false;
|
|
1278
|
-
const timerfn = function () {
|
|
1279
|
-
if (self._idle === undefined) return;
|
|
1280
|
-
if (self._idle.enabled) {
|
|
1281
|
-
// unlike NOOP, IDLE is only a valid command after authenticating
|
|
1282
|
-
if (!self.serverSupports("IDLE") || self.state !== "authenticated" || forceNoop)
|
|
1283
|
-
self._enqueue("NOOP", true);
|
|
1284
|
-
else {
|
|
1285
|
-
if (self._idle.started === undefined) {
|
|
1286
|
-
// @ts-ignore
|
|
1287
|
-
self._idle.started = 0;
|
|
1288
|
-
self._enqueue("IDLE", true);
|
|
1289
|
-
} else if (self._idle.started > 0) {
|
|
1290
|
-
const timeDiff = Date.now() - self._idle.started;
|
|
1291
|
-
if (timeDiff >= idleWait) {
|
|
1292
|
-
self._idle.enabled = false;
|
|
1293
|
-
self.debug && self.debug("=> DONE");
|
|
1294
|
-
self._sock.write("DONE" + CRLF);
|
|
1295
|
-
return;
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
self._tmrKeepalive = setTimeout(timerfn, interval);
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
};
|
|
1302
|
-
if (immediate) timerfn();
|
|
1303
|
-
else this._tmrKeepalive = setTimeout(timerfn, interval);
|
|
1304
|
-
}
|
|
1305
|
-
_login() {
|
|
1306
|
-
const self = this;
|
|
1307
|
-
let checkedNS = false;
|
|
1308
|
-
const reentry = function (err) {
|
|
1309
|
-
clearTimeout(self._tmrAuth);
|
|
1310
|
-
if (err) {
|
|
1311
|
-
self.emit("error", err, self.client, "_login");
|
|
1312
|
-
return self._sock.end();
|
|
1313
|
-
}
|
|
1314
|
-
if (self._config !== undefined && self._config.id) {
|
|
1315
|
-
self._enqueue(
|
|
1316
|
-
`ID ("name" "${escape(self._config.id.name)}" "version" "${escape(
|
|
1317
|
-
self._config.id.version,
|
|
1318
|
-
)}" "vendor" "${escape(self._config.id.vendor)}")`,
|
|
1319
|
-
reentry,
|
|
1320
|
-
);
|
|
1321
|
-
}
|
|
1322
|
-
// 2. Get the list of available namespaces (RFC2342)
|
|
1323
|
-
if (!checkedNS && self.serverSupports("NAMESPACE")) {
|
|
1324
|
-
checkedNS = true;
|
|
1325
|
-
return self._enqueue("NAMESPACE", reentry);
|
|
1326
|
-
}
|
|
1327
|
-
// 3. Get the top-level mailbox hierarchy delimiter used by the server
|
|
1328
|
-
self._enqueue('LIST "" ""', function () {
|
|
1329
|
-
self.state = "authenticated";
|
|
1330
|
-
self.emit("isready", self.inbox, self.client);
|
|
1331
|
-
});
|
|
1332
|
-
};
|
|
1333
|
-
// 1. Get the supported capabilities
|
|
1334
|
-
self._enqueue("CAPABILITY", function () {
|
|
1335
|
-
// No need to attempt the login sequence if we're on a PREAUTH connection.
|
|
1336
|
-
if (self.state === "connected") {
|
|
1337
|
-
let err;
|
|
1338
|
-
const checkCaps = function (error) {
|
|
1339
|
-
if (error) {
|
|
1340
|
-
error["source"] = "authentication";
|
|
1341
|
-
return reentry(error);
|
|
1342
|
-
}
|
|
1343
|
-
if (self._caps === undefined) {
|
|
1344
|
-
// Fetch server capabilities if they were not automatically
|
|
1345
|
-
// provided after authentication
|
|
1346
|
-
return self._enqueue("CAPABILITY", reentry);
|
|
1347
|
-
} else reentry();
|
|
1348
|
-
};
|
|
1349
|
-
if (
|
|
1350
|
-
self.serverSupports("STARTTLS") &&
|
|
1351
|
-
self._config !== undefined &&
|
|
1352
|
-
(self._config.autotls === "always" ||
|
|
1353
|
-
(self._config.autotls === "required" && self.serverSupports("LOGINDISABLED")))
|
|
1354
|
-
) {
|
|
1355
|
-
self._starttls();
|
|
1356
|
-
return;
|
|
1357
|
-
}
|
|
1358
|
-
if (self.serverSupports("LOGINDISABLED")) {
|
|
1359
|
-
err = new Error("Logging in is disabled on this server");
|
|
1360
|
-
err["source"] = "authentication";
|
|
1361
|
-
return reentry(err);
|
|
1362
|
-
}
|
|
1363
|
-
let cmd;
|
|
1364
|
-
if (self.serverSupports("AUTH=XOAUTH") && self._config !== undefined && self._config.xoauth) {
|
|
1365
|
-
self._caps = undefined;
|
|
1366
|
-
cmd = "AUTHENTICATE XOAUTH";
|
|
1367
|
-
// are there any servers that support XOAUTH/XOAUTH2 and not SASL-IR?
|
|
1368
|
-
//if (self.serverSupports('SASL-IR'))
|
|
1369
|
-
cmd += " " + escape(self._config.xoauth);
|
|
1370
|
-
self._enqueue(cmd, checkCaps);
|
|
1371
|
-
} else if (self.serverSupports("AUTH=XOAUTH2") && self._config !== undefined && self._config.xoauth2) {
|
|
1372
|
-
self._caps = undefined;
|
|
1373
|
-
cmd = "AUTHENTICATE XOAUTH2";
|
|
1374
|
-
//if (self.serverSupports('SASL-IR'))
|
|
1375
|
-
cmd += " " + escape(self._config.xoauth2);
|
|
1376
|
-
self._enqueue(cmd, checkCaps);
|
|
1377
|
-
} else if (self._config !== undefined && self._config.user && self._config.password) {
|
|
1378
|
-
if (self.serverSupports("AUTH=CRAM-MD5")) {
|
|
1379
|
-
cmd = "AUTHENTICATE CRAM-MD5";
|
|
1380
|
-
} else {
|
|
1381
|
-
cmd = 'LOGIN "' + escape(self._config.user) + '" "' + escape(self._config.password) + '"';
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
self._caps = undefined;
|
|
1385
|
-
self._enqueue(cmd, checkCaps);
|
|
1386
|
-
} else {
|
|
1387
|
-
err = new Error("No supported authentication method(s) available. " + "Unable to login.");
|
|
1388
|
-
err["source"] = "authentication";
|
|
1389
|
-
return reentry(err);
|
|
1390
|
-
}
|
|
1391
|
-
} else reentry();
|
|
1392
|
-
});
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
_authCRAMMD5(secret) {
|
|
1396
|
-
if (this._config === undefined) return;
|
|
1397
|
-
const decodedSecret = Buffer.from(secret, "base64").toString("utf8");
|
|
1398
|
-
const hmac = crypto.createHmac("md5", this._config.password);
|
|
1399
|
-
hmac.update(decodedSecret);
|
|
1400
|
-
const response = Buffer.from(this._config.user + " " + hmac.digest("hex").toLowerCase()).toString("base64");
|
|
1401
|
-
this.debug && this.debug("=> " + response);
|
|
1402
|
-
this._sock.write(response + CRLF, "utf8");
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
_starttls() {
|
|
1406
|
-
const self = this;
|
|
1407
|
-
this._enqueue("STARTTLS", function (err) {
|
|
1408
|
-
if (err || self._config === undefined) {
|
|
1409
|
-
self.emit("error", err, self.client, "_starttls");
|
|
1410
|
-
return self._sock.end();
|
|
1411
|
-
}
|
|
1412
|
-
self._isTsl = true;
|
|
1413
|
-
self._caps = undefined;
|
|
1414
|
-
self._sock.removeAllListeners("error");
|
|
1415
|
-
const tlsOptions = {};
|
|
1416
|
-
tlsOptions.host = self._config.host;
|
|
1417
|
-
// Host name may be overridden the tlsOptions
|
|
1418
|
-
for (const k in self._config.tlsOptions) tlsOptions[k] = self._config.tlsOptions[k];
|
|
1419
|
-
tlsOptions.socket = self._sock;
|
|
1420
|
-
self._sock = tls.connect(tlsOptions, function () {
|
|
1421
|
-
self._login();
|
|
1422
|
-
});
|
|
1423
|
-
self._sock.on("error", self._onError);
|
|
1424
|
-
self._sock.on("timeout", self._onSocketTimeout);
|
|
1425
|
-
self._sock.setTimeout(self._config.socketTimeout);
|
|
1426
|
-
self._parser.setStream(self._sock);
|
|
1427
|
-
});
|
|
1428
|
-
}
|
|
1429
|
-
_processQueue() {
|
|
1430
|
-
if (this._queue === undefined || this._curReq || !this._queue.length || !this._sock || !this._sock.writable)
|
|
1431
|
-
return;
|
|
1432
|
-
this._curReq = this._queue.shift();
|
|
1433
|
-
if (this._tagcount === MAX_INT) this._tagcount = 0;
|
|
1434
|
-
let prefix;
|
|
1435
|
-
if (this._curReq.type === "IDLE" || this._curReq.type === "NOOP") prefix = this._curReq.type;
|
|
1436
|
-
else if (this._tagcount !== undefined) prefix = "A" + this._tagcount++;
|
|
1437
|
-
const out = prefix + " " + this._curReq.fullcmd;
|
|
1438
|
-
this.debug && this.debug("=> " + inspect(out));
|
|
1439
|
-
this._sock.write(out + CRLF, "utf8");
|
|
1440
|
-
if (this._curReq.literalAppendData) {
|
|
1441
|
-
// LITERAL+: we are appending a mesage, and not waiting for a reply
|
|
1442
|
-
this._sockWriteAppendData(this._curReq.literalAppendData);
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
_sockWriteAppendData(appendData) {
|
|
1446
|
-
let val = appendData;
|
|
1447
|
-
if (Buffer.isBuffer(appendData)) val = val.toString("utf8");
|
|
1448
|
-
this.debug && this.debug("=> " + inspect(val));
|
|
1449
|
-
this._sock.write(val);
|
|
1450
|
-
this._sock.write(CRLF);
|
|
1451
|
-
}
|
|
1452
|
-
_enqueue(fullcmd, promote, cb) {
|
|
1453
|
-
if (typeof promote === "function") {
|
|
1454
|
-
cb = promote;
|
|
1455
|
-
promote = false;
|
|
1456
|
-
}
|
|
1457
|
-
const info = {
|
|
1458
|
-
type: fullcmd.match(RE_CMD)[1],
|
|
1459
|
-
fullcmd: fullcmd,
|
|
1460
|
-
cb: cb,
|
|
1461
|
-
cbargs: [],
|
|
1462
|
-
};
|
|
1463
|
-
const self = this;
|
|
1464
|
-
if (promote) {
|
|
1465
|
-
if (this._queue !== undefined) this._queue.unshift(info);
|
|
1466
|
-
} else {
|
|
1467
|
-
if (this._queue !== undefined) this._queue.push(info);
|
|
1468
|
-
}
|
|
1469
|
-
if (!this._curReq && this.state !== "disconnected" && this.state !== "upgrading") {
|
|
1470
|
-
// defer until next tick for requests like APPEND and FETCH where access to
|
|
1471
|
-
// the request object is needed immediately after enqueueing
|
|
1472
|
-
process.nextTick(function () {
|
|
1473
|
-
self._processQueue();
|
|
1474
|
-
});
|
|
1475
|
-
} else if (
|
|
1476
|
-
this._curReq &&
|
|
1477
|
-
this._idle !== undefined &&
|
|
1478
|
-
this._curReq.type === "IDLE" &&
|
|
1479
|
-
this._sock &&
|
|
1480
|
-
this._sock.writable &&
|
|
1481
|
-
this._idle.enabled
|
|
1482
|
-
) {
|
|
1483
|
-
this._idle.enabled = false;
|
|
1484
|
-
clearTimeout(this._tmrKeepalive);
|
|
1485
|
-
if (this._idle.started !== undefined && this._idle.started > 0) {
|
|
1486
|
-
// we've seen the continuation for our IDLE
|
|
1487
|
-
this.debug && this.debug("=> DONE");
|
|
1488
|
-
this._sock.write("DONE" + CRLF);
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
|
|
1494
|
-
// inherits(Connection, EventEmitter);
|
|
1495
|
-
|
|
1496
|
-
// END Extension methods =======================================================
|
|
1497
|
-
|
|
1498
|
-
// Namespace for seqno-based commands
|
|
1499
|
-
Object.defineProperty(Connection.prototype, "seq", {
|
|
1500
|
-
get: function () {
|
|
1501
|
-
const self = this;
|
|
1502
|
-
return {
|
|
1503
|
-
delKeywords: function (seqnos, keywords, cb) {
|
|
1504
|
-
self._store("", seqnos, { mode: "-", keywords: keywords }, cb);
|
|
1505
|
-
},
|
|
1506
|
-
addKeywords: function (seqnos, keywords, cb) {
|
|
1507
|
-
self._store("", seqnos, { mode: "+", keywords: keywords }, cb);
|
|
1508
|
-
},
|
|
1509
|
-
setKeywords: function (seqnos, keywords, cb) {
|
|
1510
|
-
self._store("", seqnos, { mode: "", keywords: keywords }, cb);
|
|
1511
|
-
},
|
|
1512
|
-
|
|
1513
|
-
delFlags: function (seqnos, flags, cb) {
|
|
1514
|
-
self._store("", seqnos, { mode: "-", flags: flags }, cb);
|
|
1515
|
-
},
|
|
1516
|
-
addFlags: function (seqnos, flags, cb) {
|
|
1517
|
-
self._store("", seqnos, { mode: "+", flags: flags }, cb);
|
|
1518
|
-
},
|
|
1519
|
-
setFlags: function (seqnos, flags, cb) {
|
|
1520
|
-
self._store("", seqnos, { mode: "", flags: flags }, cb);
|
|
1521
|
-
},
|
|
1522
|
-
|
|
1523
|
-
move: function (seqnos, boxTo, cb) {
|
|
1524
|
-
self._move("", seqnos, boxTo, cb);
|
|
1525
|
-
},
|
|
1526
|
-
copy: function (seqnos, boxTo, cb) {
|
|
1527
|
-
self._copy("", seqnos, boxTo, cb);
|
|
1528
|
-
},
|
|
1529
|
-
fetch: function (seqnos, options) {
|
|
1530
|
-
return self._fetch("", seqnos, options);
|
|
1531
|
-
},
|
|
1532
|
-
search: function (options, cb) {
|
|
1533
|
-
self._search("", options, cb);
|
|
1534
|
-
},
|
|
1535
|
-
|
|
1536
|
-
// Extensions ==============================================================
|
|
1537
|
-
delLabels: function (seqnos, labels, cb) {
|
|
1538
|
-
self._storeLabels("", seqnos, labels, "-", cb);
|
|
1539
|
-
},
|
|
1540
|
-
addLabels: function (seqnos, labels, cb) {
|
|
1541
|
-
self._storeLabels("", seqnos, labels, "+", cb);
|
|
1542
|
-
},
|
|
1543
|
-
setLabels: function (seqnos, labels, cb) {
|
|
1544
|
-
self._storeLabels("", seqnos, labels, "", cb);
|
|
1545
|
-
},
|
|
1546
|
-
|
|
1547
|
-
esearch: function (criteria, options, cb) {
|
|
1548
|
-
self._esearch("", criteria, options, cb);
|
|
1549
|
-
},
|
|
1550
|
-
|
|
1551
|
-
sort: function (sorts, options, cb) {
|
|
1552
|
-
self._sort("", sorts, options, cb);
|
|
1553
|
-
},
|
|
1554
|
-
thread: function (algorithm, criteria, cb) {
|
|
1555
|
-
self._thread("", algorithm, criteria, cb);
|
|
1556
|
-
},
|
|
1557
|
-
|
|
1558
|
-
delKeywordsSince: function (seqnos, keywords, modseq, cb) {
|
|
1559
|
-
self._store("", seqnos, { mode: "-", keywords: keywords, modseq: modseq }, cb);
|
|
1560
|
-
},
|
|
1561
|
-
addKeywordsSince: function (seqnos, keywords, modseq, cb) {
|
|
1562
|
-
self._store("", seqnos, { mode: "+", keywords: keywords, modseq: modseq }, cb);
|
|
1563
|
-
},
|
|
1564
|
-
setKeywordsSince: function (seqnos, keywords, modseq, cb) {
|
|
1565
|
-
self._store("", seqnos, { mode: "", keywords: keywords, modseq: modseq }, cb);
|
|
1566
|
-
},
|
|
1567
|
-
|
|
1568
|
-
delFlagsSince: function (seqnos, flags, modseq, cb) {
|
|
1569
|
-
self._store("", seqnos, { mode: "-", flags: flags, modseq: modseq }, cb);
|
|
1570
|
-
},
|
|
1571
|
-
addFlagsSince: function (seqnos, flags, modseq, cb) {
|
|
1572
|
-
self._store("", seqnos, { mode: "+", flags: flags, modseq: modseq }, cb);
|
|
1573
|
-
},
|
|
1574
|
-
setFlagsSince: function (seqnos, flags, modseq, cb) {
|
|
1575
|
-
self._store("", seqnos, { mode: "", flags: flags, modseq: modseq }, cb);
|
|
1576
|
-
},
|
|
1577
|
-
};
|
|
1578
|
-
},
|
|
1579
|
-
});
|
|
1580
|
-
|
|
1581
|
-
Connection.parseHeader = parseHeader; // from Parser.js
|
|
1582
|
-
|
|
1583
|
-
module.exports = Connection;
|