Skip to content

Commit 205bffb

Browse files
committed
initial commit
0 parents  commit 205bffb

File tree

6 files changed

+359
-0
lines changed

6 files changed

+359
-0
lines changed

.hgignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
syntax: glob
2+
*.8
3+
*.a

LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright © 2010 Fazlul Shahriar <[email protected]>.
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

netrc/Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright © 2010 Fazlul Shahriar <[email protected]>.
2+
# See LICENSE file for license details.
3+
4+
include $(GOROOT)/src/Make.$(GOARCH)
5+
6+
TARG=go-netrc.googlecode.com/hg/netrc
7+
GOFILES=\
8+
netrc.go\
9+
10+
include $(GOROOT)/src/Make.pkg

netrc/example.netrc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
machine mail.google.com
2+
3+
account gmail
4+
password somethingSecret
5+
6+
machine ray login demo password mypassword
7+
8+
macdef allput
9+
put src/*
10+
11+
default
12+
login anonymous
13+
14+

netrc/netrc.go

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
// Copyright © 2010 Fazlul Shahriar <[email protected]>.
2+
// See LICENSE file for license details.
3+
4+
// This package implements a parser for netrc file format.
5+
//
6+
// A netrc file usually resides in $HOME/.netrc and is traditionally used
7+
// by the ftp(1) program to look up login information (username, password,
8+
// etc.) of remote system(s). The file format is (loosely) described in
9+
// this man page: http://linux.die.net/man/5/netrc .
10+
package netrc
11+
12+
import (
13+
"bytes"
14+
"fmt"
15+
"io"
16+
"io/ioutil"
17+
"os"
18+
"unicode"
19+
"utf8"
20+
)
21+
22+
const (
23+
tkMachine = iota
24+
tkDefault
25+
tkLogin
26+
tkPassword
27+
tkAccount
28+
tkMacdef
29+
)
30+
31+
var tokenNames = []string{
32+
"Machine",
33+
"Default",
34+
"Login",
35+
"Password",
36+
"Account",
37+
"Macdef",
38+
}
39+
40+
var keywords = map[string]int{
41+
"machine": tkMachine,
42+
"default": tkDefault,
43+
"login": tkLogin,
44+
"password": tkPassword,
45+
"account": tkAccount,
46+
"macdef": tkMacdef,
47+
}
48+
49+
type Machine struct {
50+
Name string
51+
Login string
52+
Password string
53+
Account string
54+
}
55+
56+
type Macros map[string]string
57+
58+
type token struct {
59+
kind int
60+
macroName string
61+
value string
62+
}
63+
64+
type filePos struct {
65+
name string
66+
line int
67+
}
68+
69+
type Error struct {
70+
Filename string
71+
LineNum int // Line number
72+
Msg string // Error message
73+
}
74+
75+
func (e *Error) String() string {
76+
return fmt.Sprintf("%s:%d: %s", e.Filename, e.LineNum, e.Msg)
77+
}
78+
79+
func getWord(b []byte, pos *filePos) (string, []byte) {
80+
// Skip over leading whitespace
81+
i := 0
82+
for i < len(b) {
83+
r, size := utf8.DecodeRune(b[i:])
84+
if r == '\n' {
85+
pos.line++
86+
}
87+
if !unicode.IsSpace(r) {
88+
break
89+
}
90+
i += size
91+
}
92+
b = b[i:]
93+
94+
// Find end of word
95+
i = bytes.IndexFunc(b, unicode.IsSpace)
96+
if i < 0 {
97+
i = len(b)
98+
}
99+
return string(b[0:i]), b[i:]
100+
}
101+
102+
func getToken(b []byte, pos *filePos) ([]byte, *token, os.Error) {
103+
word, b := getWord(b, pos)
104+
if word == "" {
105+
return b, nil, nil // EOF reached
106+
}
107+
108+
t := new(token)
109+
var ok bool
110+
t.kind, ok = keywords[word]
111+
if !ok {
112+
return b, nil, &Error{pos.name, pos.line, "keyword expected; got " + word}
113+
}
114+
if t.kind == tkDefault {
115+
return b, t, nil
116+
}
117+
118+
word, b = getWord(b, pos)
119+
if word == "" {
120+
return b, nil, &Error{pos.name, pos.line, "word expected"}
121+
}
122+
if t.kind == tkMacdef {
123+
t.macroName = word
124+
125+
// Macro value starts on next line. The rest of current line
126+
// should contain nothing but whitespace
127+
i := 0
128+
for i < len(b) {
129+
r, size := utf8.DecodeRune(b[i:])
130+
if r == '\n' {
131+
i += size
132+
pos.line++
133+
break
134+
}
135+
if !unicode.IsSpace(r) {
136+
return b, nil, &Error{pos.name, pos.line, "unexpected word"}
137+
}
138+
i += size
139+
}
140+
b = b[i:]
141+
142+
// Find end of macro value
143+
i = bytes.Index(b, []byte("\n\n"))
144+
if i < 0 { // EOF reached
145+
i = len(b)
146+
}
147+
t.value = string(b[0:i])
148+
149+
return b[i:], t, nil
150+
}
151+
t.value = word
152+
return b, t, nil
153+
}
154+
155+
func appendMach(mach []*Machine, m *Machine) []*Machine {
156+
n := len(mach)
157+
if n+1 > cap(mach) {
158+
mach1 := make([]*Machine, 2*cap(mach)+10)
159+
copy(mach1[0:n], mach)
160+
mach = mach1[0:n]
161+
}
162+
mach = mach[0 : n+1]
163+
mach[n] = m
164+
return mach
165+
}
166+
167+
func parse(r io.Reader, pos *filePos) ([]*Machine, Macros, os.Error) {
168+
// TODO(fhs): Clear memory containing password.
169+
b, err := ioutil.ReadAll(r)
170+
if err != nil {
171+
return nil, nil, err
172+
}
173+
174+
mach := make([]*Machine, 0, 20)
175+
mac := make(Macros, 10)
176+
var defaultSeen bool
177+
var m *Machine
178+
var t *token
179+
for {
180+
b, t, err = getToken(b, pos)
181+
if err != nil {
182+
return nil, nil, err
183+
}
184+
if t == nil {
185+
break
186+
}
187+
switch t.kind {
188+
case tkMacdef:
189+
mac[t.macroName] = t.value
190+
case tkDefault:
191+
if defaultSeen {
192+
return nil, nil, &Error{pos.name, pos.line, "multiple default token"}
193+
}
194+
if m != nil {
195+
mach, m = appendMach(mach, m), nil
196+
}
197+
m = new(Machine)
198+
m.Name = ""
199+
defaultSeen = true
200+
case tkMachine:
201+
if m != nil {
202+
mach, m = appendMach(mach, m), nil
203+
}
204+
m = new(Machine)
205+
m.Name = t.value
206+
case tkLogin:
207+
if m == nil || m.Login != "" {
208+
return nil, nil, &Error{pos.name, pos.line, "unexpected token login "}
209+
}
210+
m.Login = t.value
211+
case tkPassword:
212+
if m == nil || m.Password != "" {
213+
return nil, nil, &Error{pos.name, pos.line, "unexpected token password"}
214+
}
215+
m.Password = t.value
216+
case tkAccount:
217+
if m == nil || m.Account != "" {
218+
return nil, nil, &Error{pos.name, pos.line, "unexpected token account"}
219+
}
220+
m.Account = t.value
221+
}
222+
}
223+
if m != nil {
224+
mach, m = appendMach(mach, m), nil
225+
}
226+
return mach, mac, nil
227+
}
228+
229+
// ParseFile parses the netrc file identified by filename and returns the set of
230+
// machine information and macros defined in it. The ``default'' machine,
231+
// which is intended to be used when no machine name matches, is identified
232+
// by an empty machine name. There can be only one ``default'' machine.
233+
//
234+
// If there is a parsing error, an Error is returned.
235+
func ParseFile(filename string) ([]*Machine, Macros, os.Error) {
236+
// TODO(fhs): Check if file is readable by anyone besides the user if there is password in it.
237+
fd, err := os.Open(filename, os.O_RDONLY, 0)
238+
if err != nil {
239+
return nil, nil, err
240+
}
241+
defer fd.Close()
242+
return parse(fd, &filePos{filename, 1})
243+
}
244+
245+
246+
// ParseFile parses the netrc file identified by filename and returns
247+
// the Machine named by name. If no Machine with name name is found, the
248+
// ``default'' machine is returned.
249+
func FindMachine(filename string, name string) (*Machine, os.Error) {
250+
mach, _, err := ParseFile(filename)
251+
if err != nil {
252+
return nil, err
253+
}
254+
var def, m *Machine
255+
for _, m = range mach {
256+
if m.Name == name {
257+
return m, nil
258+
}
259+
if m.Name == "" {
260+
def = m
261+
}
262+
}
263+
if m == nil && def == nil {
264+
return nil, os.NewError("no machine found")
265+
}
266+
if m != nil {
267+
return m, nil
268+
}
269+
return def, nil
270+
}

netrc/netrc_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright © 2010 Fazlul Shahriar <[email protected]>.
2+
// See LICENSE file for license details.
3+
4+
package netrc_test
5+
6+
import (
7+
. "go-netrc.googlecode.com/hg/netrc"
8+
"testing"
9+
)
10+
11+
func eqMach(a *Machine, b *Machine) bool {
12+
return a.Name == b.Name &&
13+
a.Login == b.Login &&
14+
a.Password == b.Password &&
15+
a.Account == b.Account
16+
}
17+
18+
func TestParse(t *testing.T) {
19+
mach, mac, err := ParseFile("example.netrc")
20+
if err != nil {
21+
t.Fatal(err)
22+
}
23+
24+
expectedMach := []*Machine{
25+
&Machine{"mail.google.com", "[email protected]", "somethingSecret", "gmail"},
26+
&Machine{"ray", "demo", "mypassword", ""},
27+
&Machine{"", "anonymous", "[email protected]", ""},
28+
}
29+
for i, e := range expectedMach {
30+
if !eqMach(e, mach[i]) {
31+
t.Errorf("bad machine; expected %v, got %v\n", e, mach[i])
32+
}
33+
}
34+
35+
expectedMac := Macros{
36+
"allput": "put src/*",
37+
}
38+
for k, v := range expectedMac {
39+
if v != mac[k] {
40+
t.Errorf("bad macro for %s; expected %s, got %s\n", k, v, mac[k])
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)