diff --git a/lib/Connection.js b/lib/Connection.js index b0a25ba..7cd2e7c 100644 --- a/lib/Connection.js +++ b/lib/Connection.js @@ -7,6 +7,7 @@ var tls = require('tls'), utf7 = require('utf7').imap; var Parser = require('./Parser').Parser, + parseExpr = require('./Parser').parseExpr, parseHeader = require('./Parser').parseHeader; var MAX_INT = 9007199254740992, @@ -164,12 +165,19 @@ Connection.prototype.connect = function() { toget = msg.toget; - var idx = toget.indexOf('BODY[' + info.which + ']'); - if (idx > -1) { - toget.splice(idx, 1); - msg.msgEmitter.emit('body', stream, info); - } else - stream.resume(); // a body we didn't ask for? + // here we compare the parsed version of the expression inside BODY[] + // because 'HEADER.FIELDS (TO FROM)' really is equivalent to + // 'HEADER.FIELDS ("TO" "FROM")' and some servers will actually send the + // quoted form even if the client did not use quotes + var thisbody = parseExpr(info.which); + for (var i = 0, len = toget.length; i < len; ++i) { + if (_deepEqual(thisbody, toget[i])) { + toget.splice(i, 1); + msg.msgEmitter.emit('body', stream, info); + return; + } + } + stream.resume(); // a body we didn't ask for? }); parser.on('continue', function(info) { var type = self._curReq.type; @@ -729,7 +737,7 @@ Connection.prototype._fetch = function(which, uids, options) { if (!Array.isArray(bodies)) bodies = [bodies]; for (i = 0, len = bodies.length; i < len; ++i) { - fetching.push('BODY[' + bodies[i] + ']'); + fetching.push(parseExpr(bodies[i])); cmd += ' BODY' + prefix + '[' + bodies[i] + ']'; } } @@ -1860,3 +1868,98 @@ function buildSearchQuery(options, extensions, info, isOrChild) { } return searchargs; } + +// Pulled from assert.deepEqual: +var pSlice = Array.prototype.slice; +function _deepEqual(actual, expected) { + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + + } else if (Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { + if (actual.length !== expected.length) return false; + + for (var i = 0; i < actual.length; i++) { + if (actual[i] !== expected[i]) return false; + } + + return true; + + // 7.2. If the expected value is a Date object, the actual value is + // equivalent if it is also a Date object that refers to the same time. + } else if (actual instanceof Date && expected instanceof Date) { + return actual.getTime() === expected.getTime(); + + // 7.3 If the expected value is a RegExp object, the actual value is + // equivalent if it is also a RegExp object with the same source and + // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`). + } else if (actual instanceof RegExp && expected instanceof RegExp) { + return actual.source === expected.source && + actual.global === expected.global && + actual.multiline === expected.multiline && + actual.lastIndex === expected.lastIndex && + actual.ignoreCase === expected.ignoreCase; + + // 7.4. Other pairs that do not both pass typeof value == 'object', + // equivalence is determined by ==. + } else if (typeof actual !== 'object' && typeof expected !== 'object') { + return actual == expected; + + // 7.5 For all other Object pairs, including Array objects, equivalence is + // determined by having the same number of owned properties (as verified + // with Object.prototype.hasOwnProperty.call), the same set of keys + // (although not necessarily the same order), equivalent values for every + // corresponding key, and an identical 'prototype' property. Note: this + // accounts for both named and indexed properties on Arrays. + } else { + return objEquiv(actual, expected); + } +} +function isUndefinedOrNull(value) { + return value === null || value === undefined; +} +function isArguments(object) { + return Object.prototype.toString.call(object) === '[object Arguments]'; +} +function objEquiv(a, b) { + var ka, kb, key, i; + if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) + return false; + // an identical 'prototype' property. + if (a.prototype !== b.prototype) return false; + //~~~I've managed to break Object.keys through screwy arguments passing. + // Converting to array solves the problem. + if (isArguments(a)) { + if (!isArguments(b)) { + return false; + } + a = pSlice.call(a); + b = pSlice.call(b); + return _deepEqual(a, b); + } + try { + ka = Object.keys(a); + kb = Object.keys(b); + } catch (e) {//happens when one is a string literal and the other isn't + return false; + } + // having the same number of owned properties (keys incorporates + // hasOwnProperty) + if (ka.length !== kb.length) + return false; + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] != kb[i]) + return false; + } + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!_deepEqual(a[key], b[key])) return false; + } + return true; +} \ No newline at end of file