Files
dnshelper/hostfile/managed.go
T

166 lines
4.9 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 {
// Recognise both current and legacy markers so that hosts files
// written by the old "DNSHelper" binary are parsed correctly.
if strings.Contains(line, "ekDNSHelper <<-> START CONFIG") ||
strings.Contains(line, "DNSHelper <<-> START CONFIG") {
startCount++
if startIdx == -1 {
startIdx = i
}
}
if strings.Contains(line, "ekDNSHelper <->> END CONFIG") ||
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
}
// lineMatchesHostname parses a hosts-file line ("IP<whitespace>hostname [aliases...]")
// and returns true if any hostname field (including aliases) matches,
// using case-insensitive comparison per RFC 4343.
func lineMatchesHostname(line, hostname string) bool {
fields := strings.Fields(line)
for _, f := range fields[1:] {
if strings.EqualFold(f, hostname) {
return true
}
}
return false
}
// 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 lineMatchesHostname(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 lineMatchesHostname(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
}