123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 |
- // Go MySQL Driver - A MySQL-Driver for Go's database/sql package
- //
- // Copyright 2016 The Go-MySQL-Driver Authors. All rights reserved.
- //
- // This Source Code Form is subject to the terms of the Mozilla Public
- // License, v. 2.0. If a copy of the MPL was not distributed with this file,
- // You can obtain one at http://mozilla.org/MPL/2.0/.
- package mysql
- import (
- "crypto/tls"
- "fmt"
- "net/url"
- "reflect"
- "testing"
- "time"
- )
- var testDSNs = []struct {
- in string
- out *Config
- }{{
- "username:password@protocol(address)/dbname?param=value",
- &Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
- }, {
- "username:password@protocol(address)/dbname?param=value&columnsWithAlias=true",
- &Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, ColumnsWithAlias: true},
- }, {
- "username:password@protocol(address)/dbname?param=value&columnsWithAlias=true&multiStatements=true",
- &Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, ColumnsWithAlias: true, MultiStatements: true},
- }, {
- "user@unix(/path/to/socket)/dbname?charset=utf8",
- &Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
- }, {
- "user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true",
- &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "true"},
- }, {
- "user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify",
- &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "skip-verify"},
- }, {
- "user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci&maxAllowedPacket=16777216&tls=false&allowCleartextPasswords=true&parseTime=true&rejectReadOnly=true",
- &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, TLSConfig: "false", AllowCleartextPasswords: true, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, AllowAllFiles: true, AllowOldPasswords: true, ClientFoundRows: true, MaxAllowedPacket: 16777216, ParseTime: true, RejectReadOnly: true},
- }, {
- "user:password@/dbname?allowNativePasswords=false&maxAllowedPacket=0",
- &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, AllowNativePasswords: false},
- }, {
- "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: "utf8mb4_general_ci", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
- }, {
- "/dbname",
- &Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
- }, {
- "@/",
- &Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
- }, {
- "/",
- &Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
- }, {
- "",
- &Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
- }, {
- "user:p@/ssword@/",
- &Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
- }, {
- "unix/?arg=%2Fsome%2Fpath.ext",
- &Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
- }, {
- "tcp(127.0.0.1)/dbname",
- &Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
- }, {
- "tcp(de:ad:be:ef::ca:fe)/dbname",
- &Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
- },
- }
- func TestDSNParser(t *testing.T) {
- for i, tst := range testDSNs {
- cfg, err := ParseDSN(tst.in)
- if err != nil {
- t.Error(err.Error())
- }
- // pointer not static
- cfg.tls = nil
- if !reflect.DeepEqual(cfg, tst.out) {
- t.Errorf("%d. ParseDSN(%q) mismatch:\ngot %+v\nwant %+v", i, tst.in, cfg, tst.out)
- }
- }
- }
- func TestDSNParserInvalid(t *testing.T) {
- var invalidDSNs = []string{
- "@net(addr/", // no closing brace
- "@tcp(/", // no closing brace
- "tcp(/", // no closing brace
- "(/", // no closing brace
- "net(addr)//", // unescaped
- "User:pass@tcp(1.2.3.4:3306)", // no trailing slash
- "net()/", // unknown default addr
- //"/dbname?arg=/some/unescaped/path",
- }
- for i, tst := range invalidDSNs {
- if _, err := ParseDSN(tst); err == nil {
- t.Errorf("invalid DSN #%d. (%s) didn't error!", i, tst)
- }
- }
- }
- func TestDSNReformat(t *testing.T) {
- for i, tst := range testDSNs {
- dsn1 := tst.in
- cfg1, err := ParseDSN(dsn1)
- if err != nil {
- t.Error(err.Error())
- continue
- }
- cfg1.tls = nil // pointer not static
- res1 := fmt.Sprintf("%+v", cfg1)
- dsn2 := cfg1.FormatDSN()
- cfg2, err := ParseDSN(dsn2)
- if err != nil {
- t.Error(err.Error())
- continue
- }
- cfg2.tls = nil // pointer not static
- res2 := fmt.Sprintf("%+v", cfg2)
- if res1 != res2 {
- t.Errorf("%d. %q does not match %q", i, res2, res1)
- }
- }
- }
- func TestDSNServerPubKey(t *testing.T) {
- baseDSN := "User:password@tcp(localhost:5555)/dbname?serverPubKey="
- RegisterServerPubKey("testKey", testPubKeyRSA)
- defer DeregisterServerPubKey("testKey")
- tst := baseDSN + "testKey"
- cfg, err := ParseDSN(tst)
- if err != nil {
- t.Error(err.Error())
- }
- if cfg.ServerPubKey != "testKey" {
- t.Errorf("unexpected cfg.ServerPubKey value: %v", cfg.ServerPubKey)
- }
- if cfg.pubKey != testPubKeyRSA {
- t.Error("pub key pointer doesn't match")
- }
- // Key is missing
- tst = baseDSN + "invalid_name"
- cfg, err = ParseDSN(tst)
- if err == nil {
- t.Errorf("invalid name in DSN (%s) but did not error. Got config: %#v", tst, cfg)
- }
- }
- func TestDSNServerPubKeyQueryEscape(t *testing.T) {
- const name = "&%!:"
- dsn := "User:password@tcp(localhost:5555)/dbname?serverPubKey=" + url.QueryEscape(name)
- RegisterServerPubKey(name, testPubKeyRSA)
- defer DeregisterServerPubKey(name)
- cfg, err := ParseDSN(dsn)
- if err != nil {
- t.Error(err.Error())
- }
- if cfg.pubKey != testPubKeyRSA {
- t.Error("pub key pointer doesn't match")
- }
- }
- func TestDSNWithCustomTLS(t *testing.T) {
- baseDSN := "User:password@tcp(localhost:5555)/dbname?tls="
- tlsCfg := tls.Config{}
- RegisterTLSConfig("utils_test", &tlsCfg)
- defer DeregisterTLSConfig("utils_test")
- // Custom TLS is missing
- tst := baseDSN + "invalid_tls"
- cfg, err := ParseDSN(tst)
- if err == nil {
- t.Errorf("invalid custom TLS in DSN (%s) but did not error. Got config: %#v", tst, cfg)
- }
- tst = baseDSN + "utils_test"
- // Custom TLS with a server name
- name := "foohost"
- tlsCfg.ServerName = name
- cfg, err = ParseDSN(tst)
- if err != nil {
- t.Error(err.Error())
- } else if cfg.tls.ServerName != name {
- t.Errorf("did not get the correct TLS ServerName (%s) parsing DSN (%s).", name, tst)
- }
- // Custom TLS without a server name
- name = "localhost"
- tlsCfg.ServerName = ""
- cfg, err = ParseDSN(tst)
- if err != nil {
- t.Error(err.Error())
- } else if cfg.tls.ServerName != name {
- t.Errorf("did not get the correct ServerName (%s) parsing DSN (%s).", name, tst)
- } else if tlsCfg.ServerName != "" {
- t.Errorf("tlsCfg was mutated ServerName (%s) should be empty parsing DSN (%s).", name, tst)
- }
- }
- func TestDSNTLSConfig(t *testing.T) {
- expectedServerName := "example.com"
- dsn := "tcp(example.com:1234)/?tls=true"
- cfg, err := ParseDSN(dsn)
- if err != nil {
- t.Error(err.Error())
- }
- if cfg.tls == nil {
- t.Error("cfg.tls should not be nil")
- }
- if cfg.tls.ServerName != expectedServerName {
- t.Errorf("cfg.tls.ServerName should be %q, got %q (host with port)", expectedServerName, cfg.tls.ServerName)
- }
- dsn = "tcp(example.com)/?tls=true"
- cfg, err = ParseDSN(dsn)
- if err != nil {
- t.Error(err.Error())
- }
- if cfg.tls == nil {
- t.Error("cfg.tls should not be nil")
- }
- if cfg.tls.ServerName != expectedServerName {
- t.Errorf("cfg.tls.ServerName should be %q, got %q (host without port)", expectedServerName, cfg.tls.ServerName)
- }
- }
- func TestDSNWithCustomTLSQueryEscape(t *testing.T) {
- const configKey = "&%!:"
- dsn := "User:password@tcp(localhost:5555)/dbname?tls=" + url.QueryEscape(configKey)
- name := "foohost"
- tlsCfg := tls.Config{ServerName: name}
- RegisterTLSConfig(configKey, &tlsCfg)
- defer DeregisterTLSConfig(configKey)
- cfg, err := ParseDSN(dsn)
- if err != nil {
- t.Error(err.Error())
- } else if cfg.tls.ServerName != name {
- t.Errorf("did not get the correct TLS ServerName (%s) parsing DSN (%s).", name, dsn)
- }
- }
- func TestDSNUnsafeCollation(t *testing.T) {
- _, err := ParseDSN("/dbname?collation=gbk_chinese_ci&interpolateParams=true")
- if err != errInvalidDSNUnsafeCollation {
- t.Errorf("expected %v, got %v", errInvalidDSNUnsafeCollation, err)
- }
- _, err = ParseDSN("/dbname?collation=gbk_chinese_ci&interpolateParams=false")
- if err != nil {
- t.Errorf("expected %v, got %v", nil, err)
- }
- _, err = ParseDSN("/dbname?collation=gbk_chinese_ci")
- if err != nil {
- t.Errorf("expected %v, got %v", nil, err)
- }
- _, err = ParseDSN("/dbname?collation=ascii_bin&interpolateParams=true")
- if err != nil {
- t.Errorf("expected %v, got %v", nil, err)
- }
- _, err = ParseDSN("/dbname?collation=latin1_german1_ci&interpolateParams=true")
- if err != nil {
- t.Errorf("expected %v, got %v", nil, err)
- }
- _, err = ParseDSN("/dbname?collation=utf8_general_ci&interpolateParams=true")
- if err != nil {
- t.Errorf("expected %v, got %v", nil, err)
- }
- _, err = ParseDSN("/dbname?collation=utf8mb4_general_ci&interpolateParams=true")
- if err != nil {
- t.Errorf("expected %v, got %v", nil, err)
- }
- }
- func TestParamsAreSorted(t *testing.T) {
- expected := "/dbname?interpolateParams=true&foobar=baz&quux=loo"
- cfg := NewConfig()
- cfg.DBName = "dbname"
- cfg.InterpolateParams = true
- cfg.Params = map[string]string{
- "quux": "loo",
- "foobar": "baz",
- }
- actual := cfg.FormatDSN()
- if actual != expected {
- t.Errorf("generic Config.Params were not sorted: want %#v, got %#v", expected, actual)
- }
- }
- func TestCloneConfig(t *testing.T) {
- RegisterServerPubKey("testKey", testPubKeyRSA)
- defer DeregisterServerPubKey("testKey")
- expectedServerName := "example.com"
- dsn := "tcp(example.com:1234)/?tls=true&foobar=baz&serverPubKey=testKey"
- cfg, err := ParseDSN(dsn)
- if err != nil {
- t.Fatal(err.Error())
- }
- cfg2 := cfg.Clone()
- if cfg == cfg2 {
- t.Errorf("Config.Clone did not create a separate config struct")
- }
- if cfg2.tls.ServerName != expectedServerName {
- t.Errorf("cfg.tls.ServerName should be %q, got %q (host with port)", expectedServerName, cfg.tls.ServerName)
- }
- cfg2.tls.ServerName = "example2.com"
- if cfg.tls.ServerName == cfg2.tls.ServerName {
- t.Errorf("changed cfg.tls.Server name should not propagate to original Config")
- }
- if _, ok := cfg2.Params["foobar"]; !ok {
- t.Errorf("cloned Config is missing custom params")
- }
- delete(cfg2.Params, "foobar")
- if _, ok := cfg.Params["foobar"]; !ok {
- t.Errorf("custom params in cloned Config should not propagate to original Config")
- }
- if !reflect.DeepEqual(cfg.pubKey, cfg2.pubKey) {
- t.Errorf("public key in Config should be identical")
- }
- }
- func TestNormalizeTLSConfig(t *testing.T) {
- tt := []struct {
- tlsConfig string
- want *tls.Config
- }{
- {"", nil},
- {"false", nil},
- {"true", &tls.Config{ServerName: "myserver"}},
- {"skip-verify", &tls.Config{InsecureSkipVerify: true}},
- {"preferred", &tls.Config{InsecureSkipVerify: true}},
- {"test_tls_config", &tls.Config{ServerName: "myServerName"}},
- }
- RegisterTLSConfig("test_tls_config", &tls.Config{ServerName: "myServerName"})
- defer func() { DeregisterTLSConfig("test_tls_config") }()
- for _, tc := range tt {
- t.Run(tc.tlsConfig, func(t *testing.T) {
- cfg := &Config{
- Addr: "myserver:3306",
- TLSConfig: tc.tlsConfig,
- }
- cfg.normalize()
- if cfg.tls == nil {
- if tc.want != nil {
- t.Fatal("wanted a tls config but got nil instead")
- }
- return
- }
- if cfg.tls.ServerName != tc.want.ServerName {
- t.Errorf("tls.ServerName doesn't match (want: '%s', got: '%s')",
- tc.want.ServerName, cfg.tls.ServerName)
- }
- if cfg.tls.InsecureSkipVerify != tc.want.InsecureSkipVerify {
- t.Errorf("tls.InsecureSkipVerify doesn't match (want: %T, got :%T)",
- tc.want.InsecureSkipVerify, cfg.tls.InsecureSkipVerify)
- }
- })
- }
- }
- func BenchmarkParseDSN(b *testing.B) {
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- for _, tst := range testDSNs {
- if _, err := ParseDSN(tst.in); err != nil {
- b.Error(err.Error())
- }
- }
- }
- }
|