180 lines
4.2 KiB
Go
180 lines
4.2 KiB
Go
|
package dburl
|
||
|
|
||
|
import (
|
||
|
"net/url"
|
||
|
"os"
|
||
|
stdpath "path"
|
||
|
"sort"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// contains code taken from go1.8, for purposes of backwards compatability with
|
||
|
// older go versions.
|
||
|
|
||
|
// hostname returns u.Host, without any port number.
|
||
|
//
|
||
|
// If Host is an IPv6 literal with a port number, Hostname returns the
|
||
|
// IPv6 literal without the square brackets. IPv6 literals may include
|
||
|
// a zone identifier.
|
||
|
func hostname(hostport string) string {
|
||
|
colon := strings.IndexByte(hostport, ':')
|
||
|
if colon == -1 {
|
||
|
return hostport
|
||
|
}
|
||
|
if i := strings.IndexByte(hostport, ']'); i != -1 {
|
||
|
return strings.TrimPrefix(hostport[:i], "[")
|
||
|
}
|
||
|
return hostport[:colon]
|
||
|
}
|
||
|
|
||
|
// hostport returns the port part of u.Host, without the leading colon.
|
||
|
// If u.Host doesn't contain a port, Port returns an empty string.
|
||
|
func hostport(hostport string) string {
|
||
|
colon := strings.IndexByte(hostport, ':')
|
||
|
if colon == -1 {
|
||
|
return ""
|
||
|
}
|
||
|
if i := strings.Index(hostport, "]:"); i != -1 {
|
||
|
return hostport[i+len("]:"):]
|
||
|
}
|
||
|
if strings.Contains(hostport, "]") {
|
||
|
return ""
|
||
|
}
|
||
|
return hostport[colon+len(":"):]
|
||
|
}
|
||
|
|
||
|
// genOptions takes URL values and generates options, joining together with
|
||
|
// joiner, and separated by sep, with any multi URL values joined by valSep,
|
||
|
// ignoring any values with keys in ignore.
|
||
|
//
|
||
|
// For example, to build a "ODBC" style connection string, use like the following:
|
||
|
// genOptions(u.Query(), "", "=", ";", ",")
|
||
|
func genOptions(q url.Values, joiner, assign, sep, valSep string, skipWhenEmpty bool, ignore ...string) string {
|
||
|
qlen := len(q)
|
||
|
if qlen == 0 {
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
// make ignore map
|
||
|
ig := make(map[string]bool, len(ignore))
|
||
|
for _, v := range ignore {
|
||
|
ig[strings.ToLower(v)] = true
|
||
|
}
|
||
|
|
||
|
// sort keys
|
||
|
s := make([]string, len(q))
|
||
|
var i int
|
||
|
for k := range q {
|
||
|
s[i] = k
|
||
|
i++
|
||
|
}
|
||
|
sort.Strings(s)
|
||
|
|
||
|
var opts []string
|
||
|
for _, k := range s {
|
||
|
if !ig[strings.ToLower(k)] {
|
||
|
val := strings.Join(q[k], valSep)
|
||
|
if !skipWhenEmpty || val != "" {
|
||
|
if val != "" {
|
||
|
val = assign + val
|
||
|
}
|
||
|
opts = append(opts, k+val)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if len(opts) != 0 {
|
||
|
return joiner + strings.Join(opts, sep)
|
||
|
}
|
||
|
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
// genOptionsODBC is a util wrapper around genOptions that uses the fixed settings
|
||
|
// for ODBC style connection strings.
|
||
|
func genOptionsODBC(q url.Values, skipWhenEmpty bool, ignore ...string) string {
|
||
|
return genOptions(q, "", "=", ";", ",", skipWhenEmpty, ignore...)
|
||
|
}
|
||
|
|
||
|
// genQueryOptions generates standard query options.
|
||
|
func genQueryOptions(q url.Values) string {
|
||
|
if s := q.Encode(); s != "" {
|
||
|
return "?" + s
|
||
|
}
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
// convertOptions converts an option value based on name, value pairs.
|
||
|
func convertOptions(q url.Values, pairs ...string) url.Values {
|
||
|
n := make(url.Values)
|
||
|
for k, v := range q {
|
||
|
x := make([]string, len(v))
|
||
|
for i, z := range v {
|
||
|
for j := 0; j < len(pairs); j += 2 {
|
||
|
if pairs[j] == z {
|
||
|
z = pairs[j+1]
|
||
|
}
|
||
|
}
|
||
|
x[i] = z
|
||
|
}
|
||
|
n[k] = x
|
||
|
}
|
||
|
|
||
|
return n
|
||
|
}
|
||
|
|
||
|
// mode returns the mode of the path.
|
||
|
func mode(path string) os.FileMode {
|
||
|
if fi, err := os.Stat(path); err == nil {
|
||
|
return fi.Mode()
|
||
|
}
|
||
|
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
// resolveSocket tries to resolve a path to a Unix domain socket based on the
|
||
|
// form "/path/to/socket/dbname" returning either the original path and the
|
||
|
// empty string, or the components "/path/to/socket" and "dbname", when
|
||
|
// /path/to/socket/dbname is reported by os.Stat as a socket.
|
||
|
//
|
||
|
// Used for MySQL DSNs.
|
||
|
func resolveSocket(path string) (string, string) {
|
||
|
dir, dbname := path, ""
|
||
|
for dir != "" && dir != "/" && dir != "." {
|
||
|
if m := mode(dir); m&os.ModeSocket != 0 {
|
||
|
return dir, dbname
|
||
|
}
|
||
|
dir, dbname = stdpath.Dir(dir), stdpath.Base(dir)
|
||
|
}
|
||
|
|
||
|
return path, ""
|
||
|
}
|
||
|
|
||
|
// resolveDir resolves a directory with a :port list.
|
||
|
//
|
||
|
// Used for PostgreSQL DSNs.
|
||
|
func resolveDir(path string) (string, string, string) {
|
||
|
dir := path
|
||
|
for dir != "" && dir != "/" && dir != "." {
|
||
|
port := ""
|
||
|
i, j := strings.LastIndex(dir, ":"), strings.LastIndex(dir, "/")
|
||
|
if i != -1 && i > j {
|
||
|
port = dir[i+1:]
|
||
|
dir = dir[:i]
|
||
|
}
|
||
|
|
||
|
if mode(dir)&os.ModeDir != 0 {
|
||
|
rest := strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(path, dir), ":"+port), "/")
|
||
|
return dir, port, rest
|
||
|
}
|
||
|
|
||
|
if j != -1 {
|
||
|
dir = dir[:j]
|
||
|
} else {
|
||
|
dir = ""
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return path, "", ""
|
||
|
}
|