...

Source file src/github.com/jackc/pgpassfile/pgpass.go

Documentation: github.com/jackc/pgpassfile

     1  // Package pgpassfile is a parser PostgreSQL .pgpass files.
     2  package pgpassfile
     3  
     4  import (
     5  	"bufio"
     6  	"io"
     7  	"os"
     8  	"regexp"
     9  	"strings"
    10  )
    11  
    12  // Entry represents a line in a PG passfile.
    13  type Entry struct {
    14  	Hostname string
    15  	Port     string
    16  	Database string
    17  	Username string
    18  	Password string
    19  }
    20  
    21  // Passfile is the in memory data structure representing a PG passfile.
    22  type Passfile struct {
    23  	Entries []*Entry
    24  }
    25  
    26  // ReadPassfile reads the file at path and parses it into a Passfile.
    27  func ReadPassfile(path string) (*Passfile, error) {
    28  	f, err := os.Open(path)
    29  	if err != nil {
    30  		return nil, err
    31  	}
    32  	defer f.Close()
    33  
    34  	return ParsePassfile(f)
    35  }
    36  
    37  // ParsePassfile reads r and parses it into a Passfile.
    38  func ParsePassfile(r io.Reader) (*Passfile, error) {
    39  	passfile := &Passfile{}
    40  
    41  	scanner := bufio.NewScanner(r)
    42  	for scanner.Scan() {
    43  		entry := parseLine(scanner.Text())
    44  		if entry != nil {
    45  			passfile.Entries = append(passfile.Entries, entry)
    46  		}
    47  	}
    48  
    49  	return passfile, scanner.Err()
    50  }
    51  
    52  // Match (not colons or escaped colon or escaped backslash)+. Essentially gives a split on unescaped
    53  // colon.
    54  var colonSplitterRegexp = regexp.MustCompile("(([^:]|(\\:)))+")
    55  
    56  // var colonSplitterRegexp = regexp.MustCompile("((?:[^:]|(?:\\:)|(?:\\\\))+)")
    57  
    58  // parseLine parses a line into an *Entry. It returns nil on comment lines or any other unparsable
    59  // line.
    60  func parseLine(line string) *Entry {
    61  	const (
    62  		tmpBackslash = "\r"
    63  		tmpColon     = "\n"
    64  	)
    65  
    66  	line = strings.TrimSpace(line)
    67  
    68  	if strings.HasPrefix(line, "#") {
    69  		return nil
    70  	}
    71  
    72  	line = strings.Replace(line, `\\`, tmpBackslash, -1)
    73  	line = strings.Replace(line, `\:`, tmpColon, -1)
    74  
    75  	parts := strings.Split(line, ":")
    76  	if len(parts) != 5 {
    77  		return nil
    78  	}
    79  
    80  	// Unescape escaped colons and backslashes
    81  	for i := range parts {
    82  		parts[i] = strings.Replace(parts[i], tmpBackslash, `\`, -1)
    83  		parts[i] = strings.Replace(parts[i], tmpColon, `:`, -1)
    84  	}
    85  
    86  	return &Entry{
    87  		Hostname: parts[0],
    88  		Port:     parts[1],
    89  		Database: parts[2],
    90  		Username: parts[3],
    91  		Password: parts[4],
    92  	}
    93  }
    94  
    95  // FindPassword finds the password for the provided hostname, port, database, and username. For a
    96  // Unix domain socket hostname must be set to "localhost". An empty string will be returned if no
    97  // match is found.
    98  //
    99  // See https://www.postgresql.org/docs/current/libpq-pgpass.html for more password file information.
   100  func (pf *Passfile) FindPassword(hostname, port, database, username string) (password string) {
   101  	for _, e := range pf.Entries {
   102  		if (e.Hostname == "*" || e.Hostname == hostname) &&
   103  			(e.Port == "*" || e.Port == port) &&
   104  			(e.Database == "*" || e.Database == database) &&
   105  			(e.Username == "*" || e.Username == username) {
   106  			return e.Password
   107  		}
   108  	}
   109  	return ""
   110  }
   111  

View as plain text