Update Fix

This commit is contained in:
2026-03-15 12:30:40 +01:00
parent 311ba5e7f3
commit 50be8e25f3
176 changed files with 4075 additions and 3013 deletions

View File

@@ -16,22 +16,47 @@ class HandshakeResponse {
this.authPluginData2 = handshake.authPluginData2;
this.compress = handshake.compress;
this.clientFlags = handshake.flags;
// TODO: pre-4.1 auth support
let authToken;
if (this.passwordSha1) {
authToken = auth41.calculateTokenFromPasswordSha(
this.passwordSha1,
this.authPluginData1,
this.authPluginData2
);
// Accept pre-calculated authToken and authPluginName from caller
// This allows the caller to optimize by using the server's preferred auth method
if (
handshake.authToken !== undefined &&
handshake.authPluginName !== undefined
) {
// Validate types to fail fast with clear errors
if (!Buffer.isBuffer(handshake.authToken)) {
throw new TypeError(
'HandshakeResponse authToken must be a Buffer when provided'
);
}
if (typeof handshake.authPluginName !== 'string') {
throw new TypeError(
'HandshakeResponse authPluginName must be a string when provided'
);
}
this.authToken = handshake.authToken;
this.authPluginName = handshake.authPluginName;
} else {
authToken = auth41.calculateToken(
this.password,
this.authPluginData1,
this.authPluginData2
);
// Fallback to legacy behavior: calculate mysql_native_password token
// TODO: pre-4.1 auth support
let authToken;
if (this.passwordSha1) {
authToken = auth41.calculateTokenFromPasswordSha(
this.passwordSha1,
this.authPluginData1,
this.authPluginData2
);
} else {
authToken = auth41.calculateToken(
this.password,
this.authPluginData1,
this.authPluginData2
);
}
this.authToken = authToken;
this.authPluginName = 'mysql_native_password';
}
this.authToken = authToken;
this.charsetNumber = handshake.charsetNumber;
this.encoding = CharsetToEncoding[handshake.charsetNumber];
this.connectAttributes = handshake.connectAttributes;
@@ -62,8 +87,12 @@ class HandshakeResponse {
packet.writeNullTerminatedString(this.database, encoding);
}
if (isSet('PLUGIN_AUTH')) {
// TODO: pass from config
packet.writeNullTerminatedString('mysql_native_password', 'latin1');
// Use the auth plugin name specified by the caller (optimized for server's preference)
// or fall back to mysql_native_password for backward compatibility
packet.writeNullTerminatedString(
this.authPluginName || 'mysql_native_password',
'latin1'
);
}
if (isSet('CONNECT_ATTRS')) {
const connectAttributes = this.connectAttributes || {};

View File

@@ -406,8 +406,8 @@ class Packet {
readNullTerminatedString(encoding) {
const start = this.offset;
let end = this.offset;
while (this.buffer[end]) {
end = end + 1; // TODO: handle OOB check
while (end < this.end && this.buffer[end] !== 0x00) {
end = end + 1;
}
this.offset = end + 1;
return StringParser.decode(this.buffer, encoding, start, end);
@@ -516,16 +516,20 @@ class Packet {
return result * sign;
}
// copy-paste from https://github.com/mysqljs/mysql/blob/master/lib/protocol/Parser.js
// adapted from https://github.com/mysqljs/mysql/blob/dc9c152a87ec51a1f647447268917243d2eab1fd/lib/protocol/Parser.js
parseGeometryValue() {
const buffer = this.readLengthCodedBuffer();
let offset = 4;
if (buffer === null || !buffer.length) {
return null;
}
const bufferLength = buffer.length;
function parseGeometry() {
let x, y, i, j, numPoints, line;
let x, y, i, j, numPoints, numRings, num, line;
let result = null;
if (offset + 5 > bufferLength) {
return null;
}
const byteOrder = buffer.readUInt8(offset);
offset += 1;
const wkbType = byteOrder
@@ -534,6 +538,9 @@ class Packet {
offset += 4;
switch (wkbType) {
case 1: // WKBPoint
if (offset + 16 > bufferLength) {
return null;
}
x = byteOrder
? buffer.readDoubleLE(offset)
: buffer.readDoubleBE(offset);
@@ -545,12 +552,21 @@ class Packet {
result = { x: x, y: y };
break;
case 2: // WKBLineString
if (offset + 4 > bufferLength) {
return null;
}
numPoints = byteOrder
? buffer.readUInt32LE(offset)
: buffer.readUInt32BE(offset);
offset += 4;
if (numPoints > (bufferLength - offset) / 16) {
return null;
}
result = [];
for (i = numPoints; i > 0; i--) {
if (offset + 16 > bufferLength) {
break;
}
x = byteOrder
? buffer.readDoubleLE(offset)
: buffer.readDoubleBE(offset);
@@ -563,19 +579,30 @@ class Packet {
}
break;
case 3: // WKBPolygon
// eslint-disable-next-line no-case-declarations
const numRings = byteOrder
if (offset + 4 > bufferLength) {
return null;
}
numRings = byteOrder
? buffer.readUInt32LE(offset)
: buffer.readUInt32BE(offset);
offset += 4;
if (numRings > (bufferLength - offset) / 4) {
return null;
}
result = [];
for (i = numRings; i > 0; i--) {
if (offset + 4 > bufferLength) {
break;
}
numPoints = byteOrder
? buffer.readUInt32LE(offset)
: buffer.readUInt32BE(offset);
offset += 4;
line = [];
for (j = numPoints; j > 0; j--) {
if (offset + 16 > bufferLength) {
break;
}
x = byteOrder
? buffer.readDoubleLE(offset)
: buffer.readDoubleBE(offset);
@@ -593,11 +620,16 @@ class Packet {
case 5: // WKBMultiLineString
case 6: // WKBMultiPolygon
case 7: // WKBGeometryCollection
// eslint-disable-next-line no-case-declarations
const num = byteOrder
if (offset + 4 > bufferLength) {
return null;
}
num = byteOrder
? buffer.readUInt32LE(offset)
: buffer.readUInt32BE(offset);
offset += 4;
if (num > (bufferLength - offset) / 9) {
return null;
}
result = [];
for (i = num; i > 0; i--) {
result.push(parseGeometry());
@@ -660,14 +692,27 @@ class Packet {
if (len === null) {
return null;
}
if (len === 0) {
return 0; // TODO: assert? exception?
}
// For numbers with many digits (>17), use built-in parseFloat to avoid
// precision loss from accumulated rounding errors in repeated *10 operations.
// This fixes issues #2928 (MAX_VALUE doubles) and #3690 (DECIMAL(36,18))
// where very large numbers or numbers with many fractional digits lose precision.
// The threshold of 17 is based on IEEE 754 double precision (~15-17 significant digits).
// Testing shows minimal performance impact as most real-world numbers are shorter.
if (len > 17) {
const str = this.buffer.toString('utf8', this.offset, this.offset + len);
this.offset += len;
return Number.parseFloat(str);
}
let result = 0;
const end = this.offset + len;
let factor = 1;
let pastDot = false;
let charCode = 0;
if (len === 0) {
return 0; // TODO: assert? exception?
}
if (this.buffer[this.offset] === minus) {
this.offset++;
factor = -1;
@@ -681,9 +726,13 @@ class Packet {
pastDot = true;
this.offset++;
} else if (charCode === exponent || charCode === exponentCapital) {
this.offset++;
const exponentValue = this.parseInt(end - this.offset);
return (result / factor) * Math.pow(10, exponentValue);
// Scientific notation detected - bail out to parseFloat for exact match.
// Manual calculation with Math.pow(10, exp) cannot match parseFloat()
// exactly for most non-zero exponents due to accumulated rounding errors.
const start = end - len;
const str = this.buffer.toString('utf8', start, end);
this.offset = end;
return Number.parseFloat(str);
} else {
result *= 10;
result += this.buffer[this.offset] - 48;
@@ -831,11 +880,10 @@ class Packet {
if (n === null) {
return this.writeInt8(0xfb);
}
// TODO: check that n is out of int precision
this.writeInt8(0xfe);
this.buffer.writeUInt32LE(n, this.offset);
this.buffer.writeUInt32LE(n >>> 0, this.offset);
this.offset += 4;
this.buffer.writeUInt32LE(n >> 32, this.offset);
this.buffer.writeUInt32LE(Math.floor(n / 0x100000000), this.offset);
this.offset += 4;
return this.offset;
}