Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit eb7d0dd

Browse files
erikdubbelboerdhui
authored andcommitted
Fix in the URL parser with go 1.12.8 and github.com/go-sql-driver/mysql (golang-migrate#265)
* Fix in the URL parser with go 1.12.8 and github.com/go-sql-driver/mysql Change schemeFromURL to just split the url by :// to find the scheme. It's not required to parse the whole URL. MySQL DSNs aren't valid URLs. Fixes golang-migrate#264 * The mysql driver itself also used net/url.Parse * Also fix TestPasswordUnencodedReservedURLChars * Keep backwards compatibility with url encoded username and passwords * Fix suggestions * Reuse old function names
1 parent b071731 commit eb7d0dd

File tree

5 files changed

+67
-63
lines changed

5 files changed

+67
-63
lines changed

database/mysql/mysql.go

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
)
2222

2323
import (
24-
"github.com/golang-migrate/migrate/v4"
2524
"github.com/golang-migrate/migrate/v4/database"
2625
)
2726

@@ -98,43 +97,35 @@ func WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {
9897
return mx, nil
9998
}
10099

101-
// urlToMySQLConfig takes a net/url URL and returns a go-sql-driver/mysql Config.
102-
// Manually sets username and password to avoid net/url from url-encoding the reserved URL characters
103-
func urlToMySQLConfig(u nurl.URL) (*mysql.Config, error) {
104-
origUserInfo := u.User
105-
u.User = nil
106-
107-
c, err := mysql.ParseDSN(strings.TrimPrefix(u.String(), "mysql://"))
100+
func urlToMySQLConfig(url string) (*mysql.Config, error) {
101+
config, err := mysql.ParseDSN(strings.TrimPrefix(url, "mysql://"))
108102
if err != nil {
109103
return nil, err
110104
}
111-
if origUserInfo != nil {
112-
c.User = origUserInfo.Username()
113-
if p, ok := origUserInfo.Password(); ok {
114-
c.Passwd = p
115-
}
116-
}
117-
return c, nil
118-
}
119105

120-
func (m *Mysql) Open(url string) (database.Driver, error) {
121-
purl, err := nurl.Parse(url)
106+
config.MultiStatements = true
107+
108+
// Keep backwards compatibility from when we used net/url.Parse() to parse the DSN.
109+
// net/url.Parse() would automatically unescape it for us.
110+
// See: https://play.golang.org/p/q9j1io-YICQ
111+
user, err := nurl.QueryUnescape(config.User)
122112
if err != nil {
123113
return nil, err
124114
}
115+
config.User = user
125116

126-
q := purl.Query()
127-
q.Set("multiStatements", "true")
128-
purl.RawQuery = q.Encode()
129-
130-
migrationsTable := purl.Query().Get("x-migrations-table")
117+
password, err := nurl.QueryUnescape(config.Passwd)
118+
if err != nil {
119+
return nil, err
120+
}
121+
config.Passwd = password
131122

132123
// use custom TLS?
133-
ctls := purl.Query().Get("tls")
124+
ctls := config.TLSConfig
134125
if len(ctls) > 0 {
135126
if _, isBool := readBool(ctls); !isBool && strings.ToLower(ctls) != "skip-verify" {
136127
rootCertPool := x509.NewCertPool()
137-
pem, err := ioutil.ReadFile(purl.Query().Get("x-tls-ca"))
128+
pem, err := ioutil.ReadFile(config.Params["x-tls-ca"])
138129
if err != nil {
139130
return nil, err
140131
}
@@ -144,7 +135,7 @@ func (m *Mysql) Open(url string) (database.Driver, error) {
144135
}
145136

146137
clientCert := make([]tls.Certificate, 0, 1)
147-
if ccert, ckey := purl.Query().Get("x-tls-cert"), purl.Query().Get("x-tls-key"); ccert != "" || ckey != "" {
138+
if ccert, ckey := config.Params["x-tls-cert"], config.Params["x-tls-key"]; ccert != "" || ckey != "" {
148139
if ccert == "" || ckey == "" {
149140
return nil, ErrTLSCertKeyConfig
150141
}
@@ -156,8 +147,8 @@ func (m *Mysql) Open(url string) (database.Driver, error) {
156147
}
157148

158149
insecureSkipVerify := false
159-
if len(purl.Query().Get("x-tls-insecure-skip-verify")) > 0 {
160-
x, err := strconv.ParseBool(purl.Query().Get("x-tls-insecure-skip-verify"))
150+
if len(config.Params["x-tls-insecure-skip-verify"]) > 0 {
151+
x, err := strconv.ParseBool(config.Params["x-tls-insecure-skip-verify"])
161152
if err != nil {
162153
return nil, err
163154
}
@@ -175,18 +166,23 @@ func (m *Mysql) Open(url string) (database.Driver, error) {
175166
}
176167
}
177168

178-
c, err := urlToMySQLConfig(*migrate.FilterCustomQuery(purl))
169+
return config, nil
170+
}
171+
172+
func (m *Mysql) Open(url string) (database.Driver, error) {
173+
config, err := urlToMySQLConfig(url)
179174
if err != nil {
180175
return nil, err
181176
}
182-
db, err := sql.Open("mysql", c.FormatDSN())
177+
178+
db, err := sql.Open("mysql", config.FormatDSN())
183179
if err != nil {
184180
return nil, err
185181
}
186182

187183
mx, err := WithInstance(db, &Config{
188-
DatabaseName: purl.Path,
189-
MigrationsTable: migrationsTable,
184+
DatabaseName: config.DBName,
185+
MigrationsTable: config.Params["x-migrations-table"],
190186
})
191187
if err != nil {
192188
return nil, err

database/mysql/mysql_test.go

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"log"
99

1010
"github.com/golang-migrate/migrate/v4"
11-
"net/url"
1211
"testing"
1312
)
1413

@@ -210,19 +209,13 @@ func TestURLToMySQLConfig(t *testing.T) {
210209
}
211210
for _, tc := range testcases {
212211
t.Run(tc.name, func(t *testing.T) {
213-
u, err := url.Parse(tc.urlStr)
212+
config, err := urlToMySQLConfig(tc.urlStr)
214213
if err != nil {
215214
t.Fatal("Failed to parse url string:", tc.urlStr, "error:", err)
216215
}
217-
if config, err := urlToMySQLConfig(*u); err == nil {
218-
dsn := config.FormatDSN()
219-
if dsn != tc.expectedDSN {
220-
t.Error("Got unexpected DSN:", dsn, "!=", tc.expectedDSN)
221-
}
222-
} else {
223-
if tc.expectedDSN != "" {
224-
t.Error("Got unexpected error:", err, "urlStr:", tc.urlStr)
225-
}
216+
dsn := config.FormatDSN()
217+
if dsn != tc.expectedDSN {
218+
t.Error("Got unexpected DSN:", dsn, "!=", tc.expectedDSN)
226219
}
227220
})
228221
}

database/parse_test.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,7 @@ func TestPasswordUnencodedReservedURLChars(t *testing.T) {
139139
}{
140140
{char: "!", parses: true, expectedUsername: username, expectedPassword: basePassword + "!",
141141
encodedURL: schemeAndUsernameAndSep + basePassword + "%21" + urlSuffixAndSep},
142-
{char: "#", parses: true, expectedUsername: "", expectedPassword: "",
143-
encodedURL: schemeAndUsernameAndSep + basePassword + "#" + urlSuffixAndSep},
142+
{char: "#", parses: false},
144143
{char: "$", parses: true, expectedUsername: username, expectedPassword: basePassword + "$",
145144
encodedURL: schemeAndUsernameAndSep + basePassword + "$" + urlSuffixAndSep},
146145
{char: "%", parses: false},
@@ -158,16 +157,14 @@ func TestPasswordUnencodedReservedURLChars(t *testing.T) {
158157
encodedURL: schemeAndUsernameAndSep + basePassword + "+" + urlSuffixAndSep},
159158
{char: ",", parses: true, expectedUsername: username, expectedPassword: "password,",
160159
encodedURL: schemeAndUsernameAndSep + basePassword + "," + urlSuffixAndSep},
161-
{char: "/", parses: true, expectedUsername: "", expectedPassword: "",
162-
encodedURL: schemeAndUsernameAndSep + basePassword + "/" + urlSuffixAndSep},
160+
{char: "/", parses: false},
163161
{char: ":", parses: true, expectedUsername: username, expectedPassword: "password:",
164162
encodedURL: schemeAndUsernameAndSep + basePassword + "%3A" + urlSuffixAndSep},
165163
{char: ";", parses: true, expectedUsername: username, expectedPassword: "password;",
166164
encodedURL: schemeAndUsernameAndSep + basePassword + ";" + urlSuffixAndSep},
167165
{char: "=", parses: true, expectedUsername: username, expectedPassword: "password=",
168166
encodedURL: schemeAndUsernameAndSep + basePassword + "=" + urlSuffixAndSep},
169-
{char: "?", parses: true, expectedUsername: "", expectedPassword: "",
170-
encodedURL: schemeAndUsernameAndSep + basePassword + "?" + urlSuffixAndSep},
167+
{char: "?", parses: false},
171168
{char: "@", parses: true, expectedUsername: username, expectedPassword: "password@",
172169
encodedURL: schemeAndUsernameAndSep + basePassword + "%40" + urlSuffixAndSep},
173170
{char: "[", parses: false},

util.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,14 @@ func schemeFromURL(url string) (string, error) {
7474
return "", errEmptyURL
7575
}
7676

77-
u, err := nurl.Parse(url)
78-
if err != nil {
79-
return "", err
80-
}
81-
if len(u.Scheme) == 0 {
77+
i := strings.Index(url, ":")
78+
79+
// No : or : is the first character.
80+
if i < 1 {
8281
return "", errNoScheme
8382
}
8483

85-
return u.Scheme, nil
84+
return url[0:i], nil
8685
}
8786

8887
// FilterCustomQuery filters all query values starting with `x-`

util_test.go

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,34 @@ func TestSourceSchemeFromUrlFailure(t *testing.T) {
7474
}
7575

7676
func TestDatabaseSchemeFromUrlSuccess(t *testing.T) {
77-
urlStr := "protocol://path"
78-
expected := "protocol"
79-
80-
u, err := databaseSchemeFromURL(urlStr)
81-
if err != nil {
82-
t.Fatalf("expected no error, but received %q", err)
77+
cases := []struct {
78+
name string
79+
urlStr string
80+
expected string
81+
}{
82+
{
83+
name: "Simple",
84+
urlStr: "protocol://path",
85+
expected: "protocol",
86+
},
87+
{
88+
// See issue #264
89+
name: "MySQLWithPort",
90+
urlStr: "mysql://user:pass@tcp(host:1337)/db",
91+
expected: "mysql",
92+
},
8393
}
84-
if u != expected {
85-
t.Fatalf("expected %q, but received %q", expected, u)
94+
95+
for _, tc := range cases {
96+
t.Run(tc.name, func(t *testing.T) {
97+
u, err := databaseSchemeFromURL(tc.urlStr)
98+
if err != nil {
99+
t.Fatalf("expected no error, but received %q", err)
100+
}
101+
if u != tc.expected {
102+
t.Fatalf("expected %q, but received %q", tc.expected, u)
103+
}
104+
})
86105
}
87106
}
88107

0 commit comments

Comments
 (0)