From 9a0a42c50cdaf94071d2dff0b0dc504624102c00 Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 4 Jan 2013 02:16:36 -0500 Subject: [PATCH] remove support for partial body fetching We have no way to match up a partial fetch response if less than the number of octets we requested were returned, due to the way IMAP responds to partial fetches. Example: We request BODY[TEXT]<0.32>, but BODY[TEXT] is only 16 bytes, then we get back: BODY[TEXT]<0> {16} This leaves us with no way to find out what the original length request was. Because of this and the fact that the server can return requested values in any order, I am disabling partial fetches entirely. --- lib/imap.js | 32 +++++--------- lib/imap.parsers.js | 102 +++++++++++++++++++++++++++++--------------- 2 files changed, 79 insertions(+), 55 deletions(-) diff --git a/lib/imap.js b/lib/imap.js index 372317d..323d9d5 100644 --- a/lib/imap.js +++ b/lib/imap.js @@ -23,6 +23,7 @@ var CRLF = '\r\n', }, RE_LITHEADER = /(?:((?:BODY\[.*\](?:<\d+>)?)?|[^ ]+) )?\{(\d+)\}$/i, RE_UNRESP = /^\* (OK|PREAUTH|NO|BAD) (?:\[(.+)\] )?(.+)$/i, + //RE_ISPARTIAL = /<(\d+)>$/, RE_CMD = /^([^ ]+)(?: |$)/, RE_ISHEADER = /HEADER/, REX_UNRESPDATA = XRegExp('^\\* (?:(?:(?NAMESPACE) (?(?:NIL|\\((?:\\(.+\\))+\\))) (?(?:NIL|\\((?:\\(.+\\))+\\))) (?(?:NIL|\\((?:\\(.+\\))+\\))))|(?:(?FLAGS) \\((?.*)\\))|(?:(?LIST|LSUB|XLIST) \\((?.*)\\) (?".+"|NIL) (?.+))|(?:(?(SEARCH|SORT))(?: (?.*))?)|(?:(?STATUS) (?.+) \\((?.*)\\))|(?:(?CAPABILITY) (?.+))|(?:(?BYE) (?:\\[(?.+)\\] )?(?.+)))$', 'i'), @@ -273,17 +274,10 @@ ImapConnection.prototype.connect = function(loginCb) { litType = m[1]; indata.expect = (m ? parseInt(m[2], 10) : -1); if (indata.expect > -1) { + /*if (RE_ISPARTIAL.test(litType)) + litType = litType.replace(RE_ISPARTIAL, '<$1.' + indata.expect + '>');*/ if ((m = /\* (\d+) FETCH/i.exec(indata.line)) && /^BODY\[/i.test(litType)) { - /*if (!requests[0].fetchers[litType].msg) { - msg = new ImapMessage(); - msg.seqno = parseInt(m[1], 10); - var fetches = requests[0].fetchers[litType]; - fetches.msg = msg; - for (f = 0, lenf = fetches.length; f < lenf; ++f) - fetches[f].emit('message', msg); - }*/ - msg = new ImapMessage(); msg.seqno = parseInt(m[1], 10); fetches = requests[0].fetchers[litType]; @@ -935,7 +929,7 @@ ImapConnection.prototype._fetch = function(which, uids, options, what, cb) { var toFetch = '', prefix, extensions, self = this, parse = true, headers, key, stream, opts = { markSeen: false, struct: true, size: false }, - fetchers = {}, part; + fetchers = {}; if (typeof what === 'function') { cb = what; @@ -1073,23 +1067,20 @@ ImapConnection.prototype._fetch = function(which, uids, options, what, cb) { } if (wp.body) { key = pprefix; - if (wp.body === true) { + if (wp.body === true) key += 'TEXT]'; - part = key; - } else if (typeof wp.body.start === 'number' - && wp.body.length === 'number') { + /*else if (typeof wp.body.start === 'number' + && typeof wp.body.length === 'number') { if (wp.body.start < 0) throw new Error('Invalid `start` value: ' + wp.body.start); else if (wp.body.length <= 0) throw new Error('Invalid `length` value: ' + wp.body.length); key += 'TEXT]<'; key += wp.body.start; - part = key; - part += '.'; - part += wp.body.length; + key += '.'; + key += wp.body.length; key += '>'; - part += '>'; - } else + }*/ else throw new Error('Invalid `body` value: ' + wp.body); key = key.trim(); @@ -1097,13 +1088,12 @@ ImapConnection.prototype._fetch = function(which, uids, options, what, cb) { stream = new ImapFetch(); if (!fetchers[key]) { fetchers[key] = [stream]; - toFetch += part; + toFetch += ' ' + key; } else fetchers[key].push(stream); if (!wp.headers && !wp.headersNot && typeof wp.cb === 'function') wp.cb(stream); stream = undefined; - part = undefined; key = undefined; } } else { diff --git a/lib/imap.parsers.js b/lib/imap.parsers.js index 42d14b8..32d8d9c 100644 --- a/lib/imap.parsers.js +++ b/lib/imap.parsers.js @@ -53,7 +53,7 @@ exports.parseNamespaces = function(str, literals) { else { result = exports.parseExpr(str, literals); vals = []; - for (var i=0,len=result.length; i 2) { // extension data val.extensions = []; - for (var j=2,len2=result[i].length; j 1) { + //else if (result[i].length === 2 && typeof result[i][1] === 'number') { + // OK, so ordinarily this is where we'd transform a partial fetch + // key so that it matches up with a key in our request's fetchers object + // but since IMAP sucks, we have no way to match up a partial fetch + // response if less than the number of octets we requested were returned. + // + // Example: We request BODY[TEXT]<0.32>, but BODY[TEXT] is only 16 bytes, + // then we get back: BODY[TEXT]<0> {16} + // This leaves us with no way to find out what the original + // length request was. ARGH!!!^%&#^@#$%&$! + // Because of this and the fact that the server can return requested + // values in any order, I am disabling partial fetches entirely. + + // BODY[TEXT]<0.32> + /*result[i][0] += '<'; + result[i][0] += result[i][1]; // starting octet + result[i][0] += '.'; + if (typeof result[i + 1] === 'number') + result[i][0] += result[i + 1]; + else if (typeof + result[i][0] += '>';*/ + } else if (result[i].length > 1) { // HEADER.FIELDS (foo) result[i][0] += ' ('; result[i][0] += result[i].slice(1).join(' '); result[i][0] += ')'; } if (bodies === undefined) - bodies = ['BODY[' + result[i][0] + ']', result[i+1]]; + bodies = ['BODY[' + result[i][0] + ']', result[i + 1]]; else { bodies.push('BODY[' + result[i][0] + ']'); - bodies.push(result[i+1]); + bodies.push(result[i + 1]); } } } @@ -102,22 +123,22 @@ exports.parseFetchBodies = function(str, literals) { exports.parseFetch = function(str, literals, fetchData) { literals.lp = 0; var result = exports.parseExpr(str, literals); - for (var i=0,len=result.length; i next) { if (Array.isArray(cur[next])) { part.params = {}; - for (i=0,len=cur[next].length; i next && Array.isArray(cur[next])) { part.envelope = {}; - for (i=0,len=cur[next].length; i 0) - result.push(exports.convStr(o.str.substring(lastPos+1, i), literals)); + if (i - (lastPos + 1) > 0) { + val = exports.convStr(o.str.substring(lastPos + 1, i), literals); + result.push(val); + } if ((o.str[i] === ')' || o.str[i] === ']') && !isTop) return i; lastPos = i; - } else if ((o.str[i] === '(' || o.str[i] === '[') - /*&& (i === 0 || !isAlpha(o.str.charCodeAt(i-1)))*/) { + } else if ((o.str[i] === '(' || o.str[i] === '[')) { var innerResult = []; - i = exports.parseExpr(o, literals, innerResult, i+1); + i = exports.parseExpr(o, literals, innerResult, i + 1); lastPos = i; result.push(innerResult); - } + }/* else if (i > 0 && o.str[i] === '<' && o.str[i - 1] === ']') { + lastPos = i; + inLitStart = true; + } else if (o.str[i] === '>' && inLitStart) { + val = exports.convStr(o.str.substring(lastPos + 1, i), literals); + result[result.length - 1].push(val); + inLitStart = false; + }*/ } else if (o.str[i] === '"' && - (o.str[i-1] && - (o.str[i-1] !== '\\' || (o.str[i-2] && o.str[i-2] === '\\')))) + (o.str[i - 1] && + (o.str[i - 1] !== '\\' + || (o.str[i - 2] && o.str[i - 2] === '\\') + ))) inQuote = false; - if (i+1 === len && len - (lastPos+1) > 0) - result.push(exports.convStr(o.str.substring(lastPos+1), literals)); + if (i + 1 === len && len - (lastPos + 1) > 0) + result.push(exports.convStr(o.str.substring(lastPos + 1), literals)); } return (isTop ? result : start); };