fix: parse MySQL JDBC URL credentials (#556)

This commit is contained in:
vrustx 2026-05-30 23:43:35 +08:00 committed by GitHub
parent 7c0ed986c1
commit 8ef4794105
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 55 additions and 5 deletions

View File

@ -79,6 +79,30 @@ function queryParamValue(params: string, key: string): string | undefined {
return undefined;
}
function extractMysqlCredentialParams(params: string): { username?: string; password?: string; urlParams: string } {
let username: string | undefined;
let password: string | undefined;
let foundCredentialParam = false;
const urlParams: string[] = [];
for (const part of params.split(/[&;]/)) {
if (!part) continue;
const [rawKey, ...rest] = part.split("=");
const key = decodeUrlPart(rawKey).trim().toLowerCase();
if (key === "user") {
username = decodeUrlPart(rest.join("=")).trim();
foundCredentialParam = true;
} else if (key === "password") {
password = decodeUrlPart(rest.join("=")).trim();
foundCredentialParam = true;
} else {
urlParams.push(part);
}
}
return { username, password, urlParams: foundCredentialParam ? urlParams.join("&") : params };
}
function urlParamsRequireTls(dbType: DatabaseType, params: string): boolean {
if (dbType === "mysql") {
const requireSsl = queryParamValue(params, "require_ssl")?.toLowerCase();
@ -243,7 +267,8 @@ export function parseConnectionUrl(value: string, preferredProfile?: string): Pa
if (jdbcOracle) return jdbcOracle;
const jdbcSqlServer = parseJdbcSqlServerUrl(input);
if (jdbcSqlServer) return jdbcSqlServer;
const source = input.replace(/^jdbc:/i, "");
const isJdbcUrl = /^jdbc:/i.test(input);
const source = isJdbcUrl ? input.replace(/^jdbc:/i, "") : input;
let parsed: URL;
try {
@ -264,6 +289,9 @@ export function parseConnectionUrl(value: string, preferredProfile?: string): Pa
profile.type === "redis" && normalizedFragment === "insecure"
? [urlParams, "insecure=true"].filter(Boolean).join("&")
: urlParams;
const mysqlCredentials =
isJdbcUrl && profile.type === "mysql" ? extractMysqlCredentialParams(parsedUrlParams) : undefined;
const effectiveUrlParams = mysqlCredentials?.urlParams ?? parsedUrlParams;
if (profile.type === "mongodb") {
return {
dbType: profile.type,
@ -287,11 +315,11 @@ export function parseConnectionUrl(value: string, preferredProfile?: string): Pa
driverLabel: profile.label,
host: parsed.hostname,
port: parsed.port ? Number(parsed.port) : profile.defaultPort,
username: decodeUrlPart(parsed.username),
password: decodeUrlPart(parsed.password),
username: mysqlCredentials?.username ?? decodeUrlPart(parsed.username),
password: mysqlCredentials?.password ?? decodeUrlPart(parsed.password),
database: databaseFromPath(parsed.pathname),
urlParams: parsedUrlParams,
ssl: scheme === "rediss" || scheme === "https" || urlParamsRequireTls(profile.type, parsedUrlParams),
urlParams: effectiveUrlParams,
ssl: scheme === "rediss" || scheme === "https" || urlParamsRequireTls(profile.type, effectiveUrlParams),
};
}

View File

@ -35,6 +35,28 @@ test("parses mysql TLS URL params into the SSL switch state", () => {
assert.equal(parseConnectionUrl("mysql://root@tidb.example.com:4000/test?require_ssl=true").ssl, true);
});
test("parses MySQL JDBC user and password URL params as credentials", () => {
const parsed = parseConnectionUrl(
"jdbc:mysql://127.0.0.1:1234/example?user=admin&password=pwd&useUnicode=true&characterEncoding=UTF8&useSSL=false",
);
assert.equal(parsed.dbType, "mysql");
assert.equal(parsed.host, "127.0.0.1");
assert.equal(parsed.port, 1234);
assert.equal(parsed.username, "admin");
assert.equal(parsed.password, "pwd");
assert.equal(parsed.database, "example");
assert.equal(parsed.urlParams, "useUnicode=true&characterEncoding=UTF8&useSSL=false");
});
test("leaves non-JDBC MySQL user and password URL params untouched", () => {
const parsed = parseConnectionUrl("mysql://127.0.0.1:1234/example?user=admin&password=pwd&charset=utf8mb4");
assert.equal(parsed.username, "");
assert.equal(parsed.password, "");
assert.equal(parsed.urlParams, "user=admin&password=pwd&charset=utf8mb4");
});
test("parses Redis insecure TLS URL fragments into URL params", () => {
const parsed = parseConnectionUrl("rediss://default:secret@redis.example.com:6379/0#insecure");