feat(ssh_config): Add hosts parsing; Add some tests

This commit is contained in:
Myzel394 2024-09-22 14:49:42 +02:00
parent e6900d9861
commit 8067c821e8
No known key found for this signature in database
GPG Key ID: DEC4AAB876F73185
8 changed files with 315 additions and 5 deletions

View File

@ -4,6 +4,7 @@ import (
"config-lsp/common" "config-lsp/common"
commonparser "config-lsp/common/parser" commonparser "config-lsp/common/parser"
"config-lsp/handlers/ssh_config/ast/parser" "config-lsp/handlers/ssh_config/ast/parser"
hostparser "config-lsp/handlers/ssh_config/host-parser"
"config-lsp/handlers/ssh_config/match-parser" "config-lsp/handlers/ssh_config/match-parser"
"strings" "strings"
@ -131,7 +132,7 @@ func (s *sshParserListener) ExitEntry(ctx *parser.EntryContext) {
if len(errors) > 0 { if len(errors) > 0 {
for _, err := range errors { for _, err := range errors {
s.Errors = append(s.Errors, common.LSPError{ s.Errors = append(s.Errors, common.LSPError{
Range: err.Range.ShiftHorizontal(s.sshContext.currentOption.Start.Character), Range: err.Range,
Err: err.Err, Err: err.Err,
}) })
} }
@ -153,6 +154,41 @@ func (s *sshParserListener) ExitEntry(ctx *parser.EntryContext) {
s.sshContext.currentKeyIsBlockOf = nil s.sshContext.currentKeyIsBlockOf = nil
s.sshContext.currentBlock = matchBlock s.sshContext.currentBlock = matchBlock
case SSHBlockTypeHost:
var host *hostparser.Host
hostParser := hostparser.NewHost()
errors := hostParser.Parse(
s.sshContext.currentOption.OptionValue.Value.Raw,
location.Start.Line,
s.sshContext.currentOption.OptionValue.Start.Character,
)
if len(errors) > 0 {
for _, err := range errors {
s.Errors = append(s.Errors, common.LSPError{
Range: err.Range,
Err: err.Err,
})
}
} else {
host = hostParser
}
hostBlock := &SSHHostBlock{
LocationRange: location,
HostOption: s.sshContext.currentOption,
HostValue: host,
Options: treemap.NewWith(gods.UInt32Comparator),
}
s.Config.Options.Put(
location.Start.Line,
hostBlock,
)
s.sshContext.currentKeyIsBlockOf = nil
s.sshContext.currentBlock = hostBlock
} }
return return

View File

@ -127,3 +127,132 @@ Match originalhost "192.168.0.1"
t.Errorf("Expected third entry to be User root, but got: %v", thirdEntry) t.Errorf("Expected third entry to be User root, but got: %v", thirdEntry)
} }
} }
func TestSimpleHostBlock(
t *testing.T,
) {
input := utils.Dedent(`
Ciphers 3des-cbc
Host example.com
Port 22
`)
p := NewSSHConfig()
errors := p.Parse(input)
if len(errors) != 0 {
t.Fatalf("Expected no errors, got %v", errors)
}
if !(p.Options.Size() == 2 &&
len(utils.KeysOfMap(p.CommentLines)) == 0) {
t.Errorf("Expected 2 option and no comment lines, but got: %v entries, %v comment lines", p.Options.Size(), len(p.CommentLines))
}
rawFirstEntry, _ := p.Options.Get(uint32(0))
firstEntry := rawFirstEntry.(*SSHOption)
if !(firstEntry.Value.Value == "Ciphers 3des-cbc") {
t.Errorf("Expected first entry to be Ciphers 3des-cbc, but got: %v", firstEntry)
}
rawSecondEntry, _ := p.Options.Get(uint32(2))
secondEntry := rawSecondEntry.(*SSHHostBlock)
if !(secondEntry.Options.Size() == 1 &&
secondEntry.LocationRange.Start.Line == 2 &&
secondEntry.LocationRange.Start.Character == 0 &&
secondEntry.LocationRange.End.Line == 3 &&
secondEntry.LocationRange.End.Character == 8) {
t.Errorf("Expected second entry to be Host example.com, but got: %v", secondEntry)
}
rawThirdEntry, _ := secondEntry.Options.Get(uint32(3))
thirdEntry := rawThirdEntry.(*SSHOption)
if !(thirdEntry.Value.Raw == "\tPort 22" &&
thirdEntry.Key.Value.Raw == "Port" &&
thirdEntry.OptionValue.Value.Raw == "22" &&
thirdEntry.LocationRange.Start.Line == 3 &&
thirdEntry.LocationRange.Start.Character == 0 &&
thirdEntry.LocationRange.End.Line == 3 &&
thirdEntry.LocationRange.End.Character == 8) {
t.Errorf("Expected third entry to be Port 22, but got: %v", thirdEntry)
}
rawFourthEntry, _ := p.Options.Get(uint32(3))
if !(rawFourthEntry == nil) {
t.Errorf("Expected fourth entry to be nil, but got: %v", rawFourthEntry)
}
}
func TestComplexExample(
t *testing.T,
) {
input := utils.Dedent(`
Host laptop
HostName laptop.lan
Match originalhost laptop exec "[[ $(/usr/bin/dig +short laptop.lan) == '' ]]"
HostName laptop.sdn
`)
p := NewSSHConfig()
errors := p.Parse(input)
if len(errors) != 0 {
t.Fatalf("Expected no errors, got %v", errors)
}
if !(p.Options.Size() == 2 &&
len(utils.KeysOfMap(p.CommentLines)) == 0) {
t.Errorf("Expected 2 option and no comment lines, but got: %v entries, %v comment lines", p.Options.Size(), len(p.CommentLines))
}
rawFirstEntry, _ := p.Options.Get(uint32(0))
firstBlock := rawFirstEntry.(*SSHHostBlock)
if !(firstBlock.Options.Size() == 1 &&
firstBlock.LocationRange.Start.Line == 0 &&
firstBlock.LocationRange.Start.Character == 0 &&
firstBlock.LocationRange.End.Line == 1 &&
firstBlock.LocationRange.End.Character == 23) {
t.Errorf("Expected first entry to be Host laptop, but got: %v", firstBlock)
}
rawSecondEntry, _ := firstBlock.Options.Get(uint32(1))
secondOption := rawSecondEntry.(*SSHOption)
if !(secondOption.Value.Raw == " HostName laptop.lan" &&
secondOption.Key.Value.Raw == "HostName" &&
secondOption.OptionValue.Value.Raw == "laptop.lan" &&
secondOption.LocationRange.Start.Line == 1 &&
secondOption.LocationRange.Start.Character == 0 &&
secondOption.Key.LocationRange.Start.Character == 4 &&
secondOption.LocationRange.End.Line == 1 &&
secondOption.LocationRange.End.Character == 23) {
t.Errorf("Expected second entry to be HostName laptop.lan, but got: %v", secondOption)
}
rawThirdEntry, _ := p.Options.Get(uint32(3))
secondBlock := rawThirdEntry.(*SSHMatchBlock)
if !(secondBlock.Options.Size() == 1 &&
secondBlock.LocationRange.Start.Line == 3 &&
secondBlock.LocationRange.Start.Character == 0 &&
secondBlock.LocationRange.End.Line == 4 &&
secondBlock.LocationRange.End.Character == 23) {
t.Errorf("Expected second entry to be Match originalhost laptop exec \"[[ $(/usr/bin/dig +short laptop.lan) == '' ]]\", but got: %v", secondBlock)
}
if !(secondBlock.MatchOption.LocationRange.End.Character == 78) {
t.Errorf("Expected second entry to be Match originalhost laptop exec \"[[ $(/usr/bin/dig +short laptop.lan) == '' ]]\", but got: %v", secondBlock)
}
rawFourthEntry, _ := secondBlock.Options.Get(uint32(4))
thirdOption := rawFourthEntry.(*SSHOption)
if !(thirdOption.Value.Raw == " HostName laptop.sdn" &&
thirdOption.Key.Value.Raw == "HostName" &&
thirdOption.OptionValue.Value.Raw == "laptop.sdn" &&
thirdOption.LocationRange.Start.Line == 4 &&
thirdOption.LocationRange.Start.Character == 0 &&
thirdOption.Key.LocationRange.Start.Character == 4 &&
thirdOption.LocationRange.End.Line == 4 &&
thirdOption.LocationRange.End.Character == 23) {
t.Errorf("Expected third entry to be HostName laptop.sdn, but got: %v", thirdOption)
}
}

