node-red-contrib-web-worldmap
Version:
A Node-RED node to provide a web page of a world map for plotting things on.
132 lines (122 loc) • 4.88 kB
JavaScript
require('bufferjs');
var EventEmitter = require('events').EventEmitter;
var StreamStack = require('stream-stack').StreamStack;
var BufferList = require('bufferlist');
var Headers = require('./headers');
function Parser(stream, opts) {
opts = opts || {};
opts.__proto__ = Parser.DEFAULTS;
this.stream = stream;
this.options = opts;
this._buffers = new BufferList();
this.headers = new Headers();
this._firstLineFired = false;
this._headersFired = false;
// We use an internal StreamStack instance, so that the
// end-user doesn't think that the Parser instance can
// be `pipe()`ed from.
var self = this;
this._parser = new StreamStack(stream, {
data: function onData(chunk) {
self._onData(chunk);
},
end: function onEnd() {
self._onEnd();
}
});
}
require('util').inherits(Parser, EventEmitter);
module.exports = Parser;
// The default 'options' for the Parser, can be overwritten during construction
Parser.DEFAULTS = {
emitFirstLine: false,
strictCRLF: false,
strictSpaceAfterColon: false,
allowFoldedHeaders: false
};
Parser.LF = new Buffer('\n');
Parser.CRLF = new Buffer('\r\n');
// Parsing Logic:
// - Check if _buffers contains an end-of-line delimiter:
// - If yes, slice up to the first end-of-line found:
// - If slice.length === 0, then an empty line was found. Fire the 'headers' event
// - else if the slice begins with whitespace:
// - If 'allowFoldedHeaders' is true, then append to the previous header
// - else if 'allowFoldedHeaders' is false then emit a ParserError.
// - Else parse the line into the headers array. If _buffers.length > 0, call _onData again
// - If no, do nothing, wait for next 'data' event
Parser.prototype._onData = function onData(chunk) {
if (chunk) this._buffers.push(chunk);
var buf = this._buffers.take();
var eol = buf.indexOf(Parser.CRLF);
var delimLength = Parser.CRLF.length;
if (eol === -1) {
eol = buf.indexOf(Parser.LF);
delimLength = Parser.LF.length;
if (eol !== -1 && this.options.strictCRLF) {
return this.emit('error', new Error('ParseError: Found a lone \'\\n\' char, and `strictCRLF` is true'));
}
}
if (eol !== -1) {
var slice = buf.slice(0, eol);
this._buffers.advance(eol+delimLength);
this._parseHeaderLine(slice.toString());
if (this._buffers.length > 0) {
this._onData();
}
} else {
//console.error("waiting for the next 'data' event");
}
}
// If we get the 'end' event before the 'headers' event was fired, then
// something went wrong with the upstream, and we should emit a parsing error.
Parser.prototype._onEnd = function onEnd() {
if (!this._headersFired) {
return this.emit('error', new Error('ParseError: Got "end" event before the end of headers was found'));
}
}
// Parses a single line into a key-value header pair, and adds the
// pair to the 'headers' Array. 'line' is a String.
Parser.prototype._parseHeaderLine = function parseHeaderLine(line) {
//console.error("Got header line:");
//console.error(line);
if (!this._firstLineFired && this.options.emitFirstLine) {
this._firstLineFired = true;
this.emit('firstLine', line);
} else if (line.length === 0) {
// An empty line is the end of the headers
this._onHeadersComplete();
} else if (line[0] === ' ' || line[0] === '\t') {
// A line beginning with whitespace is a folded header
if (!this.options.allowFoldedHeaders) {
return this.emit('error', new Error('ParseError: Encountered a folded header, but `allowFoldedHeaders` is false'));
}
var prevIndex = this.headers.length - 1;
var prevHeader = this.headers[prevIndex];
line = line.trimLeft();
this.headers._addHeader(prevHeader + ' ' + line, prevHeader.key, prevHeader.value + ' ' + line, prevIndex);
} else {
// A regular header line, parse like normal
var firstColon = line.indexOf(':');
if (firstColon < 1) {
return this.emit('error', new Error('ParseError: Malformed header line, no delimiter (:) found: "' + line + '"'));
}
var spaceAfterColon = line[firstColon+1] === ' ';
if (!spaceAfterColon && this.options.strictSpaceAfterColon) {
return this.emit('error', new Error('ParseError: Encountered a header line without a space after the colon, and `strictSpaceAfterColon` is true'));
}
var key = line.substring(0, firstColon);
var value = line.substring(firstColon + (spaceAfterColon ? 2 : 1));
this.headers._addHeader(line, key, value, this.headers.length);
}
}
Parser.prototype._onHeadersComplete = function onHeadersComplete() {
this._headersFired = true;
var leftover;
if (this._buffers.length > 0) {
leftover = this._buffers.take();
this._buffers.advance(leftover.length);
}
this._parser.cleanup();
this.emit('headers', this.headers, leftover);
}