package netrc import ( "bufio" "bytes" "errors" "io" "io/ioutil" "os" "os/exec" "path/filepath" "regexp" "unicode" ) // ErrInvalidNetrc means there was an error parsing the netrc file var ErrInvalidNetrc = errors.New("Invalid netrc") // Netrc file type Netrc struct { Path string machines []*Machine tokens []string } // Machine from the netrc file type Machine struct { Name string IsDefault bool tokens []string } // Parse the netrc file at the given path // It returns a Netrc instance func Parse(path string) (*Netrc, error) { file, err := read(path) if err != nil { return nil, err } netrc, err := parse(lex(file)) if err != nil { return nil, err } netrc.Path = path return netrc, nil } // Machine gets a machine by name func (n *Netrc) Machine(name string) *Machine { for _, m := range n.machines { if m.Name == name { return m } } return nil } // AddMachine adds a machine func (n *Netrc) AddMachine(name, login, password string) { machine := n.Machine(name) if machine == nil { machine = &Machine{} n.machines = append(n.machines, machine) } machine.Name = name machine.tokens = []string{"machine ", name, "\n"} machine.Set("login", login) machine.Set("password", password) } // RemoveMachine remove a machine func (n *Netrc) RemoveMachine(name string) { for i, machine := range n.machines { if machine.Name == name { n.machines = append(n.machines[:i], n.machines[i+1:]...) // continue removing but start over since the indexes changed n.RemoveMachine(name) return } } } // Render out the netrc file to a string func (n *Netrc) Render() string { var b bytes.Buffer for _, token := range n.tokens { b.WriteString(token) } for _, machine := range n.machines { for _, token := range machine.tokens { b.WriteString(token) } } return b.String() } // Save the file to disk func (n *Netrc) Save() error { body := []byte(n.Render()) if filepath.Ext(n.Path) == ".gpg" { cmd := exec.Command("gpg", "-a", "--batch", "--default-recipient-self", "-e") stdin, err := cmd.StdinPipe() if err != nil { return err } stdin.Write(body) stdin.Close() cmd.Stderr = os.Stderr body, err = cmd.Output() if err != nil { return err } } return ioutil.WriteFile(n.Path, body, 0600) } func read(path string) (io.Reader, error) { if filepath.Ext(path) == ".gpg" { cmd := exec.Command("gpg", "--batch", "--quiet", "--decrypt", path) cmd.Stderr = os.Stderr stdout, err := cmd.StdoutPipe() if err != nil { return nil, err } err = cmd.Start() if err != nil { return nil, err } return stdout, nil } return os.Open(path) } func lex(file io.Reader) []string { commentRe := regexp.MustCompile("\\s*#") scanner := bufio.NewScanner(file) scanner.Split(func(data []byte, eof bool) (int, []byte, error) { if eof && len(data) == 0 { return 0, nil, nil } inWhitespace := unicode.IsSpace(rune(data[0])) for i, c := range data { if c == '#' { // line has a comment i = commentRe.FindIndex(data)[0] if i == 0 { // currently in a comment i = bytes.IndexByte(data, '\n') if i == -1 { // no newline at end if !eof { return 0, nil, nil } i = len(data) } for i < len(data) { if !unicode.IsSpace(rune(data[i])) { break } i++ } } return i, data[0:i], nil } if unicode.IsSpace(rune(c)) != inWhitespace { return i, data[0:i], nil } } if eof { return len(data), data, nil } return 0, nil, nil }) tokens := make([]string, 0, 100) for scanner.Scan() { tokens = append(tokens, scanner.Text()) } return tokens } func parse(tokens []string) (*Netrc, error) { n := &Netrc{} n.machines = make([]*Machine, 0, 20) var machine *Machine for i, token := range tokens { // group tokens into machines if token == "machine" || token == "default" { // start new group machine = &Machine{} n.machines = append(n.machines, machine) if token == "default" { machine.IsDefault = true machine.Name = "default" } else { machine.Name = tokens[i+2] } } if machine == nil { n.tokens = append(n.tokens, token) } else { machine.tokens = append(machine.tokens, token) } } return n, nil } // Get a property from a machine func (m *Machine) Get(name string) string { i := 4 if m.IsDefault { i = 2 } for { if i+2 >= len(m.tokens) { return "" } if m.tokens[i] == name { return m.tokens[i+2] } i = i + 4 } } // Set a property on the machine func (m *Machine) Set(name, value string) { i := 4 if m.IsDefault { i = 2 } for i+2 < len(m.tokens) { if m.tokens[i] == name { m.tokens[i+2] = value return } i = i + 4 } m.tokens = append(m.tokens, " ", name, " ", value, "\n") }