|
|
|
@ -26,6 +26,8 @@ var CRLF = '\r\n',
|
|
|
|
|
RE_UNRESP = /^\* (OK|PREAUTH|NO|BAD)(?:\r\n| (?:\[(.+)\] )?(.+))$/i,
|
|
|
|
|
RE_TAGGED_RESP = /^A\d+ (OK|NO|BAD) (?:\[(.+?)\] )?(.+)$/i,
|
|
|
|
|
RE_TEXT_CODE = /([^ ]+)(?: (.*))?$/,
|
|
|
|
|
RE_RES_IDLE = /^IDLE /i,
|
|
|
|
|
RE_RES_NOOP = /^NOOP /i,
|
|
|
|
|
//RE_ISPARTIAL = /<(\d+)>$/,
|
|
|
|
|
RE_DBLQ = /"/g,
|
|
|
|
|
RE_CMD = /^([^ ]+)(?: |$)/,
|
|
|
|
@ -36,8 +38,7 @@ var CRLF = '\r\n',
|
|
|
|
|
// extension constants
|
|
|
|
|
var IDLE_NONE = 1,
|
|
|
|
|
IDLE_WAIT = 2,
|
|
|
|
|
IDLE_READY = 3,
|
|
|
|
|
IDLE_DONE = 4;
|
|
|
|
|
IDLE_IDLING = 3;
|
|
|
|
|
|
|
|
|
|
function ImapConnection(options) {
|
|
|
|
|
if (!(this instanceof ImapConnection))
|
|
|
|
@ -93,7 +94,6 @@ function ImapConnection(options) {
|
|
|
|
|
idle: {
|
|
|
|
|
MAX_WAIT: 300000, // 5 mins in ms
|
|
|
|
|
state: IDLE_NONE,
|
|
|
|
|
reIDLE: false,
|
|
|
|
|
timeStarted: undefined
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -309,7 +309,7 @@ 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);
|
|
|
|
|
|| (state.isIdle && state.ext.idle.state === IDLE_IDLING);
|
|
|
|
|
if (m = XRegExp.exec(indata.line, REX_UNRESPNUM)) {
|
|
|
|
|
// m.type = response type (numeric-based)
|
|
|
|
|
m.type = m.type.toUpperCase();
|
|
|
|
@ -624,18 +624,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;
|
|
|
|
@ -646,7 +649,6 @@ ImapConnection.prototype.connect = function(loginCb) {
|
|
|
|
|
self._resetBox();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (requests[0].cmd === 'RENAME') {
|
|
|
|
|
if (state.box._newName) {
|
|
|
|
|
state.box.name = state.box._newName;
|
|
|
|
@ -711,41 +713,33 @@ ImapConnection.prototype.connect = function(loginCb) {
|
|
|
|
|
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: '
|
|
|
|
@ -754,6 +748,30 @@ 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)
|
|
|
|
|
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() {
|
|
|
|
@ -1503,7 +1521,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;
|
|
|
|
@ -1539,47 +1556,56 @@ 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)
|
|
|
|
|
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') {
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|