From 4d6c0927770bbf4104fa21d44fab25c9b2ad40aa Mon Sep 17 00:00:00 2001 From: Brandon Bennett Date: Fri, 20 Oct 2017 00:38:32 -0600 Subject: [PATCH] dsn: escape and unescape user field Sometimes usernames may have characters that need encoding such as : or @. This fixes the problem by url escaping the username on FormatDSN and unescaping it on ParseDSN. Although the DSNs are not proper URIs escaping of username is defined in https://www.ietf.org/rfc/rfc3986.txt Fixes: #688 --- AUTHORS | 1 + dsn.go | 10 ++++++++-- dsn_test.go | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index ac36be9a7..72cb80991 100644 --- a/AUTHORS +++ b/AUTHORS @@ -15,6 +15,7 @@ Aaron Hopkins Achille Roussel Arne Hormann Asta Xie +Brandon Bennett Bulat Gaifullin Carlos Nieto Chris Moos diff --git a/dsn.go b/dsn.go index 3ade963ee..31211ab26 100644 --- a/dsn.go +++ b/dsn.go @@ -104,7 +104,7 @@ func (cfg *Config) FormatDSN() string { // [username[:password]@] if len(cfg.User) > 0 { - buf.WriteString(cfg.User) + buf.WriteString(url.QueryEscape(cfg.User)) if len(cfg.Passwd) > 0 { buf.WriteByte(':') buf.WriteString(cfg.Passwd) @@ -337,8 +337,14 @@ func ParseDSN(dsn string) (cfg *Config, err error) { break } } - cfg.User = dsn[:k] + // username may have encoded characters, try to decode + // them + user, err := url.QueryUnescape(dsn[:k]) + if err != nil { + return nil, fmt.Errorf("invalid username: %v", err) + } + cfg.User = user break } } diff --git a/dsn_test.go b/dsn_test.go index 07b223f6b..79b1976b0 100644 --- a/dsn_test.go +++ b/dsn_test.go @@ -47,6 +47,9 @@ var testDSNs = []struct { }, { "user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local", &Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, +}, { + "foo%3Abar%40%28baz%29@/dbname", + &Config{User: "foo:bar@(baz)", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, }, { "/dbname", &Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, @@ -231,6 +234,17 @@ func TestDSNUnsafeCollation(t *testing.T) { } } +func TestEscapedUser(t *testing.T) { + expected := "foo%3Abar%40%28baz%29@/" + cfg := NewConfig() + cfg.User = "foo:bar@(baz)" + actual := cfg.FormatDSN() + + if actual != expected { + t.Errorf("user was not escaped: want: %#v, got %#v", expected, actual) + } +} + func TestParamsAreSorted(t *testing.T) { expected := "/dbname?interpolateParams=true&foobar=baz&quux=loo" cfg := NewConfig()