github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/go-sql-driver/mysql/dsn.go (about) 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 9 package mysql 10 11 import ( 12 "bytes" 13 "crypto/tls" 14 "errors" 15 "fmt" 16 "net" 17 "net/url" 18 "strings" 19 "time" 20 ) 21 22 var ( 23 errInvalidDSNUnescaped = errors.New("invalid DSN: did you forget to escape a param value?") 24 errInvalidDSNAddr = errors.New("invalid DSN: network address not terminated (missing closing brace)") 25 errInvalidDSNNoSlash = errors.New("invalid DSN: missing the slash separating the database name") 26 errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations") 27 ) 28 29 // Config is a configuration parsed from a DSN string 30 type Config struct { 31 User string // Username 32 Passwd string // Password (requires User) 33 Net string // Network type 34 Addr string // Network address (requires Net) 35 DBName string // Database name 36 Params map[string]string // Connection parameters 37 Collation string // Connection collation 38 Loc *time.Location // Location for time.Time values 39 TLSConfig string // TLS configuration name 40 tls *tls.Config // TLS configuration 41 Timeout time.Duration // Dial timeout 42 ReadTimeout time.Duration // I/O read timeout 43 WriteTimeout time.Duration // I/O write timeout 44 45 AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE 46 AllowCleartextPasswords bool // Allows the cleartext client side plugin 47 AllowOldPasswords bool // Allows the old insecure password method 48 ClientFoundRows bool // Return number of matching rows instead of rows changed 49 ColumnsWithAlias bool // Prepend table alias to column names 50 InterpolateParams bool // Interpolate placeholders into query string 51 MultiStatements bool // Allow multiple statements in one query 52 ParseTime bool // Parse time values to time.Time 53 Strict bool // Return warnings as errors 54 } 55 56 // FormatDSN formats the given Config into a DSN string which can be passed to 57 // the driver. 58 func (cfg *Config) FormatDSN() string { 59 var buf bytes.Buffer 60 61 // [username[:password]@] 62 if len(cfg.User) > 0 { 63 buf.WriteString(cfg.User) 64 if len(cfg.Passwd) > 0 { 65 buf.WriteByte(':') 66 buf.WriteString(cfg.Passwd) 67 } 68 buf.WriteByte('@') 69 } 70 71 // [protocol[(address)]] 72 if len(cfg.Net) > 0 { 73 buf.WriteString(cfg.Net) 74 if len(cfg.Addr) > 0 { 75 buf.WriteByte('(') 76 buf.WriteString(cfg.Addr) 77 buf.WriteByte(')') 78 } 79 } 80 81 // /dbname 82 buf.WriteByte('/') 83 buf.WriteString(cfg.DBName) 84 85 // [?param1=value1&...¶mN=valueN] 86 hasParam := false 87 88 if cfg.AllowAllFiles { 89 hasParam = true 90 buf.WriteString("?allowAllFiles=true") 91 } 92 93 if cfg.AllowCleartextPasswords { 94 if hasParam { 95 buf.WriteString("&allowCleartextPasswords=true") 96 } else { 97 hasParam = true 98 buf.WriteString("?allowCleartextPasswords=true") 99 } 100 } 101 102 if cfg.AllowOldPasswords { 103 if hasParam { 104 buf.WriteString("&allowOldPasswords=true") 105 } else { 106 hasParam = true 107 buf.WriteString("?allowOldPasswords=true") 108 } 109 } 110 111 if cfg.ClientFoundRows { 112 if hasParam { 113 buf.WriteString("&clientFoundRows=true") 114 } else { 115 hasParam = true 116 buf.WriteString("?clientFoundRows=true") 117 } 118 } 119 120 if col := cfg.Collation; col != defaultCollation && len(col) > 0 { 121 if hasParam { 122 buf.WriteString("&collation=") 123 } else { 124 hasParam = true 125 buf.WriteString("?collation=") 126 } 127 buf.WriteString(col) 128 } 129 130 if cfg.ColumnsWithAlias { 131 if hasParam { 132 buf.WriteString("&columnsWithAlias=true") 133 } else { 134 hasParam = true 135 buf.WriteString("?columnsWithAlias=true") 136 } 137 } 138 139 if cfg.InterpolateParams { 140 if hasParam { 141 buf.WriteString("&interpolateParams=true") 142 } else { 143 hasParam = true 144 buf.WriteString("?interpolateParams=true") 145 } 146 } 147 148 if cfg.Loc != time.UTC && cfg.Loc != nil { 149 if hasParam { 150 buf.WriteString("&loc=") 151 } else { 152 hasParam = true 153 buf.WriteString("?loc=") 154 } 155 buf.WriteString(url.QueryEscape(cfg.Loc.String())) 156 } 157 158 if cfg.MultiStatements { 159 if hasParam { 160 buf.WriteString("&multiStatements=true") 161 } else { 162 hasParam = true 163 buf.WriteString("?multiStatements=true") 164 } 165 } 166 167 if cfg.ParseTime { 168 if hasParam { 169 buf.WriteString("&parseTime=true") 170 } else { 171 hasParam = true 172 buf.WriteString("?parseTime=true") 173 } 174 } 175 176 if cfg.ReadTimeout > 0 { 177 if hasParam { 178 buf.WriteString("&readTimeout=") 179 } else { 180 hasParam = true 181 buf.WriteString("?readTimeout=") 182 } 183 buf.WriteString(cfg.ReadTimeout.String()) 184 } 185 186 if cfg.Strict { 187 if hasParam { 188 buf.WriteString("&strict=true") 189 } else { 190 hasParam = true 191 buf.WriteString("?strict=true") 192 } 193 } 194 195 if cfg.Timeout > 0 { 196 if hasParam { 197 buf.WriteString("&timeout=") 198 } else { 199 hasParam = true 200 buf.WriteString("?timeout=") 201 } 202 buf.WriteString(cfg.Timeout.String()) 203 } 204 205 if len(cfg.TLSConfig) > 0 { 206 if hasParam { 207 buf.WriteString("&tls=") 208 } else { 209 hasParam = true 210 buf.WriteString("?tls=") 211 } 212 buf.WriteString(url.QueryEscape(cfg.TLSConfig)) 213 } 214 215 if cfg.WriteTimeout > 0 { 216 if hasParam { 217 buf.WriteString("&writeTimeout=") 218 } else { 219 hasParam = true 220 buf.WriteString("?writeTimeout=") 221 } 222 buf.WriteString(cfg.WriteTimeout.String()) 223 } 224 225 // other params 226 if cfg.Params != nil { 227 for param, value := range cfg.Params { 228 if hasParam { 229 buf.WriteByte('&') 230 } else { 231 hasParam = true 232 buf.WriteByte('?') 233 } 234 235 buf.WriteString(param) 236 buf.WriteByte('=') 237 buf.WriteString(url.QueryEscape(value)) 238 } 239 } 240 241 return buf.String() 242 } 243 244 // ParseDSN parses the DSN string to a Config 245 func ParseDSN(dsn string) (cfg *Config, err error) { 246 // New config with some default values 247 cfg = &Config{ 248 Loc: time.UTC, 249 Collation: defaultCollation, 250 } 251 252 // [user[:password]@][net[(addr)]]/dbname[?param1=value1¶mN=valueN] 253 // Find the last '/' (since the password or the net addr might contain a '/') 254 foundSlash := false 255 for i := len(dsn) - 1; i >= 0; i-- { 256 if dsn[i] == '/' { 257 foundSlash = true 258 var j, k int 259 260 // left part is empty if i <= 0 261 if i > 0 { 262 // [username[:password]@][protocol[(address)]] 263 // Find the last '@' in dsn[:i] 264 for j = i; j >= 0; j-- { 265 if dsn[j] == '@' { 266 // username[:password] 267 // Find the first ':' in dsn[:j] 268 for k = 0; k < j; k++ { 269 if dsn[k] == ':' { 270 cfg.Passwd = dsn[k+1 : j] 271 break 272 } 273 } 274 cfg.User = dsn[:k] 275 276 break 277 } 278 } 279 280 // [protocol[(address)]] 281 // Find the first '(' in dsn[j+1:i] 282 for k = j + 1; k < i; k++ { 283 if dsn[k] == '(' { 284 // dsn[i-1] must be == ')' if an address is specified 285 if dsn[i-1] != ')' { 286 if strings.ContainsRune(dsn[k+1:i], ')') { 287 return nil, errInvalidDSNUnescaped 288 } 289 return nil, errInvalidDSNAddr 290 } 291 cfg.Addr = dsn[k+1 : i-1] 292 break 293 } 294 } 295 cfg.Net = dsn[j+1 : k] 296 } 297 298 // dbname[?param1=value1&...¶mN=valueN] 299 // Find the first '?' in dsn[i+1:] 300 for j = i + 1; j < len(dsn); j++ { 301 if dsn[j] == '?' { 302 if err = parseDSNParams(cfg, dsn[j+1:]); err != nil { 303 return 304 } 305 break 306 } 307 } 308 cfg.DBName = dsn[i+1 : j] 309 310 break 311 } 312 } 313 314 if !foundSlash && len(dsn) > 0 { 315 return nil, errInvalidDSNNoSlash 316 } 317 318 if cfg.InterpolateParams && unsafeCollations[cfg.Collation] { 319 return nil, errInvalidDSNUnsafeCollation 320 } 321 322 // Set default network if empty 323 if cfg.Net == "" { 324 cfg.Net = "tcp" 325 } 326 327 // Set default address if empty 328 if cfg.Addr == "" { 329 switch cfg.Net { 330 case "tcp": 331 cfg.Addr = "127.0.0.1:3306" 332 case "unix": 333 cfg.Addr = "/tmp/mysql.sock" 334 default: 335 return nil, errors.New("default addr for network '" + cfg.Net + "' unknown") 336 } 337 338 } 339 340 return 341 } 342 343 // parseDSNParams parses the DSN "query string" 344 // Values must be url.QueryEscape'ed 345 func parseDSNParams(cfg *Config, params string) (err error) { 346 for _, v := range strings.Split(params, "&") { 347 param := strings.SplitN(v, "=", 2) 348 if len(param) != 2 { 349 continue 350 } 351 352 // cfg params 353 switch value := param[1]; param[0] { 354 355 // Disable INFILE whitelist / enable all files 356 case "allowAllFiles": 357 var isBool bool 358 cfg.AllowAllFiles, isBool = readBool(value) 359 if !isBool { 360 return errors.New("invalid bool value: " + value) 361 } 362 363 // Use cleartext authentication mode (MySQL 5.5.10+) 364 case "allowCleartextPasswords": 365 var isBool bool 366 cfg.AllowCleartextPasswords, isBool = readBool(value) 367 if !isBool { 368 return errors.New("invalid bool value: " + value) 369 } 370 371 // Use old authentication mode (pre MySQL 4.1) 372 case "allowOldPasswords": 373 var isBool bool 374 cfg.AllowOldPasswords, isBool = readBool(value) 375 if !isBool { 376 return errors.New("invalid bool value: " + value) 377 } 378 379 // Switch "rowsAffected" mode 380 case "clientFoundRows": 381 var isBool bool 382 cfg.ClientFoundRows, isBool = readBool(value) 383 if !isBool { 384 return errors.New("invalid bool value: " + value) 385 } 386 387 // Collation 388 case "collation": 389 cfg.Collation = value 390 break 391 392 case "columnsWithAlias": 393 var isBool bool 394 cfg.ColumnsWithAlias, isBool = readBool(value) 395 if !isBool { 396 return errors.New("invalid bool value: " + value) 397 } 398 399 // Compression 400 case "compress": 401 return errors.New("compression not implemented yet") 402 403 // Enable client side placeholder substitution 404 case "interpolateParams": 405 var isBool bool 406 cfg.InterpolateParams, isBool = readBool(value) 407 if !isBool { 408 return errors.New("invalid bool value: " + value) 409 } 410 411 // Time Location 412 case "loc": 413 if value, err = url.QueryUnescape(value); err != nil { 414 return 415 } 416 cfg.Loc, err = time.LoadLocation(value) 417 if err != nil { 418 return 419 } 420 421 // multiple statements in one query 422 case "multiStatements": 423 var isBool bool 424 cfg.MultiStatements, isBool = readBool(value) 425 if !isBool { 426 return errors.New("invalid bool value: " + value) 427 } 428 429 // time.Time parsing 430 case "parseTime": 431 var isBool bool 432 cfg.ParseTime, isBool = readBool(value) 433 if !isBool { 434 return errors.New("invalid bool value: " + value) 435 } 436 437 // I/O read Timeout 438 case "readTimeout": 439 cfg.ReadTimeout, err = time.ParseDuration(value) 440 if err != nil { 441 return 442 } 443 444 // Strict mode 445 case "strict": 446 var isBool bool 447 cfg.Strict, isBool = readBool(value) 448 if !isBool { 449 return errors.New("invalid bool value: " + value) 450 } 451 452 // Dial Timeout 453 case "timeout": 454 cfg.Timeout, err = time.ParseDuration(value) 455 if err != nil { 456 return 457 } 458 459 // TLS-Encryption 460 case "tls": 461 boolValue, isBool := readBool(value) 462 if isBool { 463 if boolValue { 464 cfg.TLSConfig = "true" 465 cfg.tls = &tls.Config{} 466 } else { 467 cfg.TLSConfig = "false" 468 } 469 } else if vl := strings.ToLower(value); vl == "skip-verify" { 470 cfg.TLSConfig = vl 471 cfg.tls = &tls.Config{InsecureSkipVerify: true} 472 } else { 473 name, err := url.QueryUnescape(value) 474 if err != nil { 475 return fmt.Errorf("invalid value for TLS config name: %v", err) 476 } 477 478 if tlsConfig, ok := tlsConfigRegister[name]; ok { 479 if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify { 480 host, _, err := net.SplitHostPort(cfg.Addr) 481 if err == nil { 482 tlsConfig.ServerName = host 483 } 484 } 485 486 cfg.TLSConfig = name 487 cfg.tls = tlsConfig 488 } else { 489 return errors.New("invalid value / unknown config name: " + name) 490 } 491 } 492 493 // I/O write Timeout 494 case "writeTimeout": 495 cfg.WriteTimeout, err = time.ParseDuration(value) 496 if err != nil { 497 return 498 } 499 500 default: 501 // lazy init 502 if cfg.Params == nil { 503 cfg.Params = make(map[string]string) 504 } 505 506 if cfg.Params[param[0]], err = url.QueryUnescape(value); err != nil { 507 return 508 } 509 } 510 } 511 512 return 513 }