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