@n8n/imap 0.7.0 → 0.8.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +19 -0
- package/README.md +754 -0
- package/deps/encoding/encoding-indexes.js +73 -0
- package/deps/encoding/encoding.js +2340 -0
- package/deps/test/utf7-imap.js +48 -0
- package/deps/test/utf7-rfc2152.js +78 -0
- package/deps/utf7.js +104 -0
- package/index.d.ts +396 -0
- package/lib/Connection.js +2131 -0
- package/lib/Parser.js +1026 -0
- package/package.json +18 -41
- package/test/test-connection-fetch-dup.js +92 -0
- package/test/test-connection-fetch-frag.js +92 -0
- package/test/test-connection-fetch-spillover.js +176 -0
- package/test/test-connection-fetch-stringbody.js +104 -0
- package/test/test-connection-idle-normal.js +144 -0
- package/test/test-connection-idle-order.js +138 -0
- package/test/test-parse-bodystructure.js +83 -0
- package/test/test-parse-envelope-addrs.js +117 -0
- package/test/test-parse-expr.js +92 -0
- package/test/test-parse-header.js +109 -0
- package/test/test-parser.js +543 -0
- package/test/test.js +4 -0
- package/LICENSE.md +0 -86
- package/LICENSE_EE.md +0 -27
- package/dist/ImapSimple.d.ts +0 -16
- package/dist/ImapSimple.js +0 -120
- package/dist/ImapSimple.js.map +0 -1
- package/dist/PartData.d.ts +0 -24
- package/dist/PartData.js +0 -99
- package/dist/PartData.js.map +0 -1
- package/dist/build.tsbuildinfo +0 -1
- package/dist/errors.d.ts +0 -12
- package/dist/errors.js +0 -30
- package/dist/errors.js.map +0 -1
- package/dist/helpers/getMessage.d.ts +0 -3
- package/dist/helpers/getMessage.js +0 -37
- package/dist/helpers/getMessage.js.map +0 -1
- package/dist/index.d.ts +0 -7
- package/dist/index.js +0 -85
- package/dist/index.js.map +0 -1
- package/dist/types.d.ts +0 -30
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
|
@@ -0,0 +1,2131 @@
|
|
|
1
|
+
var tls = require('tls'),
|
|
2
|
+
Socket = require('net').Socket,
|
|
3
|
+
EventEmitter = require('events').EventEmitter,
|
|
4
|
+
inherits = require('util').inherits,
|
|
5
|
+
inspect = require('util').inspect,
|
|
6
|
+
isDate = require('util').isDate,
|
|
7
|
+
utf7 = require('../deps/utf7').imap;
|
|
8
|
+
|
|
9
|
+
var Parser = require('./Parser').Parser,
|
|
10
|
+
parseExpr = require('./Parser').parseExpr,
|
|
11
|
+
parseHeader = require('./Parser').parseHeader;
|
|
12
|
+
|
|
13
|
+
var MAX_INT = 9007199254740992,
|
|
14
|
+
KEEPALIVE_INTERVAL = 10000,
|
|
15
|
+
MAX_IDLE_WAIT = 300000, // 5 minutes
|
|
16
|
+
MONTHS = ['Jan', 'Feb', 'Mar',
|
|
17
|
+
'Apr', 'May', 'Jun',
|
|
18
|
+
'Jul', 'Aug', 'Sep',
|
|
19
|
+
'Oct', 'Nov', 'Dec'],
|
|
20
|
+
FETCH_ATTR_MAP = {
|
|
21
|
+
'RFC822.SIZE': 'size',
|
|
22
|
+
'BODY': 'struct',
|
|
23
|
+
'BODYSTRUCTURE': 'struct',
|
|
24
|
+
'ENVELOPE': 'envelope',
|
|
25
|
+
'INTERNALDATE': 'date'
|
|
26
|
+
},
|
|
27
|
+
SPECIAL_USE_ATTRIBUTES = [
|
|
28
|
+
'\\All',
|
|
29
|
+
'\\Archive',
|
|
30
|
+
'\\Drafts',
|
|
31
|
+
'\\Flagged',
|
|
32
|
+
'\\Important',
|
|
33
|
+
'\\Junk',
|
|
34
|
+
'\\Sent',
|
|
35
|
+
'\\Trash'
|
|
36
|
+
],
|
|
37
|
+
CRLF = '\r\n',
|
|
38
|
+
RE_CMD = /^([^ ]+)(?: |$)/,
|
|
39
|
+
RE_UIDCMD_HASRESULTS = /^UID (?:FETCH|SEARCH|SORT)/,
|
|
40
|
+
RE_IDLENOOPRES = /^(IDLE|NOOP) /,
|
|
41
|
+
RE_OPENBOX = /^EXAMINE|SELECT$/,
|
|
42
|
+
RE_BODYPART = /^BODY\[/,
|
|
43
|
+
RE_INVALID_KW_CHARS = /[\(\)\{\\\"\]\%\*\x00-\x20\x7F]/,
|
|
44
|
+
RE_NUM_RANGE = /^(?:[\d]+|\*):(?:[\d]+|\*)$/,
|
|
45
|
+
RE_BACKSLASH = /\\/g,
|
|
46
|
+
RE_DBLQUOTE = /"/g,
|
|
47
|
+
RE_ESCAPE = /\\\\/g,
|
|
48
|
+
RE_INTEGER = /^\d+$/;
|
|
49
|
+
|
|
50
|
+
function Connection(config) {
|
|
51
|
+
if (!(this instanceof Connection))
|
|
52
|
+
return new Connection(config);
|
|
53
|
+
|
|
54
|
+
EventEmitter.call(this);
|
|
55
|
+
|
|
56
|
+
config || (config = {});
|
|
57
|
+
|
|
58
|
+
this._config = {
|
|
59
|
+
localAddress: config.localAddress,
|
|
60
|
+
socket: config.socket,
|
|
61
|
+
socketTimeout: config.socketTimeout || 0,
|
|
62
|
+
host: config.host || 'localhost',
|
|
63
|
+
port: config.port || 143,
|
|
64
|
+
tls: config.tls,
|
|
65
|
+
tlsOptions: config.tlsOptions,
|
|
66
|
+
autotls: config.autotls,
|
|
67
|
+
user: config.user,
|
|
68
|
+
password: config.password,
|
|
69
|
+
xoauth: config.xoauth,
|
|
70
|
+
xoauth2: config.xoauth2,
|
|
71
|
+
connTimeout: config.connTimeout || 10000,
|
|
72
|
+
authTimeout: config.authTimeout || 5000,
|
|
73
|
+
keepalive: (config.keepalive === undefined || config.keepalive === null
|
|
74
|
+
? true
|
|
75
|
+
: config.keepalive)
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
this._sock = config.socket || undefined;
|
|
79
|
+
this._tagcount = 0;
|
|
80
|
+
this._tmrConn = undefined;
|
|
81
|
+
this._tmrKeepalive = undefined;
|
|
82
|
+
this._tmrAuth = undefined;
|
|
83
|
+
this._queue = [];
|
|
84
|
+
this._box = undefined;
|
|
85
|
+
this._idle = { started: undefined, enabled: false };
|
|
86
|
+
this._parser = undefined;
|
|
87
|
+
this._curReq = undefined;
|
|
88
|
+
this.delimiter = undefined;
|
|
89
|
+
this.namespaces = undefined;
|
|
90
|
+
this.state = 'disconnected';
|
|
91
|
+
this.debug = config.debug;
|
|
92
|
+
}
|
|
93
|
+
inherits(Connection, EventEmitter);
|
|
94
|
+
|
|
95
|
+
Connection.prototype.connect = function() {
|
|
96
|
+
var config = this._config,
|
|
97
|
+
self = this,
|
|
98
|
+
socket,
|
|
99
|
+
parser,
|
|
100
|
+
tlsOptions;
|
|
101
|
+
|
|
102
|
+
socket = config.socket || new Socket();
|
|
103
|
+
socket.setKeepAlive(true);
|
|
104
|
+
this._sock = undefined;
|
|
105
|
+
this._tagcount = 0;
|
|
106
|
+
this._tmrConn = undefined;
|
|
107
|
+
this._tmrKeepalive = undefined;
|
|
108
|
+
this._tmrAuth = undefined;
|
|
109
|
+
this._queue = [];
|
|
110
|
+
this._box = undefined;
|
|
111
|
+
this._idle = { started: undefined, enabled: false };
|
|
112
|
+
this._parser = undefined;
|
|
113
|
+
this._curReq = undefined;
|
|
114
|
+
this.delimiter = undefined;
|
|
115
|
+
this.namespaces = undefined;
|
|
116
|
+
this.state = 'disconnected';
|
|
117
|
+
|
|
118
|
+
if (config.tls) {
|
|
119
|
+
tlsOptions = {};
|
|
120
|
+
// servername must be set to prevent issues with some imap server and openssl 1.1.1
|
|
121
|
+
tlsOptions.servername = config.host;
|
|
122
|
+
tlsOptions.host = config.host;
|
|
123
|
+
// Host name may be overridden the tlsOptions
|
|
124
|
+
for (var k in config.tlsOptions)
|
|
125
|
+
tlsOptions[k] = config.tlsOptions[k];
|
|
126
|
+
tlsOptions.socket = socket;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (config.tls)
|
|
130
|
+
this._sock = tls.connect(tlsOptions, onconnect);
|
|
131
|
+
else {
|
|
132
|
+
socket.once('connect', onconnect);
|
|
133
|
+
this._sock = socket;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function onconnect() {
|
|
137
|
+
clearTimeout(self._tmrConn);
|
|
138
|
+
self.state = 'connected';
|
|
139
|
+
self.debug && self.debug('[connection] Connected to host');
|
|
140
|
+
self._tmrAuth = setTimeout(function() {
|
|
141
|
+
var err = new Error('Timed out while authenticating with server');
|
|
142
|
+
err.source = 'timeout-auth';
|
|
143
|
+
self.emit('error', err);
|
|
144
|
+
socket.destroy();
|
|
145
|
+
}, config.authTimeout);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this._onError = function(err) {
|
|
149
|
+
clearTimeout(self._tmrConn);
|
|
150
|
+
clearTimeout(self._tmrAuth);
|
|
151
|
+
self.debug && self.debug('[connection] Error: ' + err);
|
|
152
|
+
err.source = 'socket';
|
|
153
|
+
self.emit('error', err);
|
|
154
|
+
};
|
|
155
|
+
this._sock.on('error', this._onError);
|
|
156
|
+
|
|
157
|
+
this._onSocketTimeout = function() {
|
|
158
|
+
clearTimeout(self._tmrConn);
|
|
159
|
+
clearTimeout(self._tmrAuth);
|
|
160
|
+
clearTimeout(self._tmrKeepalive);
|
|
161
|
+
self.state = 'disconnected';
|
|
162
|
+
self.debug && self.debug('[connection] Socket timeout');
|
|
163
|
+
|
|
164
|
+
var err = new Error('Socket timed out while talking to server');
|
|
165
|
+
err.source = 'socket-timeout';
|
|
166
|
+
self.emit('error', err);
|
|
167
|
+
socket.destroy();
|
|
168
|
+
};
|
|
169
|
+
this._sock.on('timeout', this._onSocketTimeout);
|
|
170
|
+
socket.setTimeout(config.socketTimeout);
|
|
171
|
+
|
|
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);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
socket.once('end', function() {
|
|
182
|
+
clearTimeout(self._tmrConn);
|
|
183
|
+
clearTimeout(self._tmrAuth);
|
|
184
|
+
clearTimeout(self._tmrKeepalive);
|
|
185
|
+
self.state = 'disconnected';
|
|
186
|
+
self.debug && self.debug('[connection] Ended');
|
|
187
|
+
self.emit('end');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
this._parser = parser = new Parser(this._sock, this.debug);
|
|
191
|
+
|
|
192
|
+
parser.on('untagged', function(info) {
|
|
193
|
+
self._resUntagged(info);
|
|
194
|
+
});
|
|
195
|
+
parser.on('tagged', function(info) {
|
|
196
|
+
self._resTagged(info);
|
|
197
|
+
});
|
|
198
|
+
parser.on('body', function(stream, info) {
|
|
199
|
+
var msg = self._curReq.fetchCache[info.seqno], toget;
|
|
200
|
+
|
|
201
|
+
if (msg === undefined) {
|
|
202
|
+
msg = self._curReq.fetchCache[info.seqno] = {
|
|
203
|
+
msgEmitter: new EventEmitter(),
|
|
204
|
+
toget: self._curReq.fetching.slice(0),
|
|
205
|
+
attrs: {},
|
|
206
|
+
ended: false
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
self._curReq.bodyEmitter.emit('message', msg.msgEmitter, info.seqno);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
toget = msg.toget;
|
|
213
|
+
|
|
214
|
+
// here we compare the parsed version of the expression inside BODY[]
|
|
215
|
+
// because 'HEADER.FIELDS (TO FROM)' really is equivalent to
|
|
216
|
+
// 'HEADER.FIELDS ("TO" "FROM")' and some servers will actually send the
|
|
217
|
+
// quoted form even if the client did not use quotes
|
|
218
|
+
var thisbody = parseExpr(info.which);
|
|
219
|
+
for (var i = 0, len = toget.length; i < len; ++i) {
|
|
220
|
+
if (_deepEqual(thisbody, toget[i])) {
|
|
221
|
+
toget.splice(i, 1);
|
|
222
|
+
msg.msgEmitter.emit('body', stream, info);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
stream.resume(); // a body we didn't ask for?
|
|
227
|
+
});
|
|
228
|
+
parser.on('continue', function(info) {
|
|
229
|
+
var type = self._curReq.type;
|
|
230
|
+
if (type === 'IDLE') {
|
|
231
|
+
if (self._queue.length
|
|
232
|
+
&& self._idle.started === 0
|
|
233
|
+
&& self._curReq
|
|
234
|
+
&& self._curReq.type === 'IDLE'
|
|
235
|
+
&& self._sock
|
|
236
|
+
&& self._sock.writable
|
|
237
|
+
&& !self._idle.enabled) {
|
|
238
|
+
self.debug && self.debug('=> DONE');
|
|
239
|
+
self._sock.write('DONE' + CRLF);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// now idling
|
|
243
|
+
self._idle.started = Date.now();
|
|
244
|
+
} else if (/^AUTHENTICATE XOAUTH/.test(self._curReq.fullcmd)) {
|
|
245
|
+
self._curReq.oauthError =
|
|
246
|
+
Buffer.from(info.text, 'base64').toString('utf8');
|
|
247
|
+
self.debug && self.debug('=> ' + inspect(CRLF));
|
|
248
|
+
self._sock.write(CRLF);
|
|
249
|
+
} else if (type === 'APPEND') {
|
|
250
|
+
self._sockWriteAppendData(self._curReq.appendData);
|
|
251
|
+
} else if (self._curReq.lines && self._curReq.lines.length) {
|
|
252
|
+
var line = self._curReq.lines.shift() + '\r\n';
|
|
253
|
+
self.debug && self.debug('=> ' + inspect(line));
|
|
254
|
+
self._sock.write(line, 'latin1');
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
parser.on('other', function(line) {
|
|
258
|
+
var m;
|
|
259
|
+
if (m = RE_IDLENOOPRES.exec(line)) {
|
|
260
|
+
// no longer idling
|
|
261
|
+
self._idle.enabled = false;
|
|
262
|
+
self._idle.started = undefined;
|
|
263
|
+
clearTimeout(self._tmrKeepalive);
|
|
264
|
+
|
|
265
|
+
self._curReq = undefined;
|
|
266
|
+
|
|
267
|
+
if (self._queue.length === 0
|
|
268
|
+
&& self._config.keepalive
|
|
269
|
+
&& self.state === 'authenticated'
|
|
270
|
+
&& !self._idle.enabled) {
|
|
271
|
+
self._idle.enabled = true;
|
|
272
|
+
if (m[1] === 'NOOP')
|
|
273
|
+
self._doKeepaliveTimer();
|
|
274
|
+
else
|
|
275
|
+
self._doKeepaliveTimer(true);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
self._processQueue();
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
this._tmrConn = setTimeout(function() {
|
|
283
|
+
var err = new Error('Timed out while connecting to server');
|
|
284
|
+
err.source = 'timeout';
|
|
285
|
+
self.emit('error', err);
|
|
286
|
+
socket.destroy();
|
|
287
|
+
}, config.connTimeout);
|
|
288
|
+
|
|
289
|
+
socket.connect({
|
|
290
|
+
port: config.port,
|
|
291
|
+
host: config.host,
|
|
292
|
+
localAddress: config.localAddress
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
Connection.prototype.serverSupports = function(cap) {
|
|
297
|
+
return (this._caps && this._caps.indexOf(cap) > -1);
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
Connection.prototype.destroy = function() {
|
|
301
|
+
this._queue = [];
|
|
302
|
+
this._curReq = undefined;
|
|
303
|
+
this._sock && this._sock.end();
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
Connection.prototype.end = function() {
|
|
307
|
+
var self = this;
|
|
308
|
+
this._enqueue('LOGOUT', function() {
|
|
309
|
+
self._queue = [];
|
|
310
|
+
self._curReq = undefined;
|
|
311
|
+
self._sock.end();
|
|
312
|
+
});
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
Connection.prototype.append = function(data, options, cb) {
|
|
316
|
+
var literal = this.serverSupports('LITERAL+');
|
|
317
|
+
if (typeof options === 'function') {
|
|
318
|
+
cb = options;
|
|
319
|
+
options = undefined;
|
|
320
|
+
}
|
|
321
|
+
options = options || {};
|
|
322
|
+
if (!options.mailbox) {
|
|
323
|
+
if (!this._box)
|
|
324
|
+
throw new Error('No mailbox specified or currently selected');
|
|
325
|
+
else
|
|
326
|
+
options.mailbox = this._box.name;
|
|
327
|
+
}
|
|
328
|
+
var cmd = 'APPEND "' + escape(utf7.encode(''+options.mailbox)) + '"';
|
|
329
|
+
if (options.flags) {
|
|
330
|
+
if (!Array.isArray(options.flags))
|
|
331
|
+
options.flags = [options.flags];
|
|
332
|
+
if (options.flags.length > 0) {
|
|
333
|
+
for (var 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))
|
|
342
|
+
throw new Error('`date` is not a Date object');
|
|
343
|
+
cmd += ' "';
|
|
344
|
+
cmd += options.date.getDate();
|
|
345
|
+
cmd += '-';
|
|
346
|
+
cmd += MONTHS[options.date.getMonth()];
|
|
347
|
+
cmd += '-';
|
|
348
|
+
cmd += options.date.getFullYear();
|
|
349
|
+
cmd += ' ';
|
|
350
|
+
cmd += ('0' + options.date.getHours()).slice(-2);
|
|
351
|
+
cmd += ':';
|
|
352
|
+
cmd += ('0' + options.date.getMinutes()).slice(-2);
|
|
353
|
+
cmd += ':';
|
|
354
|
+
cmd += ('0' + options.date.getSeconds()).slice(-2);
|
|
355
|
+
cmd += ((options.date.getTimezoneOffset() > 0) ? ' -' : ' +' );
|
|
356
|
+
cmd += ('0' + (-options.date.getTimezoneOffset() / 60)).slice(-2);
|
|
357
|
+
cmd += ('0' + (-options.date.getTimezoneOffset() % 60)).slice(-2);
|
|
358
|
+
cmd += '"';
|
|
359
|
+
}
|
|
360
|
+
cmd += ' {';
|
|
361
|
+
cmd += (Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data));
|
|
362
|
+
cmd += (literal ? '+' : '') + '}';
|
|
363
|
+
|
|
364
|
+
this._enqueue(cmd, cb);
|
|
365
|
+
|
|
366
|
+
if (literal)
|
|
367
|
+
this._queue[this._queue.length - 1].literalAppendData = data;
|
|
368
|
+
else
|
|
369
|
+
this._queue[this._queue.length - 1].appendData = data;
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
Connection.prototype.getSpecialUseBoxes = function(cb) {
|
|
373
|
+
this._enqueue('XLIST "" "*"', cb);
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
Connection.prototype.getBoxes = function(namespace, cb) {
|
|
377
|
+
if (typeof namespace === 'function') {
|
|
378
|
+
cb = namespace;
|
|
379
|
+
namespace = '';
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
namespace = escape(utf7.encode(''+namespace));
|
|
383
|
+
|
|
384
|
+
this._enqueue('LIST "' + namespace + '" "*"', cb);
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
Connection.prototype.id = function(identification, cb) {
|
|
388
|
+
if (!this.serverSupports('ID'))
|
|
389
|
+
throw new Error('Server does not support ID');
|
|
390
|
+
var cmd = 'ID';
|
|
391
|
+
if ((identification === null) || (Object.keys(identification).length === 0))
|
|
392
|
+
cmd += ' NIL';
|
|
393
|
+
else {
|
|
394
|
+
if (Object.keys(identification).length > 30)
|
|
395
|
+
throw new Error('Max allowed number of keys is 30');
|
|
396
|
+
var kv = [];
|
|
397
|
+
for (var k in identification) {
|
|
398
|
+
if (Buffer.byteLength(k) > 30)
|
|
399
|
+
throw new Error('Max allowed key length is 30');
|
|
400
|
+
if (Buffer.byteLength(identification[k]) > 1024)
|
|
401
|
+
throw new Error('Max allowed value length is 1024');
|
|
402
|
+
kv.push('"' + escape(k) + '"');
|
|
403
|
+
kv.push('"' + escape(identification[k]) + '"');
|
|
404
|
+
}
|
|
405
|
+
cmd += ' (' + kv.join(' ') + ')';
|
|
406
|
+
}
|
|
407
|
+
this._enqueue(cmd, cb);
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
Connection.prototype.openBox = function(name, readOnly, cb) {
|
|
411
|
+
if (this.state !== 'authenticated')
|
|
412
|
+
throw new Error('Not authenticated');
|
|
413
|
+
|
|
414
|
+
if (typeof readOnly === 'function') {
|
|
415
|
+
cb = readOnly;
|
|
416
|
+
readOnly = false;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
name = ''+name;
|
|
420
|
+
var encname = escape(utf7.encode(name)),
|
|
421
|
+
cmd = (readOnly ? 'EXAMINE' : 'SELECT'),
|
|
422
|
+
self = this;
|
|
423
|
+
|
|
424
|
+
cmd += ' "' + encname + '"';
|
|
425
|
+
|
|
426
|
+
if (this.serverSupports('CONDSTORE'))
|
|
427
|
+
cmd += ' (CONDSTORE)';
|
|
428
|
+
|
|
429
|
+
this._enqueue(cmd, function(err) {
|
|
430
|
+
if (err) {
|
|
431
|
+
self._box = undefined;
|
|
432
|
+
cb(err);
|
|
433
|
+
} else {
|
|
434
|
+
self._box.name = name;
|
|
435
|
+
cb(err, self._box);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
Connection.prototype.closeBox = function(shouldExpunge, cb) {
|
|
441
|
+
if (this._box === undefined)
|
|
442
|
+
throw new Error('No mailbox is currently selected');
|
|
443
|
+
|
|
444
|
+
var self = this;
|
|
445
|
+
|
|
446
|
+
if (typeof shouldExpunge === 'function') {
|
|
447
|
+
cb = shouldExpunge;
|
|
448
|
+
shouldExpunge = true;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (shouldExpunge) {
|
|
452
|
+
this._enqueue('CLOSE', function(err) {
|
|
453
|
+
if (!err)
|
|
454
|
+
self._box = undefined;
|
|
455
|
+
|
|
456
|
+
cb(err);
|
|
457
|
+
});
|
|
458
|
+
} else {
|
|
459
|
+
if (this.serverSupports('UNSELECT')) {
|
|
460
|
+
// use UNSELECT if available, as it claims to be "cleaner" than the
|
|
461
|
+
// alternative "hack"
|
|
462
|
+
this._enqueue('UNSELECT', function(err) {
|
|
463
|
+
if (!err)
|
|
464
|
+
self._box = undefined;
|
|
465
|
+
|
|
466
|
+
cb(err);
|
|
467
|
+
});
|
|
468
|
+
} else {
|
|
469
|
+
// "HACK": close the box without expunging by attempting to SELECT a
|
|
470
|
+
// non-existent mailbox
|
|
471
|
+
var badbox = 'NODEJSIMAPCLOSINGBOX' + Date.now();
|
|
472
|
+
this._enqueue('SELECT "' + badbox + '"', function(err) {
|
|
473
|
+
self._box = undefined;
|
|
474
|
+
cb();
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
Connection.prototype.addBox = function(name, cb) {
|
|
481
|
+
this._enqueue('CREATE "' + escape(utf7.encode(''+name)) + '"', cb);
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
Connection.prototype.delBox = function(name, cb) {
|
|
485
|
+
this._enqueue('DELETE "' + escape(utf7.encode(''+name)) + '"', cb);
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
Connection.prototype.renameBox = function(oldname, newname, cb) {
|
|
489
|
+
var encoldname = escape(utf7.encode(''+oldname)),
|
|
490
|
+
encnewname = escape(utf7.encode(''+newname)),
|
|
491
|
+
self = this;
|
|
492
|
+
|
|
493
|
+
this._enqueue('RENAME "' + encoldname + '" "' + encnewname + '"',
|
|
494
|
+
function(err) {
|
|
495
|
+
if (err)
|
|
496
|
+
return cb(err);
|
|
497
|
+
|
|
498
|
+
if (self._box
|
|
499
|
+
&& self._box.name === oldname
|
|
500
|
+
&& oldname.toUpperCase() !== 'INBOX') {
|
|
501
|
+
self._box.name = newname;
|
|
502
|
+
cb(err, self._box);
|
|
503
|
+
} else
|
|
504
|
+
cb();
|
|
505
|
+
}
|
|
506
|
+
);
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
Connection.prototype.subscribeBox = function(name, cb) {
|
|
510
|
+
this._enqueue('SUBSCRIBE "' + escape(utf7.encode(''+name)) + '"', cb);
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
Connection.prototype.unsubscribeBox = function(name, cb) {
|
|
514
|
+
this._enqueue('UNSUBSCRIBE "' + escape(utf7.encode(''+name)) + '"', cb);
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
Connection.prototype.getSubscribedBoxes = function(namespace, cb) {
|
|
518
|
+
if (typeof namespace === 'function') {
|
|
519
|
+
cb = namespace;
|
|
520
|
+
namespace = '';
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
namespace = escape(utf7.encode(''+namespace));
|
|
524
|
+
|
|
525
|
+
this._enqueue('LSUB "' + namespace + '" "*"', cb);
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
Connection.prototype.status = function(boxName, cb) {
|
|
529
|
+
if (this._box && this._box.name === boxName)
|
|
530
|
+
throw new Error('Cannot call status on currently selected mailbox');
|
|
531
|
+
|
|
532
|
+
boxName = escape(utf7.encode(''+boxName));
|
|
533
|
+
|
|
534
|
+
var info = [ 'MESSAGES', 'RECENT', 'UNSEEN', 'UIDVALIDITY', 'UIDNEXT' ];
|
|
535
|
+
|
|
536
|
+
if (this.serverSupports('CONDSTORE'))
|
|
537
|
+
info.push('HIGHESTMODSEQ');
|
|
538
|
+
|
|
539
|
+
info = info.join(' ');
|
|
540
|
+
|
|
541
|
+
this._enqueue('STATUS "' + boxName + '" (' + info + ')', cb);
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
Connection.prototype.expunge = function(uids, cb) {
|
|
545
|
+
if (typeof uids === 'function') {
|
|
546
|
+
cb = uids;
|
|
547
|
+
uids = undefined;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (uids !== undefined) {
|
|
551
|
+
if (!Array.isArray(uids))
|
|
552
|
+
uids = [uids];
|
|
553
|
+
validateUIDList(uids);
|
|
554
|
+
|
|
555
|
+
if (uids.length === 0)
|
|
556
|
+
throw new Error('Empty uid list');
|
|
557
|
+
|
|
558
|
+
uids = uids.join(',');
|
|
559
|
+
|
|
560
|
+
if (!this.serverSupports('UIDPLUS'))
|
|
561
|
+
throw new Error('Server does not support this feature (UIDPLUS)');
|
|
562
|
+
|
|
563
|
+
this._enqueue('UID EXPUNGE ' + uids, cb);
|
|
564
|
+
} else
|
|
565
|
+
this._enqueue('EXPUNGE', cb);
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
Connection.prototype.search = function(criteria, cb) {
|
|
569
|
+
this._search('UID ', criteria, cb);
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
Connection.prototype._search = function(which, criteria, cb) {
|
|
573
|
+
if (this._box === undefined)
|
|
574
|
+
throw new Error('No mailbox is currently selected');
|
|
575
|
+
else if (!Array.isArray(criteria))
|
|
576
|
+
throw new Error('Expected array for search criteria');
|
|
577
|
+
|
|
578
|
+
var cmd = which + 'SEARCH',
|
|
579
|
+
info = { hasUTF8: false /*output*/ },
|
|
580
|
+
query = buildSearchQuery(criteria, this._caps, info),
|
|
581
|
+
lines;
|
|
582
|
+
if (info.hasUTF8) {
|
|
583
|
+
cmd += ' CHARSET UTF-8';
|
|
584
|
+
lines = query.split(CRLF);
|
|
585
|
+
query = lines.shift();
|
|
586
|
+
}
|
|
587
|
+
cmd += query;
|
|
588
|
+
this._enqueue(cmd, cb);
|
|
589
|
+
if (info.hasUTF8) {
|
|
590
|
+
var req = this._queue[this._queue.length - 1];
|
|
591
|
+
req.lines = lines;
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
Connection.prototype.addFlags = function(uids, flags, cb) {
|
|
596
|
+
this._store('UID ', uids, { mode: '+', flags: flags }, cb);
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
Connection.prototype.delFlags = function(uids, flags, cb) {
|
|
600
|
+
this._store('UID ', uids, { mode: '-', flags: flags }, cb);
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
Connection.prototype.setFlags = function(uids, flags, cb) {
|
|
604
|
+
this._store('UID ', uids, { mode: '', flags: flags }, cb);
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
Connection.prototype.addKeywords = function(uids, keywords, cb) {
|
|
608
|
+
this._store('UID ', uids, { mode: '+', keywords: keywords }, cb);
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
Connection.prototype.delKeywords = function(uids, keywords, cb) {
|
|
612
|
+
this._store('UID ', uids, { mode: '-', keywords: keywords }, cb);
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
Connection.prototype.setKeywords = function(uids, keywords, cb) {
|
|
616
|
+
this._store('UID ', uids, { mode: '', keywords: keywords }, cb);
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
Connection.prototype._store = function(which, uids, cfg, cb) {
|
|
620
|
+
var mode = cfg.mode,
|
|
621
|
+
isFlags = (cfg.flags !== undefined),
|
|
622
|
+
items = (isFlags ? cfg.flags : cfg.keywords);
|
|
623
|
+
if (this._box === undefined)
|
|
624
|
+
throw new Error('No mailbox is currently selected');
|
|
625
|
+
else if (uids === undefined)
|
|
626
|
+
throw new Error('No messages specified');
|
|
627
|
+
|
|
628
|
+
if (!Array.isArray(uids))
|
|
629
|
+
uids = [uids];
|
|
630
|
+
validateUIDList(uids);
|
|
631
|
+
|
|
632
|
+
if (uids.length === 0) {
|
|
633
|
+
throw new Error('Empty '
|
|
634
|
+
+ (which === '' ? 'sequence number' : 'uid')
|
|
635
|
+
+ 'list');
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
if ((!Array.isArray(items) && typeof items !== 'string')
|
|
639
|
+
|| (Array.isArray(items) && items.length === 0))
|
|
640
|
+
throw new Error((isFlags ? 'Flags' : 'Keywords')
|
|
641
|
+
+ ' argument must be a string or a non-empty Array');
|
|
642
|
+
if (!Array.isArray(items))
|
|
643
|
+
items = [items];
|
|
644
|
+
for (var i = 0, len = items.length; i < len; ++i) {
|
|
645
|
+
if (isFlags) {
|
|
646
|
+
if (items[i][0] !== '\\')
|
|
647
|
+
items[i] = '\\' + items[i];
|
|
648
|
+
} else {
|
|
649
|
+
// keyword contains any char except control characters (%x00-1F and %x7F)
|
|
650
|
+
// and: '(', ')', '{', ' ', '%', '*', '\', '"', ']'
|
|
651
|
+
if (RE_INVALID_KW_CHARS.test(items[i])) {
|
|
652
|
+
throw new Error('The keyword "' + items[i]
|
|
653
|
+
+ '" contains invalid characters');
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
items = items.join(' ');
|
|
659
|
+
uids = uids.join(',');
|
|
660
|
+
|
|
661
|
+
var modifiers = '';
|
|
662
|
+
if (cfg.modseq !== undefined && !this._box.nomodseq)
|
|
663
|
+
modifiers += 'UNCHANGEDSINCE ' + cfg.modseq + ' ';
|
|
664
|
+
|
|
665
|
+
this._enqueue(which + 'STORE ' + uids + ' '
|
|
666
|
+
+ modifiers
|
|
667
|
+
+ mode + 'FLAGS.SILENT (' + items + ')', cb);
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
Connection.prototype.copy = function(uids, boxTo, cb) {
|
|
671
|
+
this._copy('UID ', uids, boxTo, cb);
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
Connection.prototype._copy = function(which, uids, boxTo, cb) {
|
|
675
|
+
if (this._box === undefined)
|
|
676
|
+
throw new Error('No mailbox is currently selected');
|
|
677
|
+
|
|
678
|
+
if (!Array.isArray(uids))
|
|
679
|
+
uids = [uids];
|
|
680
|
+
validateUIDList(uids);
|
|
681
|
+
|
|
682
|
+
if (uids.length === 0) {
|
|
683
|
+
throw new Error('Empty '
|
|
684
|
+
+ (which === '' ? 'sequence number' : 'uid')
|
|
685
|
+
+ 'list');
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
boxTo = escape(utf7.encode(''+boxTo));
|
|
689
|
+
|
|
690
|
+
this._enqueue(which + 'COPY ' + uids.join(',') + ' "' + boxTo + '"', cb);
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
Connection.prototype.move = function(uids, boxTo, cb) {
|
|
694
|
+
this._move('UID ', uids, boxTo, cb);
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
Connection.prototype._move = function(which, uids, boxTo, cb) {
|
|
698
|
+
if (this._box === undefined)
|
|
699
|
+
throw new Error('No mailbox is currently selected');
|
|
700
|
+
|
|
701
|
+
if (this.serverSupports('MOVE')) {
|
|
702
|
+
if (!Array.isArray(uids))
|
|
703
|
+
uids = [uids];
|
|
704
|
+
validateUIDList(uids);
|
|
705
|
+
|
|
706
|
+
if (uids.length === 0) {
|
|
707
|
+
throw new Error('Empty '
|
|
708
|
+
+ (which === '' ? 'sequence number' : 'uid')
|
|
709
|
+
+ 'list');
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
uids = uids.join(',');
|
|
713
|
+
boxTo = escape(utf7.encode(''+boxTo));
|
|
714
|
+
|
|
715
|
+
this._enqueue(which + 'MOVE ' + uids + ' "' + boxTo + '"', cb);
|
|
716
|
+
} else if (this._box.permFlags.indexOf('\\Deleted') === -1
|
|
717
|
+
&& this._box.flags.indexOf('\\Deleted') === -1) {
|
|
718
|
+
throw new Error('Cannot move message: '
|
|
719
|
+
+ 'server does not allow deletion of messages');
|
|
720
|
+
} else {
|
|
721
|
+
var deletedUIDs, task = 0, self = this;
|
|
722
|
+
this._copy(which, uids, boxTo, function ccb(err, info) {
|
|
723
|
+
if (err)
|
|
724
|
+
return cb(err, info);
|
|
725
|
+
|
|
726
|
+
if (task === 0 && which && self.serverSupports('UIDPLUS')) {
|
|
727
|
+
// UIDPLUS gives us a 'UID EXPUNGE n' command to expunge a subset of
|
|
728
|
+
// messages with the \Deleted flag set. This allows us to skip some
|
|
729
|
+
// actions.
|
|
730
|
+
task = 2;
|
|
731
|
+
}
|
|
732
|
+
// Make sure we don't expunge any messages marked as Deleted except the
|
|
733
|
+
// one we are moving
|
|
734
|
+
if (task === 0) {
|
|
735
|
+
self.search(['DELETED'], function(e, result) {
|
|
736
|
+
++task;
|
|
737
|
+
deletedUIDs = result;
|
|
738
|
+
ccb(e, info);
|
|
739
|
+
});
|
|
740
|
+
} else if (task === 1) {
|
|
741
|
+
if (deletedUIDs.length) {
|
|
742
|
+
self.delFlags(deletedUIDs, '\\Deleted', function(e) {
|
|
743
|
+
++task;
|
|
744
|
+
ccb(e, info);
|
|
745
|
+
});
|
|
746
|
+
} else {
|
|
747
|
+
++task;
|
|
748
|
+
ccb(err, info);
|
|
749
|
+
}
|
|
750
|
+
} else if (task === 2) {
|
|
751
|
+
var cbMarkDel = function(e) {
|
|
752
|
+
++task;
|
|
753
|
+
ccb(e, info);
|
|
754
|
+
};
|
|
755
|
+
if (which)
|
|
756
|
+
self.addFlags(uids, '\\Deleted', cbMarkDel);
|
|
757
|
+
else
|
|
758
|
+
self.seq.addFlags(uids, '\\Deleted', cbMarkDel);
|
|
759
|
+
} else if (task === 3) {
|
|
760
|
+
if (which && self.serverSupports('UIDPLUS')) {
|
|
761
|
+
self.expunge(uids, function(e) {
|
|
762
|
+
cb(e, info);
|
|
763
|
+
});
|
|
764
|
+
} else {
|
|
765
|
+
self.expunge(function(e) {
|
|
766
|
+
++task;
|
|
767
|
+
ccb(e, info);
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
} else if (task === 4) {
|
|
771
|
+
if (deletedUIDs.length) {
|
|
772
|
+
self.addFlags(deletedUIDs, '\\Deleted', function(e) {
|
|
773
|
+
cb(e, info);
|
|
774
|
+
});
|
|
775
|
+
} else
|
|
776
|
+
cb(err, info);
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
Connection.prototype.fetch = function(uids, options) {
|
|
783
|
+
return this._fetch('UID ', uids, options);
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
Connection.prototype._fetch = function(which, uids, options) {
|
|
787
|
+
if (uids === undefined
|
|
788
|
+
|| uids === null
|
|
789
|
+
|| (Array.isArray(uids) && uids.length === 0))
|
|
790
|
+
throw new Error('Nothing to fetch');
|
|
791
|
+
|
|
792
|
+
if (!Array.isArray(uids))
|
|
793
|
+
uids = [uids];
|
|
794
|
+
validateUIDList(uids);
|
|
795
|
+
|
|
796
|
+
if (uids.length === 0) {
|
|
797
|
+
throw new Error('Empty '
|
|
798
|
+
+ (which === '' ? 'sequence number' : 'uid')
|
|
799
|
+
+ 'list');
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
uids = uids.join(',');
|
|
803
|
+
|
|
804
|
+
var cmd = which + 'FETCH ' + uids + ' (',
|
|
805
|
+
fetching = [],
|
|
806
|
+
i, len, key;
|
|
807
|
+
|
|
808
|
+
if (this.serverSupports('X-GM-EXT-1')) {
|
|
809
|
+
fetching.push('X-GM-THRID');
|
|
810
|
+
fetching.push('X-GM-MSGID');
|
|
811
|
+
fetching.push('X-GM-LABELS');
|
|
812
|
+
}
|
|
813
|
+
if (this.serverSupports('CONDSTORE') && !this._box.nomodseq)
|
|
814
|
+
fetching.push('MODSEQ');
|
|
815
|
+
|
|
816
|
+
fetching.push('UID');
|
|
817
|
+
fetching.push('FLAGS');
|
|
818
|
+
fetching.push('INTERNALDATE');
|
|
819
|
+
|
|
820
|
+
var modifiers;
|
|
821
|
+
|
|
822
|
+
if (options) {
|
|
823
|
+
modifiers = options.modifiers;
|
|
824
|
+
if (options.envelope)
|
|
825
|
+
fetching.push('ENVELOPE');
|
|
826
|
+
if (options.struct)
|
|
827
|
+
fetching.push('BODYSTRUCTURE');
|
|
828
|
+
if (options.size)
|
|
829
|
+
fetching.push('RFC822.SIZE');
|
|
830
|
+
if (Array.isArray(options.extensions)) {
|
|
831
|
+
options.extensions.forEach(function (extension) {
|
|
832
|
+
fetching.push(extension.toUpperCase());
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
cmd += fetching.join(' ');
|
|
836
|
+
if (options.bodies !== undefined) {
|
|
837
|
+
var bodies = options.bodies,
|
|
838
|
+
prefix = (options.markSeen ? '' : '.PEEK');
|
|
839
|
+
if (!Array.isArray(bodies))
|
|
840
|
+
bodies = [bodies];
|
|
841
|
+
for (i = 0, len = bodies.length; i < len; ++i) {
|
|
842
|
+
fetching.push(parseExpr(''+bodies[i]));
|
|
843
|
+
cmd += ' BODY' + prefix + '[' + bodies[i] + ']';
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
} else
|
|
847
|
+
cmd += fetching.join(' ');
|
|
848
|
+
|
|
849
|
+
cmd += ')';
|
|
850
|
+
|
|
851
|
+
var modkeys = (typeof modifiers === 'object' ? Object.keys(modifiers) : []),
|
|
852
|
+
modstr = ' (';
|
|
853
|
+
for (i = 0, len = modkeys.length, key; i < len; ++i) {
|
|
854
|
+
key = modkeys[i].toUpperCase();
|
|
855
|
+
if (key === 'CHANGEDSINCE' && this.serverSupports('CONDSTORE')
|
|
856
|
+
&& !this._box.nomodseq)
|
|
857
|
+
modstr += key + ' ' + modifiers[modkeys[i]] + ' ';
|
|
858
|
+
}
|
|
859
|
+
if (modstr.length > 2) {
|
|
860
|
+
cmd += modstr.substring(0, modstr.length - 1);
|
|
861
|
+
cmd += ')';
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
this._enqueue(cmd);
|
|
865
|
+
var req = this._queue[this._queue.length - 1];
|
|
866
|
+
req.fetchCache = {};
|
|
867
|
+
req.fetching = fetching;
|
|
868
|
+
return (req.bodyEmitter = new EventEmitter());
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
// Extension methods ===========================================================
|
|
872
|
+
Connection.prototype.setLabels = function(uids, labels, cb) {
|
|
873
|
+
this._storeLabels('UID ', uids, labels, '', cb);
|
|
874
|
+
};
|
|
875
|
+
|
|
876
|
+
Connection.prototype.addLabels = function(uids, labels, cb) {
|
|
877
|
+
this._storeLabels('UID ', uids, labels, '+', cb);
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
Connection.prototype.delLabels = function(uids, labels, cb) {
|
|
881
|
+
this._storeLabels('UID ', uids, labels, '-', cb);
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
Connection.prototype._storeLabels = function(which, uids, labels, mode, cb) {
|
|
885
|
+
if (!this.serverSupports('X-GM-EXT-1'))
|
|
886
|
+
throw new Error('Server must support X-GM-EXT-1 capability');
|
|
887
|
+
else if (this._box === undefined)
|
|
888
|
+
throw new Error('No mailbox is currently selected');
|
|
889
|
+
else if (uids === undefined)
|
|
890
|
+
throw new Error('No messages specified');
|
|
891
|
+
|
|
892
|
+
if (!Array.isArray(uids))
|
|
893
|
+
uids = [uids];
|
|
894
|
+
validateUIDList(uids);
|
|
895
|
+
|
|
896
|
+
if (uids.length === 0) {
|
|
897
|
+
throw new Error('Empty '
|
|
898
|
+
+ (which === '' ? 'sequence number' : 'uid')
|
|
899
|
+
+ 'list');
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if ((!Array.isArray(labels) && typeof labels !== 'string')
|
|
903
|
+
|| (Array.isArray(labels) && labels.length === 0))
|
|
904
|
+
throw new Error('labels argument must be a string or a non-empty Array');
|
|
905
|
+
|
|
906
|
+
if (!Array.isArray(labels))
|
|
907
|
+
labels = [labels];
|
|
908
|
+
labels = labels.map(function(v) {
|
|
909
|
+
return '"' + escape(utf7.encode(''+v)) + '"';
|
|
910
|
+
}).join(' ');
|
|
911
|
+
|
|
912
|
+
uids = uids.join(',');
|
|
913
|
+
|
|
914
|
+
this._enqueue(which + 'STORE ' + uids + ' ' + mode
|
|
915
|
+
+ 'X-GM-LABELS.SILENT (' + labels + ')', cb);
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
Connection.prototype.sort = function(sorts, criteria, cb) {
|
|
919
|
+
this._sort('UID ', sorts, criteria, cb);
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
Connection.prototype._sort = function(which, sorts, criteria, cb) {
|
|
923
|
+
if (this._box === undefined)
|
|
924
|
+
throw new Error('No mailbox is currently selected');
|
|
925
|
+
else if (!Array.isArray(sorts) || !sorts.length)
|
|
926
|
+
throw new Error('Expected array with at least one sort criteria');
|
|
927
|
+
else if (!Array.isArray(criteria))
|
|
928
|
+
throw new Error('Expected array for search criteria');
|
|
929
|
+
else if (!this.serverSupports('SORT'))
|
|
930
|
+
throw new Error('Sort is not supported on the server');
|
|
931
|
+
|
|
932
|
+
sorts = sorts.map(function(c) {
|
|
933
|
+
if (typeof c !== 'string')
|
|
934
|
+
throw new Error('Unexpected sort criteria data type. '
|
|
935
|
+
+ 'Expected string. Got: ' + typeof criteria);
|
|
936
|
+
|
|
937
|
+
var modifier = '';
|
|
938
|
+
if (c[0] === '-') {
|
|
939
|
+
modifier = 'REVERSE ';
|
|
940
|
+
c = c.substring(1);
|
|
941
|
+
}
|
|
942
|
+
switch (c.toUpperCase()) {
|
|
943
|
+
case 'ARRIVAL':
|
|
944
|
+
case 'CC':
|
|
945
|
+
case 'DATE':
|
|
946
|
+
case 'FROM':
|
|
947
|
+
case 'SIZE':
|
|
948
|
+
case 'SUBJECT':
|
|
949
|
+
case 'TO':
|
|
950
|
+
break;
|
|
951
|
+
default:
|
|
952
|
+
throw new Error('Unexpected sort criteria: ' + c);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
return modifier + c;
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
sorts = sorts.join(' ');
|
|
959
|
+
|
|
960
|
+
var info = { hasUTF8: false /*output*/ },
|
|
961
|
+
query = buildSearchQuery(criteria, this._caps, info),
|
|
962
|
+
charset = 'US-ASCII',
|
|
963
|
+
lines;
|
|
964
|
+
if (info.hasUTF8) {
|
|
965
|
+
charset = 'UTF-8';
|
|
966
|
+
lines = query.split(CRLF);
|
|
967
|
+
query = lines.shift();
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
this._enqueue(which + 'SORT (' + sorts + ') ' + charset + query, cb);
|
|
971
|
+
if (info.hasUTF8) {
|
|
972
|
+
var req = this._queue[this._queue.length - 1];
|
|
973
|
+
req.lines = lines;
|
|
974
|
+
}
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
Connection.prototype.esearch = function(criteria, options, cb) {
|
|
978
|
+
this._esearch('UID ', criteria, options, cb);
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
Connection.prototype._esearch = function(which, criteria, options, cb) {
|
|
982
|
+
if (this._box === undefined)
|
|
983
|
+
throw new Error('No mailbox is currently selected');
|
|
984
|
+
else if (!Array.isArray(criteria))
|
|
985
|
+
throw new Error('Expected array for search options');
|
|
986
|
+
|
|
987
|
+
var info = { hasUTF8: false /*output*/ },
|
|
988
|
+
query = buildSearchQuery(criteria, this._caps, info),
|
|
989
|
+
charset = '',
|
|
990
|
+
lines;
|
|
991
|
+
if (info.hasUTF8) {
|
|
992
|
+
charset = ' CHARSET UTF-8';
|
|
993
|
+
lines = query.split(CRLF);
|
|
994
|
+
query = lines.shift();
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
if (typeof options === 'function') {
|
|
998
|
+
cb = options;
|
|
999
|
+
options = '';
|
|
1000
|
+
} else if (!options)
|
|
1001
|
+
options = '';
|
|
1002
|
+
|
|
1003
|
+
if (Array.isArray(options))
|
|
1004
|
+
options = options.join(' ');
|
|
1005
|
+
|
|
1006
|
+
this._enqueue(which + 'SEARCH RETURN (' + options + ')' + charset + query, cb);
|
|
1007
|
+
if (info.hasUTF8) {
|
|
1008
|
+
var req = this._queue[this._queue.length - 1];
|
|
1009
|
+
req.lines = lines;
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
Connection.prototype.setQuota = function(quotaRoot, limits, cb) {
|
|
1014
|
+
if (typeof limits === 'function') {
|
|
1015
|
+
cb = limits;
|
|
1016
|
+
limits = {};
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
var triplets = '';
|
|
1020
|
+
for (var l in limits) {
|
|
1021
|
+
if (triplets)
|
|
1022
|
+
triplets += ' ';
|
|
1023
|
+
triplets += l + ' ' + limits[l];
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
quotaRoot = escape(utf7.encode(''+quotaRoot));
|
|
1027
|
+
|
|
1028
|
+
this._enqueue('SETQUOTA "' + quotaRoot + '" (' + triplets + ')',
|
|
1029
|
+
function(err, quotalist) {
|
|
1030
|
+
if (err)
|
|
1031
|
+
return cb(err);
|
|
1032
|
+
|
|
1033
|
+
cb(err, quotalist ? quotalist[0] : limits);
|
|
1034
|
+
}
|
|
1035
|
+
);
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
Connection.prototype.getQuota = function(quotaRoot, cb) {
|
|
1039
|
+
quotaRoot = escape(utf7.encode(''+quotaRoot));
|
|
1040
|
+
|
|
1041
|
+
this._enqueue('GETQUOTA "' + quotaRoot + '"', function(err, quotalist) {
|
|
1042
|
+
if (err)
|
|
1043
|
+
return cb(err);
|
|
1044
|
+
|
|
1045
|
+
cb(err, quotalist[0]);
|
|
1046
|
+
});
|
|
1047
|
+
};
|
|
1048
|
+
|
|
1049
|
+
Connection.prototype.getQuotaRoot = function(boxName, cb) {
|
|
1050
|
+
boxName = escape(utf7.encode(''+boxName));
|
|
1051
|
+
|
|
1052
|
+
this._enqueue('GETQUOTAROOT "' + boxName + '"', function(err, quotalist) {
|
|
1053
|
+
if (err)
|
|
1054
|
+
return cb(err);
|
|
1055
|
+
|
|
1056
|
+
var quotas = {};
|
|
1057
|
+
if (quotalist) {
|
|
1058
|
+
for (var i = 0, len = quotalist.length; i < len; ++i)
|
|
1059
|
+
quotas[quotalist[i].root] = quotalist[i].resources;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
cb(err, quotas);
|
|
1063
|
+
});
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
Connection.prototype.thread = function(algorithm, criteria, cb) {
|
|
1067
|
+
this._thread('UID ', algorithm, criteria, cb);
|
|
1068
|
+
};
|
|
1069
|
+
|
|
1070
|
+
Connection.prototype._thread = function(which, algorithm, criteria, cb) {
|
|
1071
|
+
algorithm = algorithm.toUpperCase();
|
|
1072
|
+
|
|
1073
|
+
if (!this.serverSupports('THREAD=' + algorithm))
|
|
1074
|
+
throw new Error('Server does not support that threading algorithm');
|
|
1075
|
+
|
|
1076
|
+
var info = { hasUTF8: false /*output*/ },
|
|
1077
|
+
query = buildSearchQuery(criteria, this._caps, info),
|
|
1078
|
+
charset = 'US-ASCII',
|
|
1079
|
+
lines;
|
|
1080
|
+
if (info.hasUTF8) {
|
|
1081
|
+
charset = 'UTF-8';
|
|
1082
|
+
lines = query.split(CRLF);
|
|
1083
|
+
query = lines.shift();
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
this._enqueue(which + 'THREAD ' + algorithm + ' ' + charset + query, cb);
|
|
1087
|
+
if (info.hasUTF8) {
|
|
1088
|
+
var req = this._queue[this._queue.length - 1];
|
|
1089
|
+
req.lines = lines;
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
|
|
1093
|
+
Connection.prototype.addFlagsSince = function(uids, flags, modseq, cb) {
|
|
1094
|
+
this._store('UID ',
|
|
1095
|
+
uids,
|
|
1096
|
+
{ mode: '+', flags: flags, modseq: modseq },
|
|
1097
|
+
cb);
|
|
1098
|
+
};
|
|
1099
|
+
|
|
1100
|
+
Connection.prototype.delFlagsSince = function(uids, flags, modseq, cb) {
|
|
1101
|
+
this._store('UID ',
|
|
1102
|
+
uids,
|
|
1103
|
+
{ mode: '-', flags: flags, modseq: modseq },
|
|
1104
|
+
cb);
|
|
1105
|
+
};
|
|
1106
|
+
|
|
1107
|
+
Connection.prototype.setFlagsSince = function(uids, flags, modseq, cb) {
|
|
1108
|
+
this._store('UID ',
|
|
1109
|
+
uids,
|
|
1110
|
+
{ mode: '', flags: flags, modseq: modseq },
|
|
1111
|
+
cb);
|
|
1112
|
+
};
|
|
1113
|
+
|
|
1114
|
+
Connection.prototype.addKeywordsSince = function(uids, keywords, modseq, cb) {
|
|
1115
|
+
this._store('UID ',
|
|
1116
|
+
uids,
|
|
1117
|
+
{ mode: '+', keywords: keywords, modseq: modseq },
|
|
1118
|
+
cb);
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
Connection.prototype.delKeywordsSince = function(uids, keywords, modseq, cb) {
|
|
1122
|
+
this._store('UID ',
|
|
1123
|
+
uids,
|
|
1124
|
+
{ mode: '-', keywords: keywords, modseq: modseq },
|
|
1125
|
+
cb);
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1128
|
+
Connection.prototype.setKeywordsSince = function(uids, keywords, modseq, cb) {
|
|
1129
|
+
this._store('UID ',
|
|
1130
|
+
uids,
|
|
1131
|
+
{ mode: '', keywords: keywords, modseq: modseq },
|
|
1132
|
+
cb);
|
|
1133
|
+
};
|
|
1134
|
+
// END Extension methods =======================================================
|
|
1135
|
+
|
|
1136
|
+
// Namespace for seqno-based commands
|
|
1137
|
+
Object.defineProperty(Connection.prototype, 'seq', { get: function() {
|
|
1138
|
+
var self = this;
|
|
1139
|
+
return {
|
|
1140
|
+
delKeywords: function(seqnos, keywords, cb) {
|
|
1141
|
+
self._store('', seqnos, { mode: '-', keywords: keywords }, cb);
|
|
1142
|
+
},
|
|
1143
|
+
addKeywords: function(seqnos, keywords, cb) {
|
|
1144
|
+
self._store('', seqnos, { mode: '+', keywords: keywords }, cb);
|
|
1145
|
+
},
|
|
1146
|
+
setKeywords: function(seqnos, keywords, cb) {
|
|
1147
|
+
self._store('', seqnos, { mode: '', keywords: keywords }, cb);
|
|
1148
|
+
},
|
|
1149
|
+
|
|
1150
|
+
delFlags: function(seqnos, flags, cb) {
|
|
1151
|
+
self._store('', seqnos, { mode: '-', flags: flags }, cb);
|
|
1152
|
+
},
|
|
1153
|
+
addFlags: function(seqnos, flags, cb) {
|
|
1154
|
+
self._store('', seqnos, { mode: '+', flags: flags }, cb);
|
|
1155
|
+
},
|
|
1156
|
+
setFlags: function(seqnos, flags, cb) {
|
|
1157
|
+
self._store('', seqnos, { mode: '', flags: flags }, cb);
|
|
1158
|
+
},
|
|
1159
|
+
|
|
1160
|
+
move: function(seqnos, boxTo, cb) {
|
|
1161
|
+
self._move('', seqnos, boxTo, cb);
|
|
1162
|
+
},
|
|
1163
|
+
copy: function(seqnos, boxTo, cb) {
|
|
1164
|
+
self._copy('', seqnos, boxTo, cb);
|
|
1165
|
+
},
|
|
1166
|
+
fetch: function(seqnos, options) {
|
|
1167
|
+
return self._fetch('', seqnos, options);
|
|
1168
|
+
},
|
|
1169
|
+
search: function(options, cb) {
|
|
1170
|
+
self._search('', options, cb);
|
|
1171
|
+
},
|
|
1172
|
+
|
|
1173
|
+
// Extensions ==============================================================
|
|
1174
|
+
delLabels: function(seqnos, labels, cb) {
|
|
1175
|
+
self._storeLabels('', seqnos, labels, '-', cb);
|
|
1176
|
+
},
|
|
1177
|
+
addLabels: function(seqnos, labels, cb) {
|
|
1178
|
+
self._storeLabels('', seqnos, labels, '+', cb);
|
|
1179
|
+
},
|
|
1180
|
+
setLabels: function(seqnos, labels, cb) {
|
|
1181
|
+
self._storeLabels('', seqnos, labels, '', cb);
|
|
1182
|
+
},
|
|
1183
|
+
|
|
1184
|
+
esearch: function(criteria, options, cb) {
|
|
1185
|
+
self._esearch('', criteria, options, cb);
|
|
1186
|
+
},
|
|
1187
|
+
|
|
1188
|
+
sort: function(sorts, options, cb) {
|
|
1189
|
+
self._sort('', sorts, options, cb);
|
|
1190
|
+
},
|
|
1191
|
+
thread: function(algorithm, criteria, cb) {
|
|
1192
|
+
self._thread('', algorithm, criteria, cb);
|
|
1193
|
+
},
|
|
1194
|
+
|
|
1195
|
+
delKeywordsSince: function(seqnos, keywords, modseq, cb) {
|
|
1196
|
+
self._store('',
|
|
1197
|
+
seqnos,
|
|
1198
|
+
{ mode: '-', keywords: keywords, modseq: modseq },
|
|
1199
|
+
cb);
|
|
1200
|
+
},
|
|
1201
|
+
addKeywordsSince: function(seqnos, keywords, modseq, cb) {
|
|
1202
|
+
self._store('',
|
|
1203
|
+
seqnos,
|
|
1204
|
+
{ mode: '+', keywords: keywords, modseq: modseq },
|
|
1205
|
+
cb);
|
|
1206
|
+
},
|
|
1207
|
+
setKeywordsSince: function(seqnos, keywords, modseq, cb) {
|
|
1208
|
+
self._store('',
|
|
1209
|
+
seqnos,
|
|
1210
|
+
{ mode: '', keywords: keywords, modseq: modseq },
|
|
1211
|
+
cb);
|
|
1212
|
+
},
|
|
1213
|
+
|
|
1214
|
+
delFlagsSince: function(seqnos, flags, modseq, cb) {
|
|
1215
|
+
self._store('',
|
|
1216
|
+
seqnos,
|
|
1217
|
+
{ mode: '-', flags: flags, modseq: modseq },
|
|
1218
|
+
cb);
|
|
1219
|
+
},
|
|
1220
|
+
addFlagsSince: function(seqnos, flags, modseq, cb) {
|
|
1221
|
+
self._store('',
|
|
1222
|
+
seqnos,
|
|
1223
|
+
{ mode: '+', flags: flags, modseq: modseq },
|
|
1224
|
+
cb);
|
|
1225
|
+
},
|
|
1226
|
+
setFlagsSince: function(seqnos, flags, modseq, cb) {
|
|
1227
|
+
self._store('',
|
|
1228
|
+
seqnos,
|
|
1229
|
+
{ mode: '', flags: flags, modseq: modseq },
|
|
1230
|
+
cb);
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
}});
|
|
1234
|
+
|
|
1235
|
+
Connection.prototype._resUntagged = function(info) {
|
|
1236
|
+
var type = info.type, i, len, box, attrs, key;
|
|
1237
|
+
|
|
1238
|
+
if (type === 'bye')
|
|
1239
|
+
this._sock.end();
|
|
1240
|
+
else if (type === 'namespace')
|
|
1241
|
+
this.namespaces = info.text;
|
|
1242
|
+
else if (type === 'id')
|
|
1243
|
+
this._curReq.cbargs.push(info.text);
|
|
1244
|
+
else if (type === 'capability')
|
|
1245
|
+
this._caps = info.text.map(function(v) { return v.toUpperCase(); });
|
|
1246
|
+
else if (type === 'preauth')
|
|
1247
|
+
this.state = 'authenticated';
|
|
1248
|
+
else if (type === 'sort' || type === 'thread' || type === 'esearch')
|
|
1249
|
+
this._curReq.cbargs.push(info.text);
|
|
1250
|
+
else if (type === 'search') {
|
|
1251
|
+
if (info.text.results !== undefined) {
|
|
1252
|
+
// CONDSTORE-modified search results
|
|
1253
|
+
this._curReq.cbargs.push(info.text.results);
|
|
1254
|
+
this._curReq.cbargs.push(info.text.modseq);
|
|
1255
|
+
} else
|
|
1256
|
+
this._curReq.cbargs.push(info.text);
|
|
1257
|
+
} else if (type === 'quota') {
|
|
1258
|
+
var cbargs = this._curReq.cbargs;
|
|
1259
|
+
if (!cbargs.length)
|
|
1260
|
+
cbargs.push([]);
|
|
1261
|
+
cbargs[0].push(info.text);
|
|
1262
|
+
} else if (type === 'recent') {
|
|
1263
|
+
if (!this._box && RE_OPENBOX.test(this._curReq.type))
|
|
1264
|
+
this._createCurrentBox();
|
|
1265
|
+
if (this._box)
|
|
1266
|
+
this._box.messages.new = info.num;
|
|
1267
|
+
} else if (type === 'flags') {
|
|
1268
|
+
if (!this._box && RE_OPENBOX.test(this._curReq.type))
|
|
1269
|
+
this._createCurrentBox();
|
|
1270
|
+
if (this._box)
|
|
1271
|
+
this._box.flags = info.text;
|
|
1272
|
+
} else if (type === 'bad' || type === 'no') {
|
|
1273
|
+
if (this.state === 'connected' && !this._curReq) {
|
|
1274
|
+
clearTimeout(this._tmrConn);
|
|
1275
|
+
clearTimeout(this._tmrAuth);
|
|
1276
|
+
var err = new Error('Received negative welcome: ' + info.text);
|
|
1277
|
+
err.source = 'protocol';
|
|
1278
|
+
this.emit('error', err);
|
|
1279
|
+
this._sock.end();
|
|
1280
|
+
}
|
|
1281
|
+
} else if (type === 'exists') {
|
|
1282
|
+
if (!this._box && RE_OPENBOX.test(this._curReq.type))
|
|
1283
|
+
this._createCurrentBox();
|
|
1284
|
+
if (this._box) {
|
|
1285
|
+
var prev = this._box.messages.total,
|
|
1286
|
+
now = info.num;
|
|
1287
|
+
this._box.messages.total = now;
|
|
1288
|
+
if (now > prev && this.state === 'authenticated') {
|
|
1289
|
+
this._box.messages.new = now - prev;
|
|
1290
|
+
this.emit('mail', this._box.messages.new);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
} else if (type === 'expunge') {
|
|
1294
|
+
if (this._box) {
|
|
1295
|
+
if (this._box.messages.total > 0)
|
|
1296
|
+
--this._box.messages.total;
|
|
1297
|
+
this.emit('expunge', info.num);
|
|
1298
|
+
}
|
|
1299
|
+
} else if (type === 'ok') {
|
|
1300
|
+
if (this.state === 'connected' && !this._curReq)
|
|
1301
|
+
this._login();
|
|
1302
|
+
else if (typeof info.textCode === 'string'
|
|
1303
|
+
&& info.textCode.toUpperCase() === 'ALERT')
|
|
1304
|
+
this.emit('alert', info.text);
|
|
1305
|
+
else if (this._curReq
|
|
1306
|
+
&& info.textCode
|
|
1307
|
+
&& (RE_OPENBOX.test(this._curReq.type))) {
|
|
1308
|
+
// we're opening a mailbox
|
|
1309
|
+
|
|
1310
|
+
if (!this._box)
|
|
1311
|
+
this._createCurrentBox();
|
|
1312
|
+
|
|
1313
|
+
if (info.textCode.key)
|
|
1314
|
+
key = info.textCode.key.toUpperCase();
|
|
1315
|
+
else
|
|
1316
|
+
key = info.textCode;
|
|
1317
|
+
|
|
1318
|
+
if (key === 'UIDVALIDITY')
|
|
1319
|
+
this._box.uidvalidity = info.textCode.val;
|
|
1320
|
+
else if (key === 'UIDNEXT')
|
|
1321
|
+
this._box.uidnext = info.textCode.val;
|
|
1322
|
+
else if (key === 'HIGHESTMODSEQ')
|
|
1323
|
+
this._box.highestmodseq = ''+info.textCode.val;
|
|
1324
|
+
else if (key === 'PERMANENTFLAGS') {
|
|
1325
|
+
var idx, permFlags, keywords;
|
|
1326
|
+
this._box.permFlags = permFlags = info.textCode.val;
|
|
1327
|
+
if ((idx = this._box.permFlags.indexOf('\\*')) > -1) {
|
|
1328
|
+
this._box.newKeywords = true;
|
|
1329
|
+
permFlags.splice(idx, 1);
|
|
1330
|
+
}
|
|
1331
|
+
this._box.keywords = keywords = permFlags.filter(function(f) {
|
|
1332
|
+
return (f[0] !== '\\');
|
|
1333
|
+
});
|
|
1334
|
+
for (i = 0, len = keywords.length; i < len; ++i)
|
|
1335
|
+
permFlags.splice(permFlags.indexOf(keywords[i]), 1);
|
|
1336
|
+
} else if (key === 'UIDNOTSTICKY')
|
|
1337
|
+
this._box.persistentUIDs = false;
|
|
1338
|
+
else if (key === 'NOMODSEQ')
|
|
1339
|
+
this._box.nomodseq = true;
|
|
1340
|
+
} else if (typeof info.textCode === 'string'
|
|
1341
|
+
&& info.textCode.toUpperCase() === 'UIDVALIDITY')
|
|
1342
|
+
this.emit('uidvalidity', info.text);
|
|
1343
|
+
} else if (type === 'list' || type === 'lsub' || type === 'xlist') {
|
|
1344
|
+
if (this.delimiter === undefined)
|
|
1345
|
+
this.delimiter = info.text.delimiter;
|
|
1346
|
+
else {
|
|
1347
|
+
if (this._curReq.cbargs.length === 0)
|
|
1348
|
+
this._curReq.cbargs.push({});
|
|
1349
|
+
|
|
1350
|
+
box = {
|
|
1351
|
+
attribs: info.text.flags,
|
|
1352
|
+
delimiter: info.text.delimiter,
|
|
1353
|
+
children: null,
|
|
1354
|
+
parent: null
|
|
1355
|
+
};
|
|
1356
|
+
|
|
1357
|
+
for (i = 0, len = SPECIAL_USE_ATTRIBUTES.length; i < len; ++i)
|
|
1358
|
+
if (box.attribs.indexOf(SPECIAL_USE_ATTRIBUTES[i]) > -1)
|
|
1359
|
+
box.special_use_attrib = SPECIAL_USE_ATTRIBUTES[i];
|
|
1360
|
+
|
|
1361
|
+
var name = info.text.name,
|
|
1362
|
+
curChildren = this._curReq.cbargs[0];
|
|
1363
|
+
|
|
1364
|
+
if (box.delimiter) {
|
|
1365
|
+
var path = name.split(box.delimiter),
|
|
1366
|
+
parent = null;
|
|
1367
|
+
name = path.pop();
|
|
1368
|
+
for (i = 0, len = path.length; i < len; ++i) {
|
|
1369
|
+
if (!curChildren[path[i]])
|
|
1370
|
+
curChildren[path[i]] = {};
|
|
1371
|
+
if (!curChildren[path[i]].children)
|
|
1372
|
+
curChildren[path[i]].children = {};
|
|
1373
|
+
parent = curChildren[path[i]];
|
|
1374
|
+
curChildren = curChildren[path[i]].children;
|
|
1375
|
+
}
|
|
1376
|
+
box.parent = parent;
|
|
1377
|
+
}
|
|
1378
|
+
if (curChildren[name])
|
|
1379
|
+
box.children = curChildren[name].children;
|
|
1380
|
+
curChildren[name] = box;
|
|
1381
|
+
}
|
|
1382
|
+
} else if (type === 'status') {
|
|
1383
|
+
box = {
|
|
1384
|
+
name: info.text.name,
|
|
1385
|
+
uidnext: 0,
|
|
1386
|
+
uidvalidity: 0,
|
|
1387
|
+
messages: {
|
|
1388
|
+
total: 0,
|
|
1389
|
+
new: 0,
|
|
1390
|
+
unseen: 0
|
|
1391
|
+
}
|
|
1392
|
+
};
|
|
1393
|
+
attrs = info.text.attrs;
|
|
1394
|
+
|
|
1395
|
+
if (attrs) {
|
|
1396
|
+
if (attrs.recent !== undefined)
|
|
1397
|
+
box.messages.new = attrs.recent;
|
|
1398
|
+
if (attrs.unseen !== undefined)
|
|
1399
|
+
box.messages.unseen = attrs.unseen;
|
|
1400
|
+
if (attrs.messages !== undefined)
|
|
1401
|
+
box.messages.total = attrs.messages;
|
|
1402
|
+
if (attrs.uidnext !== undefined)
|
|
1403
|
+
box.uidnext = attrs.uidnext;
|
|
1404
|
+
if (attrs.uidvalidity !== undefined)
|
|
1405
|
+
box.uidvalidity = attrs.uidvalidity;
|
|
1406
|
+
if (attrs.highestmodseq !== undefined) // CONDSTORE
|
|
1407
|
+
box.highestmodseq = ''+attrs.highestmodseq;
|
|
1408
|
+
}
|
|
1409
|
+
this._curReq.cbargs.push(box);
|
|
1410
|
+
} else if (type === 'fetch') {
|
|
1411
|
+
if (/^(?:UID )?FETCH/.test(this._curReq.fullcmd)) {
|
|
1412
|
+
// FETCH response sent as result of FETCH request
|
|
1413
|
+
var msg = this._curReq.fetchCache[info.num],
|
|
1414
|
+
keys = Object.keys(info.text),
|
|
1415
|
+
keyslen = keys.length,
|
|
1416
|
+
toget, msgEmitter, j;
|
|
1417
|
+
|
|
1418
|
+
if (msg === undefined) {
|
|
1419
|
+
// simple case -- no bodies were streamed
|
|
1420
|
+
toget = this._curReq.fetching.slice(0);
|
|
1421
|
+
if (toget.length === 0)
|
|
1422
|
+
return;
|
|
1423
|
+
|
|
1424
|
+
msgEmitter = new EventEmitter();
|
|
1425
|
+
attrs = {};
|
|
1426
|
+
|
|
1427
|
+
this._curReq.bodyEmitter.emit('message', msgEmitter, info.num);
|
|
1428
|
+
} else {
|
|
1429
|
+
toget = msg.toget;
|
|
1430
|
+
msgEmitter = msg.msgEmitter;
|
|
1431
|
+
attrs = msg.attrs;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
i = toget.length;
|
|
1435
|
+
if (i === 0) {
|
|
1436
|
+
if (msg && !msg.ended) {
|
|
1437
|
+
msg.ended = true;
|
|
1438
|
+
process.nextTick(function() {
|
|
1439
|
+
msgEmitter.emit('end');
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
if (keyslen > 0) {
|
|
1446
|
+
while (--i >= 0) {
|
|
1447
|
+
j = keyslen;
|
|
1448
|
+
while (--j >= 0) {
|
|
1449
|
+
if (keys[j].toUpperCase() === toget[i]) {
|
|
1450
|
+
if (!RE_BODYPART.test(toget[i])) {
|
|
1451
|
+
if (toget[i] === 'X-GM-LABELS') {
|
|
1452
|
+
var labels = info.text[keys[j]];
|
|
1453
|
+
for (var k = 0, lenk = labels.length; k < lenk; ++k)
|
|
1454
|
+
labels[k] = (''+labels[k]).replace(RE_ESCAPE, '\\');
|
|
1455
|
+
}
|
|
1456
|
+
key = FETCH_ATTR_MAP[toget[i]];
|
|
1457
|
+
if (!key)
|
|
1458
|
+
key = toget[i].toLowerCase();
|
|
1459
|
+
attrs[key] = info.text[keys[j]];
|
|
1460
|
+
}
|
|
1461
|
+
toget.splice(i, 1);
|
|
1462
|
+
break;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
if (toget.length === 0) {
|
|
1469
|
+
if (msg)
|
|
1470
|
+
msg.ended = true;
|
|
1471
|
+
process.nextTick(function() {
|
|
1472
|
+
msgEmitter.emit('attributes', attrs);
|
|
1473
|
+
msgEmitter.emit('end');
|
|
1474
|
+
});
|
|
1475
|
+
} else if (msg === undefined) {
|
|
1476
|
+
this._curReq.fetchCache[info.num] = {
|
|
1477
|
+
msgEmitter: msgEmitter,
|
|
1478
|
+
toget: toget,
|
|
1479
|
+
attrs: attrs,
|
|
1480
|
+
ended: false
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
} else {
|
|
1484
|
+
// FETCH response sent as result of STORE request or sent unilaterally,
|
|
1485
|
+
// treat them as the same for now for simplicity
|
|
1486
|
+
this.emit('update', info.num, info.text);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
};
|
|
1490
|
+
|
|
1491
|
+
Connection.prototype._resTagged = function(info) {
|
|
1492
|
+
var req = this._curReq, err;
|
|
1493
|
+
|
|
1494
|
+
if (!req)
|
|
1495
|
+
return;
|
|
1496
|
+
|
|
1497
|
+
this._curReq = undefined;
|
|
1498
|
+
|
|
1499
|
+
if (info.type === 'no' || info.type === 'bad') {
|
|
1500
|
+
var errtext;
|
|
1501
|
+
if (info.text)
|
|
1502
|
+
errtext = info.text;
|
|
1503
|
+
else
|
|
1504
|
+
errtext = req.oauthError;
|
|
1505
|
+
err = new Error(errtext);
|
|
1506
|
+
err.type = info.type;
|
|
1507
|
+
err.textCode = info.textCode;
|
|
1508
|
+
err.source = 'protocol';
|
|
1509
|
+
} else if (this._box) {
|
|
1510
|
+
if (req.type === 'EXAMINE' || req.type === 'SELECT') {
|
|
1511
|
+
this._box.readOnly = (typeof info.textCode === 'string'
|
|
1512
|
+
&& info.textCode.toUpperCase() === 'READ-ONLY');
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
// According to RFC 3501, UID commands do not give errors for
|
|
1516
|
+
// non-existant user-supplied UIDs, so give the callback empty results
|
|
1517
|
+
// if we unexpectedly received no untagged responses.
|
|
1518
|
+
if (RE_UIDCMD_HASRESULTS.test(req.fullcmd) && req.cbargs.length === 0)
|
|
1519
|
+
req.cbargs.push([]);
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
if (req.bodyEmitter) {
|
|
1523
|
+
var bodyEmitter = req.bodyEmitter;
|
|
1524
|
+
if (err)
|
|
1525
|
+
bodyEmitter.emit('error', err);
|
|
1526
|
+
process.nextTick(function() {
|
|
1527
|
+
bodyEmitter.emit('end');
|
|
1528
|
+
});
|
|
1529
|
+
} else {
|
|
1530
|
+
req.cbargs.unshift(err);
|
|
1531
|
+
if (info.textCode && info.textCode.key) {
|
|
1532
|
+
var key = info.textCode.key.toUpperCase();
|
|
1533
|
+
if (key === 'APPENDUID') // [uidvalidity, newUID]
|
|
1534
|
+
req.cbargs.push(info.textCode.val[1]);
|
|
1535
|
+
else if (key === 'COPYUID') // [uidvalidity, sourceUIDs, destUIDs]
|
|
1536
|
+
req.cbargs.push(info.textCode.val[2]);
|
|
1537
|
+
}
|
|
1538
|
+
req.cb && req.cb.apply(this, req.cbargs);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
if (this._queue.length === 0
|
|
1542
|
+
&& this._config.keepalive
|
|
1543
|
+
&& this.state === 'authenticated'
|
|
1544
|
+
&& !this._idle.enabled) {
|
|
1545
|
+
this._idle.enabled = true;
|
|
1546
|
+
this._doKeepaliveTimer(true);
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
this._processQueue();
|
|
1550
|
+
};
|
|
1551
|
+
|
|
1552
|
+
Connection.prototype._createCurrentBox = function() {
|
|
1553
|
+
this._box = {
|
|
1554
|
+
name: '',
|
|
1555
|
+
flags: [],
|
|
1556
|
+
readOnly: false,
|
|
1557
|
+
uidvalidity: 0,
|
|
1558
|
+
uidnext: 0,
|
|
1559
|
+
permFlags: [],
|
|
1560
|
+
keywords: [],
|
|
1561
|
+
newKeywords: false,
|
|
1562
|
+
persistentUIDs: true,
|
|
1563
|
+
nomodseq: false,
|
|
1564
|
+
messages: {
|
|
1565
|
+
total: 0,
|
|
1566
|
+
new: 0
|
|
1567
|
+
}
|
|
1568
|
+
};
|
|
1569
|
+
};
|
|
1570
|
+
|
|
1571
|
+
Connection.prototype._doKeepaliveTimer = function(immediate) {
|
|
1572
|
+
var self = this,
|
|
1573
|
+
interval = this._config.keepalive.interval || KEEPALIVE_INTERVAL,
|
|
1574
|
+
idleWait = this._config.keepalive.idleInterval || MAX_IDLE_WAIT,
|
|
1575
|
+
forceNoop = this._config.keepalive.forceNoop || false,
|
|
1576
|
+
timerfn = function() {
|
|
1577
|
+
if (self._idle.enabled) {
|
|
1578
|
+
// unlike NOOP, IDLE is only a valid command after authenticating
|
|
1579
|
+
if (!self.serverSupports('IDLE')
|
|
1580
|
+
|| self.state !== 'authenticated'
|
|
1581
|
+
|| forceNoop)
|
|
1582
|
+
self._enqueue('NOOP', true);
|
|
1583
|
+
else {
|
|
1584
|
+
if (self._idle.started === undefined) {
|
|
1585
|
+
self._idle.started = 0;
|
|
1586
|
+
self._enqueue('IDLE', true);
|
|
1587
|
+
} else if (self._idle.started > 0) {
|
|
1588
|
+
var timeDiff = Date.now() - self._idle.started;
|
|
1589
|
+
if (timeDiff >= idleWait) {
|
|
1590
|
+
self._idle.enabled = false;
|
|
1591
|
+
self.debug && self.debug('=> DONE');
|
|
1592
|
+
self._sock.write('DONE' + CRLF);
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
self._tmrKeepalive = setTimeout(timerfn, interval);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
};
|
|
1600
|
+
|
|
1601
|
+
if (immediate)
|
|
1602
|
+
timerfn();
|
|
1603
|
+
else
|
|
1604
|
+
this._tmrKeepalive = setTimeout(timerfn, interval);
|
|
1605
|
+
};
|
|
1606
|
+
|
|
1607
|
+
Connection.prototype._login = function() {
|
|
1608
|
+
var self = this, checkedNS = false;
|
|
1609
|
+
|
|
1610
|
+
var reentry = function(err) {
|
|
1611
|
+
clearTimeout(self._tmrAuth);
|
|
1612
|
+
if (err) {
|
|
1613
|
+
self.emit('error', err);
|
|
1614
|
+
return self._sock.end();
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
// 2. Get the list of available namespaces (RFC2342)
|
|
1618
|
+
if (!checkedNS && self.serverSupports('NAMESPACE')) {
|
|
1619
|
+
checkedNS = true;
|
|
1620
|
+
return self._enqueue('NAMESPACE', reentry);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
// 3. Get the top-level mailbox hierarchy delimiter used by the server
|
|
1624
|
+
self._enqueue('LIST "" ""', function() {
|
|
1625
|
+
self.state = 'authenticated';
|
|
1626
|
+
self.emit('ready');
|
|
1627
|
+
});
|
|
1628
|
+
};
|
|
1629
|
+
|
|
1630
|
+
// 1. Get the supported capabilities
|
|
1631
|
+
self._enqueue('CAPABILITY', function() {
|
|
1632
|
+
// No need to attempt the login sequence if we're on a PREAUTH connection.
|
|
1633
|
+
if (self.state === 'connected') {
|
|
1634
|
+
var err,
|
|
1635
|
+
checkCaps = function(error) {
|
|
1636
|
+
if (error) {
|
|
1637
|
+
error.source = 'authentication';
|
|
1638
|
+
return reentry(error);
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
if (self._caps === undefined) {
|
|
1642
|
+
// Fetch server capabilities if they were not automatically
|
|
1643
|
+
// provided after authentication
|
|
1644
|
+
return self._enqueue('CAPABILITY', reentry);
|
|
1645
|
+
} else
|
|
1646
|
+
reentry();
|
|
1647
|
+
};
|
|
1648
|
+
|
|
1649
|
+
if (self.serverSupports('STARTTLS')
|
|
1650
|
+
&& (self._config.autotls === 'always'
|
|
1651
|
+
|| (self._config.autotls === 'required'
|
|
1652
|
+
&& self.serverSupports('LOGINDISABLED')))) {
|
|
1653
|
+
self._starttls();
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
if (self.serverSupports('LOGINDISABLED')) {
|
|
1658
|
+
err = new Error('Logging in is disabled on this server');
|
|
1659
|
+
err.source = 'authentication';
|
|
1660
|
+
return reentry(err);
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
var cmd;
|
|
1664
|
+
if (self.serverSupports('AUTH=XOAUTH') && self._config.xoauth) {
|
|
1665
|
+
self._caps = undefined;
|
|
1666
|
+
cmd = 'AUTHENTICATE XOAUTH';
|
|
1667
|
+
// are there any servers that support XOAUTH/XOAUTH2 and not SASL-IR?
|
|
1668
|
+
//if (self.serverSupports('SASL-IR'))
|
|
1669
|
+
cmd += ' ' + escape(self._config.xoauth);
|
|
1670
|
+
self._enqueue(cmd, checkCaps);
|
|
1671
|
+
} else if (self.serverSupports('AUTH=XOAUTH2') && self._config.xoauth2) {
|
|
1672
|
+
self._caps = undefined;
|
|
1673
|
+
cmd = 'AUTHENTICATE XOAUTH2';
|
|
1674
|
+
//if (self.serverSupports('SASL-IR'))
|
|
1675
|
+
cmd += ' ' + escape(self._config.xoauth2);
|
|
1676
|
+
self._enqueue(cmd, checkCaps);
|
|
1677
|
+
} else if (self._config.user && self._config.password) {
|
|
1678
|
+
self._caps = undefined;
|
|
1679
|
+
self._enqueue('LOGIN "' + escape(self._config.user) + '" "'
|
|
1680
|
+
+ escape(self._config.password) + '"', checkCaps);
|
|
1681
|
+
} else {
|
|
1682
|
+
err = new Error('No supported authentication method(s) available. '
|
|
1683
|
+
+ 'Unable to login.');
|
|
1684
|
+
err.source = 'authentication';
|
|
1685
|
+
return reentry(err);
|
|
1686
|
+
}
|
|
1687
|
+
} else
|
|
1688
|
+
reentry();
|
|
1689
|
+
});
|
|
1690
|
+
};
|
|
1691
|
+
|
|
1692
|
+
Connection.prototype._starttls = function() {
|
|
1693
|
+
var self = this;
|
|
1694
|
+
this._enqueue('STARTTLS', function(err) {
|
|
1695
|
+
if (err) {
|
|
1696
|
+
self.emit('error', err);
|
|
1697
|
+
return self._sock.end();
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
self._caps = undefined;
|
|
1701
|
+
self._sock.removeAllListeners('error');
|
|
1702
|
+
|
|
1703
|
+
var tlsOptions = {};
|
|
1704
|
+
|
|
1705
|
+
tlsOptions.host = this._config.host;
|
|
1706
|
+
// Host name may be overridden the tlsOptions
|
|
1707
|
+
for (var k in this._config.tlsOptions)
|
|
1708
|
+
tlsOptions[k] = this._config.tlsOptions[k];
|
|
1709
|
+
tlsOptions.socket = self._sock;
|
|
1710
|
+
|
|
1711
|
+
self._sock = tls.connect(tlsOptions, function() {
|
|
1712
|
+
self._login();
|
|
1713
|
+
});
|
|
1714
|
+
|
|
1715
|
+
self._sock.on('error', self._onError);
|
|
1716
|
+
self._sock.on('timeout', self._onSocketTimeout);
|
|
1717
|
+
self._sock.setTimeout(self._config.socketTimeout);
|
|
1718
|
+
|
|
1719
|
+
self._parser.setStream(self._sock);
|
|
1720
|
+
});
|
|
1721
|
+
};
|
|
1722
|
+
|
|
1723
|
+
Connection.prototype._processQueue = function() {
|
|
1724
|
+
if (this._curReq || !this._queue.length || !this._sock || !this._sock.writable)
|
|
1725
|
+
return;
|
|
1726
|
+
|
|
1727
|
+
this._curReq = this._queue.shift();
|
|
1728
|
+
|
|
1729
|
+
if (this._tagcount === MAX_INT)
|
|
1730
|
+
this._tagcount = 0;
|
|
1731
|
+
|
|
1732
|
+
var prefix;
|
|
1733
|
+
|
|
1734
|
+
if (this._curReq.type === 'IDLE' || this._curReq.type === 'NOOP')
|
|
1735
|
+
prefix = this._curReq.type;
|
|
1736
|
+
else
|
|
1737
|
+
prefix = 'A' + (this._tagcount++);
|
|
1738
|
+
|
|
1739
|
+
var out = prefix + ' ' + this._curReq.fullcmd;
|
|
1740
|
+
this.debug && this.debug('=> ' + inspect(out));
|
|
1741
|
+
this._sock.write(out + CRLF, 'utf8');
|
|
1742
|
+
|
|
1743
|
+
if (this._curReq.literalAppendData) {
|
|
1744
|
+
// LITERAL+: we are appending a mesage, and not waiting for a reply
|
|
1745
|
+
this._sockWriteAppendData(this._curReq.literalAppendData);
|
|
1746
|
+
}
|
|
1747
|
+
};
|
|
1748
|
+
|
|
1749
|
+
Connection.prototype._sockWriteAppendData = function(appendData)
|
|
1750
|
+
{
|
|
1751
|
+
var val = appendData;
|
|
1752
|
+
if (Buffer.isBuffer(appendData))
|
|
1753
|
+
val = val.toString('utf8');
|
|
1754
|
+
|
|
1755
|
+
this.debug && this.debug('=> ' + inspect(val));
|
|
1756
|
+
this._sock.write(val);
|
|
1757
|
+
this._sock.write(CRLF);
|
|
1758
|
+
};
|
|
1759
|
+
|
|
1760
|
+
Connection.prototype._enqueue = function(fullcmd, promote, cb) {
|
|
1761
|
+
if (typeof promote === 'function') {
|
|
1762
|
+
cb = promote;
|
|
1763
|
+
promote = false;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
var info = {
|
|
1767
|
+
type: fullcmd.match(RE_CMD)[1],
|
|
1768
|
+
fullcmd: fullcmd,
|
|
1769
|
+
cb: cb,
|
|
1770
|
+
cbargs: []
|
|
1771
|
+
},
|
|
1772
|
+
self = this;
|
|
1773
|
+
|
|
1774
|
+
if (promote)
|
|
1775
|
+
this._queue.unshift(info);
|
|
1776
|
+
else
|
|
1777
|
+
this._queue.push(info);
|
|
1778
|
+
|
|
1779
|
+
if (!this._curReq
|
|
1780
|
+
&& this.state !== 'disconnected'
|
|
1781
|
+
&& this.state !== 'upgrading') {
|
|
1782
|
+
// defer until next tick for requests like APPEND and FETCH where access to
|
|
1783
|
+
// the request object is needed immediately after enqueueing
|
|
1784
|
+
process.nextTick(function() { self._processQueue(); });
|
|
1785
|
+
} else if (this._curReq
|
|
1786
|
+
&& this._curReq.type === 'IDLE'
|
|
1787
|
+
&& this._sock
|
|
1788
|
+
&& this._sock.writable
|
|
1789
|
+
&& this._idle.enabled) {
|
|
1790
|
+
this._idle.enabled = false;
|
|
1791
|
+
clearTimeout(this._tmrKeepalive);
|
|
1792
|
+
if (this._idle.started > 0) {
|
|
1793
|
+
// we've seen the continuation for our IDLE
|
|
1794
|
+
this.debug && this.debug('=> DONE');
|
|
1795
|
+
this._sock.write('DONE' + CRLF);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
};
|
|
1799
|
+
|
|
1800
|
+
Connection.parseHeader = parseHeader; // from Parser.js
|
|
1801
|
+
|
|
1802
|
+
module.exports = Connection;
|
|
1803
|
+
|
|
1804
|
+
// utilities -------------------------------------------------------------------
|
|
1805
|
+
|
|
1806
|
+
function escape(str) {
|
|
1807
|
+
return str.replace(RE_BACKSLASH, '\\\\').replace(RE_DBLQUOTE, '\\"');
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
function validateUIDList(uids, noThrow) {
|
|
1811
|
+
for (var i = 0, len = uids.length, intval; i < len; ++i) {
|
|
1812
|
+
if (typeof uids[i] === 'string') {
|
|
1813
|
+
if (uids[i] === '*' || uids[i] === '*:*') {
|
|
1814
|
+
if (len > 1)
|
|
1815
|
+
uids = ['*'];
|
|
1816
|
+
break;
|
|
1817
|
+
} else if (RE_NUM_RANGE.test(uids[i]))
|
|
1818
|
+
continue;
|
|
1819
|
+
}
|
|
1820
|
+
intval = parseInt(''+uids[i], 10);
|
|
1821
|
+
if (isNaN(intval)) {
|
|
1822
|
+
var err = new Error('UID/seqno must be an integer, "*", or a range: '
|
|
1823
|
+
+ uids[i]);
|
|
1824
|
+
if (noThrow)
|
|
1825
|
+
return err;
|
|
1826
|
+
else
|
|
1827
|
+
throw err;
|
|
1828
|
+
} else if (intval <= 0) {
|
|
1829
|
+
var err = new Error('UID/seqno must be greater than zero');
|
|
1830
|
+
if (noThrow)
|
|
1831
|
+
return err;
|
|
1832
|
+
else
|
|
1833
|
+
throw err;
|
|
1834
|
+
} else if (typeof uids[i] !== 'number') {
|
|
1835
|
+
uids[i] = intval;
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
function hasNonASCII(str) {
|
|
1841
|
+
for (var i = 0, len = str.length; i < len; ++i) {
|
|
1842
|
+
if (str.charCodeAt(i) > 0x7F)
|
|
1843
|
+
return true;
|
|
1844
|
+
}
|
|
1845
|
+
return false;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
function buildString(str) {
|
|
1849
|
+
if (typeof str !== 'string')
|
|
1850
|
+
str = ''+str;
|
|
1851
|
+
|
|
1852
|
+
if (hasNonASCII(str)) {
|
|
1853
|
+
var buf = Buffer.from(str, 'utf8');
|
|
1854
|
+
return '{' + buf.length + '}\r\n' + buf.toString('latin1');
|
|
1855
|
+
} else
|
|
1856
|
+
return '"' + escape(str) + '"';
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
function buildSearchQuery(options, extensions, info, isOrChild) {
|
|
1860
|
+
var searchargs = '', err, val;
|
|
1861
|
+
for (var i = 0, len = options.length; i < len; ++i) {
|
|
1862
|
+
var criteria = (isOrChild ? options : options[i]),
|
|
1863
|
+
args = null,
|
|
1864
|
+
modifier = (isOrChild ? '' : ' ');
|
|
1865
|
+
if (typeof criteria === 'string')
|
|
1866
|
+
criteria = criteria.toUpperCase();
|
|
1867
|
+
else if (Array.isArray(criteria)) {
|
|
1868
|
+
if (criteria.length > 1)
|
|
1869
|
+
args = criteria.slice(1);
|
|
1870
|
+
if (criteria.length > 0)
|
|
1871
|
+
criteria = criteria[0].toUpperCase();
|
|
1872
|
+
} else
|
|
1873
|
+
throw new Error('Unexpected search option data type. '
|
|
1874
|
+
+ 'Expected string or array. Got: ' + typeof criteria);
|
|
1875
|
+
if (criteria === 'OR') {
|
|
1876
|
+
if (args.length !== 2)
|
|
1877
|
+
throw new Error('OR must have exactly two arguments');
|
|
1878
|
+
if (isOrChild)
|
|
1879
|
+
searchargs += 'OR (';
|
|
1880
|
+
else
|
|
1881
|
+
searchargs += ' OR (';
|
|
1882
|
+
searchargs += buildSearchQuery(args[0], extensions, info, true);
|
|
1883
|
+
searchargs += ') (';
|
|
1884
|
+
searchargs += buildSearchQuery(args[1], extensions, info, true);
|
|
1885
|
+
searchargs += ')';
|
|
1886
|
+
} else {
|
|
1887
|
+
if (criteria[0] === '!') {
|
|
1888
|
+
modifier += 'NOT ';
|
|
1889
|
+
criteria = criteria.substr(1);
|
|
1890
|
+
}
|
|
1891
|
+
switch(criteria) {
|
|
1892
|
+
// -- Standard criteria --
|
|
1893
|
+
case 'ALL':
|
|
1894
|
+
case 'ANSWERED':
|
|
1895
|
+
case 'DELETED':
|
|
1896
|
+
case 'DRAFT':
|
|
1897
|
+
case 'FLAGGED':
|
|
1898
|
+
case 'NEW':
|
|
1899
|
+
case 'SEEN':
|
|
1900
|
+
case 'RECENT':
|
|
1901
|
+
case 'OLD':
|
|
1902
|
+
case 'UNANSWERED':
|
|
1903
|
+
case 'UNDELETED':
|
|
1904
|
+
case 'UNDRAFT':
|
|
1905
|
+
case 'UNFLAGGED':
|
|
1906
|
+
case 'UNSEEN':
|
|
1907
|
+
searchargs += modifier + criteria;
|
|
1908
|
+
break;
|
|
1909
|
+
case 'BCC':
|
|
1910
|
+
case 'BODY':
|
|
1911
|
+
case 'CC':
|
|
1912
|
+
case 'FROM':
|
|
1913
|
+
case 'SUBJECT':
|
|
1914
|
+
case 'TEXT':
|
|
1915
|
+
case 'TO':
|
|
1916
|
+
if (!args || args.length !== 1)
|
|
1917
|
+
throw new Error('Incorrect number of arguments for search option: '
|
|
1918
|
+
+ criteria);
|
|
1919
|
+
val = buildString(args[0]);
|
|
1920
|
+
if (info && val[0] === '{')
|
|
1921
|
+
info.hasUTF8 = true;
|
|
1922
|
+
searchargs += modifier + criteria + ' ' + val;
|
|
1923
|
+
break;
|
|
1924
|
+
case 'BEFORE':
|
|
1925
|
+
case 'ON':
|
|
1926
|
+
case 'SENTBEFORE':
|
|
1927
|
+
case 'SENTON':
|
|
1928
|
+
case 'SENTSINCE':
|
|
1929
|
+
case 'SINCE':
|
|
1930
|
+
if (!args || args.length !== 1)
|
|
1931
|
+
throw new Error('Incorrect number of arguments for search option: '
|
|
1932
|
+
+ criteria);
|
|
1933
|
+
else if (!(args[0] instanceof Date)) {
|
|
1934
|
+
if ((args[0] = new Date(args[0])).toString() === 'Invalid Date')
|
|
1935
|
+
throw new Error('Search option argument must be a Date object'
|
|
1936
|
+
+ ' or a parseable date string');
|
|
1937
|
+
}
|
|
1938
|
+
searchargs += modifier + criteria + ' ' + args[0].getDate() + '-'
|
|
1939
|
+
+ MONTHS[args[0].getMonth()] + '-'
|
|
1940
|
+
+ args[0].getFullYear();
|
|
1941
|
+
break;
|
|
1942
|
+
case 'KEYWORD':
|
|
1943
|
+
case 'UNKEYWORD':
|
|
1944
|
+
if (!args || args.length !== 1)
|
|
1945
|
+
throw new Error('Incorrect number of arguments for search option: '
|
|
1946
|
+
+ criteria);
|
|
1947
|
+
searchargs += modifier + criteria + ' ' + args[0];
|
|
1948
|
+
break;
|
|
1949
|
+
case 'LARGER':
|
|
1950
|
+
case 'SMALLER':
|
|
1951
|
+
if (!args || args.length !== 1)
|
|
1952
|
+
throw new Error('Incorrect number of arguments for search option: '
|
|
1953
|
+
+ criteria);
|
|
1954
|
+
var num = parseInt(args[0], 10);
|
|
1955
|
+
if (isNaN(num))
|
|
1956
|
+
throw new Error('Search option argument must be a number');
|
|
1957
|
+
searchargs += modifier + criteria + ' ' + args[0];
|
|
1958
|
+
break;
|
|
1959
|
+
case 'HEADER':
|
|
1960
|
+
if (!args || args.length !== 2)
|
|
1961
|
+
throw new Error('Incorrect number of arguments for search option: '
|
|
1962
|
+
+ criteria);
|
|
1963
|
+
val = buildString(args[1]);
|
|
1964
|
+
if (info && val[0] === '{')
|
|
1965
|
+
info.hasUTF8 = true;
|
|
1966
|
+
searchargs += modifier + criteria + ' "' + escape(''+args[0])
|
|
1967
|
+
+ '" ' + val;
|
|
1968
|
+
break;
|
|
1969
|
+
case 'UID':
|
|
1970
|
+
if (!args)
|
|
1971
|
+
throw new Error('Incorrect number of arguments for search option: '
|
|
1972
|
+
+ criteria);
|
|
1973
|
+
validateUIDList(args);
|
|
1974
|
+
if (args.length === 0)
|
|
1975
|
+
throw new Error('Empty uid list');
|
|
1976
|
+
searchargs += modifier + criteria + ' ' + args.join(',');
|
|
1977
|
+
break;
|
|
1978
|
+
// Extensions ==========================================================
|
|
1979
|
+
case 'X-GM-MSGID': // Gmail unique message ID
|
|
1980
|
+
case 'X-GM-THRID': // Gmail thread ID
|
|
1981
|
+
if (extensions.indexOf('X-GM-EXT-1') === -1)
|
|
1982
|
+
throw new Error('IMAP extension not available for: ' + criteria);
|
|
1983
|
+
if (!args || args.length !== 1)
|
|
1984
|
+
throw new Error('Incorrect number of arguments for search option: '
|
|
1985
|
+
+ criteria);
|
|
1986
|
+
else {
|
|
1987
|
+
val = ''+args[0];
|
|
1988
|
+
if (!(RE_INTEGER.test(args[0])))
|
|
1989
|
+
throw new Error('Invalid value');
|
|
1990
|
+
}
|
|
1991
|
+
searchargs += modifier + criteria + ' ' + val;
|
|
1992
|
+
break;
|
|
1993
|
+
case 'X-GM-RAW': // Gmail search syntax
|
|
1994
|
+
if (extensions.indexOf('X-GM-EXT-1') === -1)
|
|
1995
|
+
throw new Error('IMAP extension not available for: ' + criteria);
|
|
1996
|
+
if (!args || args.length !== 1)
|
|
1997
|
+
throw new Error('Incorrect number of arguments for search option: '
|
|
1998
|
+
+ criteria);
|
|
1999
|
+
val = buildString(args[0]);
|
|
2000
|
+
if (info && val[0] === '{')
|
|
2001
|
+
info.hasUTF8 = true;
|
|
2002
|
+
searchargs += modifier + criteria + ' ' + val;
|
|
2003
|
+
break;
|
|
2004
|
+
case 'X-GM-LABELS': // Gmail labels
|
|
2005
|
+
if (extensions.indexOf('X-GM-EXT-1') === -1)
|
|
2006
|
+
throw new Error('IMAP extension not available for: ' + criteria);
|
|
2007
|
+
if (!args || args.length !== 1)
|
|
2008
|
+
throw new Error('Incorrect number of arguments for search option: '
|
|
2009
|
+
+ criteria);
|
|
2010
|
+
searchargs += modifier + criteria + ' ' + args[0];
|
|
2011
|
+
break;
|
|
2012
|
+
case 'MODSEQ':
|
|
2013
|
+
if (extensions.indexOf('CONDSTORE') === -1)
|
|
2014
|
+
throw new Error('IMAP extension not available for: ' + criteria);
|
|
2015
|
+
if (!args || args.length !== 1)
|
|
2016
|
+
throw new Error('Incorrect number of arguments for search option: '
|
|
2017
|
+
+ criteria);
|
|
2018
|
+
searchargs += modifier + criteria + ' ' + args[0];
|
|
2019
|
+
break;
|
|
2020
|
+
default:
|
|
2021
|
+
// last hope it's a seqno set
|
|
2022
|
+
// http://tools.ietf.org/html/rfc3501#section-6.4.4
|
|
2023
|
+
var seqnos = (args ? [criteria].concat(args) : [criteria]);
|
|
2024
|
+
if (!validateUIDList(seqnos, true)) {
|
|
2025
|
+
if (seqnos.length === 0)
|
|
2026
|
+
throw new Error('Empty sequence number list');
|
|
2027
|
+
searchargs += modifier + seqnos.join(',');
|
|
2028
|
+
} else
|
|
2029
|
+
throw new Error('Unexpected search option: ' + criteria);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
if (isOrChild)
|
|
2033
|
+
break;
|
|
2034
|
+
}
|
|
2035
|
+
return searchargs;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
// Pulled from assert.deepEqual:
|
|
2039
|
+
var pSlice = Array.prototype.slice;
|
|
2040
|
+
function _deepEqual(actual, expected) {
|
|
2041
|
+
// 7.1. All identical values are equivalent, as determined by ===.
|
|
2042
|
+
if (actual === expected) {
|
|
2043
|
+
return true;
|
|
2044
|
+
|
|
2045
|
+
} else if (Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) {
|
|
2046
|
+
if (actual.length !== expected.length) return false;
|
|
2047
|
+
|
|
2048
|
+
for (var i = 0; i < actual.length; i++) {
|
|
2049
|
+
if (actual[i] !== expected[i]) return false;
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
return true;
|
|
2053
|
+
|
|
2054
|
+
// 7.2. If the expected value is a Date object, the actual value is
|
|
2055
|
+
// equivalent if it is also a Date object that refers to the same time.
|
|
2056
|
+
} else if (actual instanceof Date && expected instanceof Date) {
|
|
2057
|
+
return actual.getTime() === expected.getTime();
|
|
2058
|
+
|
|
2059
|
+
// 7.3 If the expected value is a RegExp object, the actual value is
|
|
2060
|
+
// equivalent if it is also a RegExp object with the same source and
|
|
2061
|
+
// properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
|
|
2062
|
+
} else if (actual instanceof RegExp && expected instanceof RegExp) {
|
|
2063
|
+
return actual.source === expected.source &&
|
|
2064
|
+
actual.global === expected.global &&
|
|
2065
|
+
actual.multiline === expected.multiline &&
|
|
2066
|
+
actual.lastIndex === expected.lastIndex &&
|
|
2067
|
+
actual.ignoreCase === expected.ignoreCase;
|
|
2068
|
+
|
|
2069
|
+
// 7.4. Other pairs that do not both pass typeof value == 'object',
|
|
2070
|
+
// equivalence is determined by ==.
|
|
2071
|
+
} else if (typeof actual !== 'object' && typeof expected !== 'object') {
|
|
2072
|
+
return actual == expected;
|
|
2073
|
+
|
|
2074
|
+
// 7.5 For all other Object pairs, including Array objects, equivalence is
|
|
2075
|
+
// determined by having the same number of owned properties (as verified
|
|
2076
|
+
// with Object.prototype.hasOwnProperty.call), the same set of keys
|
|
2077
|
+
// (although not necessarily the same order), equivalent values for every
|
|
2078
|
+
// corresponding key, and an identical 'prototype' property. Note: this
|
|
2079
|
+
// accounts for both named and indexed properties on Arrays.
|
|
2080
|
+
} else {
|
|
2081
|
+
return objEquiv(actual, expected);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
function isUndefinedOrNull(value) {
|
|
2085
|
+
return value === null || value === undefined;
|
|
2086
|
+
}
|
|
2087
|
+
function isArguments(object) {
|
|
2088
|
+
return Object.prototype.toString.call(object) === '[object Arguments]';
|
|
2089
|
+
}
|
|
2090
|
+
function objEquiv(a, b) {
|
|
2091
|
+
var ka, kb, key, i;
|
|
2092
|
+
if (isUndefinedOrNull(a) || isUndefinedOrNull(b))
|
|
2093
|
+
return false;
|
|
2094
|
+
// an identical 'prototype' property.
|
|
2095
|
+
if (a.prototype !== b.prototype) return false;
|
|
2096
|
+
//~~~I've managed to break Object.keys through screwy arguments passing.
|
|
2097
|
+
// Converting to array solves the problem.
|
|
2098
|
+
if (isArguments(a)) {
|
|
2099
|
+
if (!isArguments(b)) {
|
|
2100
|
+
return false;
|
|
2101
|
+
}
|
|
2102
|
+
a = pSlice.call(a);
|
|
2103
|
+
b = pSlice.call(b);
|
|
2104
|
+
return _deepEqual(a, b);
|
|
2105
|
+
}
|
|
2106
|
+
try {
|
|
2107
|
+
ka = Object.keys(a);
|
|
2108
|
+
kb = Object.keys(b);
|
|
2109
|
+
} catch (e) {//happens when one is a string literal and the other isn't
|
|
2110
|
+
return false;
|
|
2111
|
+
}
|
|
2112
|
+
// having the same number of owned properties (keys incorporates
|
|
2113
|
+
// hasOwnProperty)
|
|
2114
|
+
if (ka.length !== kb.length)
|
|
2115
|
+
return false;
|
|
2116
|
+
//the same set of keys (although not necessarily the same order),
|
|
2117
|
+
ka.sort();
|
|
2118
|
+
kb.sort();
|
|
2119
|
+
//~~~cheap key test
|
|
2120
|
+
for (i = ka.length - 1; i >= 0; i--) {
|
|
2121
|
+
if (ka[i] != kb[i])
|
|
2122
|
+
return false;
|
|
2123
|
+
}
|
|
2124
|
+
//equivalent values for every corresponding key, and
|
|
2125
|
+
//~~~possibly expensive deep test
|
|
2126
|
+
for (i = ka.length - 1; i >= 0; i--) {
|
|
2127
|
+
key = ka[i];
|
|
2128
|
+
if (!_deepEqual(a[key], b[key])) return false;
|
|
2129
|
+
}
|
|
2130
|
+
return true;
|
|
2131
|
+
}
|