cloudflared-mirror/vendor/github.com/xo/dburl/url.go

236 lines
4.9 KiB
Go

package dburl
import (
"net/url"
"strings"
)
// URL wraps the standard net/url.URL type, adding OriginalScheme, Proto,
// Driver, and DSN strings.
type URL struct {
// URL is the base net/url/URL.
url.URL
// OriginalScheme is the original parsed scheme (ie, "sq", "mysql+unix", "sap", etc).
OriginalScheme string
// Proto is the specified protocol (ie, "tcp", "udp", "unix"), if provided.
Proto string
// Driver is the non-aliased SQL driver name that should be used in a call
// to sql/Open.
Driver string
// Unaliased is the unaliased driver name.
Unaliased string
// DSN is the built connection "data source name" that can be used in a
// call to sql/Open.
DSN string
// hostPortDB will be set by Gen*() funcs after determining the host, port,
// database.
//
// when empty, indicates that these values are not special, and can be
// retrieved as the host, port, and path[1:] as usual.
hostPortDB []string
}
// Parse parses urlstr, returning a URL with the OriginalScheme, Proto, Driver,
// Unaliased, and DSN fields populated.
//
// Note: if urlstr has a Opaque component (ie, URLs not specified as "scheme://"
// but "scheme:"), and the database scheme does not support opaque components,
// then Parse will attempt to re-process the URL as "scheme://<opaque>" using
// the OriginalScheme.
func Parse(urlstr string) (*URL, error) {
// parse url
u, err := url.Parse(urlstr)
if err != nil {
return nil, err
}
if u.Scheme == "" {
return nil, ErrInvalidDatabaseScheme
}
// create url
v := &URL{URL: *u, OriginalScheme: urlstr[:len(u.Scheme)], Proto: "tcp"}
// check for +protocol in scheme
var checkProto bool
if i := strings.IndexRune(v.Scheme, '+'); i != -1 {
v.Proto = urlstr[i+1 : len(u.Scheme)]
v.Scheme = v.Scheme[:i]
checkProto = true
}
// get dsn generator
scheme, ok := schemeMap[v.Scheme]
if !ok {
return nil, ErrUnknownDatabaseScheme
}
// if scheme does not understand opaque URLs, retry parsing after making a fully
// qualified URL
if !scheme.Opaque && v.Opaque != "" {
q := ""
if v.RawQuery != "" {
q = "?" + v.RawQuery
}
f := ""
if v.Fragment != "" {
f = "#" + v.Fragment
}
return Parse(v.OriginalScheme + "://" + v.Opaque + q + f)
}
if scheme.Opaque && v.Opaque == "" {
// force Opaque
v.Opaque, v.Host, v.Path, v.RawPath = v.Host+v.Path, "", "", ""
} else if v.Host == "." || (v.Host == "" && strings.TrimPrefix(v.Path, "/") != "") {
// force unix proto
v.Proto = "unix"
}
// check proto
if checkProto || v.Proto != "tcp" {
if scheme.Proto == ProtoNone {
return nil, ErrInvalidTransportProtocol
}
switch {
case scheme.Proto&ProtoAny != 0 && v.Proto != "":
case scheme.Proto&ProtoTCP != 0 && v.Proto == "tcp":
case scheme.Proto&ProtoUDP != 0 && v.Proto == "udp":
case scheme.Proto&ProtoUnix != 0 && v.Proto == "unix":
default:
return nil, ErrInvalidTransportProtocol
}
}
// set driver
v.Driver = scheme.Driver
v.Unaliased = scheme.Driver
if scheme.Override != "" {
v.Driver = scheme.Override
}
// generate dsn
v.DSN, err = scheme.Generator(v)
if err != nil {
return nil, err
}
return v, nil
}
// String satisfies the stringer interface.
func (u *URL) String() string {
p := &url.URL{
Scheme: u.OriginalScheme,
Opaque: u.Opaque,
User: u.User,
Host: u.Host,
Path: u.Path,
RawPath: u.RawPath,
RawQuery: u.RawQuery,
Fragment: u.Fragment,
}
return p.String()
}
// Short provides a short description of the user, host, and database.
func (u *URL) Short() string {
if u.Scheme == "" {
return ""
}
s := schemeMap[u.Scheme].Aliases[0]
if u.Scheme == "odbc" || u.Scheme == "oleodbc" {
n := u.Proto
if v, ok := schemeMap[n]; ok {
n = v.Aliases[0]
}
s += "+" + n
} else if u.Proto != "tcp" {
s += "+" + u.Proto
}
s += ":"
if u.User != nil {
if un := u.User.Username(); un != "" {
s += un + "@"
}
}
if u.Host != "" {
s += u.Host
}
if u.Path != "" && u.Path != "/" {
s += u.Path
}
if u.Opaque != "" {
s += u.Opaque
}
return s
}
// Normalize returns the driver, host, port, database, and user name of a URL,
// joined with sep, populating blank fields with empty.
func (u *URL) Normalize(sep, empty string, cut int) string {
s := make([]string, 5)
s[0] = u.Unaliased
if u.Proto != "tcp" && u.Proto != "unix" {
s[0] += "+" + u.Proto
}
// set host port dbname fields
if u.hostPortDB == nil {
if u.Opaque != "" {
u.hostPortDB = []string{u.Opaque, "", ""}
} else {
u.hostPortDB = []string{
hostname(u.Host),
hostport(u.Host),
strings.TrimPrefix(u.Path, "/"),
}
}
}
copy(s[1:], u.hostPortDB)
// set user
if u.User != nil {
s[4] = u.User.Username()
}
// replace blank entries ...
for i := 0; i < len(s); i++ {
if s[i] == "" {
s[i] = empty
}
}
if cut > 0 {
// cut to only populated fields
i := len(s) - 1
for ; i > cut; i-- {
if s[i] != "" {
break
}
}
s = s[:i]
}
// join
return strings.Join(s, sep)
}