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&...&paramN=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&paramN=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&...&paramN=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  }