Fix duplicate sending of commands bug, refactor literal data handling, and make idling a little smarter

fork
Brian White 13 years ago
parent 52a6d93d71
commit ed74906dc2

@ -120,8 +120,9 @@ ImapConnection.prototype.connect = function(loginCb) {
fnInit(); fnInit();
}); });
this._state.conn.cleartext.on('data', function(data) { this._state.conn.cleartext.on('data', function(data) {
var trailingCRLF = false, literalInfo, bypass = false; if (data.length === 0) return;
debug('<<RECEIVED>>: ' + util.inspect(data)); var trailingCRLF = false, literalInfo;
debug('\n<<RECEIVED>>: ' + util.inspect(data) + '\n');
if (self._state.curExpected === 0) { if (self._state.curExpected === 0) {
if (data.indexOf(CRLF) === -1) { if (data.indexOf(CRLF) === -1) {
@ -136,68 +137,75 @@ ImapConnection.prototype.connect = function(loginCb) {
// Don't mess with incoming data if it's part of a literal // Don't mess with incoming data if it's part of a literal
if (self._state.curExpected > 0) { if (self._state.curExpected > 0) {
var extra = '', curReq = self._state.requests[0]; var curReq = self._state.requests[0];
if (!curReq._done) { if (!curReq._done) {
var chunk = data;
self._state.curXferred += Buffer.byteLength(data, 'utf8'); self._state.curXferred += Buffer.byteLength(data, 'utf8');
if (self._state.curXferred <= self._state.curExpected) { if (self._state.curXferred > self._state.curExpected) {
if (curReq._msgtype === 'headers') var pos = Buffer.byteLength(data, 'utf8')-(self._state.curXferred-self._state.curExpected),
// buffer headers since they're generally not large and are extra = (new Buffer(data)).slice(pos).toString('utf8');
// processed anyway if (pos > 0)
self._state.curData += data; chunk = (new Buffer(data)).slice(0, pos).toString('utf8');
else else
curReq._msg.emit('data', data); chunk = undefined;
return; data = extra;
curReq._done = 1;
} }
var pos = Buffer.byteLength(data, 'utf8')-(self._state.curXferred-self._state.curExpected);
extra = (new Buffer(data)).slice(pos).toString('utf8'); if (chunk) {
if (pos > 0) { if (curReq._msgtype === 'headers')
if (curReq._msgtype === 'headers') { self._state.curData += chunk;
self._state.curData += (new Buffer(data)).slice(0, pos).toString('utf8'); else
curReq._msgheaders = self._state.curData; curReq._msg.emit('data', chunk);
} else
curReq._msg.emit('data', (new Buffer(data)).slice(0, pos).toString('utf8'));
} }
self._state.curData = '';
data = extra;
curReq._done = true;
} }
// make sure we have at least ")\r\n" in the post-literal data if (curReq._done) {
if (data.indexOf(CRLF) === -1) { var restDesc;
if (curReq._done === 1) {
if (curReq._msgtype === 'headers')
curReq._headers = self._state.curData;
self._state.curData = '';
curReq._done = true;
}
self._state.curData += data; self._state.curData += data;
if (restDesc = self._state.curData.match(/^(.*?)\)\r\n/)) {
if (restDesc[1]) {
restDesc[1] = restDesc[1].trim();
if (restDesc[1].length)
restDesc[1] = ' ' + restDesc[1];
} else
restDesc[1] = '';
parseFetch(curReq._desc + restDesc[1], curReq._headers, curReq._msg);
data = self._state.curData.substring(self._state.curData.indexOf(CRLF) + 2);
curReq._done = false;
self._state.curXferred = 0;
self._state.curExpected = 0;
self._state.curData = '';
curReq._msg.emit('end');
if (data.length && data[0] === '*') {
self._state.conn.cleartext.emit('data', data);
return;
}
} else
return;
} else
return; return;
}
if (self._state.curData.length)
data = self._state.curData + data;
// add any additional k/v pairs that appear after the literal data
var fetchdesc = curReq._fetchdesc + ' ' + data.substring(0, data.indexOf(CRLF)-1).trim();
parseFetch(fetchdesc, curReq._msgheaders, curReq._msg);
data = data.substr(data.indexOf(CRLF)+2);
self._state.curExpected = 0;
self._state.curXferred = 0;
self._state.curData = '';
curReq._done = false;
curReq._msg.emit('end');
if (data[0] === '*') {
// found additional responses, so don't try splitting the proceeding
// response(s) for better performance in case they have literals too
process.nextTick(function() { self._state.conn.cleartext.emit('data', data); });
return;
}
} else if (self._state.curExpected === 0 } else if (self._state.curExpected === 0
&& (literalInfo = /\{(\d+)\}$/.exec(data.substr(0, data.indexOf(CRLF))))) { && (literalInfo = data.match(/^\* \d+ FETCH .+? \{(\d+)\}\r\n/))) {
self._state.curExpected = parseInt(literalInfo[1]); self._state.curExpected = parseInt(literalInfo[1], 10);
var curReq = self._state.requests[0]; var idxCRLF = data.indexOf(CRLF),
//if (/^UID FETCH/.test(curReq.command)) { curReq = self._state.requests[0],
var type = /BODY\[(.*)\](?:\<[\d]+\>)?/.exec(data.substr(0, data.indexOf(CRLF))), type = /BODY\[(.*)\](?:\<\d+\>)?/.exec(data.substring(0, idxCRLF)),
msg = new ImapMessage(); msg = new ImapMessage(),
type = type[1]; desc = data.substring(data.indexOf("(")+1, idxCRLF).trim();
parseFetch(data.substring(data.indexOf("(")+1, data.indexOf(CRLF)), "", msg); type = type[1];
curReq._fetchdesc = data.substring(data.indexOf("(")+1, data.indexOf(CRLF)); curReq._desc = desc;
curReq._msg = msg; curReq._msg = msg;
curReq._fetcher.emit('message', msg); curReq._fetcher.emit('message', msg);
curReq._msgtype = (type.indexOf('HEADER') === 0 ? 'headers' : 'body'); curReq._msgtype = (type.indexOf('HEADER') === 0 ? 'headers' : 'body');
self._state.conn.cleartext.emit('data', data.substr(data.indexOf(CRLF)+2)); self._state.conn.cleartext.emit('data', data.substring(idxCRLF + 2));
//}
return; return;
} }
@ -353,26 +361,6 @@ ImapConnection.prototype.connect = function(loginCb) {
} }
} }
if (self._state.requests.length === 1
&& self._state.requests[0].command !== 'LOGOUT') {
if (self._state.status === STATES.BOXSELECTED &&
self._state.capabilities.indexOf('IDLE') > -1) {
// According to RFC 2177, we should re-IDLE at least every 29
// minutes to avoid disconnection by the server
self._send('IDLE', self._send, true);
}
self._state.tmrKeepalive = setTimeout(function() {
if (self._state.isIdle) {
if (self._state.ext.idle.sentIdle) {
self._state.ext.idle.timeWaited += self._state.tmoKeepalive;
if (self._state.ext.idle.timeWaited >= self._state.ext.idle.MAX_WAIT)
self._send('IDLE', self._send, true); // restart IDLE
} else
self._noop();
}
}, self._state.tmoKeepalive);
}
if (self._state.requests[0].command.indexOf('RENAME') > -1) { if (self._state.requests[0].command.indexOf('RENAME') > -1) {
self._state.box.name = self._state.box._newName; self._state.box.name = self._state.box._newName;
delete self._state.box._newName; delete self._state.box._newName;
@ -396,14 +384,36 @@ ImapConnection.prototype.connect = function(loginCb) {
} }
args.unshift(err); args.unshift(err);
self._state.requests[0].callback.apply({}, args); self._state.requests[0].callback.apply({}, args);
} else if (self._state.requests[0].command.indexOf("UID FETCH") === 0) }
self._state.requests[0]._fetcher.emit('end');
var recentCmd = self._state.requests[0].command;
self._state.requests.shift(); self._state.requests.shift();
process.nextTick(function() { self._send(); }); if (self._state.requests.length === 0
&& recentCmd !== 'LOGOUT') {
if (self._state.status === STATES.BOXSELECTED &&
self._state.capabilities.indexOf('IDLE') > -1) {
// According to RFC 2177, we should re-IDLE at least every 29
// minutes to avoid disconnection by the server
self._send('IDLE', undefined, true);
}
self._state.tmrKeepalive = setTimeout(function() {
if (self._state.isIdle) {
if (self._state.ext.idle.sentIdle) {
self._state.ext.idle.timeWaited += self._state.tmoKeepalive;
if (self._state.ext.idle.timeWaited >= self._state.ext.idle.MAX_WAIT)
self._send('IDLE', undefined, true); // restart IDLE
} else
self._noop();
}
}, self._state.tmoKeepalive);
} else
process.nextTick(function() { self._send(); });
self._state.isIdle = true; self._state.isIdle = true;
} else if (data[0] === 'IDLE') { } else if (data[0] === 'IDLE') {
process.nextTick(function() { self._send(); }); if (self._state.requests.length > 0)
process.nextTick(function() { self._send(); });
self._state.isIdle = false; self._state.isIdle = false;
} else { } else {
// unknown response // unknown response
@ -542,7 +552,7 @@ ImapConnection.prototype.fetch = function(uids, options) {
headers: true, // \_______ at most one of these can be used for any given fetch request headers: true, // \_______ at most one of these can be used for any given fetch request
body: false // / body: false // /
} }
}, toFetch, bodyRange = ''; }, toFetch, bodyRange = '', self = this;
if (typeof options !== 'object') if (typeof options !== 'object')
options = {}; options = {};
extend(true, opts, options); extend(true, opts, options);
@ -574,7 +584,15 @@ ImapConnection.prototype.fetch = function(uids, options) {
this._send('UID FETCH ' + uids.join(',') + ' (FLAGS INTERNALDATE' this._send('UID FETCH ' + uids.join(',') + ' (FLAGS INTERNALDATE'
+ (opts.request.struct ? ' BODYSTRUCTURE' : '') + (opts.request.struct ? ' BODYSTRUCTURE' : '')
+ (typeof toFetch === 'string' ? ' BODY' + (!opts.markSeen ? '.PEEK' : '') + (typeof toFetch === 'string' ? ' BODY' + (!opts.markSeen ? '.PEEK' : '')
+ '[' + toFetch + ']' + bodyRange : '') + ')'); + '[' + toFetch + ']' + bodyRange : '') + ')', function(e) {
var fetcher = self._state.requests[0]._fetcher;
if (e && fetcher)
fetcher.emit('error', e);
else if (e && !fetcher)
self.emit('error', e);
else if (fetcher)
fetcher.emit('end');
});
var imapFetcher = new ImapFetch(); var imapFetcher = new ImapFetch();
this._state.requests[this._state.requests.length-1]._fetcher = imapFetcher; this._state.requests[this._state.requests.length-1]._fetcher = imapFetcher;
return imapFetcher; return imapFetcher;
@ -781,7 +799,7 @@ ImapConnection.prototype._send = function(cmdstr, cb, bypass) {
if (cmd !== 'IDLE' && cmd !== 'DONE') if (cmd !== 'IDLE' && cmd !== 'DONE')
prefix = 'A' + ++this._state.curId + ' '; prefix = 'A' + ++this._state.curId + ' ';
this._state.conn.cleartext.write(prefix + cmd + CRLF); this._state.conn.cleartext.write(prefix + cmd + CRLF);
debug('<<SENT>>: ' + prefix + cmd); debug('\n<<SENT>>: ' + prefix + cmd + '\n');
} }
}; };

Loading…
Cancel
Save