diff --git a/frontend/src/components/ConnectionModal.tsx b/frontend/src/components/ConnectionModal.tsx index ab65afc..ce4e813 100644 --- a/frontend/src/components/ConnectionModal.tsx +++ b/frontend/src/components/ConnectionModal.tsx @@ -1853,7 +1853,7 @@ const ConnectionModal: React.FC<{ case "mongodb": return "retryWrites=true&readPreference=secondaryPreferred"; case "dameng": - return "schema=SYSDBA&escapeProcess=true"; + return "schema=SYSDBA"; case "tdengine": return "timezone=Asia%2FShanghai"; default: diff --git a/internal/db/dameng_impl.go b/internal/db/dameng_impl.go index d606b1b..ec51b2c 100644 --- a/internal/db/dameng_impl.go +++ b/internal/db/dameng_impl.go @@ -31,7 +31,6 @@ func (d *DamengDB) getDSN(config connection.ConnectionConfig) string { // or dm://user:password@host:port address := net.JoinHostPort(config.Host, strconv.Itoa(config.Port)) - escapedPassword := url.PathEscape(config.Password) q := url.Values{} if config.Database != "" { q.Set("schema", config.Database) @@ -44,15 +43,16 @@ func (d *DamengDB) getDSN(config connection.ConnectionConfig) string { q.Set("SSL_KEY_PATH", keyPath) } } - if escapedPassword != config.Password { - // 达梦驱动要求:密码包含特殊字符时,password 需 PathEscape,并添加 escapeProcess=true 让驱动解码。 - q.Set("escapeProcess", "true") - } mergeConnectionParamsFromConfig(q, config, "dm", "dameng") - dsn := fmt.Sprintf("dm://%s:%s@%s", config.User, escapedPassword, address) + // 当前达梦 Go 驱动使用字符串切分解析 DSN,认证信息不会做 URL 反解码。 + // 密码保持原样传入,避免 p%40ss 这类转义文本被当作真实密码登录。 + dsn := fmt.Sprintf("dm://%s:%s@%s", config.User, config.Password, address) encoded := q.Encode() if encoded == "" { + if strings.Contains(config.User, "?") || strings.Contains(config.Password, "?") { + return dsn + "?" + } return dsn } return dsn + "?" + encoded diff --git a/internal/db/dsn_test.go b/internal/db/dsn_test.go index 0deee14..b516196 100644 --- a/internal/db/dsn_test.go +++ b/internal/db/dsn_test.go @@ -126,7 +126,7 @@ func TestOracleDSN_EscapesUserAndPassword(t *testing.T) { } } -func TestDamengDSN_EscapesPasswordAndEnablesEscapeProcess(t *testing.T) { +func TestDamengDSN_KeepsRawPasswordForDriverParser(t *testing.T) { d := &DamengDB{} cfg := connection.ConnectionConfig{ Type: "dameng", @@ -138,20 +138,36 @@ func TestDamengDSN_EscapesPasswordAndEnablesEscapeProcess(t *testing.T) { } dsn := d.getDSN(cfg) - if strings.Contains(dsn, cfg.Password) { - t.Fatalf("dsn 包含原始密码:%s", dsn) + if !strings.Contains(dsn, "SYSDBA:p@ss:wo/rd@127.0.0.1:5236") { + t.Fatalf("dsn 未保留达梦驱动可识别的原始认证信息:%s", dsn) } - if strings.Contains(dsn, "wo/rd") || !strings.Contains(dsn, "wo%2Frd") { - t.Fatalf("dsn 未按达梦驱动要求转义密码(至少应转义 '/'):%s", dsn) + if strings.Contains(dsn, "p%40ss") || strings.Contains(dsn, "wo%2Frd") { + t.Fatalf("dsn 不应转义达梦密码,驱动不会反解码认证信息:%s", dsn) } - if !strings.Contains(dsn, "escapeProcess=true") { - t.Fatalf("dsn 缺少 escapeProcess=true:%s", dsn) + if strings.Contains(dsn, "escapeProcess=true") { + t.Fatalf("dsn 不应自动添加 escapeProcess=true:%s", dsn) } if !strings.Contains(dsn, "schema=DBName") { t.Fatalf("dsn 缺少 schema 参数:%s", dsn) } } +func TestDamengDSN_AppendsQuerySentinelForQuestionMarkInPassword(t *testing.T) { + d := &DamengDB{} + cfg := connection.ConnectionConfig{ + Type: "dameng", + Host: "127.0.0.1", + Port: 5236, + User: "SYSDBA", + Password: "p?ss", + } + + dsn := d.getDSN(cfg) + if dsn != "dm://SYSDBA:p?ss@127.0.0.1:5236?" { + t.Fatalf("dsn = %q, want raw password with trailing query sentinel", dsn) + } +} + func TestDamengDSN_AppendsSSLCertAndKeyParams(t *testing.T) { d := &DamengDB{} cfg := connection.ConnectionConfig{