Skip to content

Commit 7503ab8

Browse files
committed
Escape DSN param values
Not really a behavior change, since it was mostly broken before
1 parent dc02949 commit 7503ab8

File tree

4 files changed

+32
-12
lines changed

4 files changed

+32
-12
lines changed

README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ Possible Parameters are:
110110
* `allowOldPasswords`: `allowAllFiles=true` allows the usage of the insecure old password method. This should be avoided, but is necessary in some cases. See also [the old_passwords wiki page](https://github.com/go-sql-driver/mysql/wiki/old_passwords).
111111
* `charset`: Sets the charset used for client-server interaction ("SET NAMES `value`"). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset failes. This enables support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`).
112112
* `clientFoundRows`: `clientFoundRows=true` causes an UPDATE to return the number of matching rows instead of the number of rows changed.
113-
* `loc`: Sets the location for time.Time values (when using `parseTime=true`). The default is `UTC`. *"Local"* sets the system's location. See [time.LoadLocation](http://golang.org/pkg/time/#LoadLocation) for details.
113+
* `loc`: Sets the location for time.Time values (when using `parseTime=true`). The default is `UTC`. *"Local"* sets the system's location. See [time.LoadLocation](http://golang.org/pkg/time/#LoadLocation) for details. Please keep in mind, that param values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `US%2FPacific`.
114114
* `parseTime`: `parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
115115
* `strict`: Enable strict mode. MySQL warnings are treated as errors.
116116
* `timeout`: **Driver** side connection timeout. The value must be a string of decimal numbers, each with optional fraction and a unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. To set a server side timeout, use the parameter [`wait_timeout`](http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_wait_timeout).
@@ -122,6 +122,8 @@ All other parameters are interpreted as system variables:
122122
* `tx_isolation`: *"SET [tx_isolation](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation)=`value`"*
123123
* `param`: *"SET `param`=`value`"*
124124

125+
***The values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!***
126+
125127
#### Examples
126128
```
127129
user@unix(/path/to/socket)/dbname
@@ -132,7 +134,7 @@ user:password@tcp(localhost:5555)/dbname?autocommit=true
132134
```
133135

134136
```
135-
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?tls=skip-verify&charset=utf8mb4,utf8
137+
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?tls=skip-verify&charset=utf8mb4,utf8&sys_var=withSlash%2FandAt%40
136138
```
137139

138140
```

driver_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"io"
1616
"io/ioutil"
1717
"net"
18+
"net/url"
1819
"os"
1920
"strings"
2021
"testing"
@@ -206,7 +207,7 @@ func TestTimezoneConversion(t *testing.T) {
206207
}
207208

208209
for _, tz := range zones {
209-
runTests(t, dsn+"&parseTime=true&loc="+tz, tzTest)
210+
runTests(t, dsn+"&parseTime=true&loc="+url.QueryEscape(tz), tzTest)
210211
}
211212
}
212213

utils.go

+24-8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"fmt"
1818
"io"
1919
"log"
20+
"net/url"
2021
"os"
2122
"strings"
2223
"time"
@@ -26,7 +27,8 @@ var (
2627
errLog *log.Logger // Error Logger
2728
tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs
2829

29-
errInvalidDSN = errors.New("Invalid DSN")
30+
errInvalidDSNUnescaped = errors.New("Invalid DSN: Did you forget to escape a param value?")
31+
errInvalidDSNAddr = errors.New("Invalid DSN: Network Address not terminated (missing closing brace)")
3032
)
3133

3234
func init() {
@@ -71,13 +73,14 @@ func DeregisterTLSConfig(key string) {
7173
delete(tlsConfigRegister, key)
7274
}
7375

76+
// parseDSN parses the DSN string to a config
7477
func parseDSN(dsn string) (cfg *config, err error) {
7578
cfg = new(config)
7679

7780
// TODO: use strings.IndexByte when we can depend on Go 1.2
7881

7982
// [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
80-
// Find the last '/'
83+
// Find the last '/' (since the password might contain a '/')
8184
for i := len(dsn) - 1; i >= 0; i-- {
8285
if dsn[i] == '/' {
8386
var j int
@@ -105,7 +108,10 @@ func parseDSN(dsn string) (cfg *config, err error) {
105108
if dsn[k] == '(' {
106109
// dsn[i-1] must be == ')' if an adress is specified
107110
if dsn[i-1] != ')' {
108-
return nil, errInvalidDSN
111+
if strings.ContainsRune(dsn[k+1:i], ')') {
112+
return nil, errInvalidDSNUnescaped
113+
}
114+
return nil, errInvalidDSNAddr
109115
}
110116
cfg.addr = dsn[k+1 : i-1]
111117
break
@@ -119,7 +125,7 @@ func parseDSN(dsn string) (cfg *config, err error) {
119125

120126
// non-empty left part must contain an '@'
121127
if j < 0 {
122-
return nil, errInvalidDSN
128+
return nil, errInvalidDSNUnescaped
123129
}
124130
}
125131

@@ -157,17 +163,17 @@ func parseDSN(dsn string) (cfg *config, err error) {
157163

158164
}
159165

160-
// Set default location if not set
166+
// Set default location if empty
161167
if cfg.loc == nil {
162168
cfg.loc = time.UTC
163169
}
164170

165171
return
166172
}
167173

174+
// parseDSNParams parses the DSN "query string"
175+
// Values must be url.QueryEscape'ed
168176
func parseDSNParams(cfg *config, params string) (err error) {
169-
cfg.params = make(map[string]string)
170-
171177
for _, v := range strings.Split(params, "&") {
172178
param := strings.SplitN(v, "=", 2)
173179
if len(param) != 2 {
@@ -203,6 +209,9 @@ func parseDSNParams(cfg *config, params string) (err error) {
203209

204210
// Time Location
205211
case "loc":
212+
if value, err = url.QueryUnescape(value); err != nil {
213+
return
214+
}
206215
cfg.loc, err = time.LoadLocation(value)
207216
if err != nil {
208217
return
@@ -233,7 +242,14 @@ func parseDSNParams(cfg *config, params string) (err error) {
233242
}
234243

235244
default:
236-
cfg.params[param[0]] = value
245+
// lazy init
246+
if cfg.params == nil {
247+
cfg.params = make(map[string]string)
248+
}
249+
250+
if cfg.params[param[0]], err = url.QueryUnescape(value); err != nil {
251+
return
252+
}
237253
}
238254
}
239255

utils_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ var testDSNs = []struct {
3030
{"/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p timeout:0 tls:<nil> allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
3131
{"", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p timeout:0 tls:<nil> allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
3232
{"user:p@/ssword@/", "&{user:user passwd:p@/ssword net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p timeout:0 tls:<nil> allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
33-
{"@unix/", "&{user: passwd: net:unix addr:/tmp/mysql.sock dbname: params:map[] loc:%p timeout:0 tls:<nil> allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
33+
{"@unix/?arg=%2Fsome%2Fpath.ext", "&{user: passwd: net:unix addr:/tmp/mysql.sock dbname: params:map[arg:/some/path.ext] loc:%p timeout:0 tls:<nil> allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
3434
}
3535

3636
func TestDSNParser(t *testing.T) {
@@ -57,6 +57,7 @@ func TestDSNParser(t *testing.T) {
5757
func TestDSNParserInvalid(t *testing.T) {
5858
var invalidDSNs = []string{
5959
"asdf/dbname",
60+
"@net(addr/",
6061
//"/dbname?arg=/some/unescaped/path",
6162
}
6263

0 commit comments

Comments
 (0)