You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

172 lines
5.8 KiB

'use strict';
function JavascriptReplyParser(options) {
this.name = 'javascript';
this.buffer = new Buffer(0);
this.offset = 0;
this.bigStrSize = 0;
this.chunksSize = 0;
this.buffers = [];
this.type = 0;
this.protocolError = false;
this.offsetCache = 0;
// If returnBuffers is active, all return values are returned as buffers besides numbers and errors
if (options.return_buffers) {
this.handleReply = function (start, end) {
return this.buffer.slice(start, end);
};
} else {
this.handleReply = function (start, end) {
return this.buffer.toString('utf-8', start, end);
};
}
// If stringNumbers is activated the parser always returns numbers as string
// This is important for big numbers (number > Math.pow(2, 53)) as js numbers are 64bit floating point numbers with reduced precision
if (options.string_numbers) {
this.handleNumbers = function (start, end) {
return this.buffer.toString('ascii', start, end);
};
} else {
this.handleNumbers = function (start, end) {
return +this.buffer.toString('ascii', start, end);
};
}
}
JavascriptReplyParser.prototype.parseResult = function (type) {
var start = 0,
end = 0,
packetHeader = 0,
reply;
if (type === 36) { // $
packetHeader = this.parseHeader();
// Packets with a size of -1 are considered null
if (packetHeader === -1) {
return null;
}
end = this.offset + packetHeader;
start = this.offset;
if (end + 2 > this.buffer.length) {
this.buffers.push(this.offsetCache === 0 ? this.buffer : this.buffer.slice(this.offsetCache));
this.chunksSize = this.buffers[0].length;
// Include the packetHeader delimiter
this.bigStrSize = packetHeader + 2;
throw new Error('Wait for more data.');
}
// Set the offset to after the delimiter
this.offset = end + 2;
return this.handleReply(start, end);
} else if (type === 58) { // :
// Up to the delimiter
end = this.packetEndOffset();
start = this.offset;
// Include the delimiter
this.offset = end + 2;
// Return the coerced numeric value
return this.handleNumbers(start, end);
} else if (type === 43) { // +
end = this.packetEndOffset();
start = this.offset;
this.offset = end + 2;
return this.handleReply(start, end);
} else if (type === 42) { // *
packetHeader = this.parseHeader();
if (packetHeader === -1) {
return null;
}
reply = [];
for (var i = 0; i < packetHeader; i++) {
if (this.offset >= this.buffer.length) {
throw new Error('Wait for more data.');
}
reply.push(this.parseResult(this.buffer[this.offset++]));
}
return reply;
} else if (type === 45) { // -
end = this.packetEndOffset();
start = this.offset;
this.offset = end + 2;
return new Error(this.buffer.toString('utf-8', start, end));
}
};
JavascriptReplyParser.prototype.execute = function (buffer) {
if (this.chunksSize !== 0) {
if (this.bigStrSize > this.chunksSize + buffer.length) {
this.buffers.push(buffer);
this.chunksSize += buffer.length;
return;
}
this.buffers.push(buffer);
this.buffer = Buffer.concat(this.buffers, this.chunksSize + buffer.length);
this.buffers = [];
this.bigStrSize = 0;
this.chunksSize = 0;
} else if (this.offset >= this.buffer.length) {
this.buffer = buffer;
} else {
this.buffer = Buffer.concat([this.buffer.slice(this.offset), buffer]);
}
this.offset = 0;
this.run();
};
JavascriptReplyParser.prototype.tryParsing = function () {
try {
return this.parseResult(this.type);
} catch (err) {
// Catch the error (not enough data), rewind if it's an array,
// and wait for the next packet to appear
this.offset = this.offsetCache;
// Indicate that there's no protocol error by resetting the type too
this.type = undefined;
}
};
JavascriptReplyParser.prototype.run = function () {
// Set a rewind point. If a failure occurs, wait for the next execute()/append() and try again
this.offsetCache = this.offset;
this.type = this.buffer[this.offset++];
var reply = this.tryParsing();
while (reply !== undefined) {
if (this.type === 45) { // Errors -
this.returnError(reply);
} else {
this.returnReply(reply); // Strings + // Integers : // Bulk strings $ // Arrays *
}
this.offsetCache = this.offset;
this.type = this.buffer[this.offset++];
reply = this.tryParsing();
}
if (this.type !== undefined) {
// Reset the buffer so the parser can handle following commands properly
this.buffer = new Buffer(0);
this.returnFatalError(new Error('Protocol error, got ' + JSON.stringify(String.fromCharCode(this.type)) + ' as reply type byte'));
}
};
JavascriptReplyParser.prototype.parseHeader = function () {
var end = this.packetEndOffset(),
value = this.buffer.toString('ascii', this.offset, end) | 0;
this.offset = end + 2;
return value;
};
JavascriptReplyParser.prototype.packetEndOffset = function () {
var offset = this.offset,
len = this.buffer.length - 1;
while (this.buffer[offset] !== 0x0d && this.buffer[offset + 1] !== 0x0a) {
offset++;
if (offset >= len) {
throw new Error('Did not see LF after NL reading multi bulk count (' + offset + ' => ' + this.buffer.length + ', ' + this.offset + ')');
}
}
return offset;
};
module.exports = JavascriptReplyParser;