package dnsfilter import ( "bufio" "net" "os" "strings" ) // ParseLine extracts a domain from a single blocklist line. // Handles hosts format (0.0.0.0 domain), dnsmasq format (address=/domain/...), // and plain domain lists. Returns empty string and false for unparseable lines. func ParseLine(line string) (string, bool) { line = strings.TrimSpace(line) if line == "" || line[0] == '#' || line[0] == '!' { return "", false } // dnsmasq format: address=/domain/0.0.0.0 or server=/domain/ if strings.HasPrefix(line, "address=/") || strings.HasPrefix(line, "server=/") { parts := strings.SplitN(line, "/", 4) if len(parts) >= 3 && parts[1] != "" { return validateDomain(parts[1]) } return "", false } // Hosts format: 0.0.0.0 domain or 127.0.0.1 domain fields := strings.Fields(line) if len(fields) >= 2 { ip := fields[0] if ip == "0.0.0.0" || ip == "127.0.0.1" || ip == "::1" || ip == "::0" { domain := fields[1] // Strip inline comments if i := strings.IndexByte(domain, '#'); i >= 0 { domain = domain[:i] } return validateDomain(domain) } } // Plain domain format: one domain per line if len(fields) == 1 { return validateDomain(fields[0]) } return "", false } // validateDomain checks that a string looks like a valid domain for blocking. func validateDomain(d string) (string, bool) { d = strings.ToLower(strings.TrimSpace(d)) if d == "" || !strings.Contains(d, ".") { return "", false } // Reject known non-domains switch d { case "localhost", "localhost.localdomain", "local", "broadcasthost", "ip6-localhost", "ip6-loopback", "ip6-localnet", "ip6-mcastprefix", "ip6-allnodes", "ip6-allrouters": return "", false } // Reject IP addresses if net.ParseIP(d) != nil { return "", false } return d, true } // ParseFile reads a cached blocklist file and returns all unique domains. func ParseFile(path string) (map[string]struct{}, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() domains := make(map[string]struct{}) scanner := bufio.NewScanner(f) for scanner.Scan() { if domain, ok := ParseLine(scanner.Text()); ok { domains[domain] = struct{}{} } } return domains, scanner.Err() }