- Created a new `platform` package to handle OS-specific hosts file paths. - Added implementations for macOS, Linux, and Windows to retrieve the hosts file path. - Introduced tests for the `GetHostsFilePath` function to ensure correct behavior across platforms. feat: Add DNS resolver for IP lookups - Implemented a `resolver` package for performing DNS lookups. - Created a `DNSResolver` struct with a `LookupIP` method to handle A-record lookups and CNAME resolution. - Added comprehensive tests for various DNS scenarios, including CNAME chains and error handling. chore: Refactor project structure and update dependencies - Restructured the project to follow Go's standard package layout. - Updated `go.mod` to Go 1.20 and added `golang.org/x/net` dependency. - Removed old source files and ensured a clean build with all tests passing.
149 lines
4.3 KiB
Go
149 lines
4.3 KiB
Go
package hostfile
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// ParseManagedBlock splits raw file lines into prefix, managed, and postfix sections.
|
|
// Returns an error if the managed block markers are corrupt.
|
|
//
|
|
// Uses -1 as sentinel for "not found" because the start marker can appear at line 0.
|
|
// The current code's zero-index bug (treating index 0 as "not found") is avoided here.
|
|
func ParseManagedBlock(lines []string) (prefix, managed, postfix []string, hasManagedBlock bool, err error) {
|
|
startIdx := -1 // sentinel: not found
|
|
endIdx := -1 // sentinel: not found
|
|
startCount := 0
|
|
endCount := 0
|
|
|
|
for i, line := range lines {
|
|
if strings.Contains(line, "DNSHelper <<-> START CONFIG") {
|
|
startCount++
|
|
if startIdx == -1 {
|
|
startIdx = i
|
|
}
|
|
}
|
|
if strings.Contains(line, "DNSHelper <->> END CONFIG") {
|
|
endCount++
|
|
if endIdx == -1 {
|
|
endIdx = i
|
|
}
|
|
}
|
|
}
|
|
|
|
// Corruption detection (R-008): scan ALL lines first, then evaluate.
|
|
if startCount > 1 || endCount > 1 {
|
|
return nil, nil, nil, false, fmt.Errorf("managed block is corrupt: duplicate start/end markers found")
|
|
}
|
|
if startIdx >= 0 && endIdx < 0 {
|
|
return nil, nil, nil, false, fmt.Errorf("managed block is corrupt: start marker found at line %d but no end marker", startIdx+1)
|
|
}
|
|
if endIdx >= 0 && startIdx < 0 {
|
|
return nil, nil, nil, false, fmt.Errorf("managed block is corrupt: end marker found at line %d but no start marker", endIdx+1)
|
|
}
|
|
if startIdx >= 0 && endIdx >= 0 && endIdx < startIdx {
|
|
return nil, nil, nil, false, fmt.Errorf("managed block is corrupt: end marker (line %d) appears before start marker (line %d)", endIdx+1, startIdx+1)
|
|
}
|
|
|
|
if startIdx < 0 && endIdx < 0 {
|
|
// No managed block — all lines are prefix.
|
|
return lines, nil, nil, false, nil
|
|
}
|
|
|
|
// Valid managed block.
|
|
prefix = lines[:startIdx]
|
|
managed = lines[startIdx+1 : endIdx]
|
|
postfix = lines[endIdx+1:]
|
|
return prefix, managed, postfix, true, nil
|
|
}
|
|
|
|
// AddEntries removes existing entries for the same hostnames as the new entries,
|
|
// then appends the new entries. Returns deduplicated managed content lines.
|
|
func AddEntries(existing []string, entries []DNSEntry) []string {
|
|
// Collect hostnames being added.
|
|
hostnames := make(map[string]bool)
|
|
for _, e := range entries {
|
|
hostnames[e.Hostname] = true
|
|
}
|
|
|
|
// Keep existing lines that don't match any new hostname.
|
|
var result []string
|
|
for _, line := range existing {
|
|
keep := true
|
|
for h := range hostnames {
|
|
if strings.Contains(line, h) {
|
|
keep = false
|
|
break
|
|
}
|
|
}
|
|
if keep {
|
|
result = append(result, line)
|
|
}
|
|
}
|
|
|
|
// Append new entries.
|
|
for _, e := range entries {
|
|
result = append(result, e.String())
|
|
}
|
|
|
|
// Deduplicate preserving order.
|
|
return deduplicateLines(result)
|
|
}
|
|
|
|
// RemoveByHostname removes all managed entries matching any of the specified
|
|
// hostnames. Returns the remaining content.
|
|
//
|
|
// The correct algorithm iterates lines on the outer loop (not hosts), which
|
|
// avoids the duplication bug in the legacy removeHostsFromExistingContent
|
|
// function (dataprep.go).
|
|
func RemoveByHostname(existing []string, hostnames []string) []string {
|
|
var result []string
|
|
for _, line := range existing {
|
|
shouldRemove := false
|
|
for _, host := range hostnames {
|
|
if strings.Contains(line, host) {
|
|
shouldRemove = true
|
|
break
|
|
}
|
|
}
|
|
if !shouldRemove {
|
|
result = append(result, line)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// RemoveAll returns nil, indicating all managed entries should be removed.
|
|
// When AssembleContent receives nil/empty managed content, it omits the
|
|
// managed block entirely (markers included).
|
|
func RemoveAll() []string {
|
|
return nil
|
|
}
|
|
|
|
// AssembleContent builds the full file content from sections.
|
|
// If managed is empty, the managed block (markers included) is omitted entirely.
|
|
func AssembleContent(prefix, managed, postfix []string) []string {
|
|
var result []string
|
|
result = append(result, prefix...)
|
|
if len(managed) > 0 {
|
|
result = append(result, StartMarker)
|
|
result = append(result, managed...)
|
|
result = append(result, EndMarker)
|
|
}
|
|
result = append(result, postfix...)
|
|
return result
|
|
}
|
|
|
|
// deduplicateLines removes duplicate lines preserving order.
|
|
func deduplicateLines(lines []string) []string {
|
|
seen := make(map[string]bool)
|
|
var result []string
|
|
for _, line := range lines {
|
|
if !seen[line] {
|
|
seen[line] = true
|
|
result = append(result, line)
|
|
}
|
|
}
|
|
return result
|
|
}
|