hapi
Version:
HTTP Server framework
234 lines (189 loc) • 6.38 kB
JavaScript
// Load modules
var Events = require('events');
var Stream = require('stream');
var StreamSearch = require('streamsearch');
var Utils = require('./utils');
var internals = {
dashBuffer: new Buffer('-'),
crlfBuffer: new Buffer('\r\n'),
doubleCrlfBuffer: new Buffer('\r\n\r\n')
};
module.exports = internals.Parser = function (options) {
var self = this;
Stream.Writable.call(this);
if (!options.headerFirst && typeof options.boundary !== 'string')
throw new TypeError('Boundary required');
if (typeof options.boundary === 'string')
this.setBoundary(options.boundary);
else
this._bparser = undefined;
this._headerFirst = options.headerFirst;
this._dashes = 0;
this._isPreamble = true;
this._justMatched = false;
this._firstWrite = true;
this._inHeader = true;
this._part = undefined;
this._hparser = new HeaderParser();
this._hparser.on('header', function (header) {
self._inHeader = false;
self._part.emit('header', header);
});
return this;
};
Utils.inherits(internals.Parser, Stream.Writable);
internals.Parser.prototype._write = function (data, encoding, cb) {
if (this._headerFirst && this._isPreamble) {
if (!this._part) {
this._part = new PartStream();
this.emit('preamble', this._part);
}
var r = this._hparser.push(data);
if (!this._inHeader && r !== undefined && r < data.length)
data = data.slice(r);
else
return cb();
}
// allows for "easier" testing
if (this._firstWrite) {
this._bparser.push(crlfBuffer);
this._firstWrite = false;
}
this._bparser.push(data);
cb();
};
internals.Parser.prototype.reset = function () {
this._part = undefined;
this._bparser = undefined;
this._hparser = undefined;
};
internals.Parser.prototype.setBoundary = function (boundary) {
var self = this;
this._bparser = new StreamSearch('\r\n--' + boundary);
this._bparser.on('info', function (isMatch, data, start, end) {
self._oninfo(isMatch, data, start, end);
});
};
internals.Parser.prototype._oninfo = function (isMatch, data, start, end) {
var buf, self = this;
if (!this._part && this._justMatched && data) {
var i = 0;
while (this._dashes < 2 && (start + i) < end) {
if (data[start + i] === 45) {
++i;
++this._dashes;
} else {
if (this._dashes)
buf = internals.dashBuffer;
this._dashes = 0;
break;
}
}
if (this._dashes === 2) {
if ((start + i) < end && this._events.trailer)
this.emit('trailer', data.slice(start + i, end));
this.reset();
process.nextTick(function () { self.emit('end'); });
}
if (this._dashes)
return;
}
if (this._justMatched)
this._justMatched = false;
if (!this._part) {
this._part = new PartStream();
this.emit(this._isPreamble ? 'preamble' : 'part', this._part);
if (!this._isPreamble)
this._inHeader = true;
}
if (data && start < end) {
if (this._isPreamble || !this._inHeader) {
if (buf)
this._part.push(buf);
this._part.push(data.slice(start, end));
} else if (!this._isPreamble && this._inHeader) {
if (buf)
this._hparser.push(buf);
var r = this._hparser.push(data.slice(start, end));
if (!this._inHeader && r !== undefined && r < end)
this._oninfo(false, data, start + r, end);
}
}
if (isMatch) {
if (this._isPreamble)
this._isPreamble = false;
this._hparser.reset();
this._part.push(null);
this._part = undefined;
this._justMatched = true;
this._dashes = 0;
}
};
function PartStream() {
Stream.Readable.call(this);
};
Utils.inherits(PartStream, Stream.Readable);
PartStream.prototype._read = function (n) { };
function HeaderParser() {
Events.EventEmitter.call(this);
var self = this;
this.buffer = '';
this.header = {};
this.finished = false;
this.ss = new StreamSearch(doubleCrlfBuffer);
this.ss.on('info', function (isMatch, data, start, end) {
if (data)
self.buffer += data.toString('ascii', start, end);
if (isMatch) {
if (self.buffer)
self._parseHeader();
self.ss.matches = self.ss.maxMatches;
var header = self.header;
self.header = {};
self.buffer = '';
self.finished = true;
self.emit('header', header);
}
});
}
Utils.inherits(HeaderParser, Events.EventEmitter);
HeaderParser.prototype.push = function (data) {
var r = this.ss.push(data);
if (this.finished)
return r;
};
HeaderParser.prototype.reset = function () {
this.finished = false;
this.buffer = '';
this.header = {};
this.ss.reset();
};
HeaderParser.prototype._parseHeader = function () {
var lines = this.buffer.split(/\r\n/g), len = lines.length, m, h,
modded = false;
for (var i = 0; i < len; ++i) {
if (lines[i].length === 0)
continue;
if (lines[i][0] === '\t' || lines[i][0] === ' ') {
this.header[h][this.header[h].length - 1] += lines[i];
} else {
m = /^([^:]+):[ \t]?(.+)?$/.exec(lines[i]);
if (m) {
h = m[1].toLowerCase();
if (m[2]) {
if (this.header[h] === undefined)
this.header[h] = [m[2]];
else
this.header[h].push(m[2]);
} else
this.header[h] = [''];
} else {
this.buffer = lines[i];
modded = true;
break;
}
}
}
if (!modded)
this.buffer = '';
};