/netrc/netrc.go

https://code.google.com/p/go-netrc/ · Go · 259 lines · 208 code · 23 blank · 28 comment · 58 complexity · 1acad116572896e43ec3b2d912ee4445 MD5 · raw file

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