pgweb/pkg/connection/connection_string.go

198 lines
4.4 KiB
Go
Raw Normal View History

2015-04-30 11:47:07 -05:00
package connection
2014-12-17 21:32:50 -06:00
import (
"errors"
"fmt"
2015-08-15 21:11:09 -05:00
neturl "net/url"
"os"
2014-12-17 21:32:50 -06:00
"os/user"
"strconv"
2014-12-17 21:32:50 -06:00
"strings"
2015-04-30 11:47:07 -05:00
"github.com/jackc/pgpassfile"
2015-04-30 11:47:07 -05:00
"github.com/sosedoff/pgweb/pkg/command"
2014-12-17 21:32:50 -06:00
)
// Common errors
2018-09-13 22:25:15 -05:00
var (
errCantDetectUser = errors.New("Could not detect default username")
errInvalidURLFormat = errors.New("Invalid URL. Valid format: postgres://user:password@host:port/db?sslmode=mode")
2018-09-13 22:25:15 -05:00
)
// currentUser returns a current user name
func currentUser() (string, error) {
u, err := user.Current()
if err == nil {
return u.Username, nil
}
name := os.Getenv("USER")
if name != "" {
return name, nil
}
return "", errCantDetectUser
}
2018-09-13 22:25:15 -05:00
// Check if connection url has a correct postgres prefix
func hasValidPrefix(str string) bool {
return strings.HasPrefix(str, "postgres://") || strings.HasPrefix(str, "postgresql://")
}
// Extract all query vals and return as a map
func valsFromQuery(vals neturl.Values) map[string]string {
result := map[string]string{}
for k, v := range vals {
result[strings.ToLower(k)] = v[0]
}
return result
}
// FormatURL reformats the existing connection string
func FormatURL(opts command.Options) (string, error) {
2019-11-02 13:00:23 -05:00
url := opts.URL
2014-12-17 21:32:50 -06:00
2018-09-13 22:25:15 -05:00
// Validate connection string prefix
if !hasValidPrefix(url) {
return "", errInvalidURLFormat
2014-12-17 21:32:50 -06:00
}
2018-09-13 22:25:15 -05:00
// Validate the URL
uri, err := neturl.Parse(url)
if err != nil {
return "", errInvalidURLFormat
2018-09-13 22:25:15 -05:00
}
// Get query params
params := valsFromQuery(uri.Query())
// Determine if we need to specify sslmode if it's missing
if params["sslmode"] == "" {
2022-11-23 16:21:30 -06:00
if opts.SSLMode == "" {
2018-09-13 22:25:15 -05:00
// Only modify sslmode for local connections
if strings.Contains(uri.Host, "localhost") || strings.Contains(uri.Host, "127.0.0.1") {
params["sslmode"] = "disable"
2014-12-17 21:32:50 -06:00
}
2018-09-13 22:25:15 -05:00
} else {
2022-11-23 16:21:30 -06:00
params["sslmode"] = opts.SSLMode
2014-12-17 21:32:50 -06:00
}
}
// When password is not provided, look it up from a .pgpass file
if uri.User != nil {
pass, _ := uri.User.Password()
if pass == "" && opts.Passfile != "" {
pass = lookupPassword(opts, uri)
if pass != "" {
uri.User = neturl.UserPassword(uri.User.Username(), pass)
}
}
}
// Configure default connect timeout
if opts.OpenTimeout > 0 {
params["connect_timeout"] = strconv.Itoa(opts.OpenTimeout)
}
2018-09-13 22:25:15 -05:00
// Rebuild query params
query := neturl.Values{}
for k, v := range params {
query.Add(k, v)
2014-12-17 21:32:50 -06:00
}
2018-09-13 22:25:15 -05:00
uri.RawQuery = query.Encode()
2014-12-17 21:32:50 -06:00
2018-09-13 22:25:15 -05:00
return uri.String(), nil
2014-12-17 21:32:50 -06:00
}
// IsBlank returns true if command options do not contain connection details
2015-04-30 11:47:07 -05:00
func IsBlank(opts command.Options) bool {
2019-11-02 13:00:23 -05:00
return opts.Host == "" && opts.User == "" && opts.DbName == "" && opts.URL == ""
2014-12-17 21:56:15 -06:00
}
// BuildStringFromOptions returns a new connection string built from options
func BuildStringFromOptions(opts command.Options) (string, error) {
query := neturl.Values{}
// If connection string is provided we just use that
2019-11-02 13:00:23 -05:00
if opts.URL != "" {
return FormatURL(opts)
2014-12-17 21:32:50 -06:00
}
// Try to detect user from current OS user
if opts.User == "" {
u, err := currentUser()
2014-12-17 21:32:50 -06:00
if err == nil {
opts.User = u
2014-12-17 21:32:50 -06:00
}
}
2022-11-23 16:21:30 -06:00
if opts.SSLMode != "" {
query.Add("sslmode", opts.SSLMode)
} else {
if opts.Host == "localhost" || opts.Host == "127.0.0.1" {
query.Add("sslmode", "disable")
}
}
2022-11-23 16:21:30 -06:00
if opts.SSLCert != "" {
query.Add("sslcert", opts.SSLCert)
2020-02-05 22:09:13 -06:00
}
2022-11-23 16:21:30 -06:00
if opts.SSLKey != "" {
query.Add("sslkey", opts.SSLKey)
2020-02-05 22:09:13 -06:00
}
2022-11-23 16:21:30 -06:00
if opts.SSLRootCert != "" {
query.Add("sslrootcert", opts.SSLRootCert)
2014-12-17 21:32:50 -06:00
}
// Grab password from .pgpass file if it's available
if opts.Pass == "" && opts.Passfile != "" {
opts.Pass = lookupPassword(opts, nil)
}
// Configure default connect timeout
if opts.OpenTimeout > 0 {
query.Add("connect_timeout", strconv.Itoa(opts.OpenTimeout))
}
url := neturl.URL{
Scheme: "postgres",
Host: fmt.Sprintf("%v:%v", opts.Host, opts.Port),
User: neturl.UserPassword(opts.User, opts.Pass),
Path: fmt.Sprintf("/%s", opts.DbName),
RawQuery: query.Encode(),
2014-12-17 21:32:50 -06:00
}
return url.String(), nil
2014-12-17 21:32:50 -06:00
}
func lookupPassword(opts command.Options, url *neturl.URL) string {
if opts.Passfile == "" {
return ""
}
passfile, err := pgpassfile.ReadPassfile(opts.Passfile)
if err != nil {
fmt.Println("[WARN] .pgpassfile", opts.Passfile, "is not readable")
return ""
}
if url != nil {
var dbName string
fmt.Sscanf(url.Path, "/%s", &dbName)
return passfile.FindPassword(
url.Hostname(),
url.Port(),
dbName,
url.User.Username(),
)
}
return passfile.FindPassword(
opts.Host,
fmt.Sprintf("%d", opts.Port),
opts.DbName,
opts.User,
)
}