mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
* go modified fiddling with vendor got rid of the vendor directory revendored but with the exact same versions of things maybe better added mods for the images revendored using `GOFLAGS` instead of repeating my self vendor everything to the exact same commit hash as before and fixed ugorji Delete Deproxy.toml empty file cleaned up some file cleaned up some cruft get rid of some unused packages and exclude some Microsoft packages added flags to the variables that get pushed into docker in the makefile It works I suppose added noop excluded what we did not want even less hacky reverted to a version that has not been mangled * get rid of my experiment
612 lines
14 KiB
Go
612 lines
14 KiB
Go
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
|
//
|
|
// Copyright 2016 The Go-MySQL-Driver Authors. All rights reserved.
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package mysql
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rsa"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
errInvalidDSNUnescaped = errors.New("invalid DSN: did you forget to escape a param value?")
|
|
errInvalidDSNAddr = errors.New("invalid DSN: network address not terminated (missing closing brace)")
|
|
errInvalidDSNNoSlash = errors.New("invalid DSN: missing the slash separating the database name")
|
|
errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations")
|
|
)
|
|
|
|
// Config is a configuration parsed from a DSN string.
|
|
// If a new Config is created instead of being parsed from a DSN string,
|
|
// the NewConfig function should be used, which sets default values.
|
|
type Config struct {
|
|
User string // Username
|
|
Passwd string // Password (requires User)
|
|
Net string // Network type
|
|
Addr string // Network address (requires Net)
|
|
DBName string // Database name
|
|
Params map[string]string // Connection parameters
|
|
Collation string // Connection collation
|
|
Loc *time.Location // Location for time.Time values
|
|
MaxAllowedPacket int // Max packet size allowed
|
|
ServerPubKey string // Server public key name
|
|
pubKey *rsa.PublicKey // Server public key
|
|
TLSConfig string // TLS configuration name
|
|
tls *tls.Config // TLS configuration
|
|
Timeout time.Duration // Dial timeout
|
|
ReadTimeout time.Duration // I/O read timeout
|
|
WriteTimeout time.Duration // I/O write timeout
|
|
|
|
AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE
|
|
AllowCleartextPasswords bool // Allows the cleartext client side plugin
|
|
AllowNativePasswords bool // Allows the native password authentication method
|
|
AllowOldPasswords bool // Allows the old insecure password method
|
|
ClientFoundRows bool // Return number of matching rows instead of rows changed
|
|
ColumnsWithAlias bool // Prepend table alias to column names
|
|
InterpolateParams bool // Interpolate placeholders into query string
|
|
MultiStatements bool // Allow multiple statements in one query
|
|
ParseTime bool // Parse time values to time.Time
|
|
RejectReadOnly bool // Reject read-only connections
|
|
}
|
|
|
|
// NewConfig creates a new Config and sets default values.
|
|
func NewConfig() *Config {
|
|
return &Config{
|
|
Collation: defaultCollation,
|
|
Loc: time.UTC,
|
|
MaxAllowedPacket: defaultMaxAllowedPacket,
|
|
AllowNativePasswords: true,
|
|
}
|
|
}
|
|
|
|
func (cfg *Config) normalize() error {
|
|
if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
|
|
return errInvalidDSNUnsafeCollation
|
|
}
|
|
|
|
// Set default network if empty
|
|
if cfg.Net == "" {
|
|
cfg.Net = "tcp"
|
|
}
|
|
|
|
// Set default address if empty
|
|
if cfg.Addr == "" {
|
|
switch cfg.Net {
|
|
case "tcp":
|
|
cfg.Addr = "127.0.0.1:3306"
|
|
case "unix":
|
|
cfg.Addr = "/tmp/mysql.sock"
|
|
default:
|
|
return errors.New("default addr for network '" + cfg.Net + "' unknown")
|
|
}
|
|
|
|
} else if cfg.Net == "tcp" {
|
|
cfg.Addr = ensureHavePort(cfg.Addr)
|
|
}
|
|
|
|
if cfg.tls != nil {
|
|
if cfg.tls.ServerName == "" && !cfg.tls.InsecureSkipVerify {
|
|
host, _, err := net.SplitHostPort(cfg.Addr)
|
|
if err == nil {
|
|
cfg.tls.ServerName = host
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// FormatDSN formats the given Config into a DSN string which can be passed to
|
|
// the driver.
|
|
func (cfg *Config) FormatDSN() string {
|
|
var buf bytes.Buffer
|
|
|
|
// [username[:password]@]
|
|
if len(cfg.User) > 0 {
|
|
buf.WriteString(cfg.User)
|
|
if len(cfg.Passwd) > 0 {
|
|
buf.WriteByte(':')
|
|
buf.WriteString(cfg.Passwd)
|
|
}
|
|
buf.WriteByte('@')
|
|
}
|
|
|
|
// [protocol[(address)]]
|
|
if len(cfg.Net) > 0 {
|
|
buf.WriteString(cfg.Net)
|
|
if len(cfg.Addr) > 0 {
|
|
buf.WriteByte('(')
|
|
buf.WriteString(cfg.Addr)
|
|
buf.WriteByte(')')
|
|
}
|
|
}
|
|
|
|
// /dbname
|
|
buf.WriteByte('/')
|
|
buf.WriteString(cfg.DBName)
|
|
|
|
// [?param1=value1&...¶mN=valueN]
|
|
hasParam := false
|
|
|
|
if cfg.AllowAllFiles {
|
|
hasParam = true
|
|
buf.WriteString("?allowAllFiles=true")
|
|
}
|
|
|
|
if cfg.AllowCleartextPasswords {
|
|
if hasParam {
|
|
buf.WriteString("&allowCleartextPasswords=true")
|
|
} else {
|
|
hasParam = true
|
|
buf.WriteString("?allowCleartextPasswords=true")
|
|
}
|
|
}
|
|
|
|
if !cfg.AllowNativePasswords {
|
|
if hasParam {
|
|
buf.WriteString("&allowNativePasswords=false")
|
|
} else {
|
|
hasParam = true
|
|
buf.WriteString("?allowNativePasswords=false")
|
|
}
|
|
}
|
|
|
|
if cfg.AllowOldPasswords {
|
|
if hasParam {
|
|
buf.WriteString("&allowOldPasswords=true")
|
|
} else {
|
|
hasParam = true
|
|
buf.WriteString("?allowOldPasswords=true")
|
|
}
|
|
}
|
|
|
|
if cfg.ClientFoundRows {
|
|
if hasParam {
|
|
buf.WriteString("&clientFoundRows=true")
|
|
} else {
|
|
hasParam = true
|
|
buf.WriteString("?clientFoundRows=true")
|
|
}
|
|
}
|
|
|
|
if col := cfg.Collation; col != defaultCollation && len(col) > 0 {
|
|
if hasParam {
|
|
buf.WriteString("&collation=")
|
|
} else {
|
|
hasParam = true
|
|
buf.WriteString("?collation=")
|
|
}
|
|
buf.WriteString(col)
|
|
}
|
|
|
|
if cfg.ColumnsWithAlias {
|
|
if hasParam {
|
|
buf.WriteString("&columnsWithAlias=true")
|
|
} else {
|
|
hasParam = true
|
|
buf.WriteString("?columnsWithAlias=true")
|
|
}
|
|
}
|
|
|
|
if cfg.InterpolateParams {
|
|
if hasParam {
|
|
buf.WriteString("&interpolateParams=true")
|
|
} else {
|
|
hasParam = true
|
|
buf.WriteString("?interpolateParams=true")
|
|
}
|
|
}
|
|
|
|
if cfg.Loc != time.UTC && cfg.Loc != nil {
|
|
if hasParam {
|
|
buf.WriteString("&loc=")
|
|
} else {
|
|
hasParam = true
|
|
buf.WriteString("?loc=")
|
|
}
|
|
buf.WriteString(url.QueryEscape(cfg.Loc.String()))
|
|
}
|
|
|
|
if cfg.MultiStatements {
|
|
if hasParam {
|
|
buf.WriteString("&multiStatements=true")
|
|
} else {
|
|
hasParam = true
|
|
buf.WriteString("?multiStatements=true")
|
|
}
|
|
}
|
|
|
|
if cfg.ParseTime {
|
|
if hasParam {
|
|
buf.WriteString("&parseTime=true")
|
|
} else {
|
|
hasParam = true
|
|
buf.WriteString("?parseTime=true")
|
|
}
|
|
}
|
|
|
|
if cfg.ReadTimeout > 0 {
|
|
if hasParam {
|
|
buf.WriteString("&readTimeout=")
|
|
} else {
|
|
hasParam = true
|
|
buf.WriteString("?readTimeout=")
|
|
}
|
|
buf.WriteString(cfg.ReadTimeout.String())
|
|
}
|
|
|
|
if cfg.RejectReadOnly {
|
|
if hasParam {
|
|
buf.WriteString("&rejectReadOnly=true")
|
|
} else {
|
|
hasParam = true
|
|
buf.WriteString("?rejectReadOnly=true")
|
|
}
|
|
}
|
|
|
|
if len(cfg.ServerPubKey) > 0 {
|
|
if hasParam {
|
|
buf.WriteString("&serverPubKey=")
|
|
} else {
|
|
hasParam = true
|
|
buf.WriteString("?serverPubKey=")
|
|
}
|
|
buf.WriteString(url.QueryEscape(cfg.ServerPubKey))
|
|
}
|
|
|
|
if cfg.Timeout > 0 {
|
|
if hasParam {
|
|
buf.WriteString("&timeout=")
|
|
} else {
|
|
hasParam = true
|
|
buf.WriteString("?timeout=")
|
|
}
|
|
buf.WriteString(cfg.Timeout.String())
|
|
}
|
|
|
|
if len(cfg.TLSConfig) > 0 {
|
|
if hasParam {
|
|
buf.WriteString("&tls=")
|
|
} else {
|
|
hasParam = true
|
|
buf.WriteString("?tls=")
|
|
}
|
|
buf.WriteString(url.QueryEscape(cfg.TLSConfig))
|
|
}
|
|
|
|
if cfg.WriteTimeout > 0 {
|
|
if hasParam {
|
|
buf.WriteString("&writeTimeout=")
|
|
} else {
|
|
hasParam = true
|
|
buf.WriteString("?writeTimeout=")
|
|
}
|
|
buf.WriteString(cfg.WriteTimeout.String())
|
|
}
|
|
|
|
if cfg.MaxAllowedPacket != defaultMaxAllowedPacket {
|
|
if hasParam {
|
|
buf.WriteString("&maxAllowedPacket=")
|
|
} else {
|
|
hasParam = true
|
|
buf.WriteString("?maxAllowedPacket=")
|
|
}
|
|
buf.WriteString(strconv.Itoa(cfg.MaxAllowedPacket))
|
|
|
|
}
|
|
|
|
// other params
|
|
if cfg.Params != nil {
|
|
var params []string
|
|
for param := range cfg.Params {
|
|
params = append(params, param)
|
|
}
|
|
sort.Strings(params)
|
|
for _, param := range params {
|
|
if hasParam {
|
|
buf.WriteByte('&')
|
|
} else {
|
|
hasParam = true
|
|
buf.WriteByte('?')
|
|
}
|
|
|
|
buf.WriteString(param)
|
|
buf.WriteByte('=')
|
|
buf.WriteString(url.QueryEscape(cfg.Params[param]))
|
|
}
|
|
}
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// ParseDSN parses the DSN string to a Config
|
|
func ParseDSN(dsn string) (cfg *Config, err error) {
|
|
// New config with some default values
|
|
cfg = NewConfig()
|
|
|
|
// [user[:password]@][net[(addr)]]/dbname[?param1=value1¶mN=valueN]
|
|
// Find the last '/' (since the password or the net addr might contain a '/')
|
|
foundSlash := false
|
|
for i := len(dsn) - 1; i >= 0; i-- {
|
|
if dsn[i] == '/' {
|
|
foundSlash = true
|
|
var j, k int
|
|
|
|
// left part is empty if i <= 0
|
|
if i > 0 {
|
|
// [username[:password]@][protocol[(address)]]
|
|
// Find the last '@' in dsn[:i]
|
|
for j = i; j >= 0; j-- {
|
|
if dsn[j] == '@' {
|
|
// username[:password]
|
|
// Find the first ':' in dsn[:j]
|
|
for k = 0; k < j; k++ {
|
|
if dsn[k] == ':' {
|
|
cfg.Passwd = dsn[k+1 : j]
|
|
break
|
|
}
|
|
}
|
|
cfg.User = dsn[:k]
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
// [protocol[(address)]]
|
|
// Find the first '(' in dsn[j+1:i]
|
|
for k = j + 1; k < i; k++ {
|
|
if dsn[k] == '(' {
|
|
// dsn[i-1] must be == ')' if an address is specified
|
|
if dsn[i-1] != ')' {
|
|
if strings.ContainsRune(dsn[k+1:i], ')') {
|
|
return nil, errInvalidDSNUnescaped
|
|
}
|
|
return nil, errInvalidDSNAddr
|
|
}
|
|
cfg.Addr = dsn[k+1 : i-1]
|
|
break
|
|
}
|
|
}
|
|
cfg.Net = dsn[j+1 : k]
|
|
}
|
|
|
|
// dbname[?param1=value1&...¶mN=valueN]
|
|
// Find the first '?' in dsn[i+1:]
|
|
for j = i + 1; j < len(dsn); j++ {
|
|
if dsn[j] == '?' {
|
|
if err = parseDSNParams(cfg, dsn[j+1:]); err != nil {
|
|
return
|
|
}
|
|
break
|
|
}
|
|
}
|
|
cfg.DBName = dsn[i+1 : j]
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
if !foundSlash && len(dsn) > 0 {
|
|
return nil, errInvalidDSNNoSlash
|
|
}
|
|
|
|
if err = cfg.normalize(); err != nil {
|
|
return nil, err
|
|
}
|
|
return
|
|
}
|
|
|
|
// parseDSNParams parses the DSN "query string"
|
|
// Values must be url.QueryEscape'ed
|
|
func parseDSNParams(cfg *Config, params string) (err error) {
|
|
for _, v := range strings.Split(params, "&") {
|
|
param := strings.SplitN(v, "=", 2)
|
|
if len(param) != 2 {
|
|
continue
|
|
}
|
|
|
|
// cfg params
|
|
switch value := param[1]; param[0] {
|
|
// Disable INFILE whitelist / enable all files
|
|
case "allowAllFiles":
|
|
var isBool bool
|
|
cfg.AllowAllFiles, isBool = readBool(value)
|
|
if !isBool {
|
|
return errors.New("invalid bool value: " + value)
|
|
}
|
|
|
|
// Use cleartext authentication mode (MySQL 5.5.10+)
|
|
case "allowCleartextPasswords":
|
|
var isBool bool
|
|
cfg.AllowCleartextPasswords, isBool = readBool(value)
|
|
if !isBool {
|
|
return errors.New("invalid bool value: " + value)
|
|
}
|
|
|
|
// Use native password authentication
|
|
case "allowNativePasswords":
|
|
var isBool bool
|
|
cfg.AllowNativePasswords, isBool = readBool(value)
|
|
if !isBool {
|
|
return errors.New("invalid bool value: " + value)
|
|
}
|
|
|
|
// Use old authentication mode (pre MySQL 4.1)
|
|
case "allowOldPasswords":
|
|
var isBool bool
|
|
cfg.AllowOldPasswords, isBool = readBool(value)
|
|
if !isBool {
|
|
return errors.New("invalid bool value: " + value)
|
|
}
|
|
|
|
// Switch "rowsAffected" mode
|
|
case "clientFoundRows":
|
|
var isBool bool
|
|
cfg.ClientFoundRows, isBool = readBool(value)
|
|
if !isBool {
|
|
return errors.New("invalid bool value: " + value)
|
|
}
|
|
|
|
// Collation
|
|
case "collation":
|
|
cfg.Collation = value
|
|
break
|
|
|
|
case "columnsWithAlias":
|
|
var isBool bool
|
|
cfg.ColumnsWithAlias, isBool = readBool(value)
|
|
if !isBool {
|
|
return errors.New("invalid bool value: " + value)
|
|
}
|
|
|
|
// Compression
|
|
case "compress":
|
|
return errors.New("compression not implemented yet")
|
|
|
|
// Enable client side placeholder substitution
|
|
case "interpolateParams":
|
|
var isBool bool
|
|
cfg.InterpolateParams, isBool = readBool(value)
|
|
if !isBool {
|
|
return errors.New("invalid bool value: " + value)
|
|
}
|
|
|
|
// Time Location
|
|
case "loc":
|
|
if value, err = url.QueryUnescape(value); err != nil {
|
|
return
|
|
}
|
|
cfg.Loc, err = time.LoadLocation(value)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// multiple statements in one query
|
|
case "multiStatements":
|
|
var isBool bool
|
|
cfg.MultiStatements, isBool = readBool(value)
|
|
if !isBool {
|
|
return errors.New("invalid bool value: " + value)
|
|
}
|
|
|
|
// time.Time parsing
|
|
case "parseTime":
|
|
var isBool bool
|
|
cfg.ParseTime, isBool = readBool(value)
|
|
if !isBool {
|
|
return errors.New("invalid bool value: " + value)
|
|
}
|
|
|
|
// I/O read Timeout
|
|
case "readTimeout":
|
|
cfg.ReadTimeout, err = time.ParseDuration(value)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Reject read-only connections
|
|
case "rejectReadOnly":
|
|
var isBool bool
|
|
cfg.RejectReadOnly, isBool = readBool(value)
|
|
if !isBool {
|
|
return errors.New("invalid bool value: " + value)
|
|
}
|
|
|
|
// Server public key
|
|
case "serverPubKey":
|
|
name, err := url.QueryUnescape(value)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid value for server pub key name: %v", err)
|
|
}
|
|
|
|
if pubKey := getServerPubKey(name); pubKey != nil {
|
|
cfg.ServerPubKey = name
|
|
cfg.pubKey = pubKey
|
|
} else {
|
|
return errors.New("invalid value / unknown server pub key name: " + name)
|
|
}
|
|
|
|
// Strict mode
|
|
case "strict":
|
|
panic("strict mode has been removed. See https://github.com/go-sql-driver/mysql/wiki/strict-mode")
|
|
|
|
// Dial Timeout
|
|
case "timeout":
|
|
cfg.Timeout, err = time.ParseDuration(value)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// TLS-Encryption
|
|
case "tls":
|
|
boolValue, isBool := readBool(value)
|
|
if isBool {
|
|
if boolValue {
|
|
cfg.TLSConfig = "true"
|
|
cfg.tls = &tls.Config{}
|
|
} else {
|
|
cfg.TLSConfig = "false"
|
|
}
|
|
} else if vl := strings.ToLower(value); vl == "skip-verify" {
|
|
cfg.TLSConfig = vl
|
|
cfg.tls = &tls.Config{InsecureSkipVerify: true}
|
|
} else {
|
|
name, err := url.QueryUnescape(value)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid value for TLS config name: %v", err)
|
|
}
|
|
|
|
if tlsConfig := getTLSConfigClone(name); tlsConfig != nil {
|
|
cfg.TLSConfig = name
|
|
cfg.tls = tlsConfig
|
|
} else {
|
|
return errors.New("invalid value / unknown config name: " + name)
|
|
}
|
|
}
|
|
|
|
// I/O write Timeout
|
|
case "writeTimeout":
|
|
cfg.WriteTimeout, err = time.ParseDuration(value)
|
|
if err != nil {
|
|
return
|
|
}
|
|
case "maxAllowedPacket":
|
|
cfg.MaxAllowedPacket, err = strconv.Atoi(value)
|
|
if err != nil {
|
|
return
|
|
}
|
|
default:
|
|
// lazy init
|
|
if cfg.Params == nil {
|
|
cfg.Params = make(map[string]string)
|
|
}
|
|
|
|
if cfg.Params[param[0]], err = url.QueryUnescape(value); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func ensureHavePort(addr string) string {
|
|
if _, _, err := net.SplitHostPort(addr); err != nil {
|
|
return net.JoinHostPort(addr, "3306")
|
|
}
|
|
return addr
|
|
}
|