Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
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.
185 lines
4.2 KiB
185 lines
4.2 KiB
package ssh_config |
|
|
|
import ( |
|
"fmt" |
|
"strings" |
|
) |
|
|
|
type sshParser struct { |
|
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" { |
|
// 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) |
|
} |
|
p.config.Hosts = append(p.config.Hosts, &Host{ |
|
Patterns: patterns, |
|
Nodes: make([]Node, 0), |
|
EOLComment: comment, |
|
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.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 |
|
} |
|
kv := &KV{ |
|
Key: key.val, |
|
Value: val.val, |
|
Comment: comment, |
|
hasEquals: hasEquals, |
|
leadingSpace: uint16(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 bool, depth uint8) *Config { |
|
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), |
|
system: system, |
|
depth: depth, |
|
} |
|
parser.run() |
|
return result |
|
}
|
|
|