route/vendor/github.com/dickeyxxx/netrc/netrc.go

239 lines
4.7 KiB
Go

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")
}