19 tkMachine tkType = iota
29 var keywords = map[string]tkType{
33 "password": tkPassword,
46 // FindMachine returns the Machine in n named by name. If a machine named by
47 // name exists, it is returned. If no Machine with name name is found and there
48 // is a ``default'' machine, the ``default'' machine is returned. Otherwise, nil
50 func (n *Netrc) FindMachine(name string) (m *Machine) {
51 // TODO(bgentry): not safe for concurrency
53 for _, m = range n.machines {
67 // MarshalText implements the encoding.TextMarshaler interface to encode a
68 // Netrc into text format.
69 func (n *Netrc) MarshalText() (text []byte, err error) {
70 // TODO(bgentry): not safe for concurrency
71 for i := range n.tokens {
72 switch n.tokens[i].kind {
73 case tkComment, tkDefault, tkWhitespace: // always append these types
74 text = append(text, n.tokens[i].rawkind...)
76 if n.tokens[i].value != "" { // skip empty-value tokens
77 text = append(text, n.tokens[i].rawkind...)
80 if n.tokens[i].kind == tkMacdef {
81 text = append(text, ' ')
82 text = append(text, n.tokens[i].macroName...)
84 text = append(text, n.tokens[i].rawvalue...)
89 func (n *Netrc) NewMachine(name, login, password, account string) *Machine {
91 defer n.updateLock.Unlock()
94 if len(n.tokens) == 0 {
105 rawkind: []byte(prefix + "machine"),
107 rawvalue: []byte(" " + name),
111 rawkind: []byte("\n\tlogin"),
113 rawvalue: []byte(" " + login),
117 rawkind: []byte("\n\tpassword"),
119 rawvalue: []byte(" " + password),
121 accounttoken: &token{
123 rawkind: []byte("\n\taccount"),
125 rawvalue: []byte(" " + account),
128 n.insertMachineTokensBeforeDefault(m)
129 for i := range n.machines {
130 if n.machines[i].IsDefault() {
131 n.machines = append(append(n.machines[:i], m), n.machines[i:]...)
135 n.machines = append(n.machines, m)
139 func (n *Netrc) insertMachineTokensBeforeDefault(m *Machine) {
140 newtokens := []*token{m.nametoken}
141 if m.logintoken.value != "" {
142 newtokens = append(newtokens, m.logintoken)
144 if m.passtoken.value != "" {
145 newtokens = append(newtokens, m.passtoken)
147 if m.accounttoken.value != "" {
148 newtokens = append(newtokens, m.accounttoken)
150 for i := range n.tokens {
151 if n.tokens[i].kind == tkDefault {
152 // found the default, now insert tokens before it
153 n.tokens = append(n.tokens[:i], append(newtokens, n.tokens[i:]...)...)
157 // didn't find a default, just add the newtokens to the end
158 n.tokens = append(n.tokens, newtokens...)
162 func (n *Netrc) RemoveMachine(name string) {
164 defer n.updateLock.Unlock()
166 for i := range n.machines {
167 if n.machines[i] != nil && n.machines[i].Name == name {
169 for _, t := range []*token{
170 m.nametoken, m.logintoken, m.passtoken, m.accounttoken,
174 n.machines = append(n.machines[:i], n.machines[i+1:]...)
180 func (n *Netrc) removeToken(t *token) {
182 for i := range n.tokens {
183 if n.tokens[i] == t {
184 n.tokens = append(n.tokens[:i], n.tokens[i+1:]...)
191 // Machine contains information about a remote machine.
192 type Machine struct {
204 // IsDefault returns true if the machine is a "default" token, denoted by an
206 func (m *Machine) IsDefault() bool {
210 // UpdatePassword sets the password for the Machine m.
211 func (m *Machine) UpdatePassword(newpass string) {
213 updateTokenValue(m.passtoken, newpass)
216 // UpdateLogin sets the login for the Machine m.
217 func (m *Machine) UpdateLogin(newlogin string) {
219 updateTokenValue(m.logintoken, newlogin)
222 // UpdateAccount sets the login for the Machine m.
223 func (m *Machine) UpdateAccount(newaccount string) {
224 m.Account = newaccount
225 updateTokenValue(m.accounttoken, newaccount)
228 func updateTokenValue(t *token, value string) {
231 newraw := make([]byte, len(t.rawvalue))
232 copy(newraw, t.rawvalue)
234 bytes.TrimSuffix(newraw, []byte(oldvalue)),
239 // Macros contains all the macro definitions in a netrc file.
240 type Macros map[string]string
250 // Error represents a netrc file parse error.
252 LineNum int // Line number
253 Msg string // Error message
256 // Error returns a string representation of error e.
257 func (e *Error) Error() string {
258 return fmt.Sprintf("line %d: %s", e.LineNum, e.Msg)
261 func (e *Error) BadDefaultOrder() bool {
262 return e.Msg == errBadDefaultOrder
265 const errBadDefaultOrder = "default token must appear after all machine tokens"
267 // scanLinesKeepPrefix is a split function for a Scanner that returns each line
268 // of text. The returned token may include newlines if they are before the
269 // first non-space character. The returned line may be empty. The end-of-line
270 // marker is one optional carriage return followed by one mandatory newline. In
271 // regular expression notation, it is `\r?\n`. The last non-empty line of
272 // input will be returned even if it has no newline.
273 func scanLinesKeepPrefix(data []byte, atEOF bool) (advance int, token []byte, err error) {
274 if atEOF && len(data) == 0 {
277 // Skip leading spaces.
279 for width := 0; start < len(data); start += width {
281 r, width = utf8.DecodeRune(data[start:])
282 if !unicode.IsSpace(r) {
286 if i := bytes.IndexByte(data[start:], '\n'); i >= 0 {
287 // We have a full newline-terminated line.
288 return start + i, data[0 : start+i], nil
290 // If we're at EOF, we have a final, non-terminated line. Return it.
292 return len(data), data, nil
294 // Request more data.
298 // scanWordsKeepPrefix is a split function for a Scanner that returns each
299 // space-separated word of text, with prefixing spaces included. It will never
300 // return an empty string. The definition of space is set by unicode.IsSpace.
302 // Adapted from bufio.ScanWords().
303 func scanTokensKeepPrefix(data []byte, atEOF bool) (advance int, token []byte, err error) {
304 // Skip leading spaces.
306 for width := 0; start < len(data); start += width {
308 r, width = utf8.DecodeRune(data[start:])
309 if !unicode.IsSpace(r) {
313 if atEOF && len(data) == 0 || start == len(data) {
314 return len(data), data, nil
316 if len(data) > start && data[start] == '#' {
317 return scanLinesKeepPrefix(data, atEOF)
319 // Scan until space, marking end of word.
320 for width, i := 0, start; i < len(data); i += width {
322 r, width = utf8.DecodeRune(data[i:])
323 if unicode.IsSpace(r) {
324 return i, data[:i], nil
327 // If we're at EOF, we have a final, non-empty, non-terminated word. Return it.
328 if atEOF && len(data) > start {
329 return len(data), data, nil
331 // Request more data.
335 func newToken(rawb []byte) (*token, error) {
336 _, tkind, err := bufio.ScanWords(rawb, true)
341 t := token{rawkind: rawb}
342 t.kind, ok = keywords[string(tkind)]
344 trimmed := strings.TrimSpace(string(tkind))
346 t.kind = tkWhitespace // whitespace-only, should happen only at EOF
349 if strings.HasPrefix(trimmed, "#") {
350 t.kind = tkComment // this is a comment
353 return &t, fmt.Errorf("keyword expected; got " + string(tkind))
358 func scanValue(scanner *bufio.Scanner, pos int) ([]byte, string, int, error) {
360 raw := scanner.Bytes()
361 pos += bytes.Count(raw, []byte{'\n'})
362 return raw, strings.TrimSpace(string(raw)), pos, nil
364 if err := scanner.Err(); err != nil {
365 return nil, "", pos, &Error{pos, err.Error()}
367 return nil, "", pos, nil
370 func parse(r io.Reader, pos int) (*Netrc, error) {
371 b, err := ioutil.ReadAll(r)
376 nrc := Netrc{machines: make([]*Machine, 0, 20), macros: make(Macros, 10)}
379 var currentMacro *token
382 scanner := bufio.NewScanner(bytes.NewReader(b))
383 scanner.Split(scanTokensKeepPrefix)
386 rawb := scanner.Bytes()
390 pos += bytes.Count(rawb, []byte{'\n'})
391 t, err = newToken(rawb)
393 if currentMacro == nil {
394 return nil, &Error{pos, err.Error()}
396 currentMacro.rawvalue = append(currentMacro.rawvalue, rawb...)
400 if currentMacro != nil && bytes.Contains(rawb, []byte{'\n', '\n'}) {
401 // if macro rawvalue + rawb would contain \n\n, then macro def is over
402 currentMacro.value = strings.TrimLeft(string(currentMacro.rawvalue), "\r\n")
403 nrc.macros[currentMacro.macroName] = currentMacro.value
409 if _, t.macroName, pos, err = scanValue(scanner, pos); err != nil {
410 return nil, &Error{pos, err.Error()}
415 return nil, &Error{pos, "multiple default token"}
418 nrc.machines, m = append(nrc.machines, m), nil
425 return nil, &Error{pos, errBadDefaultOrder}
428 nrc.machines, m = append(nrc.machines, m), nil
431 if t.rawvalue, m.Name, pos, err = scanValue(scanner, pos); err != nil {
432 return nil, &Error{pos, err.Error()}
437 if m == nil || m.Login != "" {
438 return nil, &Error{pos, "unexpected token login "}
440 if t.rawvalue, m.Login, pos, err = scanValue(scanner, pos); err != nil {
441 return nil, &Error{pos, err.Error()}
446 if m == nil || m.Password != "" {
447 return nil, &Error{pos, "unexpected token password"}
449 if t.rawvalue, m.Password, pos, err = scanValue(scanner, pos); err != nil {
450 return nil, &Error{pos, err.Error()}
455 if m == nil || m.Account != "" {
456 return nil, &Error{pos, "unexpected token account"}
458 if t.rawvalue, m.Account, pos, err = scanValue(scanner, pos); err != nil {
459 return nil, &Error{pos, err.Error()}
465 nrc.tokens = append(nrc.tokens, t)
468 if err := scanner.Err(); err != nil {
473 nrc.machines, m = append(nrc.machines, m), nil
478 // ParseFile opens the file at filename and then passes its io.Reader to
480 func ParseFile(filename string) (*Netrc, error) {
481 fd, err := os.Open(filename)
489 // Parse parses from the the Reader r as a netrc file and returns the set of
490 // machine information and macros defined in it. The ``default'' machine,
491 // which is intended to be used when no machine name matches, is identified
492 // by an empty machine name. There can be only one ``default'' machine.
494 // If there is a parsing error, an Error is returned.
495 func Parse(r io.Reader) (*Netrc, error) {
499 // FindMachine parses the netrc file identified by filename and returns the
500 // Machine named by name. If a problem occurs parsing the file at filename, an
501 // error is returned. If a machine named by name exists, it is returned. If no
502 // Machine with name name is found and there is a ``default'' machine, the
503 // ``default'' machine is returned. Otherwise, nil is returned.
504 func FindMachine(filename, name string) (m *Machine, err error) {
505 n, err := ParseFile(filename)
509 return n.FindMachine(name), nil