Skip to content

Commit 0213256

Browse files
Prometheus2677FPiety0521
authored andcommitted
Don't urlencode the username and password for MySQL
Addresses: golang-migrate/migrate#69
1 parent b66b6fd commit 0213256

File tree

2 files changed

+87
-4
lines changed

2 files changed

+87
-4
lines changed

database/mysql/mysql.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,13 @@ import (
1313
nurl "net/url"
1414
"strconv"
1515
"strings"
16+
)
1617

18+
import (
1719
"github.com/go-sql-driver/mysql"
20+
)
21+
22+
import (
1823
"github.com/golang-migrate/migrate"
1924
"github.com/golang-migrate/migrate/database"
2025
)
@@ -89,6 +94,25 @@ func WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {
8994
return mx, nil
9095
}
9196

97+
// urlToMySQLConfig takes a net/url URL and returns a go-sql-driver/mysql Config.
98+
// Manually sets username and password to avoid net/url from url-encoding the reserved URL characters
99+
func urlToMySQLConfig(u nurl.URL) (*mysql.Config, error) {
100+
origUserInfo := u.User
101+
u.User = nil
102+
103+
c, err := mysql.ParseDSN(strings.TrimPrefix(u.String(), "mysql://"))
104+
if err != nil {
105+
return nil, err
106+
}
107+
if origUserInfo != nil {
108+
c.User = origUserInfo.Username()
109+
if p, ok := origUserInfo.Password(); ok {
110+
c.Passwd = p
111+
}
112+
}
113+
return c, nil
114+
}
115+
92116
func (m *Mysql) Open(url string) (database.Driver, error) {
93117
purl, err := nurl.Parse(url)
94118
if err != nil {
@@ -99,8 +123,11 @@ func (m *Mysql) Open(url string) (database.Driver, error) {
99123
q.Set("multiStatements", "true")
100124
purl.RawQuery = q.Encode()
101125

102-
db, err := sql.Open("mysql", strings.Replace(
103-
migrate.FilterCustomQuery(purl).String(), "mysql://", "", 1))
126+
c, err := urlToMySQLConfig(*migrate.FilterCustomQuery(purl))
127+
if err != nil {
128+
return nil, err
129+
}
130+
db, err := sql.Open("mysql", c.FormatDSN())
104131
if err != nil {
105132
return nil, err
106133
}

database/mysql/mysql_test.go

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ import (
44
"database/sql"
55
sqldriver "database/sql/driver"
66
"fmt"
7-
// "io/ioutil"
8-
// "log"
7+
"net/url"
98
"testing"
9+
)
1010

11+
import (
1112
"github.com/go-sql-driver/mysql"
13+
)
14+
15+
import (
1216
dt "github.com/golang-migrate/migrate/database/testing"
1317
mt "github.com/golang-migrate/migrate/testing"
1418
)
@@ -97,3 +101,55 @@ func TestLockWorks(t *testing.T) {
97101
}
98102
})
99103
}
104+
105+
func TestURLToMySQLConfig(t *testing.T) {
106+
testcases := []struct {
107+
name string
108+
urlStr string
109+
expectedDSN string // empty string signifies that an error is expected
110+
}{
111+
{name: "no user/password", urlStr: "mysql://tcp(127.0.0.1:3306)/myDB?multiStatements=true",
112+
expectedDSN: "tcp(127.0.0.1:3306)/myDB?multiStatements=true"},
113+
{name: "only user", urlStr: "mysql://username@tcp(127.0.0.1:3306)/myDB?multiStatements=true",
114+
expectedDSN: "username@tcp(127.0.0.1:3306)/myDB?multiStatements=true"},
115+
{name: "only user - with encoded :",
116+
urlStr: "mysql://username%3A@tcp(127.0.0.1:3306)/myDB?multiStatements=true",
117+
expectedDSN: "username:@tcp(127.0.0.1:3306)/myDB?multiStatements=true"},
118+
{name: "only user - with encoded @",
119+
urlStr: "mysql://username%40@tcp(127.0.0.1:3306)/myDB?multiStatements=true",
120+
expectedDSN: "username@@tcp(127.0.0.1:3306)/myDB?multiStatements=true"},
121+
{name: "user/password", urlStr: "mysql://username:password@tcp(127.0.0.1:3306)/myDB?multiStatements=true",
122+
expectedDSN: "username:password@tcp(127.0.0.1:3306)/myDB?multiStatements=true"},
123+
// Not supported yet: https://github.com/go-sql-driver/mysql/issues/591
124+
// {name: "user/password - user with encoded :",
125+
// urlStr: "mysql://username%3A:password@tcp(127.0.0.1:3306)/myDB?multiStatements=true",
126+
// expectedDSN: "username::pasword@tcp(127.0.0.1:3306)/myDB?multiStatements=true"},
127+
{name: "user/password - user with encoded @",
128+
urlStr: "mysql://username%40:password@tcp(127.0.0.1:3306)/myDB?multiStatements=true",
129+
expectedDSN: "username@:password@tcp(127.0.0.1:3306)/myDB?multiStatements=true"},
130+
{name: "user/password - password with encoded :",
131+
urlStr: "mysql://username:password%3A@tcp(127.0.0.1:3306)/myDB?multiStatements=true",
132+
expectedDSN: "username:password:@tcp(127.0.0.1:3306)/myDB?multiStatements=true"},
133+
{name: "user/password - password with encoded @",
134+
urlStr: "mysql://username:password%40@tcp(127.0.0.1:3306)/myDB?multiStatements=true",
135+
expectedDSN: "username:password@@tcp(127.0.0.1:3306)/myDB?multiStatements=true"},
136+
}
137+
for _, tc := range testcases {
138+
t.Run(tc.name, func(t *testing.T) {
139+
u, err := url.Parse(tc.urlStr)
140+
if err != nil {
141+
t.Fatal("Failed to parse url string:", tc.urlStr, "error:", err)
142+
}
143+
if config, err := urlToMySQLConfig(*u); err == nil {
144+
dsn := config.FormatDSN()
145+
if dsn != tc.expectedDSN {
146+
t.Error("Got unexpected DSN:", dsn, "!=", tc.expectedDSN)
147+
}
148+
} else {
149+
if tc.expectedDSN != "" {
150+
t.Error("Got unexpected error:", err, "urlStr:", tc.urlStr)
151+
}
152+
}
153+
})
154+
}
155+
}

0 commit comments

Comments
 (0)