github.com/pengwynn/gh@v1.0.1-0.20140118055701-14327ca3942e/Godeps/_workspace/src/code.google.com/p/go-netrc/netrc/netrc.go (about)

     1  // Copyright © 2010 Fazlul Shahriar <fshahriar@gmail.com>.
     2  // See LICENSE file for license details.
     3  
     4  // Package netrc implements a parser for netrc file format.
     5  //
     6  // A netrc file usually resides in $HOME/.netrc and is traditionally used
     7  // by the ftp(1) program to look up login information (username, password,
     8  // etc.) of remote system(s). The file format is (loosely) described in
     9  // this man page: http://linux.die.net/man/5/netrc .
    10  package netrc
    11  
    12  import (
    13  	"bytes"
    14  	"errors"
    15  	"fmt"
    16  	"io"
    17  	"io/ioutil"
    18  	"os"
    19  	"unicode"
    20  	"unicode/utf8"
    21  )
    22  
    23  const (
    24  	tkMachine = iota
    25  	tkDefault
    26  	tkLogin
    27  	tkPassword
    28  	tkAccount
    29  	tkMacdef
    30  )
    31  
    32  var tokenNames = []string{
    33  	"Machine",
    34  	"Default",
    35  	"Login",
    36  	"Password",
    37  	"Account",
    38  	"Macdef",
    39  }
    40  
    41  var keywords = map[string]int{
    42  	"machine":  tkMachine,
    43  	"default":  tkDefault,
    44  	"login":    tkLogin,
    45  	"password": tkPassword,
    46  	"account":  tkAccount,
    47  	"macdef":   tkMacdef,
    48  }
    49  
    50  // Machine contains information about a remote machine.
    51  type Machine struct {
    52  	Name     string
    53  	Login    string
    54  	Password string
    55  	Account  string
    56  }
    57  
    58  // Macros contains all the macro definitions in a netrc file.
    59  type Macros map[string]string
    60  
    61  type token struct {
    62  	kind      int
    63  	macroName string
    64  	value     string
    65  }
    66  
    67  type filePos struct {
    68  	name string
    69  	line int
    70  }
    71  
    72  // Error represents a netrc file parse error.
    73  type Error struct {
    74  	Filename string
    75  	LineNum  int    // Line number
    76  	Msg      string // Error message
    77  }
    78  
    79  // Error returns a string representation of error e.
    80  func (e *Error) Error() string {
    81  	return fmt.Sprintf("%s:%d: %s", e.Filename, e.LineNum, e.Msg)
    82  }
    83  
    84  func getWord(b []byte, pos *filePos) (string, []byte) {
    85  	// Skip over leading whitespace
    86  	i := 0
    87  	for i < len(b) {
    88  		r, size := utf8.DecodeRune(b[i:])
    89  		if r == '\n' {
    90  			pos.line++
    91  		}
    92  		if !unicode.IsSpace(r) {
    93  			break
    94  		}
    95  		i += size
    96  	}
    97  	b = b[i:]
    98  
    99  	// Find end of word
   100  	i = bytes.IndexFunc(b, unicode.IsSpace)
   101  	if i < 0 {
   102  		i = len(b)
   103  	}
   104  	return string(b[0:i]), b[i:]
   105  }
   106  
   107  func getToken(b []byte, pos *filePos) ([]byte, *token, error) {
   108  	word, b := getWord(b, pos)
   109  	if word == "" {
   110  		return b, nil, nil // EOF reached
   111  	}
   112  
   113  	t := new(token)
   114  	var ok bool
   115  	t.kind, ok = keywords[word]
   116  	if !ok {
   117  		return b, nil, &Error{pos.name, pos.line, "keyword expected; got " + word}
   118  	}
   119  	if t.kind == tkDefault {
   120  		return b, t, nil
   121  	}
   122  
   123  	word, b = getWord(b, pos)
   124  	if word == "" {
   125  		return b, nil, &Error{pos.name, pos.line, "word expected"}
   126  	}
   127  	if t.kind == tkMacdef {
   128  		t.macroName = word
   129  
   130  		// Macro value starts on next line. The rest of current line
   131  		// should contain nothing but whitespace
   132  		i := 0
   133  		for i < len(b) {
   134  			r, size := utf8.DecodeRune(b[i:])
   135  			if r == '\n' {
   136  				i += size
   137  				pos.line++
   138  				break
   139  			}
   140  			if !unicode.IsSpace(r) {
   141  				return b, nil, &Error{pos.name, pos.line, "unexpected word"}
   142  			}
   143  			i += size
   144  		}
   145  		b = b[i:]
   146  
   147  		// Find end of macro value
   148  		i = bytes.Index(b, []byte("\n\n"))
   149  		if i < 0 { // EOF reached
   150  			i = len(b)
   151  		}
   152  		t.value = string(b[0:i])
   153  
   154  		return b[i:], t, nil
   155  	}
   156  	t.value = word
   157  	return b, t, nil
   158  }
   159  
   160  func parse(r io.Reader, pos *filePos) ([]*Machine, Macros, error) {
   161  	// TODO(fhs): Clear memory containing password.
   162  	b, err := ioutil.ReadAll(r)
   163  	if err != nil {
   164  		return nil, nil, err
   165  	}
   166  
   167  	mach := make([]*Machine, 0, 20)
   168  	mac := make(Macros, 10)
   169  	var defaultSeen bool
   170  	var m *Machine
   171  	var t *token
   172  	for {
   173  		b, t, err = getToken(b, pos)
   174  		if err != nil {
   175  			return nil, nil, err
   176  		}
   177  		if t == nil {
   178  			break
   179  		}
   180  		switch t.kind {
   181  		case tkMacdef:
   182  			mac[t.macroName] = t.value
   183  		case tkDefault:
   184  			if defaultSeen {
   185  				return nil, nil, &Error{pos.name, pos.line, "multiple default token"}
   186  			}
   187  			if m != nil {
   188  				mach, m = append(mach, m), nil
   189  			}
   190  			m = new(Machine)
   191  			m.Name = ""
   192  			defaultSeen = true
   193  		case tkMachine:
   194  			if m != nil {
   195  				mach, m = append(mach, m), nil
   196  			}
   197  			m = new(Machine)
   198  			m.Name = t.value
   199  		case tkLogin:
   200  			if m == nil || m.Login != "" {
   201  				return nil, nil, &Error{pos.name, pos.line, "unexpected token login "}
   202  			}
   203  			m.Login = t.value
   204  		case tkPassword:
   205  			if m == nil || m.Password != "" {
   206  				return nil, nil, &Error{pos.name, pos.line, "unexpected token password"}
   207  			}
   208  			m.Password = t.value
   209  		case tkAccount:
   210  			if m == nil || m.Account != "" {
   211  				return nil, nil, &Error{pos.name, pos.line, "unexpected token account"}
   212  			}
   213  			m.Account = t.value
   214  		}
   215  	}
   216  	if m != nil {
   217  		mach, m = append(mach, m), nil
   218  	}
   219  	return mach, mac, nil
   220  }
   221  
   222  // ParseFile parses the netrc file identified by filename and returns the set of
   223  // machine information and macros defined in it. The ``default'' machine,
   224  // which is intended to be used when no machine name matches, is identified
   225  // by an empty machine name. There can be only one ``default'' machine.
   226  //
   227  // If there is a parsing error, an Error is returned.
   228  func ParseFile(filename string) ([]*Machine, Macros, error) {
   229  	// TODO(fhs): Check if file is readable by anyone besides the user if there is password in it.
   230  	fd, err := os.Open(filename)
   231  	if err != nil {
   232  		return nil, nil, err
   233  	}
   234  	defer fd.Close()
   235  	return parse(fd, &filePos{filename, 1})
   236  }
   237  
   238  // FindMachine parses the netrc file identified by filename and returns
   239  // the Machine named by name. If no Machine with name name is found, the
   240  // ``default'' machine is returned.
   241  func FindMachine(filename, name string) (*Machine, error) {
   242  	mach, _, err := ParseFile(filename)
   243  	if err != nil {
   244  		return nil, err
   245  	}
   246  	var def *Machine
   247  	for _, m := range mach {
   248  		if m.Name == name {
   249  			return m, nil
   250  		}
   251  		if m.Name == "" {
   252  			def = m
   253  		}
   254  	}
   255  	if def == nil {
   256  		return nil, errors.New("no machine found")
   257  	}
   258  	return def, nil
   259  }