From 8ef4794105aaf3aba59ffbeb878bf239ba899364 Mon Sep 17 00:00:00 2001 From: vrustx <279631638@qq.com> Date: Sat, 30 May 2026 23:43:35 +0800 Subject: [PATCH] fix: parse MySQL JDBC URL credentials (#556) --- apps/desktop/src/lib/connectionUrl.ts | 38 ++++++++++++++++++++---- packages/app-tests/connectionUrl.test.ts | 22 ++++++++++++++ 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/apps/desktop/src/lib/connectionUrl.ts b/apps/desktop/src/lib/connectionUrl.ts index e7fbfb794..0026c3fe4 100644 --- a/apps/desktop/src/lib/connectionUrl.ts +++ b/apps/desktop/src/lib/connectionUrl.ts @@ -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), }; } diff --git a/packages/app-tests/connectionUrl.test.ts b/packages/app-tests/connectionUrl.test.ts index f7b54b45c..588244aa3 100644 --- a/packages/app-tests/connectionUrl.test.ts +++ b/packages/app-tests/connectionUrl.test.ts @@ -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");