|
|
|
@ -22,19 +22,26 @@ var CRLF = '\r\n',
|
|
|
|
|
BOXSELECTING: 3,
|
|
|
|
|
BOXSELECTED: 4
|
|
|
|
|
},
|
|
|
|
|
RE_LITHEADER = /(?:((?:BODY\[.*\](?:<\d+>)?)?|[^ ]+) )?\{(\d+)\}$/i,
|
|
|
|
|
RE_UNRESP = /^\* (OK|PREAUTH|NO|BAD) (?:\[(.+)\] )?(.+)$/i,
|
|
|
|
|
RE_LITHEADER = /(?:((?:BODY\[.*\](?:<\d+>)?)?|[^ ]+) )?\{(\d+)\}(?:$|\r\n)/i,
|
|
|
|
|
RE_UNRESP = /^\* (OK|PREAUTH|NO|BAD)(?:\r\n|(?: \[(.+?)\])?(?: (.+))?)(?:$|\r\n)/i,
|
|
|
|
|
RE_TAGGED_RESP = /^A\d+ (OK|NO|BAD) (?:\[(.+?)\] )?(.+)(?:$|\r\n)/i,
|
|
|
|
|
RE_TEXT_CODE = /([^ ]+)(?: (.*))?$/,
|
|
|
|
|
RE_RES_IDLE = /^IDLE /i,
|
|
|
|
|
RE_RES_NOOP = /^NOOP /i,
|
|
|
|
|
RE_CMD_FETCH = /^(?:UID )?FETCH/i,
|
|
|
|
|
RE_PARTID = /^(?:[\d]+[\.]{0,1})*[\d]+$/,
|
|
|
|
|
RE_ESCAPE = /\\\\/g,
|
|
|
|
|
//RE_ISPARTIAL = /<(\d+)>$/,
|
|
|
|
|
RE_DBLQ = /"/g,
|
|
|
|
|
RE_CMD = /^([^ ]+)(?: |$)/,
|
|
|
|
|
RE_ISHEADER = /HEADER/,
|
|
|
|
|
REX_UNRESPDATA = XRegExp('^\\* (?:(?:(?<type>NAMESPACE) (?<personal>(?:NIL|\\((?:\\(.+\\))+\\))) (?<other>(?:NIL|\\((?:\\(.+\\))+\\))) (?<shared>(?:NIL|\\((?:\\(.+\\))+\\))))|(?:(?<type>FLAGS) \\((?<flags>.*)\\))|(?:(?<type>LIST|LSUB|XLIST) \\((?<flags>.*)\\) (?<delimiter>"[^"]+"|NIL) (?<mailbox>.+))|(?:(?<type>(SEARCH|SORT))(?: (?<results>.*))?)|(?:(?<type>STATUS) (?<mailbox>.+) \\((?<attributes>.*)\\))|(?:(?<type>CAPABILITY) (?<capabilities>.+))|(?:(?<type>BYE) (?:\\[(?<code>.+)\\] )?(?<message>.+)))[ \t]*(?:\r\n|$)', 'i'),
|
|
|
|
|
REX_UNRESPNUM = XRegExp('^\\* (?<num>\\d+) (?:(?<type>EXISTS)|(?<type>RECENT)|(?<type>EXPUNGE)|(?:(?<type>FETCH) \\((?<info>.*)\\)))[ \t]*(?:\r\n|$)', 'i');
|
|
|
|
|
REX_UNRESPDATA = XRegExp('^\\* (?:(?:(?<type>NAMESPACE) (?<personal>(?:NIL|\\((?:\\(.+\\))+\\))) (?<other>(?:NIL|\\((?:\\(.+\\))+\\))) (?<shared>(?:NIL|\\((?:\\(.+\\))+\\))))|(?:(?<type>FLAGS) \\((?<flags>.*)\\))|(?:(?<type>LIST|LSUB|XLIST) \\((?<flags>.*)\\) (?<delimiter>"[^"]+"|NIL) (?<mailbox>.+))|(?:(?<type>(SEARCH|SORT))(?: (?<results>.*))?)|(?:(?<type>STATUS) (?<mailbox>.+) \\((?<attributes>.*)\\))|(?:(?<type>CAPABILITY) (?<capabilities>.+))|(?:(?<type>BYE) (?:\\[(?<code>.+)\\] )?(?<message>.+)))[ \t]*(?:$|\r\n)', 'i'),
|
|
|
|
|
REX_UNRESPNUM = XRegExp('^\\* (?<num>\\d+) (?:(?<type>EXISTS)|(?<type>RECENT)|(?<type>EXPUNGE)|(?:(?<type>FETCH) \\((?<info>.*)\\)))[ \t]*(?:$|\r\n)', 'i');
|
|
|
|
|
|
|
|
|
|
// extension constants
|
|
|
|
|
var IDLE_NONE = 1,
|
|
|
|
|
IDLE_WAIT = 2,
|
|
|
|
|
IDLE_READY = 3,
|
|
|
|
|
IDLE_IDLING = 3,
|
|
|
|
|
IDLE_DONE = 4;
|
|
|
|
|
|
|
|
|
|
function ImapConnection(options) {
|
|
|
|
@ -47,7 +54,9 @@ function ImapConnection(options) {
|
|
|
|
|
password: options.password || '',
|
|
|
|
|
host: options.host || 'localhost',
|
|
|
|
|
port: options.port || 143,
|
|
|
|
|
secure: options.secure || false,
|
|
|
|
|
secure: options.secure === true ? { // secure = true means default behavior
|
|
|
|
|
rejectUnauthorized: false // Force pre-node-0.9.2 behavior
|
|
|
|
|
} : (options.secure || false),
|
|
|
|
|
connTimeout: options.connTimeout || 10000, // connection timeout in msecs
|
|
|
|
|
xoauth: options.xoauth,
|
|
|
|
|
xoauth2: options.xoauth2
|
|
|
|
@ -87,9 +96,8 @@ function ImapConnection(options) {
|
|
|
|
|
ext: {
|
|
|
|
|
// Capability-specific state info
|
|
|
|
|
idle: {
|
|
|
|
|
MAX_WAIT: 1740000, // 29 mins in ms
|
|
|
|
|
MAX_WAIT: 300000, // 5 mins in ms
|
|
|
|
|
state: IDLE_NONE,
|
|
|
|
|
reIDLE: false,
|
|
|
|
|
timeStarted: undefined
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -124,10 +132,14 @@ ImapConnection.prototype.connect = function(loginCb) {
|
|
|
|
|
socket.setTimeout(0);
|
|
|
|
|
|
|
|
|
|
if (this._options.secure) {
|
|
|
|
|
var tlsOptions = {};
|
|
|
|
|
for (var k in this._options.secure)
|
|
|
|
|
tlsOptions[k] = this._options.secure[k];
|
|
|
|
|
tlsOptions.socket = state.conn;
|
|
|
|
|
if (process.version.indexOf('v0.6.') > -1)
|
|
|
|
|
socket = tls.connect(null, { socket: state.conn }, onconnect);
|
|
|
|
|
socket = tls.connect(null, tlsOptions, onconnect);
|
|
|
|
|
else
|
|
|
|
|
socket = tls.connect({ socket: state.conn }, onconnect);
|
|
|
|
|
socket = tls.connect(tlsOptions, onconnect);
|
|
|
|
|
} else
|
|
|
|
|
state.conn.once('connect', onconnect);
|
|
|
|
|
|
|
|
|
@ -156,7 +168,7 @@ ImapConnection.prototype.connect = function(loginCb) {
|
|
|
|
|
self.emit('close', had_error);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
state.conn.on('error', function(err) {
|
|
|
|
|
socket.on('error', function(err) {
|
|
|
|
|
clearTimeout(state.tmrConn);
|
|
|
|
|
err.level = 'socket';
|
|
|
|
|
if (state.status === STATES.NOCONNECT)
|
|
|
|
@ -174,7 +186,7 @@ ImapConnection.prototype.connect = function(loginCb) {
|
|
|
|
|
return loginCb(err);
|
|
|
|
|
}
|
|
|
|
|
// Next, get the list of available namespaces if supported (RFC2342)
|
|
|
|
|
if (!checkedNS && self._serverSupports('NAMESPACE')) {
|
|
|
|
|
if (!checkedNS && self.serverSupports('NAMESPACE')) {
|
|
|
|
|
// Re-enter this function after we've obtained the available
|
|
|
|
|
// namespaces
|
|
|
|
|
checkedNS = true;
|
|
|
|
@ -299,9 +311,9 @@ ImapConnection.prototype.connect = function(loginCb) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (indata.line[0] === '*') { // Untagged server response
|
|
|
|
|
var isUnsolicited =
|
|
|
|
|
(requests[0] && requests[0].cmd === 'NOOP')
|
|
|
|
|
|| (state.isIdle && state.ext.idle.state === IDLE_READY);
|
|
|
|
|
var isUnsolicited = (requests[0] && requests[0].cmd === 'NOOP')
|
|
|
|
|
|| (state.isIdle && state.ext.idle.state !== IDLE_NONE)
|
|
|
|
|
|| !requests.length;
|
|
|
|
|
if (m = XRegExp.exec(indata.line, REX_UNRESPNUM)) {
|
|
|
|
|
// m.type = response type (numeric-based)
|
|
|
|
|
m.type = m.type.toUpperCase();
|
|
|
|
@ -311,12 +323,24 @@ ImapConnection.prototype.connect = function(loginCb) {
|
|
|
|
|
// m.info = message details
|
|
|
|
|
var data, parsed, headers, f, lenf, body, lenb, msg, bodies,
|
|
|
|
|
details, val;
|
|
|
|
|
|
|
|
|
|
isUnsolicited = isUnsolicited
|
|
|
|
|
|| (requests[0]
|
|
|
|
|
&& !RE_CMD_FETCH.test(requests[0].cmdstr));
|
|
|
|
|
|
|
|
|
|
if (!isUnsolicited)
|
|
|
|
|
bodies = parsers.parseFetchBodies(m.info, indata.literals);
|
|
|
|
|
|
|
|
|
|
details = new ImapMessage();
|
|
|
|
|
parsers.parseFetch(m.info, indata.literals, details);
|
|
|
|
|
details.seqno = parseInt(m.num, 10);
|
|
|
|
|
|
|
|
|
|
if (typeof details['x-gm-labels'] !== undefined) {
|
|
|
|
|
var labels = details['x-gm-labels'];
|
|
|
|
|
for (var i=0, len=labels.length; i<len; ++i)
|
|
|
|
|
labels[i] = labels[i].replace(RE_ESCAPE, '\\');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isUnsolicited)
|
|
|
|
|
self.emit('msgupdate', details);
|
|
|
|
|
else {
|
|
|
|
@ -531,23 +555,31 @@ ImapConnection.prototype.connect = function(loginCb) {
|
|
|
|
|
self.debug&&self.debug('[parsing incoming] saw untagged ' + m[1]);
|
|
|
|
|
switch (m[1]) {
|
|
|
|
|
case 'OK':
|
|
|
|
|
var code = m[2];
|
|
|
|
|
if (m[2] === undefined && m[3] === undefined)
|
|
|
|
|
break;
|
|
|
|
|
var code, codeval;
|
|
|
|
|
if (m[2]) {
|
|
|
|
|
code = RE_TEXT_CODE.exec(m[2]);
|
|
|
|
|
codeval = code[2];
|
|
|
|
|
code = code[1].toUpperCase();
|
|
|
|
|
}
|
|
|
|
|
if (state.status === STATES.NOAUTH) {
|
|
|
|
|
if (!state.isReady) {
|
|
|
|
|
clearTimeout(state.tmrConn);
|
|
|
|
|
state.isReady = true;
|
|
|
|
|
state.conn.emit('ready');
|
|
|
|
|
}
|
|
|
|
|
} else if (/^ALERT$/i.test(code))
|
|
|
|
|
} else if (code === 'ALERT')
|
|
|
|
|
self.emit('alert', m[3]);
|
|
|
|
|
else if (state.status === STATES.BOXSELECTING) {
|
|
|
|
|
if (m = /^UIDVALIDITY (\d+)/i.exec(code))
|
|
|
|
|
state.box.uidvalidity = parseInt(m[1], 10);
|
|
|
|
|
else if (m = /^UIDNEXT (\d+)/i.exec(code))
|
|
|
|
|
state.box.uidnext = parseInt(m[1], 10);
|
|
|
|
|
else if (m = /^PERMANENTFLAGS \((.*)\)/i.exec(code)) {
|
|
|
|
|
if (code === 'UIDVALIDITY')
|
|
|
|
|
state.box.uidvalidity = parseInt(codeval, 10);
|
|
|
|
|
else if (code === 'UIDNEXT')
|
|
|
|
|
state.box.uidnext = parseInt(codeval, 10);
|
|
|
|
|
else if (code === 'PERMANENTFLAGS') {
|
|
|
|
|
var idx, permFlags, keywords;
|
|
|
|
|
state.box.permFlags = permFlags = m[1].split(' ');
|
|
|
|
|
codeval = codeval.substr(1, codeval.length - 2);
|
|
|
|
|
state.box.permFlags = permFlags = codeval.split(' ');
|
|
|
|
|
if ((idx = state.box.permFlags.indexOf('\\*')) > -1) {
|
|
|
|
|
state.box.newKeywords = true;
|
|
|
|
|
permFlags.splice(idx, 1);
|
|
|
|
@ -562,8 +594,8 @@ ImapConnection.prototype.connect = function(loginCb) {
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else if (state.status === STATES.BOXSELECTED) {
|
|
|
|
|
if (m = /^UIDVALIDITY (\d+)/i.exec(code)) {
|
|
|
|
|
state.box.uidvalidity = parseInt(m[1], 10);
|
|
|
|
|
if (code === 'UIDVALIDITY') {
|
|
|
|
|
state.box.uidvalidity = parseInt(codeval, 10);
|
|
|
|
|
self.emit('uidvalidity', state.box.uidvalidity);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -608,18 +640,21 @@ ImapConnection.prototype.connect = function(loginCb) {
|
|
|
|
|
indata.temp = undefined;
|
|
|
|
|
indata.streaming = false;
|
|
|
|
|
indata.expect = -1;
|
|
|
|
|
|
|
|
|
|
self.debug&&self.debug(line[0] === 'A'
|
|
|
|
|
? '[parsing incoming] saw tagged response'
|
|
|
|
|
: '[parsing incoming] saw continuation response');
|
|
|
|
|
|
|
|
|
|
clearTimeout(state.tmrKeepalive);
|
|
|
|
|
|
|
|
|
|
if (line[0] === '+' && state.ext.idle.state === IDLE_WAIT) {
|
|
|
|
|
state.ext.idle.state = IDLE_READY;
|
|
|
|
|
state.ext.idle.state = IDLE_IDLING;
|
|
|
|
|
state.ext.idle.timeStarted = Date.now();
|
|
|
|
|
doKeepaliveTimer();
|
|
|
|
|
return process.nextTick(function() { self._send(); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var sendBox = false;
|
|
|
|
|
clearTimeout(state.tmrKeepalive);
|
|
|
|
|
|
|
|
|
|
if (state.status === STATES.BOXSELECTING) {
|
|
|
|
|
if (/^A\d+ OK/i.test(line)) {
|
|
|
|
|
sendBox = true;
|
|
|
|
@ -630,7 +665,6 @@ ImapConnection.prototype.connect = function(loginCb) {
|
|
|
|
|
self._resetBox();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (requests[0].cmd === 'RENAME') {
|
|
|
|
|
if (state.box._newName) {
|
|
|
|
|
state.box.name = state.box._newName;
|
|
|
|
@ -640,18 +674,24 @@ ImapConnection.prototype.connect = function(loginCb) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (typeof requests[0].callback === 'function') {
|
|
|
|
|
m = RE_TAGGED_RESP.exec(line);
|
|
|
|
|
var err = null;
|
|
|
|
|
var args = requests[0].cbargs,
|
|
|
|
|
cmdstr = requests[0].cmdstr;
|
|
|
|
|
if (line[0] === '+') {
|
|
|
|
|
if (requests[0].cmd !== 'APPEND') {
|
|
|
|
|
err = new Error('Unexpected continuation');
|
|
|
|
|
if (!m) {
|
|
|
|
|
if (requests[0].cmd === 'APPEND')
|
|
|
|
|
return requests[0].callback();
|
|
|
|
|
else {
|
|
|
|
|
var isXOAuth2 = (cmdstr.indexOf('AUTHENTICATE XOAUTH2') === 0),
|
|
|
|
|
msg = (isXOAuth2
|
|
|
|
|
? new Buffer(line.substr(2), 'base64').toString('utf8')
|
|
|
|
|
: 'Unexpected continuation');
|
|
|
|
|
err = new Error(msg);
|
|
|
|
|
err.level = 'protocol';
|
|
|
|
|
err.type = 'continuation';
|
|
|
|
|
err.type = (isXOAuth2 ? 'failure' : 'continuation');
|
|
|
|
|
err.request = cmdstr;
|
|
|
|
|
} else
|
|
|
|
|
return requests[0].callback();
|
|
|
|
|
} else if (m = /^A\d+ (NO|BAD) (?:\[(.+?)\] )?(.+)$/i.exec(line)) {
|
|
|
|
|
}
|
|
|
|
|
} else if (m[1] !== 'OK') {
|
|
|
|
|
// m[1]: error type
|
|
|
|
|
// m[2]: resp-text-code
|
|
|
|
|
// m[3]: message
|
|
|
|
@ -672,45 +712,50 @@ ImapConnection.prototype.connect = function(loginCb) {
|
|
|
|
|
) && args.length === 0)
|
|
|
|
|
args.unshift([]);
|
|
|
|
|
}
|
|
|
|
|
if (m) {
|
|
|
|
|
var msg = m[3], info;
|
|
|
|
|
if (m[2]) {
|
|
|
|
|
m = RE_TEXT_CODE.exec(m[2]);
|
|
|
|
|
info = {
|
|
|
|
|
code: m[1].toUpperCase(),
|
|
|
|
|
codeval: m[2],
|
|
|
|
|
message: msg
|
|
|
|
|
};
|
|
|
|
|
} else
|
|
|
|
|
info = { message: msg };
|
|
|
|
|
args.push(info);
|
|
|
|
|
}
|
|
|
|
|
args.unshift(err);
|
|
|
|
|
requests[0].callback.apply(self, args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var recentCmd = requests[0].cmdstr;
|
|
|
|
|
var recentCmd = requests[0].cmd;
|
|
|
|
|
requests.shift();
|
|
|
|
|
if (requests.length === 0 && recentCmd !== 'LOGOUT') {
|
|
|
|
|
if (state.status >= STATES.AUTH && self._serverSupports('IDLE')) {
|
|
|
|
|
// According to RFC 2177, we should re-IDLE at least every 29
|
|
|
|
|
// minutes to avoid disconnection by the server
|
|
|
|
|
self._send('IDLE', undefined, true);
|
|
|
|
|
}
|
|
|
|
|
state.tmrKeepalive = setTimeout(function idleHandler() {
|
|
|
|
|
if (state.isIdle) {
|
|
|
|
|
if (state.ext.idle.state === IDLE_READY) {
|
|
|
|
|
state.tmrKeepalive = setTimeout(idleHandler, state.tmoKeepalive);
|
|
|
|
|
var timeDiff = Date.now() - state.ext.idle.timeStarted;
|
|
|
|
|
if (timeDiff >= state.ext.idle.MAX_WAIT)
|
|
|
|
|
self._send('IDLE', undefined, true); // restart IDLE
|
|
|
|
|
} else if (!self._serverSupports('IDLE'))
|
|
|
|
|
self._noop();
|
|
|
|
|
}
|
|
|
|
|
}, state.tmoKeepalive);
|
|
|
|
|
} else
|
|
|
|
|
|
|
|
|
|
if (!requests.length && recentCmd !== 'LOGOUT')
|
|
|
|
|
doKeepalive();
|
|
|
|
|
else
|
|
|
|
|
process.nextTick(function() { self._send(); });
|
|
|
|
|
|
|
|
|
|
state.isIdle = true;
|
|
|
|
|
} else if (/^IDLE /i.test(indata.line)) {
|
|
|
|
|
} else if (RE_RES_IDLE.test(indata.line)) {
|
|
|
|
|
self.debug&&self.debug('[parsing incoming] saw IDLE');
|
|
|
|
|
if (requests.length)
|
|
|
|
|
process.nextTick(function() { self._send(); });
|
|
|
|
|
state.isIdle = false;
|
|
|
|
|
state.ext.idle.state = IDLE_NONE;
|
|
|
|
|
state.ext.idle.timeStated = undefined;
|
|
|
|
|
requests.shift(); // remove IDLE request
|
|
|
|
|
indata.line = undefined;
|
|
|
|
|
if (state.ext.idle.reIDLE) {
|
|
|
|
|
state.ext.idle.reIDLE = false;
|
|
|
|
|
self._send('IDLE', undefined, true);
|
|
|
|
|
}
|
|
|
|
|
state.ext.idle.state = IDLE_NONE;
|
|
|
|
|
state.ext.idle.timeStarted = undefined;
|
|
|
|
|
if (requests.length) {
|
|
|
|
|
state.isIdle = false;
|
|
|
|
|
self._send();
|
|
|
|
|
} else
|
|
|
|
|
doKeepalive();
|
|
|
|
|
} else if (RE_RES_NOOP.test(indata.line)) {
|
|
|
|
|
self.debug&&self.debug('[parsing incoming] saw NOOP');
|
|
|
|
|
requests.shift(); // remove NOOP request
|
|
|
|
|
if (!requests.length)
|
|
|
|
|
doKeepaliveTimer();
|
|
|
|
|
else
|
|
|
|
|
self._send();
|
|
|
|
|
} else {
|
|
|
|
|
// unknown response
|
|
|
|
|
self.debug&&self.debug('[parsing incoming] saw unexpected response: '
|
|
|
|
@ -719,6 +764,31 @@ ImapConnection.prototype.connect = function(loginCb) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function doKeepalive() {
|
|
|
|
|
if (state.status >= STATES.AUTH) {
|
|
|
|
|
if (self.serverSupports('IDLE'))
|
|
|
|
|
self._send('IDLE');
|
|
|
|
|
else
|
|
|
|
|
self._noop();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function doKeepaliveTimer() {
|
|
|
|
|
state.tmrKeepalive = setTimeout(function idleHandler() {
|
|
|
|
|
if (state.isIdle) {
|
|
|
|
|
if (state.ext.idle.state === IDLE_IDLING) {
|
|
|
|
|
var timeDiff = Date.now() - state.ext.idle.timeStarted;
|
|
|
|
|
if (timeDiff >= state.ext.idle.MAX_WAIT) {
|
|
|
|
|
state.ext.idle.state = IDLE_DONE;
|
|
|
|
|
self._send('DONE');
|
|
|
|
|
} else
|
|
|
|
|
state.tmrKeepalive = setTimeout(idleHandler, state.tmoKeepalive);
|
|
|
|
|
} else if (!self.serverSupports('IDLE'))
|
|
|
|
|
doKeepalive();
|
|
|
|
|
}
|
|
|
|
|
}, state.tmoKeepalive);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.conn.connect(this._options.port, this._options.host);
|
|
|
|
|
|
|
|
|
|
state.tmrConn = setTimeout(function() {
|
|
|
|
@ -796,7 +866,7 @@ ImapConnection.prototype.getBoxes = function(namespace, cb) {
|
|
|
|
|
cb = namespace;
|
|
|
|
|
namespace = '';
|
|
|
|
|
}
|
|
|
|
|
this._send((!this._serverSupports('XLIST') ? 'LIST' : 'XLIST')
|
|
|
|
|
this._send((!this.serverSupports('XLIST') ? 'LIST' : 'XLIST')
|
|
|
|
|
+ ' "' + utils.escape(utf7.encode(''+namespace)) + '" "*"', cb);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@ -837,7 +907,8 @@ ImapConnection.prototype.append = function(data, options, cb) {
|
|
|
|
|
if (options.flags) {
|
|
|
|
|
if (!Array.isArray(options.flags))
|
|
|
|
|
options.flags = [options.flags];
|
|
|
|
|
cmd += " (\\" + options.flags.join(' \\') + ")";
|
|
|
|
|
if (options.flags.length > 0)
|
|
|
|
|
cmd += " (\\" + options.flags.join(' \\') + ")";
|
|
|
|
|
}
|
|
|
|
|
if (options.date) {
|
|
|
|
|
if (!isDate(options.date))
|
|
|
|
@ -863,9 +934,9 @@ ImapConnection.prototype.append = function(data, options, cb) {
|
|
|
|
|
cmd += (Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data));
|
|
|
|
|
cmd += '}';
|
|
|
|
|
var self = this, step = 1;
|
|
|
|
|
this._send(cmd, function(err) {
|
|
|
|
|
this._send(cmd, function(err, info) {
|
|
|
|
|
if (err || step++ === 2)
|
|
|
|
|
return cb(err);
|
|
|
|
|
return cb(err, info);
|
|
|
|
|
self._state.conn.write(data);
|
|
|
|
|
self._state.conn.write(CRLF);
|
|
|
|
|
self.debug&&self.debug('\n==> ' + inspect(data.toString()) + '\n');
|
|
|
|
@ -896,7 +967,7 @@ ImapConnection.prototype._sort = function(which, sorts, options, cb) {
|
|
|
|
|
throw new Error('Expected array with at least one sort criteria');
|
|
|
|
|
if (!Array.isArray(options))
|
|
|
|
|
throw new Error('Expected array for search options');
|
|
|
|
|
if (!this._serverSupports('SORT'))
|
|
|
|
|
if (!this.serverSupports('SORT'))
|
|
|
|
|
return cb(new Error('Sorting is not supported on the server'));
|
|
|
|
|
|
|
|
|
|
var criteria = sorts.map(function(criterion) {
|
|
|
|
@ -991,9 +1062,9 @@ ImapConnection.prototype._fetch = function(which, uids, options, what, cb) {
|
|
|
|
|
for (var i = 0, wp, pprefix, len = what.length; i < len; ++i) {
|
|
|
|
|
wp = what[i];
|
|
|
|
|
parse = true;
|
|
|
|
|
if (wp.id !== undefined && !/^(?:[\d]+[\.]{0,1})*[\d]+$/.test(''+wp.id))
|
|
|
|
|
if (wp.id !== undefined && !RE_PARTID.test(''+wp.id))
|
|
|
|
|
throw new Error('Invalid part id: ' + wp.id);
|
|
|
|
|
if (( (wp.headers
|
|
|
|
|
if (( (typeof wp.headers === 'object'
|
|
|
|
|
&& (!wp.headers.fields
|
|
|
|
|
|| (Array.isArray(wp.headers.fields)
|
|
|
|
|
&& wp.headers.fields.length === 0)
|
|
|
|
@ -1001,7 +1072,7 @@ ImapConnection.prototype._fetch = function(which, uids, options, what, cb) {
|
|
|
|
|
&& wp.headers.parse === false
|
|
|
|
|
)
|
|
|
|
|
||
|
|
|
|
|
(wp.headersNot
|
|
|
|
|
(typeof wp.headersNot === 'object'
|
|
|
|
|
&& (!wp.headersNot.fields
|
|
|
|
|
|| (Array.isArray(wp.headersNot.fields)
|
|
|
|
|
&& wp.headersNot.fields.length === 0)
|
|
|
|
@ -1155,7 +1226,7 @@ ImapConnection.prototype._fetch = function(which, uids, options, what, cb) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// always fetch GMail-specific bits of information when on GMail
|
|
|
|
|
if (this._serverSupports('X-GM-EXT-1'))
|
|
|
|
|
if (this.serverSupports('X-GM-EXT-1'))
|
|
|
|
|
extensions = 'X-GM-THRID X-GM-MSGID X-GM-LABELS ';
|
|
|
|
|
|
|
|
|
|
var cmd = which;
|
|
|
|
@ -1233,7 +1304,7 @@ ImapConnection.prototype.delLabels = function(uids, labels, cb) {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ImapConnection.prototype._storeLabels = function(which, uids, labels, mode, cb) {
|
|
|
|
|
if (!this._serverSupports('X-GM-EXT-1'))
|
|
|
|
|
if (!this.serverSupports('X-GM-EXT-1'))
|
|
|
|
|
throw new Error('Server must support X-GM-EXT-1 capability');
|
|
|
|
|
if (this._state.status !== STATES.BOXSELECTED)
|
|
|
|
|
throw new Error('No mailbox is currently selected');
|
|
|
|
@ -1284,46 +1355,47 @@ ImapConnection.prototype._move = function(which, uids, boxTo, cb) {
|
|
|
|
|
throw new Error('Cannot move message: '
|
|
|
|
|
+ 'server does not allow deletion of messages');
|
|
|
|
|
} else {
|
|
|
|
|
this._copy(which, uids, boxTo, function ccb(err, reentryCount, deletedUIDs,
|
|
|
|
|
counter) {
|
|
|
|
|
if (err)
|
|
|
|
|
return cb(err);
|
|
|
|
|
|
|
|
|
|
counter = counter || 0;
|
|
|
|
|
// Make sure we don't expunge any messages marked as Deleted except the
|
|
|
|
|
// one we are moving
|
|
|
|
|
if (reentryCount === undefined) {
|
|
|
|
|
self.search(['DELETED'], function(e, result) {
|
|
|
|
|
ccb(e, 1, result);
|
|
|
|
|
});
|
|
|
|
|
} else if (reentryCount === 1) {
|
|
|
|
|
if (counter < deletedUIDs.length) {
|
|
|
|
|
self.delFlags(deletedUIDs[counter], 'Deleted', function(e) {
|
|
|
|
|
process.nextTick(function() {
|
|
|
|
|
ccb(e, reentryCount, deletedUIDs, counter + 1);
|
|
|
|
|
});
|
|
|
|
|
this._copy(which, uids, boxTo,
|
|
|
|
|
function ccb(err, info, reentryCount, deletedUIDs, counter) {
|
|
|
|
|
if (err)
|
|
|
|
|
return cb(err, info);
|
|
|
|
|
|
|
|
|
|
counter = counter || 0;
|
|
|
|
|
// Make sure we don't expunge any messages marked as Deleted except the
|
|
|
|
|
// one we are moving
|
|
|
|
|
if (reentryCount === undefined) {
|
|
|
|
|
self.search(['DELETED'], function(e, result) {
|
|
|
|
|
ccb(e, info, 1, result);
|
|
|
|
|
});
|
|
|
|
|
} else
|
|
|
|
|
ccb(err, reentryCount + 1, deletedUIDs);
|
|
|
|
|
} else if (reentryCount === 2) {
|
|
|
|
|
self.addFlags(uids, 'Deleted', function(e) {
|
|
|
|
|
ccb(e, reentryCount + 1, deletedUIDs);
|
|
|
|
|
});
|
|
|
|
|
} else if (reentryCount === 3) {
|
|
|
|
|
self.removeDeleted(function(e) {
|
|
|
|
|
ccb(e, reentryCount + 1, deletedUIDs);
|
|
|
|
|
});
|
|
|
|
|
} else if (reentryCount === 4) {
|
|
|
|
|
if (counter < deletedUIDs.length) {
|
|
|
|
|
self.addFlags(deletedUIDs[counter], 'Deleted', function(e) {
|
|
|
|
|
process.nextTick(function() {
|
|
|
|
|
ccb(e, reentryCount, deletedUIDs, counter + 1);
|
|
|
|
|
} else if (reentryCount === 1) {
|
|
|
|
|
if (counter < deletedUIDs.length) {
|
|
|
|
|
self.delFlags(deletedUIDs[counter], 'Deleted', function(e) {
|
|
|
|
|
process.nextTick(function() {
|
|
|
|
|
ccb(e, info, reentryCount, deletedUIDs, counter + 1);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
} else
|
|
|
|
|
ccb(err, info, reentryCount + 1, deletedUIDs);
|
|
|
|
|
} else if (reentryCount === 2) {
|
|
|
|
|
self.addFlags(uids, 'Deleted', function(e) {
|
|
|
|
|
ccb(e, info, reentryCount + 1, deletedUIDs);
|
|
|
|
|
});
|
|
|
|
|
} else
|
|
|
|
|
cb();
|
|
|
|
|
} else if (reentryCount === 3) {
|
|
|
|
|
self.removeDeleted(function(e) {
|
|
|
|
|
ccb(e, info, reentryCount + 1, deletedUIDs);
|
|
|
|
|
});
|
|
|
|
|
} else if (reentryCount === 4) {
|
|
|
|
|
if (counter < deletedUIDs.length) {
|
|
|
|
|
self.addFlags(deletedUIDs[counter], 'Deleted', function(e) {
|
|
|
|
|
process.nextTick(function() {
|
|
|
|
|
ccb(e, info, reentryCount, deletedUIDs, counter + 1);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
} else
|
|
|
|
|
cb(err, info);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@ -1372,7 +1444,7 @@ ImapConnection.prototype.__defineGetter__('seq', function() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Private/Internal Functions
|
|
|
|
|
ImapConnection.prototype._serverSupports = function(capability) {
|
|
|
|
|
ImapConnection.prototype.serverSupports = function(capability) {
|
|
|
|
|
return (this.capabilities.indexOf(capability) > -1);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@ -1435,13 +1507,13 @@ ImapConnection.prototype._login = function(cb) {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (this._state.status === STATES.NOAUTH) {
|
|
|
|
|
if (this._serverSupports('LOGINDISABLED'))
|
|
|
|
|
if (this.serverSupports('LOGINDISABLED'))
|
|
|
|
|
return cb(new Error('Logging in is disabled on this server'));
|
|
|
|
|
|
|
|
|
|
if (this._serverSupports('AUTH=XOAUTH') && this._options.xoauth) {
|
|
|
|
|
if (this.serverSupports('AUTH=XOAUTH') && this._options.xoauth) {
|
|
|
|
|
this._send('AUTHENTICATE XOAUTH ' + utils.escape(this._options.xoauth),
|
|
|
|
|
fnReturn);
|
|
|
|
|
} else if (this._serverSupports('AUTH=XOAUTH2') && this._options.xoauth2) {
|
|
|
|
|
} else if (this.serverSupports('AUTH=XOAUTH2') && this._options.xoauth2) {
|
|
|
|
|
this._send('AUTHENTICATE XOAUTH2 ' + utils.escape(this._options.xoauth2),
|
|
|
|
|
fnReturn);
|
|
|
|
|
} else if (this._options.username && this._options.password) {
|
|
|
|
@ -1467,7 +1539,6 @@ ImapConnection.prototype._reset = function() {
|
|
|
|
|
this._state.tmrConn = null;
|
|
|
|
|
this._state.ext.idle.state = IDLE_NONE;
|
|
|
|
|
this._state.ext.idle.timeStarted = undefined;
|
|
|
|
|
this._state.ext.idle.reIDLE = false;
|
|
|
|
|
|
|
|
|
|
this._state.indata.literals = [];
|
|
|
|
|
this._state.indata.line = undefined;
|
|
|
|
@ -1503,47 +1574,59 @@ ImapConnection.prototype._noop = function() {
|
|
|
|
|
this._send('NOOP');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ImapConnection.prototype._send = function(cmdstr, cb, bypass) {
|
|
|
|
|
ImapConnection.prototype._send = function(cmdstr, cb) {
|
|
|
|
|
if (!this._state.conn.writable)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (cmdstr !== undefined && !bypass) {
|
|
|
|
|
this._state.requests.push({
|
|
|
|
|
var reqs = this._state.requests, idle = this._state.ext.idle;
|
|
|
|
|
|
|
|
|
|
if (cmdstr !== undefined) {
|
|
|
|
|
var info = {
|
|
|
|
|
cmd: cmdstr.match(RE_CMD)[1],
|
|
|
|
|
cmdstr: cmdstr,
|
|
|
|
|
callback: cb,
|
|
|
|
|
cbargs: []
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
if (cmdstr === 'IDLE' || cmdstr === 'DONE' || cmdstr === 'NOOP')
|
|
|
|
|
reqs.unshift(info);
|
|
|
|
|
else
|
|
|
|
|
reqs.push(info);
|
|
|
|
|
}
|
|
|
|
|
if (this._state.ext.idle.state === IDLE_WAIT
|
|
|
|
|
|| (this._state.ext.idle.state === IDLE_DONE && cmdstr !== 'DONE'))
|
|
|
|
|
|
|
|
|
|
if (idle.state !== IDLE_NONE && cmdstr !== 'DONE') {
|
|
|
|
|
if ((cmdstr !== undefined || reqs.length > 1)
|
|
|
|
|
&& idle.state === IDLE_IDLING) {
|
|
|
|
|
idle.state = IDLE_DONE;
|
|
|
|
|
this._send('DONE');
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
if ((cmdstr === undefined && this._state.requests.length)
|
|
|
|
|
|| this._state.requests.length === 1 || bypass) {
|
|
|
|
|
var prefix = '',
|
|
|
|
|
cmd = (bypass ? cmdstr : this._state.requests[0].cmdstr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((cmdstr === undefined && reqs.length) || reqs.length === 1
|
|
|
|
|
|| cmdstr === 'DONE') {
|
|
|
|
|
var prefix = '', curReq = reqs[0];
|
|
|
|
|
|
|
|
|
|
cmdstr = curReq.cmdstr;
|
|
|
|
|
|
|
|
|
|
clearTimeout(this._state.tmrKeepalive);
|
|
|
|
|
if (this._state.ext.idle.state === IDLE_READY && cmd !== 'DONE') {
|
|
|
|
|
this._state.ext.idle.state = IDLE_DONE;
|
|
|
|
|
if (cmd === 'IDLE')
|
|
|
|
|
this._state.ext.idle.reIDLE = true;
|
|
|
|
|
return this._send('DONE', undefined, true);
|
|
|
|
|
} else if (cmd === 'IDLE') {
|
|
|
|
|
// we use a different prefix to differentiate and disregard the tagged
|
|
|
|
|
// response the server will send us when we issue DONE
|
|
|
|
|
|
|
|
|
|
if (cmdstr === 'IDLE') {
|
|
|
|
|
// we use a different prefix to differentiate and disregard the tagged
|
|
|
|
|
// response the server will send us when we issue DONE
|
|
|
|
|
prefix = 'IDLE ';
|
|
|
|
|
this._state.ext.idle.state = IDLE_WAIT;
|
|
|
|
|
}
|
|
|
|
|
if (cmd !== 'IDLE' && cmd !== 'DONE')
|
|
|
|
|
} else if (cmdstr === 'NOOP')
|
|
|
|
|
prefix = 'NOOP ';
|
|
|
|
|
else if (cmdstr !== 'DONE')
|
|
|
|
|
prefix = 'A' + (++this._state.curId) + ' ';
|
|
|
|
|
this._state.conn.write(prefix);
|
|
|
|
|
this._state.conn.write(cmd);
|
|
|
|
|
this._state.conn.write(CRLF);
|
|
|
|
|
this.debug&&this.debug('\n==> ' + prefix + cmd + '\n');
|
|
|
|
|
if (this._state.requests[0]
|
|
|
|
|
&& (this._state.requests[0].cmd === 'EXAMINE'
|
|
|
|
|
|| this._state.requests[0].cmd === 'SELECT'))
|
|
|
|
|
|
|
|
|
|
this._state.conn.write(prefix + cmdstr + CRLF);
|
|
|
|
|
this.debug&&this.debug('\n==> ' + prefix + cmdstr + '\n');
|
|
|
|
|
|
|
|
|
|
if (curReq.cmd === 'EXAMINE' || curReq.cmd === 'SELECT')
|
|
|
|
|
this._state.status = STATES.BOXSELECTING;
|
|
|
|
|
else if (cmdstr === 'DONE')
|
|
|
|
|
reqs.shift();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|