Files

201 lines
4.7 KiB
Go
Raw Permalink Normal View History

2017-04-16 21:13:13 -07:00
package ssh_config
import (
"fmt"
"strings"
"unicode"
2017-04-16 21:13:13 -07:00
)
type sshParser struct {
flow chan token
config *Config
tokensBuffer []token
currentTable []string
seenTableKeys []string
2017-04-24 11:06:33 -07:00
// /etc/ssh parser or local parser - used to find the default for relative
// filepaths in the Include directive
system bool
depth uint8
2017-04-16 21:13:13 -07:00
}
type sshParserStateFn func() sshParserStateFn
// Formats and panics an error message based on a token
2017-04-24 11:06:33 -07:00
func (p *sshParser) raiseErrorf(tok *token, msg string, args ...interface{}) {
// TODO this format is ugly
2017-04-16 21:13:13 -07:00
panic(tok.Position.String() + ": " + fmt.Sprintf(msg, args...))
}
2017-04-24 11:06:33 -07:00
func (p *sshParser) raiseError(tok *token, err error) {
if err == ErrDepthExceeded {
panic(err)
}
// TODO this format is ugly
2017-04-24 11:06:33 -07:00
panic(tok.Position.String() + ": " + err.Error())
}
2017-04-16 21:13:13 -07:00
func (p *sshParser) run() {
for state := p.parseStart; state != nil; {
state = state()
}
}
func (p *sshParser) peek() *token {
if len(p.tokensBuffer) != 0 {
return &(p.tokensBuffer[0])
}
tok, ok := <-p.flow
if !ok {
return nil
}
p.tokensBuffer = append(p.tokensBuffer, tok)
return &tok
}
func (p *sshParser) getToken() *token {
if len(p.tokensBuffer) != 0 {
tok := p.tokensBuffer[0]
p.tokensBuffer = p.tokensBuffer[1:]
return &tok
}
tok, ok := <-p.flow
if !ok {
return nil
}
return &tok
}
func (p *sshParser) parseStart() sshParserStateFn {
tok := p.peek()
// end of stream, parsing is finished
if tok == nil {
return nil
}
switch tok.typ {
case tokenComment, tokenEmptyLine:
return p.parseComment
case tokenKey:
return p.parseKV
case tokenEOF:
return nil
default:
2017-04-24 11:06:33 -07:00
p.raiseErrorf(tok, fmt.Sprintf("unexpected token %q\n", tok))
2017-04-16 21:13:13 -07:00
}
return nil
}
func (p *sshParser) parseKV() sshParserStateFn {
key := p.getToken()
2017-04-23 11:42:22 -07:00
hasEquals := false
2017-04-16 21:13:13 -07:00
val := p.getToken()
2017-04-23 11:42:22 -07:00
if val.typ == tokenEquals {
hasEquals = true
val = p.getToken()
}
2017-04-16 21:13:13 -07:00
comment := ""
tok := p.peek()
if tok == nil {
tok = &token{typ: tokenEOF}
}
2017-04-16 21:13:13 -07:00
if tok.typ == tokenComment && tok.Position.Line == val.Position.Line {
tok = p.getToken()
comment = tok.val
}
if strings.ToLower(key.val) == "match" {
// https://github.com/kevinburke/ssh_config/issues/6
p.raiseErrorf(val, "ssh_config: Match directive parsing is unsupported")
return nil
}
if strings.ToLower(key.val) == "host" {
2017-04-23 11:42:22 -07:00
strPatterns := strings.Split(val.val, " ")
2018-01-27 00:28:50 -08:00
patterns := make([]*Pattern, 0)
2017-04-23 11:42:22 -07:00
for i := range strPatterns {
if strPatterns[i] == "" {
2018-01-27 00:28:50 -08:00
continue
2017-04-16 21:13:13 -07:00
}
2017-04-23 11:42:22 -07:00
pat, err := NewPattern(strPatterns[i])
if err != nil {
2017-04-24 11:06:33 -07:00
p.raiseErrorf(val, "Invalid host pattern: %v", err)
2017-04-23 11:42:22 -07:00
return nil
}
2018-01-27 00:28:50 -08:00
patterns = append(patterns, pat)
2017-04-23 11:42:22 -07:00
}
// val.val at this point could be e.g. "example.com "
hostval := strings.TrimRightFunc(val.val, unicode.IsSpace)
spaceBeforeComment := val.val[len(hostval):]
val.val = hostval
2017-04-16 21:13:13 -07:00
p.config.Hosts = append(p.config.Hosts, &Host{
Patterns: patterns,
Nodes: make([]Node, 0),
EOLComment: comment,
spaceBeforeComment: spaceBeforeComment,
hasEquals: hasEquals,
2017-04-16 21:13:13 -07:00
})
return p.parseStart
}
lastHost := p.config.Hosts[len(p.config.Hosts)-1]
if strings.ToLower(key.val) == "include" {
2017-05-25 08:00:10 -07:00
inc, err := NewInclude(strings.Split(val.val, " "), hasEquals, key.Position, comment, p.system, p.depth+1)
2017-04-24 11:06:33 -07:00
if err == ErrDepthExceeded {
p.raiseError(val, err)
return nil
}
if err != nil {
p.raiseErrorf(val, "Error parsing Include directive: %v", err)
return nil
}
lastHost.Nodes = append(lastHost.Nodes, inc)
return p.parseStart
}
shortval := strings.TrimRightFunc(val.val, unicode.IsSpace)
spaceAfterValue := val.val[len(shortval):]
2017-04-16 21:13:13 -07:00
kv := &KV{
Key: key.val,
Value: shortval,
spaceAfterValue: spaceAfterValue,
Comment: comment,
hasEquals: hasEquals,
leadingSpace: key.Position.Col - 1,
position: key.Position,
2017-04-16 21:13:13 -07:00
}
lastHost.Nodes = append(lastHost.Nodes, kv)
return p.parseStart
}
func (p *sshParser) parseComment() sshParserStateFn {
comment := p.getToken()
lastHost := p.config.Hosts[len(p.config.Hosts)-1]
lastHost.Nodes = append(lastHost.Nodes, &Empty{
Comment: comment.val,
// account for the "#" as well
leadingSpace: comment.Position.Col - 2,
position: comment.Position,
})
return p.parseStart
}
2017-04-24 11:06:33 -07:00
func parseSSH(flow chan token, system bool, depth uint8) *Config {
// Ensure we consume tokens to completion even if parser exits early
defer func() {
for range flow {
}
}()
2017-04-16 21:13:13 -07:00
result := newConfig()
result.position = Position{1, 1}
parser := &sshParser{
flow: flow,
config: result,
tokensBuffer: make([]token, 0),
currentTable: make([]string, 0),
seenTableKeys: make([]string, 0),
2017-04-24 11:06:33 -07:00
system: system,
depth: depth,
2017-04-16 21:13:13 -07:00
}
parser.run()
return result
}