You've already forked ssh_config
mirror of
https://github.com/wavetermdev/ssh_config.git
synced 2026-04-22 15:25:24 -07:00
6409e4292f
The ignoreMatchDirective flag did not exist in the newConfig function. Because this function is called when generating a config for connections in an included file, those connections automatically assumed the value was false. This change adds the flag so it can be changed to true if it is also set for the outer configuration.
205 lines
5.0 KiB
Go
205 lines
5.0 KiB
Go
package ssh_config
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
type sshParser struct {
|
|
ignoreMatchDirective bool
|
|
flow chan token
|
|
config *Config
|
|
tokensBuffer []token
|
|
currentTable []string
|
|
seenTableKeys []string
|
|
// /etc/ssh parser or local parser - used to find the default for relative
|
|
// filepaths in the Include directive
|
|
system bool
|
|
depth uint8
|
|
}
|
|
|
|
type sshParserStateFn func() sshParserStateFn
|
|
|
|
// Formats and panics an error message based on a token
|
|
func (p *sshParser) raiseErrorf(tok *token, msg string, args ...interface{}) {
|
|
// TODO this format is ugly
|
|
panic(tok.Position.String() + ": " + fmt.Sprintf(msg, args...))
|
|
}
|
|
|
|
func (p *sshParser) raiseError(tok *token, err error) {
|
|
if err == ErrDepthExceeded {
|
|
panic(err)
|
|
}
|
|
// TODO this format is ugly
|
|
panic(tok.Position.String() + ": " + err.Error())
|
|
}
|
|
|
|
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:
|
|
p.raiseErrorf(tok, fmt.Sprintf("unexpected token %q\n", tok))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *sshParser) parseKV() sshParserStateFn {
|
|
key := p.getToken()
|
|
hasEquals := false
|
|
val := p.getToken()
|
|
if val.typ == tokenEquals {
|
|
hasEquals = true
|
|
val = p.getToken()
|
|
}
|
|
comment := ""
|
|
tok := p.peek()
|
|
if tok == nil {
|
|
tok = &token{typ: tokenEOF}
|
|
}
|
|
if tok.typ == tokenComment && tok.Position.Line == val.Position.Line {
|
|
tok = p.getToken()
|
|
comment = tok.val
|
|
}
|
|
if strings.ToLower(key.val) == "match" && !p.ignoreMatchDirective {
|
|
// 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" {
|
|
strPatterns := strings.Split(val.val, " ")
|
|
patterns := make([]*Pattern, 0)
|
|
for i := range strPatterns {
|
|
if strPatterns[i] == "" {
|
|
continue
|
|
}
|
|
pat, err := NewPattern(strPatterns[i])
|
|
if err != nil {
|
|
p.raiseErrorf(val, "Invalid host pattern: %v", err)
|
|
return nil
|
|
}
|
|
patterns = append(patterns, pat)
|
|
}
|
|
// 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
|
|
p.config.ignoreMatchDirective = p.ignoreMatchDirective
|
|
p.config.Hosts = append(p.config.Hosts, &Host{
|
|
Patterns: patterns,
|
|
Nodes: make([]Node, 0),
|
|
EOLComment: comment,
|
|
spaceBeforeComment: spaceBeforeComment,
|
|
hasEquals: hasEquals,
|
|
},
|
|
)
|
|
return p.parseStart
|
|
}
|
|
lastHost := p.config.Hosts[len(p.config.Hosts)-1]
|
|
if strings.ToLower(key.val) == "include" {
|
|
inc, err := NewInclude(strings.Split(val.val, " "), hasEquals, key.Position, comment, p.system, p.ignoreMatchDirective, p.depth+1)
|
|
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):]
|
|
kv := &KV{
|
|
Key: key.val,
|
|
Value: shortval,
|
|
spaceAfterValue: spaceAfterValue,
|
|
Comment: comment,
|
|
hasEquals: hasEquals,
|
|
leadingSpace: key.Position.Col - 1,
|
|
position: key.Position,
|
|
}
|
|
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
|
|
}
|
|
|
|
func parseSSH(flow chan token, system, ignoreMatchDirective bool, depth uint8) *Config {
|
|
// Ensure we consume tokens to completion even if parser exits early
|
|
defer func() {
|
|
for range flow {
|
|
}
|
|
}()
|
|
|
|
result := newConfig(ignoreMatchDirective)
|
|
result.position = Position{1, 1}
|
|
parser := &sshParser{
|
|
ignoreMatchDirective: ignoreMatchDirective,
|
|
flow: flow,
|
|
config: result,
|
|
tokensBuffer: make([]token, 0),
|
|
currentTable: make([]string, 0),
|
|
seenTableKeys: make([]string, 0),
|
|
system: system,
|
|
depth: depth,
|
|
}
|
|
parser.run()
|
|
return result
|
|
}
|