Skip to content
Permalink
Browse files

Make netrc public package of leatherman

  • Loading branch information...
frioux committed Apr 6, 2019
1 parent 23af02f commit 3fee3e2a5e4935089211ac7d95f7b4aedf6076e1
1 go.mod
@@ -5,7 +5,6 @@ require (
github.com/PuerkitoBio/goquery v1.5.0
github.com/erikdubbelboer/gspt v0.0.0-20190125194910-e68493906b83
github.com/frioux/mozcookiejar v0.0.2
github.com/frioux/netrc v0.0.0-20190125054817-37b89b8d2a2d
github.com/frioux/shellquote v0.0.2
github.com/fsnotify/fsnotify v1.4.7
github.com/headzoo/surf v1.0.0
2 go.sum
@@ -11,8 +11,6 @@ github.com/erikdubbelboer/gspt v0.0.0-20190125194910-e68493906b83 h1:ngHdSomn2My
github.com/erikdubbelboer/gspt v0.0.0-20190125194910-e68493906b83/go.mod h1:v6o7m/E9bfvm79dE1iFiF+3T7zLBnrjYjkWMa1J+Hv0=
github.com/frioux/mozcookiejar v0.0.2 h1:vAPbreMlaQsMPbedqv+h9OUMyLmtwWr9b1LqmOPcEYA=
github.com/frioux/mozcookiejar v0.0.2/go.mod h1:XA6YqSJViOtLsWH5dRSOj6nh/laBXu9EAdGFF6NEE9Q=
github.com/frioux/netrc v0.0.0-20190125054817-37b89b8d2a2d h1:1529lO2sMK3+Pm7D6uRZcAhzp1WAqeyzXdHbg1RFncU=
github.com/frioux/netrc v0.0.0-20190125054817-37b89b8d2a2d/go.mod h1:Y4qsRBVQEhuujHdjar/vt5NUZ6tB31Ua5hx47En02+0=
github.com/frioux/shellquote v0.0.2 h1:CvQ1aMCS/xhhyGF4JIeA49bhE3W1s5XdBkwmhH3BceQ=
github.com/frioux/shellquote v0.0.2/go.mod h1:1VoFO5LSUNqwAKp2QjSpf9b4PZ4ff+uKXPEjonuyJh8=
github.com/frioux/yaml v0.0.0-20180422015826-390d32f04db5 h1:YMX30YzQ7HSrUMJV8QnQ5toCxoS4lsxD7DjDMUvoa/k=
@@ -7,7 +7,7 @@ import (
"os/user"
"path/filepath"

"github.com/frioux/netrc"
"github.com/frioux/leatherman/pkg/netrc"
"github.com/pkg/errors"
)

@@ -0,0 +1,172 @@
package netrc

import (
"bufio"
"bytes"
"errors"
"io"
"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
logins []*Login
tokens []string
}

// Login from the netrc file
type Login 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 login by machine name
func (n *Netrc) Machine(name string) *Login {
for _, m := range n.logins {
if m.Name == name {
return m
}
}
return nil
}

// MachineAndLogin gets a login by machine name and login name
func (n *Netrc) MachineAndLogin(name, login string) *Login {
for _, m := range n.logins {
if m.Name == name && m.Get("login") == login {
return m
}
}
return nil
}

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.logins = make([]*Login, 0, 20)
var machine *Login
for i, token := range tokens {
// group tokens into machines
if token == "machine" || token == "default" {
// start new group
machine = &Login{}
n.logins = append(n.logins, 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 *Login) 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
}
}
@@ -0,0 +1,81 @@
package netrc

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestLogin(t *testing.T) {
f, err := Parse("./testdata/login.netrc")
assert.Nil(t, err)
heroku := f.Machine("api.heroku.com")
assert.Equal(t, "jeff@heroku.com", heroku.Get("login"))
assert.Equal(t, "foo", heroku.Get("password"))

heroku2 := f.MachineAndLogin("api.heroku.com", "jeff2@heroku.com")
assert.Equal(t, heroku2.Get("login"), "jeff2@heroku.com")
assert.Equal(t, heroku2.Get("password"), "bar")
}

func TestSampleMulti(t *testing.T) {
f, err := Parse("./testdata/sample_multi.netrc")
assert.Nil(t, err)
assert.Equal(t, f.Machine("m").Get("login"), "lm")
assert.Equal(t, f.Machine("m").Get("password"), "pm")
assert.Equal(t, f.Machine("n").Get("login"), "ln")
assert.Equal(t, f.Machine("n").Get("password"), "pn")
}

func TestSampleMultiWithDefault(t *testing.T) {
f, err := Parse("./testdata/sample_multi_with_default.netrc")
assert.Nil(t, err)
assert.Equal(t, f.Machine("m").Get("login"), "lm")
assert.Equal(t, f.Machine("m").Get("password"), "pm")
assert.Equal(t, f.Machine("n").Get("login"), "ln")
assert.Equal(t, f.Machine("n").Get("password"), "pn")
}

func TestNewlineless(t *testing.T) {
f, err := Parse("./testdata/newlineless.netrc")
assert.Nil(t, err)
assert.Equal(t, f.Machine("m").Get("login"), "l")
assert.Equal(t, f.Machine("m").Get("password"), "p")
}

func TestBadDefaultOrder(t *testing.T) {
f, err := Parse("./testdata/bad_default_order.netrc")
assert.Nil(t, err)
assert.Equal(t, f.Machine("mail.google.com").Get("login"), "joe@gmail.com")
assert.Equal(t, f.Machine("mail.google.com").Get("password"), "somethingSecret")
assert.Equal(t, f.Machine("ray").Get("login"), "demo")
assert.Equal(t, f.Machine("ray").Get("password"), "mypassword")
}

func TestDefaultOnly(t *testing.T) {
f, err := Parse("./testdata/default_only.netrc")
assert.Nil(t, err)
assert.Equal(t, f.Machine("default").Get("login"), "ld")
assert.Equal(t, f.Machine("default").Get("password"), "pd")
}

func TestGood(t *testing.T) {
f, err := Parse("./testdata/good.netrc")
assert.Nil(t, err)
assert.Equal(t, f.Machine("mail.google.com").Get("login"), "joe@gmail.com")
assert.Equal(t, f.Machine("mail.google.com").Get("account"), "justagmail")
assert.Equal(t, f.Machine("mail.google.com").Get("password"), "somethingSecret")
}

func TestPassword(t *testing.T) {
f, err := Parse("./testdata/password.netrc")
assert.Nil(t, err)
assert.Equal(t, f.Machine("m").Get("password"), "p")
}

func TestPermissive(t *testing.T) {
f, err := Parse("./testdata/permissive.netrc")
assert.Nil(t, err)
assert.Equal(t, f.Machine("m").Get("login"), "l")
assert.Equal(t, f.Machine("m").Get("password"), "p")
}
@@ -0,0 +1,13 @@
# I am a comment
machine mail.google.com
login joe@gmail.com
account gmail
password somethingSecret
# I am another comment

default
login anonymous
password joe@example.com

machine ray login demo password mypassword

Oops, something went wrong.

0 comments on commit 3fee3e2

Please sign in to comment.
You can’t perform that action at this time.