dsn.go 8.4 KB

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