Skip to content

Commit b8edec2

Browse files
committed
ability to update password and add new machines
Noted several methods that were not yet safe for concurrent use.
1 parent 8867249 commit b8edec2

File tree

2 files changed

+210
-18
lines changed

2 files changed

+210
-18
lines changed

netrc/netrc.go

Lines changed: 74 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,31 +37,36 @@ var keywords = map[string]tkType{
3737
}
3838

3939
type Netrc struct {
40-
pre string
4140
tokens []*token
4241
machines []*Machine
4342
macros Macros
4443
}
4544

46-
func (n *Netrc) FindMachine(name string) (*Machine, error) {
45+
// FindMachine returns the Machine in n named by name. If a machine named by
46+
// name exists, it is returned and isDefault is false. If no Machine with name
47+
// name is found and there is a ``default'' machine, the ``default'' machine is
48+
// returned and isDefault is true. Otherwise, an error is returned.
49+
func (n *Netrc) FindMachine(name string) (m *Machine, isDefault bool, err error) {
50+
// TODO(bgentry): not safe for concurrency
4751
var def *Machine
48-
for _, m := range n.machines {
52+
for _, m = range n.machines {
4953
if m.Name == name {
50-
return m, nil
54+
return m, false, nil
5155
}
5256
if m.Name == "" {
5357
def = m
5458
}
5559
}
5660
if def == nil {
57-
return nil, errors.New("no machine found")
61+
return nil, false, errors.New("no machine found")
5862
}
59-
return def, nil
63+
return def, true, nil
6064
}
6165

6266
// MarshalText implements the encoding.TextMarshaler interface to encode a
6367
// Netrc into text format.
6468
func (n *Netrc) MarshalText() (text []byte, err error) {
69+
// TODO(bgentry): not safe for concurrency
6570
for i := range n.tokens {
6671
text = append(text, n.tokens[i].rawkind...)
6772
switch n.tokens[i].kind {
@@ -74,12 +79,63 @@ func (n *Netrc) MarshalText() (text []byte, err error) {
7479
return
7580
}
7681

82+
func (n *Netrc) NewMachine(name, login, password, account string) *Machine {
83+
// TODO(bgentry): not safe for concurrency
84+
m := &Machine{
85+
Name: name,
86+
Login: login,
87+
Password: password,
88+
Account: account,
89+
90+
nametoken: &token{
91+
kind: tkMachine,
92+
rawkind: []byte("\nmachine"),
93+
value: name,
94+
},
95+
logintoken: &token{
96+
kind: tkLogin,
97+
rawkind: []byte("\n\tlogin"),
98+
value: login,
99+
},
100+
passtoken: &token{
101+
kind: tkPassword,
102+
rawkind: []byte("\n\tpassword"),
103+
value: password,
104+
},
105+
accounttoken: &token{
106+
kind: tkAccount,
107+
rawkind: []byte("\n\taccount"),
108+
value: account,
109+
},
110+
}
111+
n.machines = append(n.machines, m)
112+
return m
113+
}
114+
77115
// Machine contains information about a remote machine.
78116
type Machine struct {
79117
Name string
80118
Login string
81119
Password string
82120
Account string
121+
122+
nametoken *token
123+
logintoken *token
124+
passtoken *token
125+
accounttoken *token
126+
}
127+
128+
// UpdatePassword sets the password for the Machine m.
129+
func (m *Machine) UpdatePassword(newpass string) error {
130+
if len(newpass) == 0 {
131+
return fmt.Errorf("newpass must be at least 1 letter")
132+
}
133+
m.Password = newpass
134+
oldpass := m.passtoken.value
135+
m.passtoken.value = newpass
136+
m.passtoken.rawvalue = bytes.TrimSuffix(m.passtoken.rawvalue, []byte(oldpass))
137+
m.passtoken.rawvalue = append(m.passtoken.rawvalue, []byte(newpass)...)
138+
return nil
83139
}
84140

85141
// Macros contains all the macro definitions in a netrc file.
@@ -114,7 +170,7 @@ const errBadDefaultOrder = "default token must appear after all machine tokens"
114170
// of text. The returned token may include newlines if they are before the
115171
// first non-space character. The returned line may be empty. The end-of-line
116172
// marker is one optional carriage return followed by one mandatory newline. In
117-
// regular expression notation, it is `\r?\n`. The last non-empty line of
173+
// regular expression notation, it is `\r?\n`. The last non-empty line of
118174
// input will be returned even if it has no newline.
119175
func scanLinesKeepPrefix(data []byte, atEOF bool) (advance int, token []byte, err error) {
120176
if atEOF && len(data) == 0 {
@@ -278,6 +334,7 @@ func parse(r io.Reader, pos int) (*Netrc, error) {
278334
return nil, &Error{pos, err.Error()}
279335
}
280336
t.value = m.Name
337+
m.nametoken = t
281338
case tkLogin:
282339
if m == nil || m.Login != "" {
283340
return nil, &Error{pos, "unexpected token login "}
@@ -286,6 +343,7 @@ func parse(r io.Reader, pos int) (*Netrc, error) {
286343
return nil, &Error{pos, err.Error()}
287344
}
288345
t.value = m.Login
346+
m.logintoken = t
289347
case tkPassword:
290348
if m == nil || m.Password != "" {
291349
return nil, &Error{pos, "unexpected token password"}
@@ -294,6 +352,7 @@ func parse(r io.Reader, pos int) (*Netrc, error) {
294352
return nil, &Error{pos, err.Error()}
295353
}
296354
t.value = m.Password
355+
m.passtoken = t
297356
case tkAccount:
298357
if m == nil || m.Account != "" {
299358
return nil, &Error{pos, "unexpected token account"}
@@ -302,6 +361,7 @@ func parse(r io.Reader, pos int) (*Netrc, error) {
302361
return nil, &Error{pos, err.Error()}
303362
}
304363
t.value = m.Account
364+
m.accounttoken = t
305365
}
306366

307367
nrc.tokens = append(nrc.tokens, t)
@@ -338,13 +398,15 @@ func Parse(r io.Reader) (*Netrc, error) {
338398
return parse(r, 1)
339399
}
340400

341-
// FindMachine parses the netrc file identified by filename and returns
342-
// the Machine named by name. If no Machine with name name is found, the
343-
// ``default'' machine is returned.
344-
func FindMachine(filename, name string) (*Machine, error) {
401+
// FindMachine parses the netrc file identified by filename and returns the
402+
// Machine named by name. If a machine named by name exists, it is returned and
403+
// isDefault is false. If no Machine with name name is found and there is a
404+
// ``default'' machine, the ``default'' machine is returned and isDefault is
405+
// true. Otherwise, an error is returned.
406+
func FindMachine(filename, name string) (m *Machine, isDefault bool, err error) {
345407
n, err := ParseFile(filename)
346408
if err != nil {
347-
return nil, err
409+
return nil, false, err
348410
}
349411
return n.FindMachine(name)
350412
}

netrc/netrc_test.go

Lines changed: 136 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import (
1212
)
1313

1414
var expectedMachines = []*Machine{
15-
&Machine{"mail.google.com", "joe@gmail.com", "somethingSecret", "gmail"},
16-
&Machine{"ray", "demo", "mypassword", ""},
17-
&Machine{"weirdlogin", "uname", "pass#pass", ""},
18-
&Machine{"", "anonymous", "joe@example.com", ""},
15+
&Machine{Name: "mail.google.com", Login: "joe@gmail.com", Password: "somethingSecret", Account: "gmail"},
16+
&Machine{Name: "ray", Login: "demo", Password: "mypassword", Account: ""},
17+
&Machine{Name: "weirdlogin", Login: "uname", Password: "pass#pass", Account: ""},
18+
&Machine{Name: "", Login: "anonymous", Password: "joe@example.com", Account: ""},
1919
}
2020
var expectedMacros = Macros{
2121
"allput": "put src/*",
@@ -121,21 +121,45 @@ func TestParseFile(t *testing.T) {
121121
}
122122

123123
func TestFindMachine(t *testing.T) {
124-
m, err := FindMachine("examples/good.netrc", "ray")
124+
m, def, err := FindMachine("examples/good.netrc", "ray")
125125
if err != nil {
126126
t.Fatal(err)
127127
}
128128
if !eqMachine(m, expectedMachines[1]) {
129129
t.Errorf("bad machine; expected %v, got %v\n", expectedMachines[1], m)
130130
}
131+
if def {
132+
t.Errorf("expected def to be false")
133+
}
131134

132-
m, err = FindMachine("examples/good.netrc", "non.existent")
135+
m, def, err = FindMachine("examples/good.netrc", "non.existent")
133136
if err != nil {
134137
t.Fatal(err)
135138
}
136139
if !eqMachine(m, expectedMachines[3]) {
137140
t.Errorf("bad machine; expected %v, got %v\n", expectedMachines[3], m)
138141
}
142+
if !def {
143+
t.Errorf("expected def to be true")
144+
}
145+
}
146+
147+
func TestNetrcFindMachine(t *testing.T) {
148+
n, err := ParseFile("examples/good.netrc")
149+
if err != nil {
150+
t.Fatal(err)
151+
}
152+
153+
m, def, err := n.FindMachine("ray")
154+
if err != nil {
155+
t.Fatal(err)
156+
}
157+
if !eqMachine(m, expectedMachines[1]) {
158+
t.Errorf("bad machine; expected %v, got %v\n", expectedMachines[1], m)
159+
}
160+
if def {
161+
t.Errorf("expected def to be false")
162+
}
139163
}
140164

141165
func TestMarshalText(t *testing.T) {
@@ -159,6 +183,112 @@ func TestMarshalText(t *testing.T) {
159183
}
160184
}
161185

186+
func TestNewMachine(t *testing.T) {
187+
n, err := ParseFile("examples/good.netrc")
188+
if err != nil {
189+
t.Fatal(err)
190+
}
191+
nameVal := "heroku.com"
192+
loginVal := "dodging-samurai-42@heroku.com"
193+
passwordVal := "octocatdodgeballchampions"
194+
accountVal := "someacct"
195+
m := n.NewMachine(nameVal, loginVal, passwordVal, accountVal)
196+
if m == nil {
197+
t.Fatalf("NewMachine() returned nil")
198+
}
199+
// check values
200+
if m.Name != nameVal {
201+
t.Errorf("m.Name expected %q, got %q", nameVal, m.Name)
202+
}
203+
if m.Login != loginVal {
204+
t.Errorf("m.Login expected %q, got %q", loginVal, m.Login)
205+
}
206+
if m.Password != passwordVal {
207+
t.Errorf("m.Password expected %q, got %q", passwordVal, m.Password)
208+
}
209+
if m.Account != accountVal {
210+
t.Errorf("m.Account expected %q, got %q", accountVal, m.Account)
211+
}
212+
// check tokens
213+
checkToken(t, "nametoken", m.nametoken, tkMachine, "\nmachine", nameVal)
214+
checkToken(t, "logintoken", m.logintoken, tkLogin, "\n\tlogin", loginVal)
215+
checkToken(t, "passtoken", m.passtoken, tkPassword, "\n\tpassword", passwordVal)
216+
checkToken(t, "accounttoken", m.accounttoken, tkAccount, "\n\taccount", accountVal)
217+
}
218+
219+
func checkToken(t *testing.T, name string, tok *token, kind tkType, rawkind, value string) {
220+
if tok == nil {
221+
t.Errorf("%s not defined", name)
222+
return
223+
}
224+
if tok.kind != kind {
225+
t.Errorf("%s expected kind %d, got %d", name, kind, tok.kind)
226+
}
227+
if string(tok.rawkind) != rawkind {
228+
t.Errorf("%s expected rawkind %q, got %q", name, rawkind, string(tok.rawkind))
229+
}
230+
if tok.value != value {
231+
t.Errorf("%s expected value %q, got %q", name, value, tok.value)
232+
}
233+
if tok.value != value {
234+
t.Errorf("%s expected value %q, got %q", name, value, tok.value)
235+
}
236+
}
237+
238+
type tokenss struct {
239+
kind tkType
240+
macroName string
241+
value string
242+
rawkind []byte
243+
rawvalue []byte
244+
}
245+
246+
func TestUpdatePassword(t *testing.T) {
247+
n, err := ParseFile("examples/good.netrc")
248+
if err != nil {
249+
t.Fatal(err)
250+
}
251+
252+
tests := []struct {
253+
exists bool
254+
name string
255+
newlogin string
256+
newpassword string
257+
}{
258+
{true, "ray", "joe2@gmail.com", "supernewpass"},
259+
{false, "heroku.com", "dodging-samurai-42@heroku.com", "octocatdodgeballchampions"},
260+
}
261+
262+
for _, test := range tests {
263+
m, def, err := n.FindMachine(test.name)
264+
if err != nil {
265+
t.Fatal(err)
266+
}
267+
if def == test.exists {
268+
t.Errorf("expected machine %s to not exist, but it did", test.name)
269+
} else {
270+
if !test.exists {
271+
m = n.NewMachine(test.name, test.newlogin, test.newpassword, "")
272+
}
273+
if m == nil {
274+
t.Errorf("machine %s was nil", test.name)
275+
continue
276+
}
277+
m.UpdatePassword(test.newpassword)
278+
m, _, err := n.FindMachine(test.name)
279+
if err != nil {
280+
t.Fatal(err)
281+
}
282+
if m.Password != test.newpassword {
283+
t.Errorf("expected new password %q, got %q", test.newpassword, m.Password)
284+
}
285+
if m.passtoken.value != test.newpassword {
286+
t.Errorf("expected m.passtoken %q, got %q", test.newpassword, m.passtoken.value)
287+
}
288+
}
289+
}
290+
}
291+
162292
func netrcReader(filename string, t *testing.T) io.Reader {
163293
b, err := ioutil.ReadFile(filename)
164294
if err != nil {

0 commit comments

Comments
 (0)