You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

parser.go 4.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  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 == nil {
  87. tok = &token{typ: tokenEOF}
  88. }
  89. if tok.typ == tokenComment && tok.Position.Line == val.Position.Line {
  90. tok = p.getToken()
  91. comment = tok.val
  92. }
  93. if strings.ToLower(key.val) == "match" {
  94. // https://github.com/kevinburke/ssh_config/issues/6
  95. p.raiseErrorf(val, "ssh_config: Match directive parsing is unsupported")
  96. return nil
  97. }
  98. if strings.ToLower(key.val) == "host" {
  99. strPatterns := strings.Split(val.val, " ")
  100. patterns := make([]*Pattern, 0)
  101. for i := range strPatterns {
  102. if strPatterns[i] == "" {
  103. continue
  104. }
  105. pat, err := NewPattern(strPatterns[i])
  106. if err != nil {
  107. p.raiseErrorf(val, "Invalid host pattern: %v", err)
  108. return nil
  109. }
  110. patterns = append(patterns, pat)
  111. }
  112. p.config.Hosts = append(p.config.Hosts, &Host{
  113. Patterns: patterns,
  114. Nodes: make([]Node, 0),
  115. EOLComment: comment,
  116. hasEquals: hasEquals,
  117. })
  118. return p.parseStart
  119. }
  120. lastHost := p.config.Hosts[len(p.config.Hosts)-1]
  121. if strings.ToLower(key.val) == "include" {
  122. inc, err := NewInclude(strings.Split(val.val, " "), hasEquals, key.Position, comment, p.system, p.depth+1)
  123. if err == ErrDepthExceeded {
  124. p.raiseError(val, err)
  125. return nil
  126. }
  127. if err != nil {
  128. p.raiseErrorf(val, "Error parsing Include directive: %v", err)
  129. return nil
  130. }
  131. lastHost.Nodes = append(lastHost.Nodes, inc)
  132. return p.parseStart
  133. }
  134. kv := &KV{
  135. Key: key.val,
  136. Value: val.val,
  137. Comment: comment,
  138. hasEquals: hasEquals,
  139. leadingSpace: key.Position.Col - 1,
  140. position: key.Position,
  141. }
  142. lastHost.Nodes = append(lastHost.Nodes, kv)
  143. return p.parseStart
  144. }
  145. func (p *sshParser) parseComment() sshParserStateFn {
  146. comment := p.getToken()
  147. lastHost := p.config.Hosts[len(p.config.Hosts)-1]
  148. lastHost.Nodes = append(lastHost.Nodes, &Empty{
  149. Comment: comment.val,
  150. // account for the "#" as well
  151. leadingSpace: comment.Position.Col - 2,
  152. position: comment.Position,
  153. })
  154. return p.parseStart
  155. }
  156. func parseSSH(flow chan token, system bool, depth uint8) *Config {
  157. // Ensure we consume tokens to completion even if parser exits early
  158. defer func() {
  159. for range flow {
  160. }
  161. }()
  162. result := newConfig()
  163. result.position = Position{1, 1}
  164. parser := &sshParser{
  165. flow: flow,
  166. config: result,
  167. tokensBuffer: make([]token, 0),
  168. currentTable: make([]string, 0),
  169. seenTableKeys: make([]string, 0),
  170. system: system,
  171. depth: depth,
  172. }
  173. parser.run()
  174. return result
  175. }