From 960f365bc930bf229f9eb5bd520c2ff7e542419f Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 25 Aug 2024 13:11:59 +0200 Subject: [PATCH] feat(hosts): Add resolver analyzation --- doc-values/extra-values.go | 7 ++ handlers/hosts/fields/documentation-fields.go | 10 ++ handlers/hosts/handlers/analyzer.go | 93 ++++++++++++++ handlers/hosts/handlers/analyzer_test.go | 117 ++++++++++++++++++ handlers/hosts/handlers/errors.go | 12 ++ handlers/hosts/tree/handler_test.go | 7 +- handlers/hosts/tree/hosts.go | 5 +- handlers/hosts/tree/listener.go | 25 +++- 8 files changed, 270 insertions(+), 6 deletions(-) create mode 100644 handlers/hosts/fields/documentation-fields.go create mode 100644 handlers/hosts/handlers/analyzer.go create mode 100644 handlers/hosts/handlers/analyzer_test.go create mode 100644 handlers/hosts/handlers/errors.go diff --git a/doc-values/extra-values.go b/doc-values/extra-values.go index 3a47e9c..d804c31 100644 --- a/doc-values/extra-values.go +++ b/doc-values/extra-values.go @@ -3,6 +3,7 @@ package docvalues import ( "config-lsp/utils" "os" + "regexp" "strings" ) @@ -173,3 +174,9 @@ func SingleEnumValue(value string) EnumValue { }, } } + +func DomainValue() Value { + return RegexValue{ + Regex: *regexp.MustCompile(`^.+?\..+$`), + } +} diff --git a/handlers/hosts/fields/documentation-fields.go b/handlers/hosts/fields/documentation-fields.go new file mode 100644 index 0000000..ccf564c --- /dev/null +++ b/handlers/hosts/fields/documentation-fields.go @@ -0,0 +1,10 @@ +package fields + +import docvalues "config-lsp/doc-values" + +var IPAddressField = docvalues.IPAddressValue{ + AllowIPv4: true, + AllowIPv6: true, +} + +var HostnameField = docvalues.DomainValue() diff --git a/handlers/hosts/handlers/analyzer.go b/handlers/hosts/handlers/analyzer.go new file mode 100644 index 0000000..a14c35f --- /dev/null +++ b/handlers/hosts/handlers/analyzer.go @@ -0,0 +1,93 @@ +package handlers + +import ( + "config-lsp/common" + "config-lsp/handlers/hosts/tree" + "config-lsp/utils" + "net" +) + +type ResolverEntry struct { + IPv4Address net.IP + IPv6Address net.IP + Line uint32 +} + +type Resolver struct { + Entries map[string]ResolverEntry +} + +func createEntry( + line uint32, + ip net.IP, +) ResolverEntry { + entry := ResolverEntry{ + Line: line, + } + + if ipv4 := ip.To4(); ipv4 != nil { + entry.IPv4Address = ipv4 + } else if ipv6 := ip.To16(); ipv6 != nil { + entry.IPv6Address = ipv6 + } + + return entry +} + +type hostnameEntry struct { + Location common.LocationRange + HostName string +} + +func CreateResolverFromParser(p tree.HostsParser) (Resolver, []common.LSPError) { + errors := make([]common.LSPError, 0) + resolver := Resolver{ + Entries: make(map[string]ResolverEntry), + } + + for lineNumber, entry := range p.Tree.Entries { + if entry.IPAddress != nil && entry.Hostname != nil { + hostNames := append( + []hostnameEntry{ + { + Location: entry.Hostname.Location, + HostName: entry.Hostname.Value, + }, + }, + utils.Map( + entry.Aliases, + func(alias *tree.HostsHostname) hostnameEntry { + return hostnameEntry{ + Location: alias.Location, + HostName: alias.Value, + } + }, + )..., + ) + + for _, hostName := range hostNames { + entry := createEntry( + lineNumber, + entry.IPAddress.Value.IP, + ) + + if resolv, found := resolver.Entries[hostName.HostName]; found { + errors = append( + errors, + common.LSPError{ + Range: hostName.Location, + Err: DuplicateHostEntry{ + AlreadyFoundAt: resolv.Line, + Hostname: hostName.HostName, + }, + }, + ) + } else { + resolver.Entries[hostName.HostName] = entry + } + } + } + } + + return resolver, errors +} diff --git a/handlers/hosts/handlers/analyzer_test.go b/handlers/hosts/handlers/analyzer_test.go new file mode 100644 index 0000000..9f55a94 --- /dev/null +++ b/handlers/hosts/handlers/analyzer_test.go @@ -0,0 +1,117 @@ +package handlers + +import ( + "config-lsp/handlers/hosts/tree" + "config-lsp/utils" + "testing" +) + +func TestResolverEntriesWorksWithNonOverlapping( + t *testing.T, +) { + input := utils.Dedent(` +1.2.3.4 hello.com +5.5.5.5 world.com +`) + + parser := tree.CreateNewHostsParser() + errors := parser.Parse(input) + + if len(errors) != 0 { + t.Fatalf("PARER FAILED! Expected no errors, but got %v", errors) + } + + resolver, errors := CreateResolverFromParser(parser) + + if len(errors) != 0 { + t.Errorf("Expected no errors, but got %v", errors) + } + + if len(resolver.Entries) != 2 { + t.Errorf("Expected 2 entries, but got %v", len(resolver.Entries)) + } + + if !(resolver.Entries["hello.com"].IPv4Address.String() == "1.2.3.4") { + t.Errorf("Expected hello.com to be 1.2.3.4, but got %v", resolver.Entries["hello.com"].IPv4Address) + } + + if !(resolver.Entries["world.com"].IPv4Address.String() == "5.5.5.5") { + t.Errorf("Expected world.com to be 5.5.5.5, but got %v", resolver.Entries["world.com"].IPv4Address) + } + + if !(resolver.Entries["hello.com"].IPv6Address == nil) { + t.Errorf("Expected hello.com to have no IPv6 address, but got %v", resolver.Entries["hello.com"].IPv6Address) + } + + if !(resolver.Entries["world.com"].IPv6Address == nil) { + t.Errorf("Expected world.com to have no IPv6 address, but got %v", resolver.Entries["world.com"].IPv6Address) + } +} + +func TestResolverEntriesWithSimpleOverlapping( + t *testing.T, +) { + input := utils.Dedent(` +1.2.3.4 hello.com +5.5.5.5 hello.com +`) + + parser := tree.CreateNewHostsParser() + errors := parser.Parse(input) + + if len(errors) != 0 { + t.Fatalf("PARER FAILED! Expected no errors, but got %v", errors) + } + + resolver, errors := CreateResolverFromParser(parser) + + if !(len(errors) == 1) { + t.Errorf("Expected 1 error, but got %v", len(errors)) + } + + if len(resolver.Entries) != 1 { + t.Errorf("Expected 1 entry, but got %v", len(resolver.Entries)) + } + + if !(resolver.Entries["hello.com"].IPv4Address.String() == "1.2.3.4") { + t.Errorf("Expected hello.com to be 1.2.3.4, but got %v", resolver.Entries["hello.com"].IPv4Address) + } +} + +func TestResolverEntriesWithComplexOverlapping( + t *testing.T, +) { + input := utils.Dedent(` +1.2.3.4 hello.com test.com +5.5.5.5 check.com test.com +`) + + parser := tree.CreateNewHostsParser() + errors := parser.Parse(input) + + if len(errors) != 0 { + t.Fatalf("PARER FAILED! Expected no errors, but got %v", errors) + } + + resolver, errors := CreateResolverFromParser(parser) + + if !(len(errors) == 1) { + t.Errorf("Expected 1 error, but got %v", len(errors)) + } + + if len(resolver.Entries) != 3 { + t.Errorf("Expected 3 entries, but got %v", len(resolver.Entries)) + } + + if !(resolver.Entries["hello.com"].IPv4Address.String() == "1.2.3.4") { + t.Errorf("Expected hello.com to be 1.2.3.4, but got %v", resolver.Entries["hello.com"].IPv4Address) + } + + if !(resolver.Entries["check.com"].IPv4Address.String() == "5.5.5.5") { + t.Errorf("Expected check.com to be 5.5.5.5, but got %v", resolver.Entries["check.com"].IPv4Address) + } + + if !(resolver.Entries["test.com"].IPv4Address.String() == "1.2.3.4") { + t.Errorf("Expected test.com to have no IPv4 address, but got %v", resolver.Entries["test.com"].IPv4Address) + } +} diff --git a/handlers/hosts/handlers/errors.go b/handlers/hosts/handlers/errors.go new file mode 100644 index 0000000..af08ee3 --- /dev/null +++ b/handlers/hosts/handlers/errors.go @@ -0,0 +1,12 @@ +package handlers + +import "fmt" + +type DuplicateHostEntry struct { + AlreadyFoundAt uint32 + Hostname string +} + +func (d DuplicateHostEntry) Error() string { + return fmt.Sprintf("'%s' already defined on line %d", d.Hostname, d.AlreadyFoundAt) +} diff --git a/handlers/hosts/tree/handler_test.go b/handlers/hosts/tree/handler_test.go index b6e27a4..ff6a015 100644 --- a/handlers/hosts/tree/handler_test.go +++ b/handlers/hosts/tree/handler_test.go @@ -2,6 +2,7 @@ package tree import ( "config-lsp/utils" + "net" "testing" ) @@ -27,7 +28,7 @@ func TestValidSimpleExampleWorks( t.Errorf("Expected IP address to be present, but got nil") } - if !(parser.Tree.Entries[1].IPAddress.Value == "1.2.3.4") { + if !(parser.Tree.Entries[1].IPAddress.Value.String() == net.ParseIP("1.2.3.4").String()) { t.Errorf("Expected IP address to be 1.2.3.4, but got %v", parser.Tree.Entries[1].IPAddress.Value) } @@ -94,7 +95,7 @@ func TestValidComplexExampleWorks( t.Errorf("Expected IP address to be present, but got nil") } - if !(parser.Tree.Entries[3].IPAddress.Value == "1.2.3.4") { + if !(parser.Tree.Entries[3].IPAddress.Value.String() == net.ParseIP("1.2.3.4").String()) { t.Errorf("Expected IP address to be 1.2.3.4, but got %v", parser.Tree.Entries[2].IPAddress.Value) } @@ -129,7 +130,7 @@ func TestInvalidExampleWorks( t.Errorf("Expected no comment lines, but got %v", len(parser.CommentLines)) } - if !(parser.Tree.Entries[1].IPAddress.Value == "1.2.3.4") { + if !(parser.Tree.Entries[1].IPAddress.Value.String() == net.ParseIP("1.2.3.4").String()) { t.Errorf("Expected IP address to be nil, but got %v", parser.Tree.Entries[1].IPAddress) } diff --git a/handlers/hosts/tree/hosts.go b/handlers/hosts/tree/hosts.go index d9bd8a6..3c678ef 100644 --- a/handlers/hosts/tree/hosts.go +++ b/handlers/hosts/tree/hosts.go @@ -3,6 +3,7 @@ package tree import ( "config-lsp/common" "fmt" + "net" "github.com/antlr4-go/antlr/v4" ) @@ -48,7 +49,7 @@ func (p HostsEntry) String() string { str := fmt.Sprintf("HostsEntry(%v)", p.Location) if p.IPAddress != nil { - str += " " + p.IPAddress.Value + str += " " + p.IPAddress.Value.String() } if p.Hostname != nil { @@ -64,7 +65,7 @@ func (p HostsEntry) String() string { type HostsIPAddress struct { Location common.LocationRange - Value string + Value net.IPAddr } type HostsHostname struct { diff --git a/handlers/hosts/tree/listener.go b/handlers/hosts/tree/listener.go index 65e4a98..a994ac1 100644 --- a/handlers/hosts/tree/listener.go +++ b/handlers/hosts/tree/listener.go @@ -2,7 +2,9 @@ package tree import ( "config-lsp/common" + docvalues "config-lsp/doc-values" "config-lsp/handlers/hosts/parser" + "net" "github.com/antlr4-go/antlr/v4" ) @@ -14,6 +16,7 @@ type hostsListenerContext struct { type hostsParserListener struct { *parser.BaseHostsListener Parser *HostsParser + Errors []common.LSPError hostsContext hostsListenerContext } @@ -35,11 +38,30 @@ func (s *hostsParserListener) EnterIpAddress(ctx *parser.IpAddressContext) { location := characterRangeFromCtx(ctx.BaseParserRuleContext) location.ChangeBothLines(s.hostsContext.line) + ip := net.ParseIP(ctx.GetText()) + + if ip == nil { + s.Errors = append(s.Errors, common.LSPError{ + Range: location, + Err: docvalues.InvalidIPAddress{}, + }) + return + } + + ipAddr, err := net.ResolveIPAddr("ip", ip.String()) + + if err != nil { + s.Errors = append(s.Errors, common.LSPError{ + Range: location, + Err: docvalues.InvalidIPAddress{}, + }) + } + entry := s.Parser.Tree.Entries[location.Start.Line] entry.IPAddress = &HostsIPAddress{ Location: location, - Value: ctx.GetText(), + Value: *ipAddr, } } @@ -91,6 +113,7 @@ func createHostsFileListener( hostsContext: hostsListenerContext{ line: line, }, + Errors: make([]common.LSPError, 0), } }