Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapt to Cloudflare Workers environment #2289

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
38 changes: 12 additions & 26 deletions lib/auth_41.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,7 @@
server stores sha1(sha1(password)) ( hash_stag2)
*/

const crypto = require('crypto');

function sha1(msg, msg1, msg2) {
const hash = crypto.createHash('sha1');
hash.update(msg);
if (msg1) {
hash.update(msg1);
}

if (msg2) {
hash.update(msg2);
}

return hash.digest();
}
const crypto = require('./utils/crypto');

function xor(a, b) {
const result = Buffer.allocUnsafe(a.length);
Expand All @@ -50,37 +36,37 @@ function xor(a, b) {

exports.xor = xor;

function token(password, scramble1, scramble2) {
async function token(password, scramble1, scramble2) {
if (!password) {
return Buffer.alloc(0);
}
const stage1 = sha1(password);
return exports.calculateTokenFromPasswordSha(stage1, scramble1, scramble2);
const stage1 = await crypto.sha1(password);
return await exports.calculateTokenFromPasswordSha(stage1, scramble1, scramble2);
}

exports.calculateTokenFromPasswordSha = function(
exports.calculateTokenFromPasswordSha = async function(
passwordSha,
scramble1,
scramble2
) {
// we use AUTH 41 here, and we need only the bytes we just need.
const authPluginData1 = scramble1.slice(0, 8);
const authPluginData2 = scramble2.slice(0, 12);
const stage2 = sha1(passwordSha);
const stage3 = sha1(authPluginData1, authPluginData2, stage2);
const stage2 = await crypto.sha1(passwordSha);
const stage3 = await crypto.sha1(authPluginData1, authPluginData2, stage2);
return xor(stage3, passwordSha);
};

exports.calculateToken = token;

exports.verifyToken = function(publicSeed1, publicSeed2, token, doubleSha) {
const hashStage1 = xor(token, sha1(publicSeed1, publicSeed2, doubleSha));
const candidateHash2 = sha1(hashStage1);
exports.verifyToken = async function(publicSeed1, publicSeed2, token, doubleSha) {
const hashStage1 = xor(token, await crypto.sha1(publicSeed1, publicSeed2, doubleSha));
const candidateHash2 = await crypto.sha1(hashStage1);
return candidateHash2.compare(doubleSha) === 0;
};

exports.doubleSha1 = function(password) {
return sha1(sha1(password));
exports.doubleSha1 = async function(password) {
return await crypto.sha1(await crypto.sha1(password));
};

function xorRotating(a, seed) {
Expand Down
6 changes: 3 additions & 3 deletions lib/auth_plugins/mysql_native_password.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ module.exports = pluginOptions => ({ connection, command }) => {
command.passwordSha1 ||
pluginOptions.passwordSha1 ||
connection.config.passwordSha1;
return data => {
return async data => {
const authPluginData1 = data.slice(0, 8);
const authPluginData2 = data.slice(8, 20);
let authToken;
if (passwordSha1) {
authToken = auth41.calculateTokenFromPasswordSha(
authToken = await auth41.calculateTokenFromPasswordSha(
passwordSha1,
authPluginData1,
authPluginData2
);
} else {
authToken = auth41.calculateToken(
authToken = await auth41.calculateToken(
password,
authPluginData1,
authPluginData2
Expand Down
6 changes: 5 additions & 1 deletion lib/commands/change_user.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ class ChangeUser extends Command {
connection.clientEncoding = CharsetToEncoding[this.charsetNumber];
// clear prepared statements cache as all statements become invalid after changeUser
connection._statements.clear();
connection.writePacket(newPacket.toPacket());
newPacket.toPacket().then(packet => {
connection.writePacket(packet);
}).catch(err => {
this.onResult(err);
});
// check if the server supports multi-factor authentication
const multiFactorAuthentication = connection.serverCapabilityFlags & ClientConstants.MULTI_FACTOR_AUTHENTICATION;
if (multiFactorAuthentication) {
Expand Down
29 changes: 10 additions & 19 deletions lib/commands/client_handshake.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const Packets = require('../packets/index.js');
const ClientConstants = require('../constants/client.js');
const CharsetToEncoding = require('../constants/charset_encodings.js');
const auth41 = require('../auth_41.js');
const {secureStream} = require('../stream.js');

function flagNames(flags) {
const res = [];
Expand Down Expand Up @@ -46,7 +47,7 @@ class ClientHandshake extends Command {
connection.writePacket(sslRequest.toPacket());
}

sendCredentials(connection) {
async sendCredentials(connection) {
if (connection.config.debug) {
// eslint-disable-next-line
console.log(
Expand Down Expand Up @@ -80,22 +81,22 @@ class ClientHandshake extends Command {
compress: connection.config.compress,
connectAttributes: connection.config.connectAttributes
});
connection.writePacket(handshakeResponse.toPacket());
connection.writePacket(await handshakeResponse.toPacket());
}

calculateNativePasswordAuthToken(authPluginData) {
async calculateNativePasswordAuthToken(authPluginData) {
// TODO: dont split into authPluginData1 and authPluginData2, instead join when 1 & 2 received
const authPluginData1 = authPluginData.slice(0, 8);
const authPluginData2 = authPluginData.slice(8, 20);
let authToken;
if (this.passwordSha1) {
authToken = auth41.calculateTokenFromPasswordSha(
authToken = await auth41.calculateTokenFromPasswordSha(
this.passwordSha1,
authPluginData1,
authPluginData2
);
} else {
authToken = auth41.calculateToken(
authToken = await auth41.calculateToken(
this.password,
authPluginData1,
authPluginData2
Expand Down Expand Up @@ -146,21 +147,11 @@ class ClientHandshake extends Command {
// send ssl upgrade request and immediately upgrade connection to secure
this.clientFlags |= ClientConstants.SSL;
this.sendSSLRequest(connection);
connection.startTLS(err => {
// after connection is secure
if (err) {
// SSL negotiation error are fatal
err.code = 'HANDSHAKE_SSL_ERROR';
err.fatal = true;
this.emit('error', err);
return;
}
// rest of communication is encrypted
this.sendCredentials(connection);
});
} else {
this.sendCredentials(connection);
secureStream(connection)
}
this.sendCredentials(connection).catch(err => {
this.emit('error', err);
});
if (multiFactorAuthentication) {
// if the server supports multi-factor authentication, we enable it in
// the client
Expand Down
7 changes: 3 additions & 4 deletions lib/commands/command.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';

const EventEmitter = require('events').EventEmitter;
const Timers = require('timers');

class Command extends EventEmitter {
constructor() {
Expand Down Expand Up @@ -29,7 +28,7 @@ class Command extends EventEmitter {
const err = packet.asError(connection.clientEncoding);
err.sql = this.sql || this.query;
if (this.queryTimeout) {
Timers.clearTimeout(this.queryTimeout);
clearTimeout(this.queryTimeout);
this.queryTimeout = null;
}
if (this.onResult) {
Expand All @@ -45,10 +44,10 @@ class Command extends EventEmitter {
this.next = this.next(packet, connection);
if (this.next) {
return false;
}
}
this.emit('end');
return true;

}
}

Expand Down
18 changes: 11 additions & 7 deletions lib/commands/query.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
'use strict';

const process = require('process');
const Timers = require('timers');

const Readable = require('stream').Readable;

const Command = require('./command.js');
const Packets = require('../packets/index.js');
const getTextParser = require('../parsers/text_parser.js');
const getStaticTextParser = require('../parsers/static_text_parser.js');
const ServerStatus = require('../constants/server_status.js');

const EmptyPacket = new Packets.Packet(0, Buffer.allocUnsafe(4), 0, 4);
Expand Down Expand Up @@ -69,7 +69,7 @@ class Query extends Command {
}
// else clear timer
if (this.queryTimeout) {
Timers.clearTimeout(this.queryTimeout);
clearTimeout(this.queryTimeout);
this.queryTimeout = null;
}
if (this.onResult) {
Expand Down Expand Up @@ -212,7 +212,11 @@ class Query extends Command {
if (this._receivedFieldsCount === this._fieldCount) {
const fields = this._fields[this._resultIndex];
this.emit('fields', fields);
this._rowParser = new (getTextParser(fields, this.options, connection.config))(fields);
if (this.options.useStaticParser) {
this._rowParser = getStaticTextParser(fields, this.options, connection.config);
} else {
this._rowParser = new (getTextParser(fields, this.options, connection.config))(fields);
}
return Query.prototype.fieldsEOF;
}
return Query.prototype.readField;
Expand All @@ -227,7 +231,7 @@ class Query extends Command {
}

/* eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */
row(packet, _connection) {
row(packet, _connection) {
if (packet.isEOF()) {
const status = packet.eofStatusFlags();
const moreResults = status & ServerStatus.SERVER_MORE_RESULTS_EXISTS;
Expand Down Expand Up @@ -290,7 +294,7 @@ class Query extends Command {
_setTimeout() {
if (this.timeout) {
const timeoutHandler = this._handleTimeoutError.bind(this);
this.queryTimeout = Timers.setTimeout(
this.queryTimeout = setTimeout(
timeoutHandler,
this.timeout
);
Expand All @@ -299,10 +303,10 @@ class Query extends Command {

_handleTimeoutError() {
if (this.queryTimeout) {
Timers.clearTimeout(this.queryTimeout);
clearTimeout(this.queryTimeout);
this.queryTimeout = null;
}

const err = new Error('Query inactivity timeout');
err.errorno = 'PROTOCOL_SEQUENCE_TIMEOUT';
err.code = 'PROTOCOL_SEQUENCE_TIMEOUT';
Expand Down
25 changes: 13 additions & 12 deletions lib/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@

'use strict';

const Net = require('net');
const Tls = require('tls');
const Timers = require('timers');
const EventEmitter = require('events').EventEmitter;
const Readable = require('stream').Readable;
const Queue = require('denque');
Expand All @@ -29,6 +26,7 @@ const Packets = require('./packets/index.js');
const Commands = require('./commands/index.js');
const ConnectionConfig = require('./connection_config.js');
const CharsetToEncoding = require('./constants/charset_encodings.js');
const {getStream} = require("./stream");

let _connectionId = 0;

Expand All @@ -44,10 +42,12 @@ class Connection extends EventEmitter {
// TODO: use `/usr/local/mysql/bin/mysql_config --socket` output? as default socketPath
// if there is no host/port and no socketPath parameters?
if (!opts.config.stream) {
this.stream = getStream(!!opts.config.ssl);
shiyuhang0 marked this conversation as resolved.
Show resolved Hide resolved
if (opts.config.socketPath) {
this.stream = Net.connect(opts.config.socketPath);
// FIXME
this.stream.connect(opts.config.socketPath);
} else {
this.stream = Net.connect(
this.stream.connect(
opts.config.port,
opts.config.host
);
Expand Down Expand Up @@ -98,7 +98,7 @@ class Connection extends EventEmitter {
});
this.stream.on('data', data => {
if (this.connectTimeout) {
Timers.clearTimeout(this.connectTimeout);
clearTimeout(this.connectTimeout);
this.connectTimeout = null;
}
this.packetParser.execute(data);
Expand Down Expand Up @@ -148,7 +148,7 @@ class Connection extends EventEmitter {
this.serverEncoding = 'utf8';
if (this.config.connectTimeout) {
const timeoutHandler = this._handleTimeoutError.bind(this);
this.connectTimeout = Timers.setTimeout(
this.connectTimeout = setTimeout(
timeoutHandler,
this.config.connectTimeout
);
Expand Down Expand Up @@ -186,7 +186,7 @@ class Connection extends EventEmitter {

_handleNetworkError(err) {
if (this.connectTimeout) {
Timers.clearTimeout(this.connectTimeout);
clearTimeout(this.connectTimeout);
this.connectTimeout = null;
}
// Do not throw an error when a connection ends with a RST,ACK packet
Expand All @@ -198,7 +198,7 @@ class Connection extends EventEmitter {

_handleTimeoutError() {
if (this.connectTimeout) {
Timers.clearTimeout(this.connectTimeout);
clearTimeout(this.connectTimeout);
this.connectTimeout = null;
}
this.stream.destroy && this.stream.destroy();
Expand All @@ -213,7 +213,7 @@ class Connection extends EventEmitter {
// called on stream error or unexpected termination
_notifyError(err) {
if (this.connectTimeout) {
Timers.clearTimeout(this.connectTimeout);
clearTimeout(this.connectTimeout);
this.connectTimeout = null;
}
// prevent from emitting 'PROTOCOL_CONNECTION_LOST' after EPIPE or ECONNRESET
Expand Down Expand Up @@ -340,6 +340,7 @@ class Connection extends EventEmitter {

// 0.11+ environment
startTLS(onSecure) {
const Tls = require('tls');
if (this.config.debug) {
// eslint-disable-next-line no-console
console.log('Upgrading connection to TLS');
Expand Down Expand Up @@ -408,7 +409,7 @@ class Connection extends EventEmitter {
err.code = code || 'PROTOCOL_ERROR';
this.emit('error', err);
}

get fatalError() {
return this._fatalError;
}
Expand Down Expand Up @@ -760,7 +761,7 @@ class Connection extends EventEmitter {

close() {
if (this.connectTimeout) {
Timers.clearTimeout(this.connectTimeout);
clearTimeout(this.connectTimeout);
this.connectTimeout = null;
}
this._closing = true;
Expand Down
4 changes: 3 additions & 1 deletion lib/connection_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ const validOptions = {
idleTimeout: 1,
Promise: 1,
queueLimit: 1,
waitForConnections: 1
waitForConnections: 1,
useStaticParser: 1
};

class ConnectionConfig {
Expand Down Expand Up @@ -180,6 +181,7 @@ class ConnectionConfig {
};
this.connectAttributes = { ...defaultConnectAttributes, ...(options.connectAttributes || {})};
this.maxPreparedStatements = options.maxPreparedStatements || 16000;
this.useStaticParser = options.useStaticParser || false;
}

static mergeFlags(default_flags, user_flags) {
Expand Down
Loading