123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110 |
- // Package pgpassfile is a parser PostgreSQL .pgpass files.
- 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 ""
- }
|