From 0b042e0e14dfde6a26e881bbbcf0c6f9746da51e Mon Sep 17 00:00:00 2001 From: Nicolas Chambrier Date: Tue, 12 Mar 2013 16:32:45 +0100 Subject: [PATCH 01/23] Allow custom TLS options (fix #181) --- lib/imap.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/imap.js b/lib/imap.js index dfdb9ac..cbded17 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -47,7 +47,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 @@ -124,10 +126,15 @@ 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); From 30c11e47105a9495c0524621027ef27974e255a4 Mon Sep 17 00:00:00 2001 From: mscdex Date: Sat, 16 Mar 2013 22:23:50 -0400 Subject: [PATCH 02/23] make serverSupports() public --- lib/imap.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/imap.js b/lib/imap.js index dfdb9ac..14eb96c 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -174,7 +174,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; @@ -679,7 +679,7 @@ ImapConnection.prototype.connect = function(loginCb) { var recentCmd = requests[0].cmdstr; requests.shift(); if (requests.length === 0 && recentCmd !== 'LOGOUT') { - if (state.status >= STATES.AUTH && self._serverSupports('IDLE')) { + 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); @@ -691,7 +691,7 @@ ImapConnection.prototype.connect = function(loginCb) { 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')) + } else if (!self.serverSupports('IDLE')) self._noop(); } }, state.tmoKeepalive); @@ -796,7 +796,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); }; @@ -896,7 +896,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) { @@ -1155,7 +1155,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 +1233,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'); @@ -1372,7 +1372,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 +1435,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) { From 037fcc2fffccf6e385f49a9576721392958a4bf1 Mon Sep 17 00:00:00 2001 From: mscdex Date: Mon, 18 Mar 2013 00:34:34 -0400 Subject: [PATCH 03/23] ignore untagged OK responses that contain no other information --- lib/imap.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/imap.js b/lib/imap.js index 14eb96c..6acee4e 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -23,7 +23,7 @@ var CRLF = '\r\n', BOXSELECTED: 4 }, RE_LITHEADER = /(?:((?:BODY\[.*\](?:<\d+>)?)?|[^ ]+) )?\{(\d+)\}$/i, - RE_UNRESP = /^\* (OK|PREAUTH|NO|BAD) (?:\[(.+)\] )?(.+)$/i, + RE_UNRESP = /^\* (OK|PREAUTH|NO|BAD)(?:\r\n| (?:\[(.+)\] )?(.+))$/i, //RE_ISPARTIAL = /<(\d+)>$/, RE_DBLQ = /"/g, RE_CMD = /^([^ ]+)(?: |$)/, @@ -532,6 +532,8 @@ ImapConnection.prototype.connect = function(loginCb) { switch (m[1]) { case 'OK': var code = m[2]; + if (code === undefined) + break; if (state.status === STATES.NOAUTH) { if (!state.isReady) { clearTimeout(state.tmrConn); From 2a00c830584294410ef0eb2c9fa988afe5fb44ad Mon Sep 17 00:00:00 2001 From: mscdex Date: Mon, 18 Mar 2013 00:38:46 -0400 Subject: [PATCH 04/23] fix style --- lib/imap.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/imap.js b/lib/imap.js index 0e2906e..5140a8f 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -127,9 +127,8 @@ ImapConnection.prototype.connect = function(loginCb) { if (this._options.secure) { var tlsOptions = {}; - for (var k in this._options.secure) { + 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, tlsOptions, onconnect); From b974f4da64d49035edd3e854354a5f1609a69ab1 Mon Sep 17 00:00:00 2001 From: mscdex Date: Mon, 18 Mar 2013 09:54:05 -0400 Subject: [PATCH 05/23] listen for 'error' on both plain and cleartext sockets --- lib/imap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/imap.js b/lib/imap.js index 5140a8f..7d43915 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -162,7 +162,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) From 9950b29876ba0ba13d727050efb04459696818e7 Mon Sep 17 00:00:00 2001 From: Mike Mee Date: Tue, 19 Mar 2013 14:47:19 -0700 Subject: [PATCH 06/23] Fix append with empty flags array --- lib/imap.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/imap.js b/lib/imap.js index 7d43915..2ff26e9 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -845,7 +845,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)) From 6b0b8ff8b479ed5e2aa3df03d7f364deb0165381 Mon Sep 17 00:00:00 2001 From: mscdex Date: Wed, 20 Mar 2013 13:47:08 -0400 Subject: [PATCH 07/23] fix ignoring of 'useless' untagged OK responses --- lib/imap.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/imap.js b/lib/imap.js index 7d43915..e5e2987 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -537,9 +537,9 @@ 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 (code === undefined) + if (m[2] === undefined && m[3] === undefined) break; + var code = m[2]; if (state.status === STATES.NOAUTH) { if (!state.isReady) { clearTimeout(state.tmrConn); From 69d23e446f3cf905fe8c82378484f71b276fbb3f Mon Sep 17 00:00:00 2001 From: mscdex Date: Wed, 20 Mar 2013 22:21:42 -0400 Subject: [PATCH 08/23] always pass any response text code and message for tagged responses to the callback as the last argument --- lib/imap.js | 46 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/lib/imap.js b/lib/imap.js index e5e2987..cbc6549 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -24,6 +24,8 @@ var CRLF = '\r\n', }, RE_LITHEADER = /(?:((?:BODY\[.*\](?:<\d+>)?)?|[^ ]+) )?\{(\d+)\}$/i, RE_UNRESP = /^\* (OK|PREAUTH|NO|BAD)(?:\r\n| (?:\[(.+)\] )?(.+))$/i, + RE_TAGGED_RESP = /^A\d+ (OK|NO|BAD) (?:\[(.+?)\] )?(.+)$/i, + RE_TEXT_CODE = /([^ ]+)(?: (.*))?$/, //RE_ISPARTIAL = /<(\d+)>$/, RE_DBLQ = /"/g, RE_CMD = /^([^ ]+)(?: |$)/, @@ -539,23 +541,29 @@ ImapConnection.prototype.connect = function(loginCb) { case 'OK': if (m[2] === undefined && m[3] === undefined) break; - var code = m[2]; + 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); @@ -570,8 +578,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); } } @@ -648,10 +656,11 @@ 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 (!m) { if (requests[0].cmd !== 'APPEND') { err = new Error('Unexpected continuation'); err.level = 'protocol'; @@ -659,7 +668,7 @@ ImapConnection.prototype.connect = function(loginCb) { 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 @@ -680,6 +689,19 @@ 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); } From 6f2adf53e30b3e0f5efc1c010e8242c1eaeeeb38 Mon Sep 17 00:00:00 2001 From: Chotiwat Chawannakul Date: Fri, 29 Mar 2013 14:14:29 +0700 Subject: [PATCH 09/23] Pass response text code and message for tagged response in append and move --- lib/imap.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/imap.js b/lib/imap.js index cbc6549..6d8b81f 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -893,9 +893,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'); @@ -1314,44 +1314,44 @@ 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, + this._copy(which, uids, boxTo, function ccb(err, info, reentryCount, deletedUIDs, counter) { if (err) - return cb(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, 1, result); + ccb(e, info, 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); + ccb(e, info, reentryCount, deletedUIDs, counter + 1); }); }); } else - ccb(err, reentryCount + 1, deletedUIDs); + ccb(err, info, reentryCount + 1, deletedUIDs); } else if (reentryCount === 2) { self.addFlags(uids, 'Deleted', function(e) { - ccb(e, reentryCount + 1, deletedUIDs); + ccb(e, info, reentryCount + 1, deletedUIDs); }); } else if (reentryCount === 3) { self.removeDeleted(function(e) { - ccb(e, reentryCount + 1, deletedUIDs); + 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, reentryCount, deletedUIDs, counter + 1); + ccb(e, info, reentryCount, deletedUIDs, counter + 1); }); }); } else - cb(); + cb(err, info); } }); } From ce87915fb0a283d99ff163ded66c8ff5d00bf104 Mon Sep 17 00:00:00 2001 From: mscdex Date: Thu, 4 Apr 2013 16:34:48 -0400 Subject: [PATCH 10/23] pass base64-decoded message on continuation to callback as error for xoauth2 --- lib/imap.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/imap.js b/lib/imap.js index f45a16c..ef29b4b 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -661,13 +661,18 @@ ImapConnection.prototype.connect = function(loginCb) { var args = requests[0].cbargs, cmdstr = requests[0].cmdstr; if (!m) { - if (requests[0].cmd !== 'APPEND') { - err = new Error('Unexpected continuation'); + 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[1] !== 'OK') { // m[1]: error type // m[2]: resp-text-code From 95902ec77057acd6df6da8993fd2e7ec2f2deb54 Mon Sep 17 00:00:00 2001 From: mscdex Date: Sun, 7 Apr 2013 00:59:55 -0400 Subject: [PATCH 11/23] change MAX_WAIT from 29 to 5 minutes for re-idling --- lib/imap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/imap.js b/lib/imap.js index ef29b4b..0b40189 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -91,7 +91,7 @@ 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 From a99b2c0084c6cdf3fe983afdb697e3c1cf0b9523 Mon Sep 17 00:00:00 2001 From: mscdex Date: Sun, 7 Apr 2013 01:04:43 -0400 Subject: [PATCH 12/23] simplify IDLE mechanism and _send() logic --- lib/imap.js | 156 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 91 insertions(+), 65 deletions(-) diff --git a/lib/imap.js b/lib/imap.js index 0b40189..014e799 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -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') { - // 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(); } }; From b786c82eec3c944d61c5e7676b8c8d7373d60a82 Mon Sep 17 00:00:00 2001 From: mscdex Date: Sun, 7 Apr 2013 01:05:50 -0400 Subject: [PATCH 13/23] fix style in move() --- lib/imap.js | 73 +++++++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/lib/imap.js b/lib/imap.js index 014e799..8f842ab 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -1338,46 +1338,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, 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 if (reentryCount === 1) { - if (counter < deletedUIDs.length) { - self.delFlags(deletedUIDs[counter], 'Deleted', function(e) { - process.nextTick(function() { - ccb(e, info, 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, info, reentryCount + 1, deletedUIDs); - } else if (reentryCount === 2) { - self.addFlags(uids, 'Deleted', function(e) { - ccb(e, info, reentryCount + 1, deletedUIDs); - }); - } 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 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(err, info); + } 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); + } } - }); + ); } }; From 1e020b47c12a5ee703609f868d804cbe2b6bc947 Mon Sep 17 00:00:00 2001 From: mscdex Date: Sun, 7 Apr 2013 11:03:22 -0400 Subject: [PATCH 14/23] cache part id check regexp and perform type check in object(s) passed to fetch() --- lib/imap.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/imap.js b/lib/imap.js index 8f842ab..3b236b2 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -28,6 +28,7 @@ var CRLF = '\r\n', RE_TEXT_CODE = /([^ ]+)(?: (.*))?$/, RE_RES_IDLE = /^IDLE /i, RE_RES_NOOP = /^NOOP /i, + RE_PARTID = /^(?:[\d]+[\.]{0,1})*[\d]+$/, //RE_ISPARTIAL = /<(\d+)>$/, RE_DBLQ = /"/g, RE_CMD = /^([^ ]+)(?: |$)/, @@ -1045,9 +1046,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) @@ -1055,7 +1056,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) From 26c6a7827c33cc60add810e596034d03eea548b2 Mon Sep 17 00:00:00 2001 From: mscdex Date: Sun, 7 Apr 2013 19:10:27 -0400 Subject: [PATCH 15/23] guard against duplicate DONE commands --- lib/imap.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/imap.js b/lib/imap.js index 3b236b2..76ce23e 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -39,7 +39,8 @@ var CRLF = '\r\n', // extension constants var IDLE_NONE = 1, IDLE_WAIT = 2, - IDLE_IDLING = 3; + IDLE_IDLING = 3, + IDLE_DONE = 4; function ImapConnection(options) { if (!(this instanceof ImapConnection)) @@ -763,9 +764,10 @@ ImapConnection.prototype.connect = function(loginCb) { 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) + if (timeDiff >= state.ext.idle.MAX_WAIT) { + state.ext.idle.state = IDLE_DONE; self._send('DONE'); - else + } else state.tmrKeepalive = setTimeout(idleHandler, state.tmoKeepalive); } else if (!self.serverSupports('IDLE')) doKeepalive(); @@ -1578,8 +1580,10 @@ ImapConnection.prototype._send = function(cmdstr, cb) { } if (idle.state !== IDLE_NONE && cmdstr !== 'DONE') { - if (cmdstr !== undefined) + if (cmdstr !== undefined && idle.state !== IDLE_DONE) { + idle.state = IDLE_DONE; this._send('DONE'); + } return; } From 3d7b5e4cc8cc148a7a09e1a03bdc27aef4dea324 Mon Sep 17 00:00:00 2001 From: mscdex Date: Sun, 7 Apr 2013 19:37:28 -0400 Subject: [PATCH 16/23] update unsolicited response check --- lib/imap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/imap.js b/lib/imap.js index 76ce23e..0d334ec 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -311,7 +311,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_IDLING); + || (state.isIdle && state.ext.idle.state !== IDLE_NONE); if (m = XRegExp.exec(indata.line, REX_UNRESPNUM)) { // m.type = response type (numeric-based) m.type = m.type.toUpperCase(); From 685a6825720a5defbcf88a678548151268b25355 Mon Sep 17 00:00:00 2001 From: mscdex Date: Sun, 7 Apr 2013 20:44:05 -0400 Subject: [PATCH 17/23] more unsolicited response checks --- lib/imap.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/imap.js b/lib/imap.js index 0d334ec..738d9d3 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -309,9 +309,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_NONE); + 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(); @@ -321,8 +321,13 @@ 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] && requests[0].cmd !== 'FETCH'); + 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); From 502a2aaf4ba0d987a4cfa3aca0182bfcfe9c8b94 Mon Sep 17 00:00:00 2001 From: mscdex Date: Sun, 7 Apr 2013 23:31:30 -0400 Subject: [PATCH 18/23] fix fetch command check when determining a response was unsolicited --- lib/imap.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/imap.js b/lib/imap.js index 738d9d3..1520048 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -28,6 +28,7 @@ var CRLF = '\r\n', 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_ISPARTIAL = /<(\d+)>$/, RE_DBLQ = /"/g, @@ -323,7 +324,8 @@ ImapConnection.prototype.connect = function(loginCb) { details, val; isUnsolicited = isUnsolicited - || (requests[0] && requests[0].cmd !== 'FETCH'); + || (requests[0] + && !RE_CMD_FETCH.test(requests[0].cmdstr)); if (!isUnsolicited) bodies = parsers.parseFetchBodies(m.info, indata.literals); From f1c59a746825bf33e10ce51e1324d765b6581b64 Mon Sep 17 00:00:00 2001 From: mscdex Date: Tue, 9 Apr 2013 12:41:00 -0400 Subject: [PATCH 19/23] adjust untagged response regexp to allow for text code only --- lib/imap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/imap.js b/lib/imap.js index 1520048..afbd9f0 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -23,7 +23,7 @@ var CRLF = '\r\n', BOXSELECTED: 4 }, RE_LITHEADER = /(?:((?:BODY\[.*\](?:<\d+>)?)?|[^ ]+) )?\{(\d+)\}$/i, - RE_UNRESP = /^\* (OK|PREAUTH|NO|BAD)(?:\r\n| (?:\[(.+)\] )?(.+))$/i, + 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, From d6b3c0c5525a8e7da8d0abe5e0b4389921cb304b Mon Sep 17 00:00:00 2001 From: mscdex Date: Wed, 10 Apr 2013 11:49:41 -0400 Subject: [PATCH 20/23] fix line ending check for string literal detection --- lib/imap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/imap.js b/lib/imap.js index afbd9f0..461306e 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -22,7 +22,7 @@ var CRLF = '\r\n', BOXSELECTING: 3, BOXSELECTED: 4 }, - RE_LITHEADER = /(?:((?:BODY\[.*\](?:<\d+>)?)?|[^ ]+) )?\{(\d+)\}$/i, + RE_LITHEADER = /(?:((?:BODY\[.*\](?:<\d+>)?)?|[^ ]+) )?\{(\d+)\}\r\n/i, RE_UNRESP = /^\* (OK|PREAUTH|NO|BAD)(?:\r\n|(?: \[(.+?)\])?(?: (.+))?)$/i, RE_TAGGED_RESP = /^A\d+ (OK|NO|BAD) (?:\[(.+?)\] )?(.+)$/i, RE_TEXT_CODE = /([^ ]+)(?: (.*))?$/, From 835518ca89734fd4bda283b9b11fe059a8c53613 Mon Sep 17 00:00:00 2001 From: mscdex Date: Wed, 10 Apr 2013 12:22:01 -0400 Subject: [PATCH 21/23] use end of string or CRLF in regexps --- lib/imap.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/imap.js b/lib/imap.js index 461306e..d90e53d 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -22,9 +22,9 @@ var CRLF = '\r\n', BOXSELECTING: 3, BOXSELECTED: 4 }, - RE_LITHEADER = /(?:((?:BODY\[.*\](?:<\d+>)?)?|[^ ]+) )?\{(\d+)\}\r\n/i, - RE_UNRESP = /^\* (OK|PREAUTH|NO|BAD)(?:\r\n|(?: \[(.+?)\])?(?: (.+))?)$/i, - RE_TAGGED_RESP = /^A\d+ (OK|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, @@ -34,8 +34,8 @@ var CRLF = '\r\n', RE_DBLQ = /"/g, RE_CMD = /^([^ ]+)(?: |$)/, RE_ISHEADER = /HEADER/, - REX_UNRESPDATA = XRegExp('^\\* (?:(?:(?NAMESPACE) (?(?:NIL|\\((?:\\(.+\\))+\\))) (?(?:NIL|\\((?:\\(.+\\))+\\))) (?(?:NIL|\\((?:\\(.+\\))+\\))))|(?:(?FLAGS) \\((?.*)\\))|(?:(?LIST|LSUB|XLIST) \\((?.*)\\) (?".+"|NIL) (?.+))|(?:(?(SEARCH|SORT))(?: (?.*))?)|(?:(?STATUS) (?.+) \\((?.*)\\))|(?:(?CAPABILITY) (?.+))|(?:(?BYE) (?:\\[(?.+)\\] )?(?.+)))[ \t]*(?:\r\n|$)', 'i'), - REX_UNRESPNUM = XRegExp('^\\* (?\\d+) (?:(?EXISTS)|(?RECENT)|(?EXPUNGE)|(?:(?FETCH) \\((?.*)\\)))[ \t]*(?:\r\n|$)', 'i'); + REX_UNRESPDATA = XRegExp('^\\* (?:(?:(?NAMESPACE) (?(?:NIL|\\((?:\\(.+\\))+\\))) (?(?:NIL|\\((?:\\(.+\\))+\\))) (?(?:NIL|\\((?:\\(.+\\))+\\))))|(?:(?FLAGS) \\((?.*)\\))|(?:(?LIST|LSUB|XLIST) \\((?.*)\\) (?".+"|NIL) (?.+))|(?:(?(SEARCH|SORT))(?: (?.*))?)|(?:(?STATUS) (?.+) \\((?.*)\\))|(?:(?CAPABILITY) (?.+))|(?:(?BYE) (?:\\[(?.+)\\] )?(?.+)))[ \t]*(?:$|\r\n)', 'i'), + REX_UNRESPNUM = XRegExp('^\\* (?\\d+) (?:(?EXISTS)|(?RECENT)|(?EXPUNGE)|(?:(?FETCH) \\((?.*)\\)))[ \t]*(?:$|\r\n)', 'i'); // extension constants var IDLE_NONE = 1, From 08ec77377047632e27d9918a19201e74a2b63d37 Mon Sep 17 00:00:00 2001 From: mscdex Date: Wed, 10 Apr 2013 19:18:08 -0400 Subject: [PATCH 22/23] explicitly wait for idle continuation before sending DONE --- lib/imap.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/imap.js b/lib/imap.js index d90e53d..49b02ae 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -1587,7 +1587,8 @@ ImapConnection.prototype._send = function(cmdstr, cb) { } if (idle.state !== IDLE_NONE && cmdstr !== 'DONE') { - if (cmdstr !== undefined && idle.state !== IDLE_DONE) { + if ((cmdstr !== undefined || reqs.length > 1) + && idle.state === IDLE_IDLING) { idle.state = IDLE_DONE; this._send('DONE'); } From e4a4fed9214ae359a6055471b0d83d4c43211b6b Mon Sep 17 00:00:00 2001 From: mscdex Date: Thu, 11 Apr 2013 18:13:50 -0400 Subject: [PATCH 23/23] un-escape gmail labels --- lib/imap.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/imap.js b/lib/imap.js index 49b02ae..f019d14 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -30,6 +30,7 @@ var CRLF = '\r\n', 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 = /^([^ ]+)(?: |$)/, @@ -334,6 +335,12 @@ ImapConnection.prototype.connect = function(loginCb) { 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