Files
dnshelper/hostfile/managed.go
T
dyoder f0ce5a4042 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.
2026-03-03 18:37:02 -05:00

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
}