html-re/node_modules/mysql/lib/protocol/Parser.js

492 lines
13 KiB
JavaScript

var PacketHeader = require('./PacketHeader');
var BigNumber = require('bignumber.js');
var Buffer = require('safe-buffer').Buffer;
var BufferList = require('./BufferList');
var MAX_PACKET_LENGTH = Math.pow(2, 24) - 1;
var MUL_32BIT = Math.pow(2, 32);
var PACKET_HEADER_LENGTH = 4;
module.exports = Parser;
function Parser(options) {
options = options || {};
this._supportBigNumbers = options.config && options.config.supportBigNumbers;
this._buffer = Buffer.alloc(0);
this._nextBuffers = new BufferList();
this._longPacketBuffers = new BufferList();
this._offset = 0;
this._packetEnd = null;
this._packetHeader = null;
this._packetOffset = null;
this._onError = options.onError || function(err) { throw err; };
this._onPacket = options.onPacket || function() {};
this._nextPacketNumber = 0;
this._encoding = 'utf-8';
this._paused = false;
}
Parser.prototype.write = function write(chunk) {
this._nextBuffers.push(chunk);
while (!this._paused) {
var packetHeader = this._tryReadPacketHeader();
if (!packetHeader) {
break;
}
if (!this._combineNextBuffers(packetHeader.length)) {
break;
}
this._parsePacket(packetHeader);
}
};
Parser.prototype.append = function append(chunk) {
if (!chunk || chunk.length === 0) {
return;
}
// Calculate slice ranges
var sliceEnd = this._buffer.length;
var sliceStart = this._packetOffset === null
? this._offset
: this._packetOffset;
var sliceLength = sliceEnd - sliceStart;
// Get chunk data
var buffer = null;
var chunks = !(chunk instanceof Array || Array.isArray(chunk)) ? [chunk] : chunk;
var length = 0;
var offset = 0;
for (var i = 0; i < chunks.length; i++) {
length += chunks[i].length;
}
if (sliceLength !== 0) {
// Create a new Buffer
buffer = Buffer.allocUnsafe(sliceLength + length);
offset = 0;
// Copy data slice
offset += this._buffer.copy(buffer, 0, sliceStart, sliceEnd);
// Copy chunks
for (var i = 0; i < chunks.length; i++) {
offset += chunks[i].copy(buffer, offset);
}
} else if (chunks.length > 1) {
// Create a new Buffer
buffer = Buffer.allocUnsafe(length);
offset = 0;
// Copy chunks
for (var i = 0; i < chunks.length; i++) {
offset += chunks[i].copy(buffer, offset);
}
} else {
// Buffer is the only chunk
buffer = chunks[0];
}
// Adjust data-tracking pointers
this._buffer = buffer;
this._offset = this._offset - sliceStart;
this._packetEnd = this._packetEnd !== null
? this._packetEnd - sliceStart
: null;
this._packetOffset = this._packetOffset !== null
? this._packetOffset - sliceStart
: null;
};
Parser.prototype.pause = function() {
this._paused = true;
};
Parser.prototype.resume = function() {
this._paused = false;
// nextTick() to avoid entering write() multiple times within the same stack
// which would cause problems as write manipulates the state of the object.
process.nextTick(this.write.bind(this));
};
Parser.prototype.peak = function peak(offset) {
return this._buffer[this._offset + (offset >>> 0)];
};
Parser.prototype.parseUnsignedNumber = function parseUnsignedNumber(bytes) {
if (bytes === 1) {
return this._buffer[this._offset++];
}
var buffer = this._buffer;
var offset = this._offset + bytes - 1;
var value = 0;
if (bytes > 4) {
var err = new Error('parseUnsignedNumber: Supports only up to 4 bytes');
err.offset = (this._offset - this._packetOffset - 1);
err.code = 'PARSER_UNSIGNED_TOO_LONG';
throw err;
}
while (offset >= this._offset) {
value = ((value << 8) | buffer[offset]) >>> 0;
offset--;
}
this._offset += bytes;
return value;
};
Parser.prototype.parseLengthCodedString = function() {
var length = this.parseLengthCodedNumber();
if (length === null) {
return null;
}
return this.parseString(length);
};
Parser.prototype.parseLengthCodedBuffer = function() {
var length = this.parseLengthCodedNumber();
if (length === null) {
return null;
}
return this.parseBuffer(length);
};
Parser.prototype.parseLengthCodedNumber = function parseLengthCodedNumber() {
if (this._offset >= this._buffer.length) {
var err = new Error('Parser: read past end');
err.offset = (this._offset - this._packetOffset);
err.code = 'PARSER_READ_PAST_END';
throw err;
}
var bits = this._buffer[this._offset++];
if (bits <= 250) {
return bits;
}
switch (bits) {
case 251:
return null;
case 252:
return this.parseUnsignedNumber(2);
case 253:
return this.parseUnsignedNumber(3);
case 254:
break;
default:
var err = new Error('Unexpected first byte' + (bits ? ': 0x' + bits.toString(16) : ''));
err.offset = (this._offset - this._packetOffset - 1);
err.code = 'PARSER_BAD_LENGTH_BYTE';
throw err;
}
var low = this.parseUnsignedNumber(4);
var high = this.parseUnsignedNumber(4);
var value;
if (high >>> 21) {
value = BigNumber(MUL_32BIT).times(high).plus(low).toString();
if (this._supportBigNumbers) {
return value;
}
var err = new Error(
'parseLengthCodedNumber: JS precision range exceeded, ' +
'number is >= 53 bit: "' + value + '"'
);
err.offset = (this._offset - this._packetOffset - 8);
err.code = 'PARSER_JS_PRECISION_RANGE_EXCEEDED';
throw err;
}
value = low + (MUL_32BIT * high);
return value;
};
Parser.prototype.parseFiller = function(length) {
return this.parseBuffer(length);
};
Parser.prototype.parseNullTerminatedBuffer = function() {
var end = this._nullByteOffset();
var value = this._buffer.slice(this._offset, end);
this._offset = end + 1;
return value;
};
Parser.prototype.parseNullTerminatedString = function() {
var end = this._nullByteOffset();
var value = this._buffer.toString(this._encoding, this._offset, end);
this._offset = end + 1;
return value;
};
Parser.prototype._nullByteOffset = function() {
var offset = this._offset;
while (this._buffer[offset] !== 0x00) {
offset++;
if (offset >= this._buffer.length) {
var err = new Error('Offset of null terminated string not found.');
err.offset = (this._offset - this._packetOffset);
err.code = 'PARSER_MISSING_NULL_BYTE';
throw err;
}
}
return offset;
};
Parser.prototype.parsePacketTerminatedBuffer = function parsePacketTerminatedBuffer() {
var length = this._packetEnd - this._offset;
return this.parseBuffer(length);
};
Parser.prototype.parsePacketTerminatedString = function() {
var length = this._packetEnd - this._offset;
return this.parseString(length);
};
Parser.prototype.parseBuffer = function(length) {
var response = Buffer.alloc(length);
this._buffer.copy(response, 0, this._offset, this._offset + length);
this._offset += length;
return response;
};
Parser.prototype.parseString = function(length) {
var offset = this._offset;
var end = offset + length;
var value = this._buffer.toString(this._encoding, offset, end);
this._offset = end;
return value;
};
Parser.prototype.parseGeometryValue = function() {
var buffer = this.parseLengthCodedBuffer();
var offset = 4;
if (buffer === null || !buffer.length) {
return null;
}
function parseGeometry() {
var result = null;
var byteOrder = buffer.readUInt8(offset); offset += 1;
var wkbType = byteOrder ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
switch (wkbType) {
case 1: // WKBPoint
var x = byteOrder ? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
var y = byteOrder ? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
result = {x: x, y: y};
break;
case 2: // WKBLineString
var numPoints = byteOrder ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
result = [];
for (var i = numPoints; i > 0; i--) {
var x = byteOrder ? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
var y = byteOrder ? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
result.push({x: x, y: y});
}
break;
case 3: // WKBPolygon
var numRings = byteOrder ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
result = [];
for (var i = numRings; i > 0; i--) {
var numPoints = byteOrder ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
var line = [];
for (var j = numPoints; j > 0; j--) {
var x = byteOrder ? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
var y = byteOrder ? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
line.push({x: x, y: y});
}
result.push(line);
}
break;
case 4: // WKBMultiPoint
case 5: // WKBMultiLineString
case 6: // WKBMultiPolygon
case 7: // WKBGeometryCollection
var num = byteOrder ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
var result = [];
for (var i = num; i > 0; i--) {
result.push(parseGeometry());
}
break;
}
return result;
}
return parseGeometry();
};
Parser.prototype.reachedPacketEnd = function() {
return this._offset === this._packetEnd;
};
Parser.prototype.incrementPacketNumber = function() {
var currentPacketNumber = this._nextPacketNumber;
this._nextPacketNumber = (this._nextPacketNumber + 1) % 256;
return currentPacketNumber;
};
Parser.prototype.resetPacketNumber = function() {
this._nextPacketNumber = 0;
};
Parser.prototype.packetLength = function packetLength() {
if (!this._packetHeader) {
return null;
}
return this._packetHeader.length + this._longPacketBuffers.size;
};
Parser.prototype._combineNextBuffers = function _combineNextBuffers(bytes) {
var length = this._buffer.length - this._offset;
if (length >= bytes) {
return true;
}
if ((length + this._nextBuffers.size) < bytes) {
return false;
}
var buffers = [];
var bytesNeeded = bytes - length;
while (bytesNeeded > 0) {
var buffer = this._nextBuffers.shift();
buffers.push(buffer);
bytesNeeded -= buffer.length;
}
this.append(buffers);
return true;
};
Parser.prototype._combineLongPacketBuffers = function _combineLongPacketBuffers() {
if (!this._longPacketBuffers.size) {
return;
}
// Calculate bytes
var remainingBytes = this._buffer.length - this._offset;
var trailingPacketBytes = this._buffer.length - this._packetEnd;
// Create buffer
var buf = null;
var buffer = Buffer.allocUnsafe(remainingBytes + this._longPacketBuffers.size);
var offset = 0;
// Copy long buffers
while ((buf = this._longPacketBuffers.shift())) {
offset += buf.copy(buffer, offset);
}
// Copy remaining bytes
this._buffer.copy(buffer, offset, this._offset);
this._buffer = buffer;
this._offset = 0;
this._packetEnd = this._buffer.length - trailingPacketBytes;
this._packetOffset = 0;
};
Parser.prototype._parsePacket = function _parsePacket(packetHeader) {
this._packetEnd = this._offset + packetHeader.length;
this._packetOffset = this._offset;
if (packetHeader.length === MAX_PACKET_LENGTH) {
this._longPacketBuffers.push(this._buffer.slice(this._packetOffset, this._packetEnd));
this._advanceToNextPacket();
return;
}
this._combineLongPacketBuffers();
var hadException = true;
try {
this._onPacket(packetHeader);
hadException = false;
} catch (err) {
if (!err || typeof err.code !== 'string' || err.code.substr(0, 7) !== 'PARSER_') {
throw err; // Rethrow non-MySQL errors
}
// Pass down parser errors
this._onError(err);
hadException = false;
} finally {
this._advanceToNextPacket();
// If there was an exception, the parser while loop will be broken out
// of after the finally block. So schedule a blank write to re-enter it
// to continue parsing any bytes that may already have been received.
if (hadException) {
process.nextTick(this.write.bind(this));
}
}
};
Parser.prototype._tryReadPacketHeader = function _tryReadPacketHeader() {
if (this._packetHeader) {
return this._packetHeader;
}
if (!this._combineNextBuffers(PACKET_HEADER_LENGTH)) {
return null;
}
this._packetHeader = new PacketHeader(
this.parseUnsignedNumber(3),
this.parseUnsignedNumber(1)
);
if (this._packetHeader.number !== this._nextPacketNumber) {
var err = new Error(
'Packets out of order. Got: ' + this._packetHeader.number + ' ' +
'Expected: ' + this._nextPacketNumber
);
err.code = 'PROTOCOL_PACKETS_OUT_OF_ORDER';
err.fatal = true;
this._onError(err);
}
this.incrementPacketNumber();
return this._packetHeader;
};
Parser.prototype._advanceToNextPacket = function() {
this._offset = this._packetEnd;
this._packetHeader = null;
this._packetEnd = null;
this._packetOffset = null;
};