Merge pull request #715 from github/netrc
Add netrc support for LFS API requests
This commit is contained in:
commit
2cf082fa14
3
Nut.toml
3
Nut.toml
@ -9,6 +9,7 @@ authors = [
|
||||
|
||||
[dependencies]
|
||||
|
||||
"github.com/bgentry/go-netrc/netrc" = "9fd32a8b3d3d3f9d43c341bfe098430e07609480"
|
||||
"github.com/cheggaaa/pb" = "bd14546a551971ae7f460e6d6e527c5b56cd38d7"
|
||||
"github.com/kr/pretty" = "088c856450c08c03eb32f7a6c221e6eefaa10e6f"
|
||||
"github.com/kr/pty" = "5cf931ef8f76dccd0910001d74a58a7fca84a83d"
|
||||
@ -18,6 +19,6 @@ authors = [
|
||||
"github.com/rubyist/tracerx" = "d7bcc0bc315bed2a841841bee5dbecc8d7d7582f"
|
||||
"github.com/spf13/cobra" = "c55cdf33856a08e4822738728b41783292812889"
|
||||
"github.com/spf13/pflag" = "580b9be06c33d8ba9dcc8757ea56b7642472c2f5"
|
||||
"github.com/ThomsonReutersEikon/go-ntlm/ntlm" = "52b7efa603f1b809167b528b8bbaa467e36fdc02"
|
||||
"github.com/technoweenie/assert" = "b25ea301d127043ffacf3b2545726e79b6632139"
|
||||
"github.com/technoweenie/go-contentaddressable" = "38171def3cd15e3b76eb156219b3d48704643899"
|
||||
"github.com/ThomsonReutersEikon/go-ntlm/ntlm" = "52b7efa603f1b809167b528b8bbaa467e36fdc02"
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/github/git-lfs/git"
|
||||
"github.com/github/git-lfs/vendor/_nuts/github.com/ThomsonReutersEikon/go-ntlm/ntlm"
|
||||
"github.com/github/git-lfs/vendor/_nuts/github.com/bgentry/go-netrc/netrc"
|
||||
"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
|
||||
)
|
||||
|
||||
@ -61,6 +62,7 @@ type Configuration struct {
|
||||
fetchExcludePaths []string
|
||||
fetchPruneConfig *FetchPruneConfig
|
||||
manualEndpoint *Endpoint
|
||||
parsedNetrc netrcfinder
|
||||
}
|
||||
|
||||
func NewConfig() *Configuration {
|
||||
@ -209,6 +211,20 @@ func (c *Configuration) SetAccess(operation string, authType string) {
|
||||
c.SetEndpointAccess(c.Endpoint(operation), authType)
|
||||
}
|
||||
|
||||
func (c *Configuration) FindNetrcHost(host string) (*netrc.Machine, error) {
|
||||
c.loading.Lock()
|
||||
defer c.loading.Unlock()
|
||||
if c.parsedNetrc == nil {
|
||||
n, err := c.parseNetrc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.parsedNetrc = n
|
||||
}
|
||||
|
||||
return c.parsedNetrc.FindMachine(host), nil
|
||||
}
|
||||
|
||||
func (c *Configuration) EndpointAccess(e Endpoint) string {
|
||||
key := fmt.Sprintf("lfs.%s.access", e.Url)
|
||||
if v, ok := c.GitConfig(key); ok && len(v) > 0 {
|
||||
|
32
lfs/config_netrc.go
Normal file
32
lfs/config_netrc.go
Normal file
@ -0,0 +1,32 @@
|
||||
package lfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/github/git-lfs/vendor/_nuts/github.com/bgentry/go-netrc/netrc"
|
||||
)
|
||||
|
||||
type netrcfinder interface {
|
||||
FindMachine(string) *netrc.Machine
|
||||
}
|
||||
|
||||
type noNetrc struct{}
|
||||
|
||||
func (n *noNetrc) FindMachine(host string) *netrc.Machine {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Configuration) parseNetrc() (netrcfinder, error) {
|
||||
home := c.Getenv("HOME")
|
||||
if len(home) == 0 {
|
||||
return &noNetrc{}, nil
|
||||
}
|
||||
|
||||
nrcfilename := filepath.Join(home, netrcBasename)
|
||||
if _, err := os.Stat(nrcfilename); err != nil {
|
||||
return &noNetrc{}, nil
|
||||
}
|
||||
|
||||
return netrc.ParseFile(nrcfilename)
|
||||
}
|
4
lfs/config_nix.go
Normal file
4
lfs/config_nix.go
Normal file
@ -0,0 +1,4 @@
|
||||
// +build !windows
|
||||
package lfs
|
||||
|
||||
var netrcBasename = ".netrc"
|
4
lfs/config_windows.go
Normal file
4
lfs/config_windows.go
Normal file
@ -0,0 +1,4 @@
|
||||
// +build windows
|
||||
package lfs
|
||||
|
||||
var netrcBasename = "_netrc"
|
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
@ -26,9 +27,10 @@ func getCreds(req *http.Request) (Creds, error) {
|
||||
// getCredsForAPI gets the credentials for LFS API requests and sets the given
|
||||
// request's Authorization header with them using Basic Authentication.
|
||||
// 1. Check the LFS URL for authentication. Ex: http://user:pass@example.com
|
||||
// 2. Check the Git remote URL for authentication IF it's the same scheme and
|
||||
// 2. Check netrc for authentication.
|
||||
// 3. Check the Git remote URL for authentication IF it's the same scheme and
|
||||
// host of the LFS URL.
|
||||
// 3. Ask 'git credential' to fill in the password from one of the above URLs.
|
||||
// 4. Ask 'git credential' to fill in the password from one of the above URLs.
|
||||
//
|
||||
// This prefers the Git remote URL for checking credentials so that users only
|
||||
// have to enter their passwords once for Git and Git LFS. It uses the same
|
||||
@ -47,6 +49,10 @@ func getCredsForAPI(req *http.Request) (Creds, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if setCredURLFromNetrc(req) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return fillCredentials(req, credsUrl)
|
||||
}
|
||||
|
||||
@ -90,6 +96,21 @@ func getCredURLForAPI(req *http.Request) (*url.URL, error) {
|
||||
return credsUrl, nil
|
||||
}
|
||||
|
||||
func setCredURLFromNetrc(req *http.Request) bool {
|
||||
host, _, err := net.SplitHostPort(req.URL.Host)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
machine, err := Config.FindNetrcHost(host)
|
||||
if err != nil || machine == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
setRequestAuth(req, machine.Login, machine.Password)
|
||||
return true
|
||||
}
|
||||
|
||||
func skipCredsCheck(req *http.Request) bool {
|
||||
if Config.NtlmAccess(getOperationForHttpRequest(req)) {
|
||||
return false
|
||||
|
@ -250,6 +250,14 @@ func lfsBatchHandler(w http.ResponseWriter, r *http.Request, repo string) {
|
||||
return
|
||||
}
|
||||
|
||||
if repo == "netrctest" {
|
||||
user, pass, err := extractAuth(r.Header.Get("Authorization"))
|
||||
if err != nil || (user != "netrcuser" || pass != "netrcpass") {
|
||||
w.WriteHeader(403)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type batchReq struct {
|
||||
Operation string `json:"operation"`
|
||||
Objects []lfsObject `json:"objects"`
|
||||
@ -540,6 +548,25 @@ func newLfsStorage() *lfsStorage {
|
||||
}
|
||||
}
|
||||
|
||||
func extractAuth(auth string) (string, string, error) {
|
||||
if strings.HasPrefix(auth, "Basic ") {
|
||||
decodeBy, err := base64.StdEncoding.DecodeString(auth[6:len(auth)])
|
||||
decoded := string(decodeBy)
|
||||
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
parts := strings.SplitN(decoded, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
return parts[0], parts[1], nil
|
||||
}
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
func skipIfBadAuth(w http.ResponseWriter, r *http.Request) bool {
|
||||
auth := r.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
@ -547,33 +574,26 @@ func skipIfBadAuth(w http.ResponseWriter, r *http.Request) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(auth, "Basic ") {
|
||||
decodeBy, err := base64.StdEncoding.DecodeString(auth[6:len(auth)])
|
||||
decoded := string(decodeBy)
|
||||
|
||||
user, pass, err := extractAuth(auth)
|
||||
if err != nil {
|
||||
w.WriteHeader(403)
|
||||
log.Printf("Error decoding auth: %s\n", err)
|
||||
return true
|
||||
}
|
||||
|
||||
parts := strings.SplitN(decoded, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
switch parts[0] {
|
||||
switch user {
|
||||
case "user":
|
||||
if parts[1] == "pass" {
|
||||
if pass == "pass" {
|
||||
return false
|
||||
}
|
||||
case "netrcuser":
|
||||
return false
|
||||
case "path":
|
||||
if strings.HasPrefix(r.URL.Path, "/"+parts[1]) {
|
||||
if strings.HasPrefix(r.URL.Path, "/"+pass) {
|
||||
return false
|
||||
}
|
||||
log.Printf("auth attempt against: %q", r.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("auth does not match: %q", decoded)
|
||||
}
|
||||
|
||||
w.WriteHeader(403)
|
||||
log.Printf("Bad auth: %q\n", auth)
|
||||
|
@ -139,3 +139,61 @@ password=path"
|
||||
[ "$expected" = "$(cat cred.log)" ]
|
||||
)
|
||||
end_test
|
||||
|
||||
begin_test "credentials from netrc"
|
||||
(
|
||||
set -e
|
||||
|
||||
printf "machine localhost\nlogin netrcuser\npassword netrcpass\n" >> "$HOME/.netrc"
|
||||
echo $HOME
|
||||
echo "GITSERVER $GITSERVER"
|
||||
cat $HOME/.netrc
|
||||
|
||||
|
||||
reponame="netrctest"
|
||||
setup_remote_repo "$reponame"
|
||||
|
||||
clone_repo "$reponame" repo
|
||||
|
||||
# Need a remote named "localhost" or 127.0.0.1 in netrc will interfere with the other auth
|
||||
git remote add "netrc" "$(echo $GITSERVER | sed s/127.0.0.1/localhost/)/netrctest"
|
||||
git lfs env
|
||||
|
||||
git lfs track "*.dat"
|
||||
echo "push a" > a.dat
|
||||
git add .gitattributes a.dat
|
||||
git commit -m "add a.dat"
|
||||
|
||||
git lfs push netrc master 2>&1 | tee push.log
|
||||
grep "(1 of 1 files)" push.log
|
||||
)
|
||||
end_test
|
||||
|
||||
begin_test "credentials from netrc with bad password"
|
||||
(
|
||||
set -e
|
||||
|
||||
printf "machine localhost\nlogin netrcuser\npassword badpass\n" >> "$HOME/.netrc"
|
||||
echo $HOME
|
||||
echo "GITSERVER $GITSERVER"
|
||||
cat $HOME/.netrc
|
||||
|
||||
|
||||
reponame="netrctest"
|
||||
setup_remote_repo "$reponame"
|
||||
|
||||
clone_repo "$reponame" repo2
|
||||
|
||||
# Need a remote named "localhost" or 127.0.0.1 in netrc will interfere with the other auth
|
||||
git remote add "netrc" "$(echo $GITSERVER | sed s/127.0.0.1/localhost/)/netrctest"
|
||||
git lfs env
|
||||
|
||||
git lfs track "*.dat"
|
||||
echo "push a" > a.dat
|
||||
git add .gitattributes a.dat
|
||||
git commit -m "add a.dat"
|
||||
|
||||
git push netrc master 2>&1 | tee push.log
|
||||
[ "0" = "$(grep -c "(1 of 1 files)" push.log)" ]
|
||||
)
|
||||
end_test
|
||||
|
3
vendor/_nuts/github.com/bgentry/go-netrc/.hgignore
vendored
Normal file
3
vendor/_nuts/github.com/bgentry/go-netrc/.hgignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
syntax: glob
|
||||
*.8
|
||||
*.a
|
20
vendor/_nuts/github.com/bgentry/go-netrc/LICENSE
vendored
Normal file
20
vendor/_nuts/github.com/bgentry/go-netrc/LICENSE
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
Original version Copyright © 2010 Fazlul Shahriar <fshahriar@gmail.com>. Newer
|
||||
portions Copyright © 2014 Blake Gentry <blakesgentry@gmail.com>.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
9
vendor/_nuts/github.com/bgentry/go-netrc/README.md
vendored
Normal file
9
vendor/_nuts/github.com/bgentry/go-netrc/README.md
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
# go-netrc
|
||||
|
||||
A Golang package for reading and writing netrc files. This package can parse netrc
|
||||
files, make changes to them, and then serialize them back to netrc format, while
|
||||
preserving any whitespace that was present in the source file.
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/bgentry/go-netrc?status.png)][godoc]
|
||||
|
||||
[godoc]: https://godoc.org/github.com/bgentry/go-netrc "go-netrc on Godoc.org"
|
13
vendor/_nuts/github.com/bgentry/go-netrc/netrc/examples/bad_default_order.netrc
vendored
Normal file
13
vendor/_nuts/github.com/bgentry/go-netrc/netrc/examples/bad_default_order.netrc
vendored
Normal file
@ -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
|
||||
|
22
vendor/_nuts/github.com/bgentry/go-netrc/netrc/examples/good.netrc
vendored
Normal file
22
vendor/_nuts/github.com/bgentry/go-netrc/netrc/examples/good.netrc
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
# I am a comment
|
||||
machine mail.google.com
|
||||
login joe@gmail.com
|
||||
account justagmail #end of line comment with trailing space
|
||||
password somethingSecret
|
||||
# I am another comment
|
||||
|
||||
macdef allput
|
||||
put src/*
|
||||
|
||||
macdef allput2
|
||||
put src/*
|
||||
put src2/*
|
||||
|
||||
machine ray login demo password mypassword
|
||||
|
||||
machine weirdlogin login uname password pass#pass
|
||||
|
||||
default
|
||||
login anonymous
|
||||
password joe@example.com
|
||||
|
510
vendor/_nuts/github.com/bgentry/go-netrc/netrc/netrc.go
vendored
Normal file
510
vendor/_nuts/github.com/bgentry/go-netrc/netrc/netrc.go
vendored
Normal file
@ -0,0 +1,510 @@
|
||||
package netrc
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type tkType int
|
||||
|
||||
const (
|
||||
tkMachine tkType = iota
|
||||
tkDefault
|
||||
tkLogin
|
||||
tkPassword
|
||||
tkAccount
|
||||
tkMacdef
|
||||
tkComment
|
||||
tkWhitespace
|
||||
)
|
||||
|
||||
var keywords = map[string]tkType{
|
||||
"machine": tkMachine,
|
||||
"default": tkDefault,
|
||||
"login": tkLogin,
|
||||
"password": tkPassword,
|
||||
"account": tkAccount,
|
||||
"macdef": tkMacdef,
|
||||
"#": tkComment,
|
||||
}
|
||||
|
||||
type Netrc struct {
|
||||
tokens []*token
|
||||
machines []*Machine
|
||||
macros Macros
|
||||
updateLock sync.Mutex
|
||||
}
|
||||
|
||||
// FindMachine returns the Machine in n named by name. If a machine named by
|
||||
// name exists, it is returned. If no Machine with name name is found and there
|
||||
// is a ``default'' machine, the ``default'' machine is returned. Otherwise, nil
|
||||
// is returned.
|
||||
func (n *Netrc) FindMachine(name string) (m *Machine) {
|
||||
// TODO(bgentry): not safe for concurrency
|
||||
var def *Machine
|
||||
for _, m = range n.machines {
|
||||
if m.Name == name {
|
||||
return m
|
||||
}
|
||||
if m.IsDefault() {
|
||||
def = m
|
||||
}
|
||||
}
|
||||
if def == nil {
|
||||
return nil
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// MarshalText implements the encoding.TextMarshaler interface to encode a
|
||||
// Netrc into text format.
|
||||
func (n *Netrc) MarshalText() (text []byte, err error) {
|
||||
// TODO(bgentry): not safe for concurrency
|
||||
for i := range n.tokens {
|
||||
switch n.tokens[i].kind {
|
||||
case tkComment, tkDefault, tkWhitespace: // always append these types
|
||||
text = append(text, n.tokens[i].rawkind...)
|
||||
default:
|
||||
if n.tokens[i].value != "" { // skip empty-value tokens
|
||||
text = append(text, n.tokens[i].rawkind...)
|
||||
}
|
||||
}
|
||||
if n.tokens[i].kind == tkMacdef {
|
||||
text = append(text, ' ')
|
||||
text = append(text, n.tokens[i].macroName...)
|
||||
}
|
||||
text = append(text, n.tokens[i].rawvalue...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (n *Netrc) NewMachine(name, login, password, account string) *Machine {
|
||||
n.updateLock.Lock()
|
||||
defer n.updateLock.Unlock()
|
||||
|
||||
prefix := "\n"
|
||||
if len(n.tokens) == 0 {
|
||||
prefix = ""
|
||||
}
|
||||
m := &Machine{
|
||||
Name: name,
|
||||
Login: login,
|
||||
Password: password,
|
||||
Account: account,
|
||||
|
||||
nametoken: &token{
|
||||
kind: tkMachine,
|
||||
rawkind: []byte(prefix + "machine"),
|
||||
value: name,
|
||||
rawvalue: []byte(" " + name),
|
||||
},
|
||||
logintoken: &token{
|
||||
kind: tkLogin,
|
||||
rawkind: []byte("\n\tlogin"),
|
||||
value: login,
|
||||
rawvalue: []byte(" " + login),
|
||||
},
|
||||
passtoken: &token{
|
||||
kind: tkPassword,
|
||||
rawkind: []byte("\n\tpassword"),
|
||||
value: password,
|
||||
rawvalue: []byte(" " + password),
|
||||
},
|
||||
accounttoken: &token{
|
||||
kind: tkAccount,
|
||||
rawkind: []byte("\n\taccount"),
|
||||
value: account,
|
||||
rawvalue: []byte(" " + account),
|
||||
},
|
||||
}
|
||||
n.insertMachineTokensBeforeDefault(m)
|
||||
for i := range n.machines {
|
||||
if n.machines[i].IsDefault() {
|
||||
n.machines = append(append(n.machines[:i], m), n.machines[i:]...)
|
||||
return m
|
||||
}
|
||||
}
|
||||
n.machines = append(n.machines, m)
|
||||
return m
|
||||
}
|
||||
|
||||
func (n *Netrc) insertMachineTokensBeforeDefault(m *Machine) {
|
||||
newtokens := []*token{m.nametoken}
|
||||
if m.logintoken.value != "" {
|
||||
newtokens = append(newtokens, m.logintoken)
|
||||
}
|
||||
if m.passtoken.value != "" {
|
||||
newtokens = append(newtokens, m.passtoken)
|
||||
}
|
||||
if m.accounttoken.value != "" {
|
||||
newtokens = append(newtokens, m.accounttoken)
|
||||
}
|
||||
for i := range n.tokens {
|
||||
if n.tokens[i].kind == tkDefault {
|
||||
// found the default, now insert tokens before it
|
||||
n.tokens = append(n.tokens[:i], append(newtokens, n.tokens[i:]...)...)
|
||||
return
|
||||
}
|
||||
}
|
||||
// didn't find a default, just add the newtokens to the end
|
||||
n.tokens = append(n.tokens, newtokens...)
|
||||
return
|
||||
}
|
||||
|
||||
func (n *Netrc) RemoveMachine(name string) {
|
||||
n.updateLock.Lock()
|
||||
defer n.updateLock.Unlock()
|
||||
|
||||
for i := range n.machines {
|
||||
if n.machines[i] != nil && n.machines[i].Name == name {
|
||||
m := n.machines[i]
|
||||
for _, t := range []*token{
|
||||
m.nametoken, m.logintoken, m.passtoken, m.accounttoken,
|
||||
} {
|
||||
n.removeToken(t)
|
||||
}
|
||||
n.machines = append(n.machines[:i], n.machines[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Netrc) removeToken(t *token) {
|
||||
if t != nil {
|
||||
for i := range n.tokens {
|
||||
if n.tokens[i] == t {
|
||||
n.tokens = append(n.tokens[:i], n.tokens[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Machine contains information about a remote machine.
|
||||
type Machine struct {
|
||||
Name string
|
||||
Login string
|
||||
Password string
|
||||
Account string
|
||||
|
||||
nametoken *token
|
||||
logintoken *token
|
||||
passtoken *token
|
||||
accounttoken *token
|
||||
}
|
||||
|
||||
// IsDefault returns true if the machine is a "default" token, denoted by an
|
||||
// empty name.
|
||||
func (m *Machine) IsDefault() bool {
|
||||
return m.Name == ""
|
||||
}
|
||||
|
||||
// UpdatePassword sets the password for the Machine m.
|
||||
func (m *Machine) UpdatePassword(newpass string) {
|
||||
m.Password = newpass
|
||||
updateTokenValue(m.passtoken, newpass)
|
||||
}
|
||||
|
||||
// UpdateLogin sets the login for the Machine m.
|
||||
func (m *Machine) UpdateLogin(newlogin string) {
|
||||
m.Login = newlogin
|
||||
updateTokenValue(m.logintoken, newlogin)
|
||||
}
|
||||
|
||||
// UpdateAccount sets the login for the Machine m.
|
||||
func (m *Machine) UpdateAccount(newaccount string) {
|
||||
m.Account = newaccount
|
||||
updateTokenValue(m.accounttoken, newaccount)
|
||||
}
|
||||
|
||||
func updateTokenValue(t *token, value string) {
|
||||
oldvalue := t.value
|
||||
t.value = value
|
||||
newraw := make([]byte, len(t.rawvalue))
|
||||
copy(newraw, t.rawvalue)
|
||||
t.rawvalue = append(
|
||||
bytes.TrimSuffix(newraw, []byte(oldvalue)),
|
||||
[]byte(value)...,
|
||||
)
|
||||
}
|
||||
|
||||
// Macros contains all the macro definitions in a netrc file.
|
||||
type Macros map[string]string
|
||||
|
||||
type token struct {
|
||||
kind tkType
|
||||
macroName string
|
||||
value string
|
||||
rawkind []byte
|
||||
rawvalue []byte
|
||||
}
|
||||
|
||||
// Error represents a netrc file parse error.
|
||||
type Error struct {
|
||||
LineNum int // Line number
|
||||
Msg string // Error message
|
||||
}
|
||||
|
||||
// Error returns a string representation of error e.
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("line %d: %s", e.LineNum, e.Msg)
|
||||
}
|
||||
|
||||
func (e *Error) BadDefaultOrder() bool {
|
||||
return e.Msg == errBadDefaultOrder
|
||||
}
|
||||
|
||||
const errBadDefaultOrder = "default token must appear after all machine tokens"
|
||||
|
||||
// scanLinesKeepPrefix is a split function for a Scanner that returns each line
|
||||
// of text. The returned token may include newlines if they are before the
|
||||
// first non-space character. The returned line may be empty. The end-of-line
|
||||
// marker is one optional carriage return followed by one mandatory newline. In
|
||||
// regular expression notation, it is `\r?\n`. The last non-empty line of
|
||||
// input will be returned even if it has no newline.
|
||||
func scanLinesKeepPrefix(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
if atEOF && len(data) == 0 {
|
||||
return 0, nil, nil
|
||||
}
|
||||
// Skip leading spaces.
|
||||
start := 0
|
||||
for width := 0; start < len(data); start += width {
|
||||
var r rune
|
||||
r, width = utf8.DecodeRune(data[start:])
|
||||
if !unicode.IsSpace(r) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i := bytes.IndexByte(data[start:], '\n'); i >= 0 {
|
||||
// We have a full newline-terminated line.
|
||||
return start + i, data[0 : start+i], nil
|
||||
}
|
||||
// If we're at EOF, we have a final, non-terminated line. Return it.
|
||||
if atEOF {
|
||||
return len(data), data, nil
|
||||
}
|
||||
// Request more data.
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
// scanWordsKeepPrefix is a split function for a Scanner that returns each
|
||||
// space-separated word of text, with prefixing spaces included. It will never
|
||||
// return an empty string. The definition of space is set by unicode.IsSpace.
|
||||
//
|
||||
// Adapted from bufio.ScanWords().
|
||||
func scanTokensKeepPrefix(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
// Skip leading spaces.
|
||||
start := 0
|
||||
for width := 0; start < len(data); start += width {
|
||||
var r rune
|
||||
r, width = utf8.DecodeRune(data[start:])
|
||||
if !unicode.IsSpace(r) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if atEOF && len(data) == 0 || start == len(data) {
|
||||
return len(data), data, nil
|
||||
}
|
||||
if len(data) > start && data[start] == '#' {
|
||||
return scanLinesKeepPrefix(data, atEOF)
|
||||
}
|
||||
// Scan until space, marking end of word.
|
||||
for width, i := 0, start; i < len(data); i += width {
|
||||
var r rune
|
||||
r, width = utf8.DecodeRune(data[i:])
|
||||
if unicode.IsSpace(r) {
|
||||
return i, data[:i], nil
|
||||
}
|
||||
}
|
||||
// If we're at EOF, we have a final, non-empty, non-terminated word. Return it.
|
||||
if atEOF && len(data) > start {
|
||||
return len(data), data, nil
|
||||
}
|
||||
// Request more data.
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
func newToken(rawb []byte) (*token, error) {
|
||||
_, tkind, err := bufio.ScanWords(rawb, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ok bool
|
||||
t := token{rawkind: rawb}
|
||||
t.kind, ok = keywords[string(tkind)]
|
||||
if !ok {
|
||||
trimmed := strings.TrimSpace(string(tkind))
|
||||
if trimmed == "" {
|
||||
t.kind = tkWhitespace // whitespace-only, should happen only at EOF
|
||||
return &t, nil
|
||||
}
|
||||
if strings.HasPrefix(trimmed, "#") {
|
||||
t.kind = tkComment // this is a comment
|
||||
return &t, nil
|
||||
}
|
||||
return &t, fmt.Errorf("keyword expected; got " + string(tkind))
|
||||
}
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
func scanValue(scanner *bufio.Scanner, pos int) ([]byte, string, int, error) {
|
||||
if scanner.Scan() {
|
||||
raw := scanner.Bytes()
|
||||
pos += bytes.Count(raw, []byte{'\n'})
|
||||
return raw, strings.TrimSpace(string(raw)), pos, nil
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, "", pos, &Error{pos, err.Error()}
|
||||
}
|
||||
return nil, "", pos, nil
|
||||
}
|
||||
|
||||
func parse(r io.Reader, pos int) (*Netrc, error) {
|
||||
b, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nrc := Netrc{machines: make([]*Machine, 0, 20), macros: make(Macros, 10)}
|
||||
|
||||
defaultSeen := false
|
||||
var currentMacro *token
|
||||
var m *Machine
|
||||
var t *token
|
||||
scanner := bufio.NewScanner(bytes.NewReader(b))
|
||||
scanner.Split(scanTokensKeepPrefix)
|
||||
|
||||
for scanner.Scan() {
|
||||
rawb := scanner.Bytes()
|
||||
if len(rawb) == 0 {
|
||||
break
|
||||
}
|
||||
pos += bytes.Count(rawb, []byte{'\n'})
|
||||
t, err = newToken(rawb)
|
||||
if err != nil {
|
||||
if currentMacro == nil {
|
||||
return nil, &Error{pos, err.Error()}
|
||||
}
|
||||
currentMacro.rawvalue = append(currentMacro.rawvalue, rawb...)
|
||||
continue
|
||||
}
|
||||
|
||||
if currentMacro != nil && bytes.Contains(rawb, []byte{'\n', '\n'}) {
|
||||
// if macro rawvalue + rawb would contain \n\n, then macro def is over
|
||||
currentMacro.value = strings.TrimLeft(string(currentMacro.rawvalue), "\r\n")
|
||||
nrc.macros[currentMacro.macroName] = currentMacro.value
|
||||
currentMacro = nil
|
||||
}
|
||||
|
||||
switch t.kind {
|
||||
case tkMacdef:
|
||||
if _, t.macroName, pos, err = scanValue(scanner, pos); err != nil {
|
||||
return nil, &Error{pos, err.Error()}
|
||||
}
|
||||
currentMacro = t
|
||||
case tkDefault:
|
||||
if defaultSeen {
|
||||
return nil, &Error{pos, "multiple default token"}
|
||||
}
|
||||
if m != nil {
|
||||
nrc.machines, m = append(nrc.machines, m), nil
|
||||
}
|
||||
m = new(Machine)
|
||||
m.Name = ""
|
||||
defaultSeen = true
|
||||
case tkMachine:
|
||||
if defaultSeen {
|
||||
return nil, &Error{pos, errBadDefaultOrder}
|
||||
}
|
||||
if m != nil {
|
||||
nrc.machines, m = append(nrc.machines, m), nil
|
||||
}
|
||||
m = new(Machine)
|
||||
if t.rawvalue, m.Name, pos, err = scanValue(scanner, pos); err != nil {
|
||||
return nil, &Error{pos, err.Error()}
|
||||
}
|
||||
t.value = m.Name
|
||||
m.nametoken = t
|
||||
case tkLogin:
|
||||
if m == nil || m.Login != "" {
|
||||
return nil, &Error{pos, "unexpected token login "}
|
||||
}
|
||||
if t.rawvalue, m.Login, pos, err = scanValue(scanner, pos); err != nil {
|
||||
return nil, &Error{pos, err.Error()}
|
||||
}
|
||||
t.value = m.Login
|
||||
m.logintoken = t
|
||||
case tkPassword:
|
||||
if m == nil || m.Password != "" {
|
||||
return nil, &Error{pos, "unexpected token password"}
|
||||
}
|
||||
if t.rawvalue, m.Password, pos, err = scanValue(scanner, pos); err != nil {
|
||||
return nil, &Error{pos, err.Error()}
|
||||
}
|
||||
t.value = m.Password
|
||||
m.passtoken = t
|
||||
case tkAccount:
|
||||
if m == nil || m.Account != "" {
|
||||
return nil, &Error{pos, "unexpected token account"}
|
||||
}
|
||||
if t.rawvalue, m.Account, pos, err = scanValue(scanner, pos); err != nil {
|
||||
return nil, &Error{pos, err.Error()}
|
||||
}
|
||||
t.value = m.Account
|
||||
m.accounttoken = t
|
||||
}
|
||||
|
||||
nrc.tokens = append(nrc.tokens, t)
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if m != nil {
|
||||
nrc.machines, m = append(nrc.machines, m), nil
|
||||
}
|
||||
return &nrc, nil
|
||||
}
|
||||
|
||||
// ParseFile opens the file at filename and then passes its io.Reader to
|
||||
// Parse().
|
||||
func ParseFile(filename string) (*Netrc, error) {
|
||||
fd, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fd.Close()
|
||||
return Parse(fd)
|
||||
}
|
||||
|
||||
// Parse parses from the the Reader r as a netrc file and returns the set of
|
||||
// machine information and macros defined in it. The ``default'' machine,
|
||||
// which is intended to be used when no machine name matches, is identified
|
||||
// by an empty machine name. There can be only one ``default'' machine.
|
||||
//
|
||||
// If there is a parsing error, an Error is returned.
|
||||
func Parse(r io.Reader) (*Netrc, error) {
|
||||
return parse(r, 1)
|
||||
}
|
||||
|
||||
// FindMachine parses the netrc file identified by filename and returns the
|
||||
// Machine named by name. If a problem occurs parsing the file at filename, an
|
||||
// error is returned. If a machine named by name exists, it is returned. If no
|
||||
// Machine with name name is found and there is a ``default'' machine, the
|
||||
// ``default'' machine is returned. Otherwise, nil is returned.
|
||||
func FindMachine(filename, name string) (m *Machine, err error) {
|
||||
n, err := ParseFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return n.FindMachine(name), nil
|
||||
}
|
559
vendor/_nuts/github.com/bgentry/go-netrc/netrc/netrc_test.go
vendored
Normal file
559
vendor/_nuts/github.com/bgentry/go-netrc/netrc/netrc_test.go
vendored
Normal file
@ -0,0 +1,559 @@
|
||||
// Copyright © 2010 Fazlul Shahriar <fshahriar@gmail.com> and
|
||||
// Copyright © 2014 Blake Gentry <blakesgentry@gmail.com>.
|
||||
// See LICENSE file for license details.
|
||||
|
||||
package netrc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var expectedMachines = []*Machine{
|
||||
&Machine{Name: "mail.google.com", Login: "joe@gmail.com", Password: "somethingSecret", Account: "justagmail"},
|
||||
&Machine{Name: "ray", Login: "demo", Password: "mypassword", Account: ""},
|
||||
&Machine{Name: "weirdlogin", Login: "uname", Password: "pass#pass", Account: ""},
|
||||
&Machine{Name: "", Login: "anonymous", Password: "joe@example.com", Account: ""},
|
||||
}
|
||||
var expectedMacros = Macros{
|
||||
"allput": "put src/*",
|
||||
"allput2": " put src/*\nput src2/*",
|
||||
}
|
||||
|
||||
func eqMachine(a *Machine, b *Machine) bool {
|
||||
return a.Name == b.Name &&
|
||||
a.Login == b.Login &&
|
||||
a.Password == b.Password &&
|
||||
a.Account == b.Account
|
||||
}
|
||||
|
||||
func testExpected(n *Netrc, t *testing.T) {
|
||||
if len(expectedMachines) != len(n.machines) {
|
||||
t.Errorf("expected %d machines, got %d", len(expectedMachines), len(n.machines))
|
||||
} else {
|
||||
for i, e := range expectedMachines {
|
||||
if !eqMachine(e, n.machines[i]) {
|
||||
t.Errorf("bad machine; expected %v, got %v\n", e, n.machines[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(expectedMacros) != len(n.macros) {
|
||||
t.Errorf("expected %d macros, got %d", len(expectedMacros), len(n.macros))
|
||||
} else {
|
||||
for k, v := range expectedMacros {
|
||||
if v != n.macros[k] {
|
||||
t.Errorf("bad macro for %s; expected %q, got %q\n", k, v, n.macros[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var newTokenTests = []struct {
|
||||
rawkind string
|
||||
tkind tkType
|
||||
}{
|
||||
{"machine", tkMachine},
|
||||
{"\n\n\tmachine", tkMachine},
|
||||
{"\n machine", tkMachine},
|
||||
{"default", tkDefault},
|
||||
{"login", tkLogin},
|
||||
{"password", tkPassword},
|
||||
{"account", tkAccount},
|
||||
{"macdef", tkMacdef},
|
||||
{"\n # comment stuff ", tkComment},
|
||||
{"\n # I am another comment", tkComment},
|
||||
{"\n\t\n ", tkWhitespace},
|
||||
}
|
||||
|
||||
var newTokenInvalidTests = []string{
|
||||
" junk",
|
||||
"sdfdsf",
|
||||
"account#unspaced comment",
|
||||
}
|
||||
|
||||
func TestNewToken(t *testing.T) {
|
||||
for _, tktest := range newTokenTests {
|
||||
tok, err := newToken([]byte(tktest.rawkind))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tok.kind != tktest.tkind {
|
||||
t.Errorf("expected tok.kind %d, got %d", tktest.tkind, tok.kind)
|
||||
}
|
||||
if string(tok.rawkind) != tktest.rawkind {
|
||||
t.Errorf("expected tok.rawkind %q, got %q", tktest.rawkind, string(tok.rawkind))
|
||||
}
|
||||
}
|
||||
|
||||
for _, tktest := range newTokenInvalidTests {
|
||||
_, err := newToken([]byte(tktest))
|
||||
if err == nil {
|
||||
t.Errorf("expected error with %q, got none", tktest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
r := netrcReader("examples/good.netrc", t)
|
||||
n, err := Parse(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testExpected(n, t)
|
||||
}
|
||||
|
||||
func TestParseFile(t *testing.T) {
|
||||
n, err := ParseFile("examples/good.netrc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testExpected(n, t)
|
||||
|
||||
_, err = ParseFile("examples/bad_default_order.netrc")
|
||||
if err == nil {
|
||||
t.Error("expected an error parsing bad_default_order.netrc, got none")
|
||||
} else if !err.(*Error).BadDefaultOrder() {
|
||||
t.Error("expected BadDefaultOrder() to be true, got false")
|
||||
}
|
||||
|
||||
_, err = ParseFile("examples/this_file_doesnt_exist.netrc")
|
||||
if err == nil {
|
||||
t.Error("expected an error loading this_file_doesnt_exist.netrc, got none")
|
||||
} else if _, ok := err.(*os.PathError); !ok {
|
||||
t.Errorf("expected *os.Error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindMachine(t *testing.T) {
|
||||
m, err := FindMachine("examples/good.netrc", "ray")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !eqMachine(m, expectedMachines[1]) {
|
||||
t.Errorf("bad machine; expected %v, got %v\n", expectedMachines[1], m)
|
||||
}
|
||||
if m.IsDefault() {
|
||||
t.Errorf("expected m.IsDefault() to be false")
|
||||
}
|
||||
|
||||
m, err = FindMachine("examples/good.netrc", "non.existent")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !eqMachine(m, expectedMachines[3]) {
|
||||
t.Errorf("bad machine; expected %v, got %v\n", expectedMachines[3], m)
|
||||
}
|
||||
if !m.IsDefault() {
|
||||
t.Errorf("expected m.IsDefault() to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetrcFindMachine(t *testing.T) {
|
||||
n, err := ParseFile("examples/good.netrc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
m := n.FindMachine("ray")
|
||||
if !eqMachine(m, expectedMachines[1]) {
|
||||
t.Errorf("bad machine; expected %v, got %v\n", expectedMachines[1], m)
|
||||
}
|
||||
if m.IsDefault() {
|
||||
t.Errorf("expected def to be false")
|
||||
}
|
||||
|
||||
n = &Netrc{}
|
||||
m = n.FindMachine("nonexistent")
|
||||
if m != nil {
|
||||
t.Errorf("expected nil, got %v", m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalText(t *testing.T) {
|
||||
// load up expected netrc Marshal output
|
||||
expected, err := ioutil.ReadAll(netrcReader("examples/good.netrc", t))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
n, err := ParseFile("examples/good.netrc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
result, err := n.MarshalText()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(result) != string(expected) {
|
||||
t.Errorf("expected:\n%q\ngot:\n%q", string(expected), string(result))
|
||||
}
|
||||
|
||||
// make sure tokens w/ no value are not serialized
|
||||
m := n.FindMachine("mail.google.com")
|
||||
m.UpdatePassword("")
|
||||
result, err = n.MarshalText()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(string(result), "\tpassword \n") {
|
||||
fmt.Println(string(result))
|
||||
t.Errorf("expected zero-value password token to not be serialzed")
|
||||
}
|
||||
}
|
||||
|
||||
var newMachineTests = []struct {
|
||||
name string
|
||||
login string
|
||||
password string
|
||||
account string
|
||||
}{
|
||||
{"heroku.com", "dodging-samurai-42@heroku.com", "octocatdodgeballchampions", "2011+2013"},
|
||||
{"bgentry.io", "special@test.com", "noacct", ""},
|
||||
{"github.io", "2@test.com", "", "acctwithnopass"},
|
||||
{"someotherapi.com", "", "passonly", ""},
|
||||
}
|
||||
|
||||
func TestNewMachine(t *testing.T) {
|
||||
n, err := ParseFile("examples/good.netrc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testNewMachine(t, n)
|
||||
n = &Netrc{}
|
||||
testNewMachine(t, n)
|
||||
|
||||
// make sure that tokens without a value are not serialized at all
|
||||
for _, test := range newMachineTests {
|
||||
n = &Netrc{}
|
||||
_ = n.NewMachine(test.name, test.login, test.password, test.account)
|
||||
|
||||
bodyb, _ := n.MarshalText()
|
||||
body := string(bodyb)
|
||||
|
||||
// ensure desired values are present when they should be
|
||||
if !strings.Contains(body, "machine") {
|
||||
t.Errorf("NewMachine() %s missing keyword 'machine'", test.name)
|
||||
}
|
||||
if !strings.Contains(body, test.name) {
|
||||
t.Errorf("NewMachine() %s missing value %q", test.name, test.name)
|
||||
}
|
||||
if test.login != "" && !strings.Contains(body, "login "+test.login) {
|
||||
t.Errorf("NewMachine() %s missing value %q", test.name, "login "+test.login)
|
||||
}
|
||||
if test.password != "" && !strings.Contains(body, "password "+test.password) {
|
||||
t.Errorf("NewMachine() %s missing value %q", test.name, "password "+test.password)
|
||||
}
|
||||
if test.account != "" && !strings.Contains(body, "account "+test.account) {
|
||||
t.Errorf("NewMachine() %s missing value %q", test.name, "account "+test.account)
|
||||
}
|
||||
|
||||
// ensure undesired values are not present when they shouldn't be
|
||||
if test.login == "" && strings.Contains(body, "login") {
|
||||
t.Errorf("NewMachine() %s contains unexpected value %q", test.name, "login")
|
||||
}
|
||||
if test.password == "" && strings.Contains(body, "password") {
|
||||
t.Errorf("NewMachine() %s contains unexpected value %q", test.name, "password")
|
||||
}
|
||||
if test.account == "" && strings.Contains(body, "account") {
|
||||
t.Errorf("NewMachine() %s contains unexpected value %q", test.name, "account")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testNewMachine(t *testing.T, n *Netrc) {
|
||||
for _, test := range newMachineTests {
|
||||
mcount := len(n.machines)
|
||||
// sanity check
|
||||
bodyb, _ := n.MarshalText()
|
||||
body := string(bodyb)
|
||||
for _, value := range []string{test.name, test.login, test.password, test.account} {
|
||||
if value != "" && strings.Contains(body, value) {
|
||||
t.Errorf("MarshalText() before NewMachine() contained unexpected %q", value)
|
||||
}
|
||||
}
|
||||
|
||||
// test prefix for machine token
|
||||
prefix := "\n"
|
||||
if len(n.tokens) == 0 {
|
||||
prefix = ""
|
||||
}
|
||||
|
||||
m := n.NewMachine(test.name, test.login, test.password, test.account)
|
||||
if m == nil {
|
||||
t.Fatalf("NewMachine() returned nil")
|
||||
}
|
||||
|
||||
if len(n.machines) != mcount+1 {
|
||||
t.Errorf("n.machines count expected %d, got %d", mcount+1, len(n.machines))
|
||||
}
|
||||
// check values
|
||||
if m.Name != test.name {
|
||||
t.Errorf("m.Name expected %q, got %q", test.name, m.Name)
|
||||
}
|
||||
if m.Login != test.login {
|
||||
t.Errorf("m.Login expected %q, got %q", test.login, m.Login)
|
||||
}
|
||||
if m.Password != test.password {
|
||||
t.Errorf("m.Password expected %q, got %q", test.password, m.Password)
|
||||
}
|
||||
if m.Account != test.account {
|
||||
t.Errorf("m.Account expected %q, got %q", test.account, m.Account)
|
||||
}
|
||||
// check tokens
|
||||
checkToken(t, "nametoken", m.nametoken, tkMachine, prefix+"machine", test.name)
|
||||
checkToken(t, "logintoken", m.logintoken, tkLogin, "\n\tlogin", test.login)
|
||||
checkToken(t, "passtoken", m.passtoken, tkPassword, "\n\tpassword", test.password)
|
||||
checkToken(t, "accounttoken", m.accounttoken, tkAccount, "\n\taccount", test.account)
|
||||
// check marshal output
|
||||
bodyb, _ = n.MarshalText()
|
||||
body = string(bodyb)
|
||||
for _, value := range []string{test.name, test.login, test.password, test.account} {
|
||||
if !strings.Contains(body, value) {
|
||||
t.Errorf("MarshalText() after NewMachine() did not include %q as expected", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkToken(t *testing.T, name string, tok *token, kind tkType, rawkind, value string) {
|
||||
if tok == nil {
|
||||
t.Errorf("%s not defined", name)
|
||||
return
|
||||
}
|
||||
if tok.kind != kind {
|
||||
t.Errorf("%s expected kind %d, got %d", name, kind, tok.kind)
|
||||
}
|
||||
if string(tok.rawkind) != rawkind {
|
||||
t.Errorf("%s expected rawkind %q, got %q", name, rawkind, string(tok.rawkind))
|
||||
}
|
||||
if tok.value != value {
|
||||
t.Errorf("%s expected value %q, got %q", name, value, tok.value)
|
||||
}
|
||||
if tok.value != value {
|
||||
t.Errorf("%s expected value %q, got %q", name, value, tok.value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewMachineGoesBeforeDefault(t *testing.T) {
|
||||
n, err := ParseFile("examples/good.netrc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m := n.NewMachine("mymachine", "mylogin", "mypassword", "myaccount")
|
||||
if m2 := n.machines[len(n.machines)-2]; m2 != m {
|
||||
t.Errorf("expected machine %v, got %v", m, m2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveMachine(t *testing.T) {
|
||||
n, err := ParseFile("examples/good.netrc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []string{"mail.google.com", "weirdlogin"}
|
||||
|
||||
for _, name := range tests {
|
||||
mcount := len(n.machines)
|
||||
// sanity check
|
||||
m := n.FindMachine(name)
|
||||
if m == nil {
|
||||
t.Fatalf("machine %q not found", name)
|
||||
}
|
||||
if m.IsDefault() {
|
||||
t.Fatalf("expected machine %q, got default instead", name)
|
||||
}
|
||||
n.RemoveMachine(name)
|
||||
|
||||
if len(n.machines) != mcount-1 {
|
||||
t.Errorf("n.machines count expected %d, got %d", mcount-1, len(n.machines))
|
||||
}
|
||||
|
||||
// make sure Machine is no longer returned by FindMachine()
|
||||
if m2 := n.FindMachine(name); m2 != nil && !m2.IsDefault() {
|
||||
t.Errorf("Machine %q not removed from Machines list", name)
|
||||
}
|
||||
|
||||
// make sure tokens are not present in tokens list
|
||||
for _, token := range []*token{m.nametoken, m.logintoken, m.passtoken, m.accounttoken} {
|
||||
if token != nil {
|
||||
for _, tok2 := range n.tokens {
|
||||
if tok2 == token {
|
||||
t.Errorf("token not removed from tokens list: %v", token)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bodyb, _ := n.MarshalText()
|
||||
body := string(bodyb)
|
||||
for _, value := range []string{m.Name, m.Login, m.Password, m.Account} {
|
||||
if value != "" && strings.Contains(body, value) {
|
||||
t.Errorf("MarshalText() after RemoveMachine() contained unexpected %q", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateLogin(t *testing.T) {
|
||||
n, err := ParseFile("examples/good.netrc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
exists bool
|
||||
name string
|
||||
oldlogin string
|
||||
newlogin string
|
||||
}{
|
||||
{true, "mail.google.com", "joe@gmail.com", "joe2@gmail.com"},
|
||||
{false, "heroku.com", "", "dodging-samurai-42@heroku.com"},
|
||||
}
|
||||
|
||||
bodyb, _ := n.MarshalText()
|
||||
body := string(bodyb)
|
||||
for _, test := range tests {
|
||||
if strings.Contains(body, test.newlogin) {
|
||||
t.Errorf("MarshalText() before UpdateLogin() contained unexpected %q", test.newlogin)
|
||||
}
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
m := n.FindMachine(test.name)
|
||||
if m.IsDefault() == test.exists {
|
||||
t.Errorf("expected machine %s to not exist, but it did", test.name)
|
||||
} else {
|
||||
if !test.exists {
|
||||
m = n.NewMachine(test.name, test.newlogin, "", "")
|
||||
}
|
||||
if m == nil {
|
||||
t.Errorf("machine %s was nil", test.name)
|
||||
continue
|
||||
}
|
||||
m.UpdateLogin(test.newlogin)
|
||||
m := n.FindMachine(test.name)
|
||||
if m.Login != test.newlogin {
|
||||
t.Errorf("expected new login %q, got %q", test.newlogin, m.Login)
|
||||
}
|
||||
if m.logintoken.value != test.newlogin {
|
||||
t.Errorf("expected m.logintoken %q, got %q", test.newlogin, m.logintoken.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bodyb, _ = n.MarshalText()
|
||||
body = string(bodyb)
|
||||
for _, test := range tests {
|
||||
if test.exists && strings.Contains(body, test.oldlogin) {
|
||||
t.Errorf("MarshalText() after UpdateLogin() contained unexpected %q", test.oldlogin)
|
||||
}
|
||||
if !strings.Contains(body, test.newlogin) {
|
||||
t.Errorf("MarshalText after UpdatePassword did not contain %q as expected", test.newlogin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdatePassword(t *testing.T) {
|
||||
n, err := ParseFile("examples/good.netrc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
exists bool
|
||||
name string
|
||||
oldpassword string
|
||||
newpassword string
|
||||
}{
|
||||
{true, "ray", "mypassword", "supernewpass"},
|
||||
{false, "heroku.com", "", "octocatdodgeballchampions"},
|
||||
}
|
||||
|
||||
bodyb, _ := n.MarshalText()
|
||||
body := string(bodyb)
|
||||
for _, test := range tests {
|
||||
if test.exists && !strings.Contains(body, test.oldpassword) {
|
||||
t.Errorf("MarshalText() before UpdatePassword() did not include %q as expected", test.oldpassword)
|
||||
}
|
||||
if strings.Contains(body, test.newpassword) {
|
||||
t.Errorf("MarshalText() before UpdatePassword() contained unexpected %q", test.newpassword)
|
||||
}
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
m := n.FindMachine(test.name)
|
||||
if m.IsDefault() == test.exists {
|
||||
t.Errorf("expected machine %s to not exist, but it did", test.name)
|
||||
} else {
|
||||
if !test.exists {
|
||||
m = n.NewMachine(test.name, "", test.newpassword, "")
|
||||
}
|
||||
if m == nil {
|
||||
t.Errorf("machine %s was nil", test.name)
|
||||
continue
|
||||
}
|
||||
m.UpdatePassword(test.newpassword)
|
||||
m = n.FindMachine(test.name)
|
||||
if m.Password != test.newpassword {
|
||||
t.Errorf("expected new password %q, got %q", test.newpassword, m.Password)
|
||||
}
|
||||
if m.passtoken.value != test.newpassword {
|
||||
t.Errorf("expected m.passtoken %q, got %q", test.newpassword, m.passtoken.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bodyb, _ = n.MarshalText()
|
||||
body = string(bodyb)
|
||||
for _, test := range tests {
|
||||
if test.exists && strings.Contains(body, test.oldpassword) {
|
||||
t.Errorf("MarshalText() after UpdatePassword() contained unexpected %q", test.oldpassword)
|
||||
}
|
||||
if !strings.Contains(body, test.newpassword) {
|
||||
t.Errorf("MarshalText() after UpdatePassword() did not contain %q as expected", test.newpassword)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewFile(t *testing.T) {
|
||||
var n Netrc
|
||||
|
||||
result, err := n.MarshalText()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(result) != "" {
|
||||
t.Errorf("expected empty result=\"\", got %q", string(result))
|
||||
}
|
||||
|
||||
n.NewMachine("netrctest.heroku.com", "auser", "apassword", "")
|
||||
|
||||
result, err = n.MarshalText()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := `machine netrctest.heroku.com
|
||||
login auser
|
||||
password apassword`
|
||||
|
||||
if string(result) != expected {
|
||||
t.Errorf("expected result:\n%q\ngot:\n%q", expected, string(result))
|
||||
}
|
||||
}
|
||||
|
||||
func netrcReader(filename string, t *testing.T) io.Reader {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return bytes.NewReader(b)
|
||||
}
|
Loading…
Reference in New Issue
Block a user