parser.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. package ssh_config
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. type sshParser struct {
  7. flow chan token
  8. config *Config
  9. tokensBuffer []token
  10. currentTable []string
  11. seenTableKeys []string
  12. // /etc/ssh parser or local parser - used to find the default for relative
  13. // filepaths in the Include directive
  14. system bool
  15. depth uint8
  16. }
  17. type sshParserStateFn func() sshParserStateFn
  18. // Formats and panics an error message based on a token
  19. func (p *sshParser) raiseErrorf(tok *token, msg string, args ...interface{}) {
  20. // TODO this format is ugly
  21. panic(tok.Position.String() + ": " + fmt.Sprintf(msg, args...))
  22. }
  23. func (p *sshParser) raiseError(tok *token, err error) {
  24. if err == ErrDepthExceeded {
  25. panic(err)
  26. }
  27. // TODO this format is ugly
  28. panic(tok.Position.String() + ": " + err.Error())
  29. }
  30. func (p *sshParser) run() {
  31. for state := p.parseStart; state != nil; {
  32. state = state()
  33. }
  34. }
  35. func (p *sshParser) peek() *token {
  36. if len(p.tokensBuffer) != 0 {
  37. return &(p.tokensBuffer[0])
  38. }
  39. tok, ok := <-p.flow
  40. if !ok {
  41. return nil
  42. }
  43. p.tokensBuffer = append(p.tokensBuffer, tok)
  44. return &tok
  45. }
  46. func (p *sshParser) getToken() *token {
  47. if len(p.tokensBuffer) != 0 {
  48. tok := p.tokensBuffer[0]
  49. p.tokensBuffer = p.tokensBuffer[1:]
  50. return &tok
  51. }
  52. tok, ok := <-p.flow
  53. if !ok {
  54. return nil
  55. }
  56. return &tok
  57. }
  58. func (p *sshParser) parseStart() sshParserStateFn {
  59. tok := p.peek()
  60. // end of stream, parsing is finished
  61. if tok == nil {
  62. return nil
  63. }
  64. switch tok.typ {
  65. case tokenComment, tokenEmptyLine:
  66. return p.parseComment
  67. case tokenKey:
  68. return p.parseKV
  69. case tokenEOF:
  70. return nil
  71. default:
  72. p.raiseErrorf(tok, fmt.Sprintf("unexpected token %q\n", tok))
  73. }
  74. return nil
  75. }
  76. func (p *sshParser) parseKV() sshParserStateFn {
  77. key := p.getToken()
  78. hasEquals := false
  79. val := p.getToken()
  80. if val.typ == tokenEquals {
  81. hasEquals = true
  82. val = p.getToken()
  83. }
  84. comment := ""
  85. tok := p.peek()
  86. if tok.typ == tokenComment && tok.Position.Line == val.Position.Line {
  87. tok = p.getToken()
  88. comment = tok.val
  89. }
  90. if strings.ToLower(key.val) == "match" {
  91. // https://github.com/kevinburke/ssh_config/issues/6
  92. p.raiseErrorf(val, "ssh_config: Match directive parsing is unsupported")
  93. return nil
  94. }
  95. if strings.ToLower(key.val) == "host" {
  96. strPatterns := strings.Split(val.val, " ")
  97. patterns := make([]*Pattern, 0)
  98. for i := range strPatterns {
  99. if strPatterns[i] == "" {
  100. continue
  101. }
  102. pat, err := NewPattern(strPatterns[i])
  103. if err != nil {
  104. p.raiseErrorf(val, "Invalid host pattern: %v", err)
  105. return nil
  106. }
  107. patterns = append(patterns, pat)
  108. }
  109. p.config.Hosts = append(p.config.Hosts, &Host{
  110. Patterns: patterns,
  111. Nodes: make([]Node, 0),
  112. EOLComment: comment,
  113. hasEquals: hasEquals,
  114. })
  115. return p.parseStart
  116. }
  117. lastHost := p.config.Hosts[len(p.config.Hosts)-1]
  118. if strings.ToLower(key.val) == "include" {
  119. inc, err := NewInclude(strings.Split(val.val, " "), hasEquals, key.Position, comment, p.system, p.depth+1)
  120. if err == ErrDepthExceeded {
  121. p.raiseError(val, err)
  122. return nil
  123. }
  124. if err != nil {
  125. p.raiseErrorf(val, "Error parsing Include directive: %v", err)
  126. return nil
  127. }
  128. lastHost.Nodes = append(lastHost.Nodes, inc)
  129. return p.parseStart
  130. }
  131. kv := &KV{
  132. Key: key.val,
  133. Value: val.val,
  134. Comment: comment,
  135. hasEquals: hasEquals,
  136. leadingSpace: uint16(key.Position.Col) - 1,
  137. position: key.Position,
  138. }
  139. lastHost.Nodes = append(lastHost.Nodes, kv)
  140. return p.parseStart
  141. }
  142. func (p *sshParser) parseComment() sshParserStateFn {
  143. comment := p.getToken()
  144. lastHost := p.config.Hosts[len(p.config.Hosts)-1]
  145. lastHost.Nodes = append(lastHost.Nodes, &Empty{
  146. Comment: comment.val,
  147. // account for the "#" as well
  148. leadingSpace: comment.Position.Col - 2,
  149. position: comment.Position,
  150. })
  151. return p.parseStart
  152. }
  153. func parseSSH(flow chan token, system bool, depth uint8) *Config {
  154. result := newConfig()
  155. result.position = Position{1, 1}
  156. parser := &sshParser{
  157. flow: flow,
  158. config: result,
  159. tokensBuffer: make([]token, 0),
  160. currentTable: make([]string, 0),
  161. seenTableKeys: make([]string, 0),
  162. system: system,
  163. depth: depth,
  164. }
  165. parser.run()
  166. return result
  167. }