View File

@ -3,7 +3,9 @@ package ast
import ( import (
"config-lsp/common" "config-lsp/common"
commonparser "config-lsp/common/parser" commonparser "config-lsp/common/parser"
hostparser "config-lsp/handlers/ssh_config/host-parser"
"config-lsp/handlers/ssh_config/match-parser" "config-lsp/handlers/ssh_config/match-parser"
"github.com/emirpasic/gods/maps/treemap" "github.com/emirpasic/gods/maps/treemap"
) )
@ -44,10 +46,10 @@ type SSHMatchBlock struct {
type SSHHostBlock struct { type SSHHostBlock struct {
common.LocationRange common.LocationRange
HostOption *SSHOption HostOption *SSHOption
HostValue string HostValue *hostparser.Host
// [uint32]*SSHOption -> line number -> *SSHOption // [uint32]*SSHOption -> line number -> *SSHOption
Others *treemap.Map Options *treemap.Map
} }
type SSHConfig struct { type SSHConfig struct {

View File

@ -32,7 +32,7 @@ func (b *SSHHostBlock) GetBlockType() SSHBlockType {
} }
func (b *SSHHostBlock) AddOption(option *SSHOption) { func (b *SSHHostBlock) AddOption(option *SSHOption) {
b.Others.Put(option.LocationRange.Start.Line, option) b.Options.Put(option.LocationRange.Start.Line, option)
} }
func (b *SSHHostBlock) SetEnd(end common.Location) { func (b *SSHHostBlock) SetEnd(end common.Location) {

View File

@ -0,0 +1,16 @@
package hostparser
import (
"config-lsp/common"
commonparser "config-lsp/common/parser"
)
type Host struct {
Hosts []*HostValue
}
type HostValue struct {
common.LocationRange
Value commonparser.ParsedString
}

View File

@ -0,0 +1,54 @@
package hostparser
import (
"config-lsp/common"
"regexp"
commonparser "config-lsp/common/parser"
)
func NewHost() *Host {
match := new(Host)
match.Clear()
return match
}
func (h *Host) Clear() {
h.Hosts = make([]*HostValue, 0)
}
var textPattern = regexp.MustCompile(`\S+`)
func (h *Host) Parse(
input string,
line uint32,
startCharacter uint32,
) []common.LSPError {
hostsIndexes := textPattern.FindAllStringIndex(input, -1)
for _, hostIndex := range hostsIndexes {
startIndex := hostIndex[0]
endIndex := hostIndex[1]
rawHost := input[startIndex:endIndex]
value := commonparser.ParseRawString(rawHost, commonparser.FullFeatures)
host := HostValue{
LocationRange: common.LocationRange{
Start: common.Location{
Line: line,
Character: startCharacter + uint32(startIndex),
},
End: common.Location{
Line: line,
Character: startCharacter + uint32(endIndex),
},
},
Value: value,
}
h.Hosts = append(h.Hosts, &host)
}
return nil
}

View File

@ -0,0 +1,73 @@
package hostparser
import "testing"
func TestSimpleExample(
t *testing.T,
) {
input := `example.com`
host := NewHost()
offset := uint32(8)
errs := host.Parse(input, 4, offset)
if len(errs) > 0 {
t.Fatalf("Expected no errors, got %v", errs)
}
if !(len(host.Hosts) == 1) {
t.Errorf("Expected 1 host, got %v", len(host.Hosts))
}
if !(host.Hosts[0].Value.Raw == "example.com") {
t.Errorf("Expected host to be 'example.com', got %v", host.Hosts[0].Value.Raw)
}
if !(host.Hosts[0].Start.Line == 4 && host.Hosts[0].Start.Character == 0+offset && host.Hosts[0].End.Character == 11+offset) {
t.Errorf("Expected host to be at line 4, characters 0-11, got %v", host.Hosts[0])
}
if !(host.Hosts[0].Value.Value == "example.com") {
t.Errorf("Expected host value to be 'example.com', got %v", host.Hosts[0].Value.Value)
}
}
func TestMultipleExample(
t *testing.T,
) {
input := `example.com example.org example.net`
host := NewHost()
offset := uint32(8)
errs := host.Parse(input, 4, offset)
if len(errs) > 0 {
t.Fatalf("Expected no errors, got %v", errs)
}
if !(len(host.Hosts) == 3) {
t.Errorf("Expected 3 hosts, got %v", len(host.Hosts))
}
}
func TestIncompleteExample(
t *testing.T,
) {
input := `example.com `
host := NewHost()
offset := uint32(8)
errs := host.Parse(input, 4, offset)
if len(errs) > 0 {
t.Fatalf("Expected no errors, got %v", errs)
}
if !(len(host.Hosts) == 1) {
t.Errorf("Expected 1 hosts, got %v", len(host.Hosts))
}
if !(host.Hosts[0].Value.Raw == "example.com") {
t.Errorf("Expected host to be 'example.com', got %v", host.Hosts[0].Value.Raw)
}
}

View File

@ -126,7 +126,7 @@ func (s *sshdParserListener) ExitEntry(ctx *parser.EntryContext) {
if len(errors) > 0 { if len(errors) > 0 {
for _, err := range errors { for _, err := range errors {
s.Errors = append(s.Errors, common.LSPError{ s.Errors = append(s.Errors, common.LSPError{
Range: err.Range.ShiftHorizontal(s.sshdContext.currentOption.Start.Character), Range: err.Range,
Err: err.Err, Err: err.Err,
}) })
} }