diff --git a/imap.js b/imap.js index a533186..b7dea75 100644 --- a/imap.js +++ b/imap.js @@ -120,8 +120,9 @@ ImapConnection.prototype.connect = function(loginCb) { fnInit(); }); this._state.conn.cleartext.on('data', function(data) { - var trailingCRLF = false, literalInfo, bypass = false; - debug('<>: ' + util.inspect(data)); + if (data.length === 0) return; + var trailingCRLF = false, literalInfo; + debug('\n<>: ' + util.inspect(data) + '\n'); if (self._state.curExpected === 0) { 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 if (self._state.curExpected > 0) { - var extra = '', curReq = self._state.requests[0]; + var curReq = self._state.requests[0]; + if (!curReq._done) { + var chunk = data; self._state.curXferred += Buffer.byteLength(data, 'utf8'); - if (self._state.curXferred <= self._state.curExpected) { - if (curReq._msgtype === 'headers') - // buffer headers since they're generally not large and are - // processed anyway - self._state.curData += data; + if (self._state.curXferred > self._state.curExpected) { + var pos = Buffer.byteLength(data, 'utf8')-(self._state.curXferred-self._state.curExpected), + extra = (new Buffer(data)).slice(pos).toString('utf8'); + if (pos > 0) + chunk = (new Buffer(data)).slice(0, pos).toString('utf8'); else - curReq._msg.emit('data', data); - return; + chunk = undefined; + 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 (pos > 0) { - if (curReq._msgtype === 'headers') { - self._state.curData += (new Buffer(data)).slice(0, pos).toString('utf8'); - curReq._msgheaders = self._state.curData; - } else - curReq._msg.emit('data', (new Buffer(data)).slice(0, pos).toString('utf8')); + + if (chunk) { + if (curReq._msgtype === 'headers') + self._state.curData += chunk; + else + curReq._msg.emit('data', chunk); } - self._state.curData = ''; - data = extra; - curReq._done = true; } - // make sure we have at least ")\r\n" in the post-literal data - if (data.indexOf(CRLF) === -1) { + if (curReq._done) { + 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; + + 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; - } - 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 - && (literalInfo = /\{(\d+)\}$/.exec(data.substr(0, data.indexOf(CRLF))))) { - self._state.curExpected = parseInt(literalInfo[1]); - var curReq = self._state.requests[0]; - //if (/^UID FETCH/.test(curReq.command)) { - var type = /BODY\[(.*)\](?:\<[\d]+\>)?/.exec(data.substr(0, data.indexOf(CRLF))), - msg = new ImapMessage(); - type = type[1]; - parseFetch(data.substring(data.indexOf("(")+1, data.indexOf(CRLF)), "", msg); - curReq._fetchdesc = data.substring(data.indexOf("(")+1, data.indexOf(CRLF)); - curReq._msg = msg; - curReq._fetcher.emit('message', msg); - curReq._msgtype = (type.indexOf('HEADER') === 0 ? 'headers' : 'body'); - self._state.conn.cleartext.emit('data', data.substr(data.indexOf(CRLF)+2)); - //} + && (literalInfo = data.match(/^\* \d+ FETCH .+? \{(\d+)\}\r\n/))) { + self._state.curExpected = parseInt(literalInfo[1], 10); + var idxCRLF = data.indexOf(CRLF), + curReq = self._state.requests[0], + type = /BODY\[(.*)\](?:\<\d+\>)?/.exec(data.substring(0, idxCRLF)), + msg = new ImapMessage(), + desc = data.substring(data.indexOf("(")+1, idxCRLF).trim(); + type = type[1]; + curReq._desc = desc; + curReq._msg = msg; + curReq._fetcher.emit('message', msg); + curReq._msgtype = (type.indexOf('HEADER') === 0 ? 'headers' : 'body'); + self._state.conn.cleartext.emit('data', data.substring(idxCRLF + 2)); 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) { self._state.box.name = self._state.box._newName; delete self._state.box._newName; @@ -396,14 +384,36 @@ ImapConnection.prototype.connect = function(loginCb) { } args.unshift(err); 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(); - 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; } 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; } else { // 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 body: false // / } - }, toFetch, bodyRange = ''; + }, toFetch, bodyRange = '', self = this; if (typeof options !== 'object') options = {}; extend(true, opts, options); @@ -574,7 +584,15 @@ ImapConnection.prototype.fetch = function(uids, options) { this._send('UID FETCH ' + uids.join(',') + ' (FLAGS INTERNALDATE' + (opts.request.struct ? ' BODYSTRUCTURE' : '') + (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(); this._state.requests[this._state.requests.length-1]._fetcher = imapFetcher; return imapFetcher; @@ -781,7 +799,7 @@ ImapConnection.prototype._send = function(cmdstr, cb, bypass) { if (cmd !== 'IDLE' && cmd !== 'DONE') prefix = 'A' + ++this._state.curId + ' '; this._state.conn.cleartext.write(prefix + cmd + CRLF); - debug('<>: ' + prefix + cmd); + debug('\n<>: ' + prefix + cmd + '\n'); } };