aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/bgentry/go-netrc/netrc/netrc.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/bgentry/go-netrc/netrc/netrc.go')
-rw-r--r--vendor/github.com/bgentry/go-netrc/netrc/netrc.go510
1 files changed, 510 insertions, 0 deletions
diff --git a/vendor/github.com/bgentry/go-netrc/netrc/netrc.go b/vendor/github.com/bgentry/go-netrc/netrc/netrc.go
new file mode 100644
index 0000000..ea49987
--- /dev/null
+++ b/vendor/github.com/bgentry/go-netrc/netrc/netrc.go
@@ -0,0 +1,510 @@
1package netrc
2
3import (
4 "bufio"
5 "bytes"
6 "fmt"
7 "io"
8 "io/ioutil"
9 "os"
10 "strings"
11 "sync"
12 "unicode"
13 "unicode/utf8"
14)
15
16type tkType int
17
18const (
19 tkMachine tkType = iota
20 tkDefault
21 tkLogin
22 tkPassword
23 tkAccount
24 tkMacdef
25 tkComment
26 tkWhitespace
27)
28
29var keywords = map[string]tkType{
30 "machine": tkMachine,
31 "default": tkDefault,
32 "login": tkLogin,
33 "password": tkPassword,
34 "account": tkAccount,
35 "macdef": tkMacdef,
36 "#": tkComment,
37}
38
39type Netrc struct {
40 tokens []*token
41 machines []*Machine
42 macros Macros
43 updateLock sync.Mutex
44}
45
46// FindMachine returns the Machine in n named by name. If a machine named by
47// name exists, it is returned. If no Machine with name name is found and there
48// is a ``default'' machine, the ``default'' machine is returned. Otherwise, nil
49// is returned.
50func (n *Netrc) FindMachine(name string) (m *Machine) {
51 // TODO(bgentry): not safe for concurrency
52 var def *Machine
53 for _, m = range n.machines {
54 if m.Name == name {
55 return m
56 }
57 if m.IsDefault() {
58 def = m
59 }
60 }
61 if def == nil {
62 return nil
63 }
64 return def
65}
66
67// MarshalText implements the encoding.TextMarshaler interface to encode a
68// Netrc into text format.
69func (n *Netrc) MarshalText() (text []byte, err error) {
70 // TODO(bgentry): not safe for concurrency
71 for i := range n.tokens {
72 switch n.tokens[i].kind {
73 case tkComment, tkDefault, tkWhitespace: // always append these types
74 text = append(text, n.tokens[i].rawkind...)
75 default:
76 if n.tokens[i].value != "" { // skip empty-value tokens
77 text = append(text, n.tokens[i].rawkind...)
78 }
79 }
80 if n.tokens[i].kind == tkMacdef {
81 text = append(text, ' ')
82 text = append(text, n.tokens[i].macroName...)
83 }
84 text = append(text, n.tokens[i].rawvalue...)
85 }
86 return
87}
88
89func (n *Netrc) NewMachine(name, login, password, account string) *Machine {
90 n.updateLock.Lock()
91 defer n.updateLock.Unlock()
92
93 prefix := "\n"
94 if len(n.tokens) == 0 {
95 prefix = ""
96 }
97 m := &Machine{
98 Name: name,
99 Login: login,
100 Password: password,
101 Account: account,
102
103 nametoken: &token{
104 kind: tkMachine,
105 rawkind: []byte(prefix + "machine"),
106 value: name,
107 rawvalue: []byte(" " + name),
108 },
109 logintoken: &token{
110 kind: tkLogin,
111 rawkind: []byte("\n\tlogin"),
112 value: login,
113 rawvalue: []byte(" " + login),
114 },
115 passtoken: &token{
116 kind: tkPassword,
117 rawkind: []byte("\n\tpassword"),
118 value: password,
119 rawvalue: []byte(" " + password),
120 },
121 accounttoken: &token{
122 kind: tkAccount,
123 rawkind: []byte("\n\taccount"),
124 value: account,
125 rawvalue: []byte(" " + account),
126 },
127 }
128 n.insertMachineTokensBeforeDefault(m)
129 for i := range n.machines {
130 if n.machines[i].IsDefault() {
131 n.machines = append(append(n.machines[:i], m), n.machines[i:]...)
132 return m
133 }
134 }
135 n.machines = append(n.machines, m)
136 return m
137}
138
139func (n *Netrc) insertMachineTokensBeforeDefault(m *Machine) {
140 newtokens := []*token{m.nametoken}
141 if m.logintoken.value != "" {
142 newtokens = append(newtokens, m.logintoken)
143 }
144 if m.passtoken.value != "" {
145 newtokens = append(newtokens, m.passtoken)
146 }
147 if m.accounttoken.value != "" {
148 newtokens = append(newtokens, m.accounttoken)
149 }
150 for i := range n.tokens {
151 if n.tokens[i].kind == tkDefault {
152 // found the default, now insert tokens before it
153 n.tokens = append(n.tokens[:i], append(newtokens, n.tokens[i:]...)...)
154 return
155 }
156 }
157 // didn't find a default, just add the newtokens to the end
158 n.tokens = append(n.tokens, newtokens...)
159 return
160}
161
162func (n *Netrc) RemoveMachine(name string) {
163 n.updateLock.Lock()
164 defer n.updateLock.Unlock()
165
166 for i := range n.machines {
167 if n.machines[i] != nil && n.machines[i].Name == name {
168 m := n.machines[i]
169 for _, t := range []*token{
170 m.nametoken, m.logintoken, m.passtoken, m.accounttoken,
171 } {
172 n.removeToken(t)
173 }
174 n.machines = append(n.machines[:i], n.machines[i+1:]...)
175 return
176 }
177 }
178}
179
180func (n *Netrc) removeToken(t *token) {
181 if t != nil {
182 for i := range n.tokens {
183 if n.tokens[i] == t {
184 n.tokens = append(n.tokens[:i], n.tokens[i+1:]...)
185 return
186 }
187 }
188 }
189}
190
191// Machine contains information about a remote machine.
192type Machine struct {
193 Name string
194 Login string
195 Password string
196 Account string
197
198 nametoken *token
199 logintoken *token
200 passtoken *token
201 accounttoken *token
202}
203
204// IsDefault returns true if the machine is a "default" token, denoted by an
205// empty name.
206func (m *Machine) IsDefault() bool {
207 return m.Name == ""
208}
209
210// UpdatePassword sets the password for the Machine m.
211func (m *Machine) UpdatePassword(newpass string) {
212 m.Password = newpass
213 updateTokenValue(m.passtoken, newpass)
214}
215
216// UpdateLogin sets the login for the Machine m.
217func (m *Machine) UpdateLogin(newlogin string) {
218 m.Login = newlogin
219 updateTokenValue(m.logintoken, newlogin)
220}
221
222// UpdateAccount sets the login for the Machine m.
223func (m *Machine) UpdateAccount(newaccount string) {
224 m.Account = newaccount
225 updateTokenValue(m.accounttoken, newaccount)
226}
227
228func updateTokenValue(t *token, value string) {
229 oldvalue := t.value
230 t.value = value
231 newraw := make([]byte, len(t.rawvalue))
232 copy(newraw, t.rawvalue)
233 t.rawvalue = append(
234 bytes.TrimSuffix(newraw, []byte(oldvalue)),
235 []byte(value)...,
236 )
237}
238
239// Macros contains all the macro definitions in a netrc file.
240type Macros map[string]string
241
242type token struct {
243 kind tkType
244 macroName string
245 value string
246 rawkind []byte
247 rawvalue []byte
248}
249
250// Error represents a netrc file parse error.
251type Error struct {
252 LineNum int // Line number
253 Msg string // Error message
254}
255
256// Error returns a string representation of error e.
257func (e *Error) Error() string {
258 return fmt.Sprintf("line %d: %s", e.LineNum, e.Msg)
259}
260
261func (e *Error) BadDefaultOrder() bool {
262 return e.Msg == errBadDefaultOrder
263}
264
265const errBadDefaultOrder = "default token must appear after all machine tokens"
266
267// scanLinesKeepPrefix is a split function for a Scanner that returns each line
268// of text. The returned token may include newlines if they are before the
269// first non-space character. The returned line may be empty. The end-of-line
270// marker is one optional carriage return followed by one mandatory newline. In
271// regular expression notation, it is `\r?\n`. The last non-empty line of
272// input will be returned even if it has no newline.
273func scanLinesKeepPrefix(data []byte, atEOF bool) (advance int, token []byte, err error) {
274 if atEOF && len(data) == 0 {
275 return 0, nil, nil
276 }
277 // Skip leading spaces.
278 start := 0
279 for width := 0; start < len(data); start += width {
280 var r rune
281 r, width = utf8.DecodeRune(data[start:])
282 if !unicode.IsSpace(r) {
283 break
284 }
285 }
286 if i := bytes.IndexByte(data[start:], '\n'); i >= 0 {
287 // We have a full newline-terminated line.
288 return start + i, data[0 : start+i], nil
289 }
290 // If we're at EOF, we have a final, non-terminated line. Return it.
291 if atEOF {
292 return len(data), data, nil
293 }
294 // Request more data.
295 return 0, nil, nil
296}
297
298// scanWordsKeepPrefix is a split function for a Scanner that returns each
299// space-separated word of text, with prefixing spaces included. It will never
300// return an empty string. The definition of space is set by unicode.IsSpace.
301//
302// Adapted from bufio.ScanWords().
303func scanTokensKeepPrefix(data []byte, atEOF bool) (advance int, token []byte, err error) {
304 // Skip leading spaces.
305 start := 0
306 for width := 0; start < len(data); start += width {
307 var r rune
308 r, width = utf8.DecodeRune(data[start:])
309 if !unicode.IsSpace(r) {
310 break
311 }
312 }
313 if atEOF && len(data) == 0 || start == len(data) {
314 return len(data), data, nil
315 }
316 if len(data) > start && data[start] == '#' {
317 return scanLinesKeepPrefix(data, atEOF)
318 }
319 // Scan until space, marking end of word.
320 for width, i := 0, start; i < len(data); i += width {
321 var r rune
322 r, width = utf8.DecodeRune(data[i:])
323 if unicode.IsSpace(r) {
324 return i, data[:i], nil
325 }
326 }
327 // If we're at EOF, we have a final, non-empty, non-terminated word. Return it.
328 if atEOF && len(data) > start {
329 return len(data), data, nil
330 }
331 // Request more data.
332 return 0, nil, nil
333}
334
335func newToken(rawb []byte) (*token, error) {
336 _, tkind, err := bufio.ScanWords(rawb, true)
337 if err != nil {
338 return nil, err
339 }
340 var ok bool
341 t := token{rawkind: rawb}
342 t.kind, ok = keywords[string(tkind)]
343 if !ok {
344 trimmed := strings.TrimSpace(string(tkind))
345 if trimmed == "" {
346 t.kind = tkWhitespace // whitespace-only, should happen only at EOF
347 return &t, nil
348 }
349 if strings.HasPrefix(trimmed, "#") {
350 t.kind = tkComment // this is a comment
351 return &t, nil
352 }
353 return &t, fmt.Errorf("keyword expected; got " + string(tkind))
354 }
355 return &t, nil
356}
357
358func scanValue(scanner *bufio.Scanner, pos int) ([]byte, string, int, error) {
359 if scanner.Scan() {
360 raw := scanner.Bytes()
361 pos += bytes.Count(raw, []byte{'\n'})
362 return raw, strings.TrimSpace(string(raw)), pos, nil
363 }
364 if err := scanner.Err(); err != nil {
365 return nil, "", pos, &Error{pos, err.Error()}
366 }
367 return nil, "", pos, nil
368}
369
370func parse(r io.Reader, pos int) (*Netrc, error) {
371 b, err := ioutil.ReadAll(r)
372 if err != nil {
373 return nil, err
374 }
375
376 nrc := Netrc{machines: make([]*Machine, 0, 20), macros: make(Macros, 10)}
377
378 defaultSeen := false
379 var currentMacro *token
380 var m *Machine
381 var t *token
382 scanner := bufio.NewScanner(bytes.NewReader(b))
383 scanner.Split(scanTokensKeepPrefix)
384
385 for scanner.Scan() {
386 rawb := scanner.Bytes()
387 if len(rawb) == 0 {
388 break
389 }
390 pos += bytes.Count(rawb, []byte{'\n'})
391 t, err = newToken(rawb)
392 if err != nil {
393 if currentMacro == nil {
394 return nil, &Error{pos, err.Error()}
395 }
396 currentMacro.rawvalue = append(currentMacro.rawvalue, rawb...)
397 continue
398 }
399
400 if currentMacro != nil && bytes.Contains(rawb, []byte{'\n', '\n'}) {
401 // if macro rawvalue + rawb would contain \n\n, then macro def is over
402 currentMacro.value = strings.TrimLeft(string(currentMacro.rawvalue), "\r\n")
403 nrc.macros[currentMacro.macroName] = currentMacro.value
404 currentMacro = nil
405 }
406
407 switch t.kind {
408 case tkMacdef:
409 if _, t.macroName, pos, err = scanValue(scanner, pos); err != nil {
410 return nil, &Error{pos, err.Error()}
411 }
412 currentMacro = t
413 case tkDefault:
414 if defaultSeen {
415 return nil, &Error{pos, "multiple default token"}
416 }
417 if m != nil {
418 nrc.machines, m = append(nrc.machines, m), nil
419 }
420 m = new(Machine)
421 m.Name = ""
422 defaultSeen = true
423 case tkMachine:
424 if defaultSeen {
425 return nil, &Error{pos, errBadDefaultOrder}
426 }
427 if m != nil {
428 nrc.machines, m = append(nrc.machines, m), nil
429 }
430 m = new(Machine)
431 if t.rawvalue, m.Name, pos, err = scanValue(scanner, pos); err != nil {
432 return nil, &Error{pos, err.Error()}
433 }
434 t.value = m.Name
435 m.nametoken = t
436 case tkLogin:
437 if m == nil || m.Login != "" {
438 return nil, &Error{pos, "unexpected token login "}
439 }
440 if t.rawvalue, m.Login, pos, err = scanValue(scanner, pos); err != nil {
441 return nil, &Error{pos, err.Error()}
442 }
443 t.value = m.Login
444 m.logintoken = t
445 case tkPassword:
446 if m == nil || m.Password != "" {
447 return nil, &Error{pos, "unexpected token password"}
448 }
449 if t.rawvalue, m.Password, pos, err = scanValue(scanner, pos); err != nil {
450 return nil, &Error{pos, err.Error()}
451 }
452 t.value = m.Password
453 m.passtoken = t
454 case tkAccount:
455 if m == nil || m.Account != "" {
456 return nil, &Error{pos, "unexpected token account"}
457 }
458 if t.rawvalue, m.Account, pos, err = scanValue(scanner, pos); err != nil {
459 return nil, &Error{pos, err.Error()}
460 }
461 t.value = m.Account
462 m.accounttoken = t
463 }
464
465 nrc.tokens = append(nrc.tokens, t)
466 }
467
468 if err := scanner.Err(); err != nil {
469 return nil, err
470 }
471
472 if m != nil {
473 nrc.machines, m = append(nrc.machines, m), nil
474 }
475 return &nrc, nil
476}
477
478// ParseFile opens the file at filename and then passes its io.Reader to
479// Parse().
480func ParseFile(filename string) (*Netrc, error) {
481 fd, err := os.Open(filename)
482 if err != nil {
483 return nil, err
484 }
485 defer fd.Close()
486 return Parse(fd)
487}
488
489// Parse parses from the the Reader r as a netrc file and returns the set of
490// machine information and macros defined in it. The ``default'' machine,
491// which is intended to be used when no machine name matches, is identified
492// by an empty machine name. There can be only one ``default'' machine.
493//
494// If there is a parsing error, an Error is returned.
495func Parse(r io.Reader) (*Netrc, error) {
496 return parse(r, 1)
497}
498
499// FindMachine parses the netrc file identified by filename and returns the
500// Machine named by name. If a problem occurs parsing the file at filename, an
501// error is returned. If a machine named by name exists, it is returned. If no
502// Machine with name name is found and there is a ``default'' machine, the
503// ``default'' machine is returned. Otherwise, nil is returned.
504func FindMachine(filename, name string) (m *Machine, err error) {
505 n, err := ParseFile(filename)
506 if err != nil {
507 return nil, err
508 }
509 return n.FindMachine(name), nil
510}