Restructure connect process
- Moved lots of connection logic to pgconn from pgx - Extracted pgpassfile package
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
package pgpassfile
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Entry represents a line in a PG passfile.
|
||||
type Entry struct {
|
||||
Hostname string
|
||||
Port string
|
||||
Database string
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// Passfile is the in memory data structure representing a PG passfile.
|
||||
type Passfile struct {
|
||||
Entries []*Entry
|
||||
}
|
||||
|
||||
// ReadPassfile reads the file at path and parses it into a Passfile.
|
||||
func ReadPassfile(path string) (*Passfile, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return ParsePassfile(f)
|
||||
}
|
||||
|
||||
// ParsePassfile reads r and parses it into a Passfile.
|
||||
func ParsePassfile(r io.Reader) (*Passfile, error) {
|
||||
passfile := &Passfile{}
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
entry := parseLine(scanner.Text())
|
||||
if entry != nil {
|
||||
passfile.Entries = append(passfile.Entries, entry)
|
||||
}
|
||||
}
|
||||
|
||||
return passfile, scanner.Err()
|
||||
}
|
||||
|
||||
// Match (not colons or escaped colon or escaped backslash)+. Essentially gives a split on unescaped
|
||||
// colon.
|
||||
var colonSplitterRegexp = regexp.MustCompile("(([^:]|(\\:)))+")
|
||||
|
||||
// var colonSplitterRegexp = regexp.MustCompile("((?:[^:]|(?:\\:)|(?:\\\\))+)")
|
||||
|
||||
// parseLine parses a line into an *Entry. It returns nil on comment lines or any other unparsable
|
||||
// line.
|
||||
func parseLine(line string) *Entry {
|
||||
const (
|
||||
tmpBackslash = "\r"
|
||||
tmpColon = "\n"
|
||||
)
|
||||
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
if strings.HasPrefix(line, "#") {
|
||||
return nil
|
||||
}
|
||||
|
||||
line = strings.Replace(line, `\\`, tmpBackslash, -1)
|
||||
line = strings.Replace(line, `\:`, tmpColon, -1)
|
||||
|
||||
parts := strings.Split(line, ":")
|
||||
if len(parts) != 5 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unescape escaped colons and backslashes
|
||||
for i := range parts {
|
||||
parts[i] = strings.Replace(parts[i], tmpBackslash, `\`, -1)
|
||||
parts[i] = strings.Replace(parts[i], tmpColon, `:`, -1)
|
||||
}
|
||||
|
||||
return &Entry{
|
||||
Hostname: parts[0],
|
||||
Port: parts[1],
|
||||
Database: parts[2],
|
||||
Username: parts[3],
|
||||
Password: parts[4],
|
||||
}
|
||||
}
|
||||
|
||||
// FindPassword finds the password for the provided hostname, port, database, and username. For a
|
||||
// Unix domain socket hostname must be set to "localhost". An empty string will be returned if no
|
||||
// match is found.
|
||||
//
|
||||
// See https://www.postgresql.org/docs/current/libpq-pgpass.html for more password file information.
|
||||
func (pf *Passfile) FindPassword(hostname, port, database, username string) (password string) {
|
||||
for _, e := range pf.Entries {
|
||||
if (e.Hostname == "*" || e.Hostname == hostname) &&
|
||||
(e.Port == "*" || e.Port == port) &&
|
||||
(e.Database == "*" || e.Database == database) &&
|
||||
(e.Username == "*" || e.Username == username) {
|
||||
return e.Password
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package pgpassfile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func unescape(s string) string {
|
||||
s = strings.Replace(s, `\:`, `:`, -1)
|
||||
s = strings.Replace(s, `\\`, `\`, -1)
|
||||
return s
|
||||
}
|
||||
|
||||
var passfile = [][]string{
|
||||
{"test1", "5432", "larrydb", "larry", "whatstheidea"},
|
||||
{"test1", "5432", "moedb", "moe", "imbecile"},
|
||||
{"test1", "5432", "curlydb", "curly", "nyuknyuknyuk"},
|
||||
{"test2", "5432", "*", "shemp", "heymoe"},
|
||||
{"test2", "5432", "*", "*", `test\\ing\:`},
|
||||
{"localhost", "*", "*", "*", "sesam"},
|
||||
{"test3", "*", "", "", "swordfish"}, // user will be filled later
|
||||
}
|
||||
|
||||
func TestParsePassFile(t *testing.T) {
|
||||
buf := bytes.NewBufferString(`# A comment
|
||||
test1:5432:larrydb:larry:whatstheidea
|
||||
test1:5432:moedb:moe:imbecile
|
||||
test1:5432:curlydb:curly:nyuknyuknyuk
|
||||
test2:5432:*:shemp:heymoe
|
||||
test2:5432:*:*:test\\ing\:
|
||||
localhost:*:*:*:sesam
|
||||
`)
|
||||
|
||||
passfile, err := ParsePassfile(buf)
|
||||
require.Nil(t, err)
|
||||
|
||||
assert.Len(t, passfile.Entries, 6)
|
||||
|
||||
assert.Equal(t, "whatstheidea", passfile.FindPassword("test1", "5432", "larrydb", "larry"))
|
||||
assert.Equal(t, "imbecile", passfile.FindPassword("test1", "5432", "moedb", "moe"))
|
||||
assert.Equal(t, `test\ing:`, passfile.FindPassword("test2", "5432", "something", "else"))
|
||||
assert.Equal(t, "sesam", passfile.FindPassword("localhost", "9999", "foo", "bare"))
|
||||
|
||||
assert.Equal(t, "", passfile.FindPassword("wrong", "5432", "larrydb", "larry"))
|
||||
assert.Equal(t, "", passfile.FindPassword("test1", "wrong", "larrydb", "larry"))
|
||||
assert.Equal(t, "", passfile.FindPassword("test1", "5432", "wrong", "larry"))
|
||||
assert.Equal(t, "", passfile.FindPassword("test1", "5432", "larrydb", "wrong"))
|
||||
}
|
||||
Reference in New Issue
Block a user