feat: Implement platform-specific hosts file path retrieval
- 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.
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user