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/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;