Use custom message parser instead of node core's HTTP response parser

node core's HTTP response parser's header value unfolding removes any and all whitespace when concatenating lines together.

The new custom parser also removes any and all whitespace, but replaces all of said whitespace with a single space. Some message parsers behave this way also, while others choose to only remove the CRLF and preserve any other proceeding, leading whitespace.
fork
Brian White 12 years ago
parent 27d2ae8c8d
commit 4f8fbcbe01

@ -1,7 +1,7 @@
var util = require('util'),
Socket = require('net').Socket,
EventEmitter = require('events').EventEmitter,
HTTPParser = process.binding('http_parser').HTTPParser;
MIMEParser = require('./mimeparser');
var parsers = require('./imap.parsers'),
utils = require('./imap.utilities');
@ -16,8 +16,7 @@ var CRLF = '\r\n',
BOXSELECTED: 4
},
BOX_ATTRIBS = ['NOINFERIORS', 'NOSELECT', 'MARKED', 'UNMARKED'],
FAKE_HTTP_RESPONSE = new Buffer('HTTP/1.1 200 OK\r\n');
reFetch = /^\* (\d+) FETCH .+? \{(\d+)\}\r\n/;
RE_FETCH = /^\* (\d+) FETCH .+? \{(\d+)\}\r\n/;
// extension constants
var IDLE_NONE = 1,
@ -198,7 +197,7 @@ ImapConnection.prototype.connect = function(loginCb) {
if (chunk && chunk.length) {
if (curReq._useParser)
self._state.parser.execute(chunk, 0, chunk.length);
self._state.parser.execute(chunk);
else
curReq._msg.emit('data', chunk);
}
@ -241,7 +240,7 @@ ImapConnection.prototype.connect = function(loginCb) {
} else
return;
} else if (self._state.curExpected === 0
&& (literalInfo = (strdata = data.toString()).match(reFetch))) {
&& (literalInfo = (strdata = data.toString()).match(RE_FETCH))) {
self._state.curExpected = parseInt(literalInfo[2], 10);
var idxCRLF = utils.bufferIndexOf(data, CRLF),
curReq = self._state.requests[0],
@ -255,35 +254,20 @@ ImapConnection.prototype.connect = function(loginCb) {
curReq._msg = msg;
curReq._fetcher.emit('message', msg);
if (curReq._useParser) {
// use node's built-in HTTP parser for parsing headers or headers and
// bodies
if (self._state.parser)
self._state.parser.reinitialize(HTTPParser.RESPONSE);
else {
self._state.parser = new HTTPParser(HTTPParser.RESPONSE);
self._state.parser.onHeadersComplete = function(info) {
var headers = {};
for (var i=0,k,len=info.headers.length; i<len; i+=2) {
k = info.headers[i].toLowerCase();
if (headers[k] !== undefined)
headers[k].push(info.headers[i + 1]);
else
headers[k] = [info.headers[i + 1]];
}
self._state.requests[0]._msg.headers = headers;
return false;
};
self._state.parser.onBody = function(b, start, len) {
b = b.slice(start, start + len);
self._state.requests[0]._msg.emit('data', b);
};
curReq._msg.headers = {};
if (!self._state.parser) {
self._state.parser = new MIMEParser();
self._state.parser.on('header', function(name, val) {
name = name.toLowerCase();
if (curReq._msg.headers[name] !== undefined)
curReq._msg.headers[name].push(val);
else
curReq._msg.headers[name] = [val];
});
self._state.parser.on('data', function(str) {
curReq._msg.emit('data', str);
});
}
self._state.parser.execute(FAKE_HTTP_RESPONSE, 0,
FAKE_HTTP_RESPONSE.length);
}
self._state.conn.cleartext.emit('data', data.slice(idxCRLF + 2));
return;

@ -0,0 +1,121 @@
var inherits = require('util').inherits,
EventEmitter = require('events').EventEmitter;
var PARSE_HEADER_NAME = 0,
PARSE_HEADER_VAL = 1,
PARSE_BODY = 2;
var CR = 13,
LF = 10,
COLON = 58,
SPACE = 32,
TAB = 9,
RE_FOLD = /\r\n\s+/g;
var MIMEParser = module.exports = function() {
this._state = PARSE_HEADER_NAME;
this._hdrname = '';
this._hdrval = '';
this._sawCR = false;
this._sawLF = false;
this._needUnfold = false;
}
inherits(MIMEParser, EventEmitter);
MIMEParser.prototype.execute = function(b, start, end) {
if (this._state == PARSE_BODY) {
var chunk;
if ((start === undefined && end === undefined)
|| (start === 0 && end === b.length))
chunk = b;
else
chunk = b.slice(start, end);
return this.emit('data', chunk);
}
start || (start = 0);
end || (end = b.length);
var i = start, finished = false;
while (i < end) {
if (this._state === PARSE_HEADER_NAME) {
if (i > start)
start = i;
while (i < end) {
if (b[i] === COLON) {
this._state = PARSE_HEADER_VAL;
finished = true;
break;
}
++i;
}
if (this._state === PARSE_HEADER_NAME)
this._hdrname += b.toString('ascii', start, end);
else if (finished) {
this._hdrname += b.toString('ascii', start, (i < end ? i : end));
finished = false;
++i;
}
} else if (this._state === PARSE_HEADER_VAL) {
if (i > start)
start = i;
while (i < end) {
if (b[i] === CR) {
if (!(this._sawCR && this._sawLF)) {
this._sawCR = true;
this._sawLF = false;
}
} else if (b[i] === LF && this._sawCR) {
if (this._sawLF) {
this._state = PARSE_BODY;
this._sawCR = false;
this._sawLF = false;
finished = true;
break;
}
this._sawLF = true;
} else {
if (this._sawCR && this._sawLF) {
if (b[i] !== SPACE && b[i] !== TAB) {
this._state = PARSE_HEADER_NAME;
this._sawCR = false;
this._sawLF = false;
finished = true;
break;
} else {
this._needUnfold = true;
// unfold
/*this._hdrval += b.toString('ascii', start, (i < end ? i - 2 : end));
start = i;*/
}
}
this._sawCR = false;
}
++i;
}
if (this._state === PARSE_HEADER_VAL)
this._hdrval += b.toString('ascii', start, (i < end ? i : end));
else if (finished) {
this._hdrval += b.toString('ascii', start, (i < end ? i - 2 : end));
if (this._needUnfold)
this._hdrval = this._hdrval.replace(RE_FOLD, ' ');
this.emit('header', this._hdrname, this._hdrval.trim());
this._hdrname = '';
this._hdrval = '';
this._needUnfold = false;
finished = false;
if (this._state === PARSE_BODY) {
if ((i + 1) < end)
this.emit('data', b.slice(i + 1, end));
return;
}
}
}
}
};
MIMEParser.prototype.finish = function() {
this._state = PARSE_HEADER_NAME;
this._hdrname = '';
this._hdrval = '';
this._sawCR = false;
this._sawLF = false;
this._needUnfold = false;
};
Loading…
Cancel
Save