Skip to content

Commit 773c8b6

Browse files
authored
fix: support dsn contains special character (#151)
1 parent 273c06a commit 773c8b6

File tree

2 files changed

+79
-5
lines changed

2 files changed

+79
-5
lines changed

dsn.go

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -207,24 +207,73 @@ func (cfg *Config) makeDefaultConfigValue() {
207207
}
208208
}
209209

210+
func needEscape(s string) bool {
211+
unescaped, err := url.QueryUnescape(s)
212+
if err != nil {
213+
return true
214+
}
215+
return url.QueryEscape(unescaped) != s
216+
}
217+
218+
func autoEncodeUserPassInDSN(dsn string) (string, error) {
219+
i := strings.Index(dsn, "://")
220+
if i == -1 {
221+
return dsn, nil
222+
}
223+
rest := dsn[i+3:]
224+
atIdx := strings.Index(rest, "@")
225+
if atIdx == -1 {
226+
return dsn, nil
227+
}
228+
userinfo := rest[:atIdx]
229+
user := userinfo
230+
pass := ""
231+
if idx := strings.Index(userinfo, ":"); idx != -1 {
232+
user = userinfo[:idx]
233+
pass = userinfo[idx+1:]
234+
}
235+
var encUser, encPass string
236+
if needEscape(user) {
237+
encUser = url.QueryEscape(user)
238+
} else {
239+
encUser = user
240+
}
241+
if needEscape(pass) {
242+
encPass = url.QueryEscape(pass)
243+
} else {
244+
encPass = pass
245+
}
246+
var encUserinfo string
247+
if pass != "" {
248+
encUserinfo = encUser + ":" + encPass
249+
} else {
250+
encUserinfo = encUser
251+
}
252+
encodedDSN := dsn[:i+3] + encUserinfo + rest[atIdx:]
253+
return encodedDSN, nil
254+
}
255+
210256
// ParseDSN parses the DSN string to a Config
211257
func ParseDSN(dsn string) (*Config, error) {
212-
u, err := url.Parse(dsn)
258+
encodedDSN, err := autoEncodeUserPassInDSN(dsn)
213259
if err != nil {
214260
return nil, err
215261
}
262+
u, err := url.Parse(encodedDSN)
263+
if err != nil {
264+
logger.Error("ParseDSN", "err", err)
265+
return nil, err
266+
}
216267
cfg := NewConfig()
217268

218269
if strings.HasSuffix(u.Scheme, "http") {
219270
cfg.SSLMode = SSL_MODE_DISABLE
220271
}
221272

222273
if len(u.Path) > 1 {
223-
// skip '/'
224274
cfg.Database = u.Path[1:]
225275
}
226276
if u.User != nil {
227-
// it is expected that empty password will be dropped out on Parse and Format
228277
cfg.User = u.User.Username()
229278
if passwd, ok := u.User.Password(); ok {
230279
cfg.Password = passwd
@@ -253,10 +302,8 @@ func ParseDSN(dsn string) (*Config, error) {
253302
cfg.Host = net.JoinHostPort(u.Host, "443")
254303
}
255304
}
256-
257305
return cfg, nil
258306
}
259-
260307
func (cfg *Config) Connect(ctx context.Context) (driver.Conn, error) {
261308
return DatabendDriver{}.OpenWithConfig(ctx, cfg)
262309
}

dsn_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,40 @@
11
package godatabend
22

33
import (
4+
"fmt"
5+
"net/url"
46
"testing"
57
"time"
68

79
"github.com/stretchr/testify/assert"
810
"github.com/stretchr/testify/require"
911
)
1012

13+
func TestParseDSNWithEncodedChars(t *testing.T) {
14+
username := "test_user"
15+
password := "pa$$?word:abc@123"
16+
dsn := fmt.Sprintf("databend+https://%s:%s@host:443/db?param1=value1&param2=value2", url.QueryEscape(username), url.QueryEscape(password))
17+
cfg, err := ParseDSN(dsn)
18+
require.Nil(t, err)
19+
20+
assert.Equal(t, username, cfg.User)
21+
assert.Equal(t, password, cfg.Password)
22+
assert.Equal(t, "host:443", cfg.Host)
23+
assert.Equal(t, "db", cfg.Database)
24+
}
25+
func TestParseDSNWithSpecialChars(t *testing.T) {
26+
dsn := "databend+https://use%#$^&r:pa$$?word@host:443/db?param1=value1&param2=value2"
27+
cfg, err := ParseDSN(dsn)
28+
require.Nil(t, err)
29+
30+
assert.Equal(t, "use%#$^&r", cfg.User)
31+
assert.Equal(t, "pa$$?word", cfg.Password)
32+
assert.Equal(t, "host:443", cfg.Host)
33+
assert.Equal(t, "db", cfg.Database)
34+
assert.Equal(t, "value1", cfg.Params["param1"])
35+
assert.Equal(t, "value2", cfg.Params["param2"])
36+
}
37+
1138
func TestFormatDSN(t *testing.T) {
1239
dsn := "databend+https://username:[email protected]/test?role=test_role&empty_field_as=null&timeout=1s&wait_time_secs=10&max_rows_in_buffer=5000000&max_rows_per_page=10000&tls_config=tls-settings&warehouse=wh&sessionParam1=sessionValue1"
1340
cfg, err := ParseDSN(dsn)

0 commit comments

Comments
 (0)