dsn.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  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. "errors"
  12. "fmt"
  13. "net"
  14. "net/url"
  15. "strings"
  16. "time"
  17. )
  18. var (
  19. errInvalidDSNUnescaped = errors.New("invalid DSN: did you forget to escape a param value?")
  20. errInvalidDSNAddr = errors.New("invalid DSN: network address not terminated (missing closing brace)")
  21. errInvalidDSNNoSlash = errors.New("invalid DSN: missing the slash separating the database name")
  22. errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations")
  23. )
  24. // Config is a configuration parsed from a DSN string
  25. type Config struct {
  26. User string // Username
  27. Passwd string // Password
  28. Net string // Network type
  29. Addr string // Network address
  30. DBName string // Database name
  31. Params map[string]string // Connection parameters
  32. Loc *time.Location // Location for time.Time values
  33. TLS *tls.Config // TLS configuration
  34. Timeout time.Duration // Dial timeout
  35. ReadTimeout time.Duration // I/O read timeout
  36. WriteTimeout time.Duration // I/O write timeout
  37. Collation uint8 // Connection collation
  38. AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE
  39. AllowCleartextPasswords bool // Allows the cleartext client side plugin
  40. AllowOldPasswords bool // Allows the old insecure password method
  41. ClientFoundRows bool // Return number of matching rows instead of rows changed
  42. ColumnsWithAlias bool // Prepend table alias to column names
  43. InterpolateParams bool // Interpolate placeholders into query string
  44. ParseTime bool // Parse time values to time.Time
  45. Strict bool // Return warnings as errors
  46. }
  47. // ParseDSN parses the DSN string to a Config
  48. func ParseDSN(dsn string) (cfg *Config, err error) {
  49. // New config with some default values
  50. cfg = &Config{
  51. Loc: time.UTC,
  52. Collation: defaultCollation,
  53. }
  54. // [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
  55. // Find the last '/' (since the password or the net addr might contain a '/')
  56. foundSlash := false
  57. for i := len(dsn) - 1; i >= 0; i-- {
  58. if dsn[i] == '/' {
  59. foundSlash = true
  60. var j, k int
  61. // left part is empty if i <= 0
  62. if i > 0 {
  63. // [username[:password]@][protocol[(address)]]
  64. // Find the last '@' in dsn[:i]
  65. for j = i; j >= 0; j-- {
  66. if dsn[j] == '@' {
  67. // username[:password]
  68. // Find the first ':' in dsn[:j]
  69. for k = 0; k < j; k++ {
  70. if dsn[k] == ':' {
  71. cfg.Passwd = dsn[k+1 : j]
  72. break
  73. }
  74. }
  75. cfg.User = dsn[:k]
  76. break
  77. }
  78. }
  79. // [protocol[(address)]]
  80. // Find the first '(' in dsn[j+1:i]
  81. for k = j + 1; k < i; k++ {
  82. if dsn[k] == '(' {
  83. // dsn[i-1] must be == ')' if an address is specified
  84. if dsn[i-1] != ')' {
  85. if strings.ContainsRune(dsn[k+1:i], ')') {
  86. return nil, errInvalidDSNUnescaped
  87. }
  88. return nil, errInvalidDSNAddr
  89. }
  90. cfg.Addr = dsn[k+1 : i-1]
  91. break
  92. }
  93. }
  94. cfg.Net = dsn[j+1 : k]
  95. }
  96. // dbname[?param1=value1&...&paramN=valueN]
  97. // Find the first '?' in dsn[i+1:]
  98. for j = i + 1; j < len(dsn); j++ {
  99. if dsn[j] == '?' {
  100. if err = parseDSNParams(cfg, dsn[j+1:]); err != nil {
  101. return
  102. }
  103. break
  104. }
  105. }
  106. cfg.DBName = dsn[i+1 : j]
  107. break
  108. }
  109. }
  110. if !foundSlash && len(dsn) > 0 {
  111. return nil, errInvalidDSNNoSlash
  112. }
  113. if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
  114. return nil, errInvalidDSNUnsafeCollation
  115. }
  116. // Set default network if empty
  117. if cfg.Net == "" {
  118. cfg.Net = "tcp"
  119. }
  120. // Set default address if empty
  121. if cfg.Addr == "" {
  122. switch cfg.Net {
  123. case "tcp":
  124. cfg.Addr = "127.0.0.1:3306"
  125. case "unix":
  126. cfg.Addr = "/tmp/mysql.sock"
  127. default:
  128. return nil, errors.New("default addr for network '" + cfg.Net + "' unknown")
  129. }
  130. }
  131. return
  132. }
  133. // parseDSNParams parses the DSN "query string"
  134. // Values must be url.QueryEscape'ed
  135. func parseDSNParams(cfg *Config, params string) (err error) {
  136. for _, v := range strings.Split(params, "&") {
  137. param := strings.SplitN(v, "=", 2)
  138. if len(param) != 2 {
  139. continue
  140. }
  141. // cfg params
  142. switch value := param[1]; param[0] {
  143. // Disable INFILE whitelist / enable all files
  144. case "allowAllFiles":
  145. var isBool bool
  146. cfg.AllowAllFiles, isBool = readBool(value)
  147. if !isBool {
  148. return errors.New("invalid bool value: " + value)
  149. }
  150. // Use cleartext authentication mode (MySQL 5.5.10+)
  151. case "allowCleartextPasswords":
  152. var isBool bool
  153. cfg.AllowCleartextPasswords, isBool = readBool(value)
  154. if !isBool {
  155. return errors.New("invalid bool value: " + value)
  156. }
  157. // Use old authentication mode (pre MySQL 4.1)
  158. case "allowOldPasswords":
  159. var isBool bool
  160. cfg.AllowOldPasswords, isBool = readBool(value)
  161. if !isBool {
  162. return errors.New("invalid bool value: " + value)
  163. }
  164. // Switch "rowsAffected" mode
  165. case "clientFoundRows":
  166. var isBool bool
  167. cfg.ClientFoundRows, isBool = readBool(value)
  168. if !isBool {
  169. return errors.New("invalid bool value: " + value)
  170. }
  171. // Collation
  172. case "collation":
  173. collation, ok := collations[value]
  174. if !ok {
  175. // Note possibility for false negatives:
  176. // could be triggered although the collation is valid if the
  177. // collations map does not contain entries the server supports.
  178. err = errors.New("unknown collation")
  179. return
  180. }
  181. cfg.Collation = collation
  182. break
  183. case "columnsWithAlias":
  184. var isBool bool
  185. cfg.ColumnsWithAlias, isBool = readBool(value)
  186. if !isBool {
  187. return errors.New("invalid bool value: " + value)
  188. }
  189. // Compression
  190. case "compress":
  191. return errors.New("compression not implemented yet")
  192. // Enable client side placeholder substitution
  193. case "interpolateParams":
  194. var isBool bool
  195. cfg.InterpolateParams, isBool = readBool(value)
  196. if !isBool {
  197. return errors.New("invalid bool value: " + value)
  198. }
  199. // Time Location
  200. case "loc":
  201. if value, err = url.QueryUnescape(value); err != nil {
  202. return
  203. }
  204. cfg.Loc, err = time.LoadLocation(value)
  205. if err != nil {
  206. return
  207. }
  208. // time.Time parsing
  209. case "parseTime":
  210. var isBool bool
  211. cfg.ParseTime, isBool = readBool(value)
  212. if !isBool {
  213. return errors.New("invalid bool value: " + value)
  214. }
  215. // I/O read Timeout
  216. case "readTimeout":
  217. cfg.ReadTimeout, err = time.ParseDuration(value)
  218. if err != nil {
  219. return
  220. }
  221. // Strict mode
  222. case "strict":
  223. var isBool bool
  224. cfg.Strict, isBool = readBool(value)
  225. if !isBool {
  226. return errors.New("invalid bool value: " + value)
  227. }
  228. // Dial Timeout
  229. case "timeout":
  230. cfg.Timeout, err = time.ParseDuration(value)
  231. if err != nil {
  232. return
  233. }
  234. // TLS-Encryption
  235. case "tls":
  236. boolValue, isBool := readBool(value)
  237. if isBool {
  238. if boolValue {
  239. cfg.TLS = &tls.Config{}
  240. }
  241. } else if value, err := url.QueryUnescape(value); err != nil {
  242. return fmt.Errorf("invalid value for TLS config name: %v", err)
  243. } else {
  244. if strings.ToLower(value) == "skip-verify" {
  245. cfg.TLS = &tls.Config{InsecureSkipVerify: true}
  246. } else if tlsConfig, ok := tlsConfigRegister[value]; ok {
  247. if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
  248. host, _, err := net.SplitHostPort(cfg.Addr)
  249. if err == nil {
  250. tlsConfig.ServerName = host
  251. }
  252. }
  253. cfg.TLS = tlsConfig
  254. } else {
  255. return errors.New("invalid value / unknown config name: " + value)
  256. }
  257. }
  258. // I/O write Timeout
  259. case "writeTimeout":
  260. cfg.WriteTimeout, err = time.ParseDuration(value)
  261. if err != nil {
  262. return
  263. }
  264. default:
  265. // lazy init
  266. if cfg.Params == nil {
  267. cfg.Params = make(map[string]string)
  268. }
  269. if cfg.Params[param[0]], err = url.QueryUnescape(value); err != nil {
  270. return
  271. }
  272. }
  273. }
  274. return
  275. }