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 }