dsn_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. // Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2. //
  3. // Copyright 2016 The Go-MySQL-Driver Authors. All rights reserved.
  4. //
  5. // This Source Code Form is subject to the terms of the Mozilla Public
  6. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7. // You can obtain one at http://mozilla.org/MPL/2.0/.
  8. package mysql
  9. import (
  10. "crypto/tls"
  11. "fmt"
  12. "net/url"
  13. "reflect"
  14. "testing"
  15. "time"
  16. )
  17. var testDSNs = []struct {
  18. in string
  19. out *Config
  20. }{{
  21. "username:password@protocol(address)/dbname?param=value",
  22. &Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
  23. }, {
  24. "username:password@protocol(address)/dbname?param=value&columnsWithAlias=true",
  25. &Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, ColumnsWithAlias: true},
  26. }, {
  27. "username:password@protocol(address)/dbname?param=value&columnsWithAlias=true&multiStatements=true",
  28. &Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, ColumnsWithAlias: true, MultiStatements: true},
  29. }, {
  30. "user@unix(/path/to/socket)/dbname?charset=utf8",
  31. &Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
  32. }, {
  33. "user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true",
  34. &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "true"},
  35. }, {
  36. "user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify",
  37. &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "skip-verify"},
  38. }, {
  39. "user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci&maxAllowedPacket=16777216",
  40. &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, AllowAllFiles: true, AllowOldPasswords: true, ClientFoundRows: true, MaxAllowedPacket: 16777216},
  41. }, {
  42. "user:password@/dbname?allowNativePasswords=false&maxAllowedPacket=0",
  43. &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, AllowNativePasswords: false},
  44. }, {
  45. "user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local",
  46. &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},
  47. }, {
  48. "/dbname",
  49. &Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
  50. }, {
  51. "@/",
  52. &Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
  53. }, {
  54. "/",
  55. &Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
  56. }, {
  57. "",
  58. &Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
  59. }, {
  60. "user:p@/ssword@/",
  61. &Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
  62. }, {
  63. "unix/?arg=%2Fsome%2Fpath.ext",
  64. &Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
  65. }, {
  66. "tcp(127.0.0.1)/dbname",
  67. &Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
  68. }, {
  69. "tcp(de:ad:be:ef::ca:fe)/dbname",
  70. &Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
  71. },
  72. }
  73. func TestDSNParser(t *testing.T) {
  74. for i, tst := range testDSNs {
  75. cfg, err := ParseDSN(tst.in)
  76. if err != nil {
  77. t.Error(err.Error())
  78. }
  79. // pointer not static
  80. cfg.tls = nil
  81. if !reflect.DeepEqual(cfg, tst.out) {
  82. t.Errorf("%d. ParseDSN(%q) mismatch:\ngot %+v\nwant %+v", i, tst.in, cfg, tst.out)
  83. }
  84. }
  85. }
  86. func TestDSNParserInvalid(t *testing.T) {
  87. var invalidDSNs = []string{
  88. "@net(addr/", // no closing brace
  89. "@tcp(/", // no closing brace
  90. "tcp(/", // no closing brace
  91. "(/", // no closing brace
  92. "net(addr)//", // unescaped
  93. "User:pass@tcp(1.2.3.4:3306)", // no trailing slash
  94. "net()/", // unknown default addr
  95. //"/dbname?arg=/some/unescaped/path",
  96. }
  97. for i, tst := range invalidDSNs {
  98. if _, err := ParseDSN(tst); err == nil {
  99. t.Errorf("invalid DSN #%d. (%s) didn't error!", i, tst)
  100. }
  101. }
  102. }
  103. func TestDSNReformat(t *testing.T) {
  104. for i, tst := range testDSNs {
  105. dsn1 := tst.in
  106. cfg1, err := ParseDSN(dsn1)
  107. if err != nil {
  108. t.Error(err.Error())
  109. continue
  110. }
  111. cfg1.tls = nil // pointer not static
  112. res1 := fmt.Sprintf("%+v", cfg1)
  113. dsn2 := cfg1.FormatDSN()
  114. cfg2, err := ParseDSN(dsn2)
  115. if err != nil {
  116. t.Error(err.Error())
  117. continue
  118. }
  119. cfg2.tls = nil // pointer not static
  120. res2 := fmt.Sprintf("%+v", cfg2)
  121. if res1 != res2 {
  122. t.Errorf("%d. %q does not match %q", i, res2, res1)
  123. }
  124. }
  125. }
  126. func TestDSNServerPubKey(t *testing.T) {
  127. baseDSN := "User:password@tcp(localhost:5555)/dbname?serverPubKey="
  128. RegisterServerPubKey("testKey", testPubKeyRSA)
  129. defer DeregisterServerPubKey("testKey")
  130. tst := baseDSN + "testKey"
  131. cfg, err := ParseDSN(tst)
  132. if err != nil {
  133. t.Error(err.Error())
  134. }
  135. if cfg.ServerPubKey != "testKey" {
  136. t.Errorf("unexpected cfg.ServerPubKey value: %v", cfg.ServerPubKey)
  137. }
  138. if cfg.pubKey != testPubKeyRSA {
  139. t.Error("pub key pointer doesn't match")
  140. }
  141. // Key is missing
  142. tst = baseDSN + "invalid_name"
  143. cfg, err = ParseDSN(tst)
  144. if err == nil {
  145. t.Errorf("invalid name in DSN (%s) but did not error. Got config: %#v", tst, cfg)
  146. }
  147. }
  148. func TestDSNServerPubKeyQueryEscape(t *testing.T) {
  149. const name = "&%!:"
  150. dsn := "User:password@tcp(localhost:5555)/dbname?serverPubKey=" + url.QueryEscape(name)
  151. RegisterServerPubKey(name, testPubKeyRSA)
  152. defer DeregisterServerPubKey(name)
  153. cfg, err := ParseDSN(dsn)
  154. if err != nil {
  155. t.Error(err.Error())
  156. }
  157. if cfg.pubKey != testPubKeyRSA {
  158. t.Error("pub key pointer doesn't match")
  159. }
  160. }
  161. func TestDSNWithCustomTLS(t *testing.T) {
  162. baseDSN := "User:password@tcp(localhost:5555)/dbname?tls="
  163. tlsCfg := tls.Config{}
  164. RegisterTLSConfig("utils_test", &tlsCfg)
  165. defer DeregisterTLSConfig("utils_test")
  166. // Custom TLS is missing
  167. tst := baseDSN + "invalid_tls"
  168. cfg, err := ParseDSN(tst)
  169. if err == nil {
  170. t.Errorf("invalid custom TLS in DSN (%s) but did not error. Got config: %#v", tst, cfg)
  171. }
  172. tst = baseDSN + "utils_test"
  173. // Custom TLS with a server name
  174. name := "foohost"
  175. tlsCfg.ServerName = name
  176. cfg, err = ParseDSN(tst)
  177. if err != nil {
  178. t.Error(err.Error())
  179. } else if cfg.tls.ServerName != name {
  180. t.Errorf("did not get the correct TLS ServerName (%s) parsing DSN (%s).", name, tst)
  181. }
  182. // Custom TLS without a server name
  183. name = "localhost"
  184. tlsCfg.ServerName = ""
  185. cfg, err = ParseDSN(tst)
  186. if err != nil {
  187. t.Error(err.Error())
  188. } else if cfg.tls.ServerName != name {
  189. t.Errorf("did not get the correct ServerName (%s) parsing DSN (%s).", name, tst)
  190. } else if tlsCfg.ServerName != "" {
  191. t.Errorf("tlsCfg was mutated ServerName (%s) should be empty parsing DSN (%s).", name, tst)
  192. }
  193. }
  194. func TestDSNTLSConfig(t *testing.T) {
  195. expectedServerName := "example.com"
  196. dsn := "tcp(example.com:1234)/?tls=true"
  197. cfg, err := ParseDSN(dsn)
  198. if err != nil {
  199. t.Error(err.Error())
  200. }
  201. if cfg.tls == nil {
  202. t.Error("cfg.tls should not be nil")
  203. }
  204. if cfg.tls.ServerName != expectedServerName {
  205. t.Errorf("cfg.tls.ServerName should be %q, got %q (host with port)", expectedServerName, cfg.tls.ServerName)
  206. }
  207. dsn = "tcp(example.com)/?tls=true"
  208. cfg, err = ParseDSN(dsn)
  209. if err != nil {
  210. t.Error(err.Error())
  211. }
  212. if cfg.tls == nil {
  213. t.Error("cfg.tls should not be nil")
  214. }
  215. if cfg.tls.ServerName != expectedServerName {
  216. t.Errorf("cfg.tls.ServerName should be %q, got %q (host without port)", expectedServerName, cfg.tls.ServerName)
  217. }
  218. }
  219. func TestDSNWithCustomTLSQueryEscape(t *testing.T) {
  220. const configKey = "&%!:"
  221. dsn := "User:password@tcp(localhost:5555)/dbname?tls=" + url.QueryEscape(configKey)
  222. name := "foohost"
  223. tlsCfg := tls.Config{ServerName: name}
  224. RegisterTLSConfig(configKey, &tlsCfg)
  225. defer DeregisterTLSConfig(configKey)
  226. cfg, err := ParseDSN(dsn)
  227. if err != nil {
  228. t.Error(err.Error())
  229. } else if cfg.tls.ServerName != name {
  230. t.Errorf("did not get the correct TLS ServerName (%s) parsing DSN (%s).", name, dsn)
  231. }
  232. }
  233. func TestDSNUnsafeCollation(t *testing.T) {
  234. _, err := ParseDSN("/dbname?collation=gbk_chinese_ci&interpolateParams=true")
  235. if err != errInvalidDSNUnsafeCollation {
  236. t.Errorf("expected %v, got %v", errInvalidDSNUnsafeCollation, err)
  237. }
  238. _, err = ParseDSN("/dbname?collation=gbk_chinese_ci&interpolateParams=false")
  239. if err != nil {
  240. t.Errorf("expected %v, got %v", nil, err)
  241. }
  242. _, err = ParseDSN("/dbname?collation=gbk_chinese_ci")
  243. if err != nil {
  244. t.Errorf("expected %v, got %v", nil, err)
  245. }
  246. _, err = ParseDSN("/dbname?collation=ascii_bin&interpolateParams=true")
  247. if err != nil {
  248. t.Errorf("expected %v, got %v", nil, err)
  249. }
  250. _, err = ParseDSN("/dbname?collation=latin1_german1_ci&interpolateParams=true")
  251. if err != nil {
  252. t.Errorf("expected %v, got %v", nil, err)
  253. }
  254. _, err = ParseDSN("/dbname?collation=utf8_general_ci&interpolateParams=true")
  255. if err != nil {
  256. t.Errorf("expected %v, got %v", nil, err)
  257. }
  258. _, err = ParseDSN("/dbname?collation=utf8mb4_general_ci&interpolateParams=true")
  259. if err != nil {
  260. t.Errorf("expected %v, got %v", nil, err)
  261. }
  262. }
  263. func TestParamsAreSorted(t *testing.T) {
  264. expected := "/dbname?interpolateParams=true&foobar=baz&quux=loo"
  265. cfg := NewConfig()
  266. cfg.DBName = "dbname"
  267. cfg.InterpolateParams = true
  268. cfg.Params = map[string]string{
  269. "quux": "loo",
  270. "foobar": "baz",
  271. }
  272. actual := cfg.FormatDSN()
  273. if actual != expected {
  274. t.Errorf("generic Config.Params were not sorted: want %#v, got %#v", expected, actual)
  275. }
  276. }
  277. func BenchmarkParseDSN(b *testing.B) {
  278. b.ReportAllocs()
  279. for i := 0; i < b.N; i++ {
  280. for _, tst := range testDSNs {
  281. if _, err := ParseDSN(tst.in); err != nil {
  282. b.Error(err.Error())
  283. }
  284. }
  285. }
  286. }