package blocklist import ( "bufio" "os" "strings" ) type Blocklist struct { domains map[string]struct{} } func Load(path string) (*Blocklist, error) { bl := &Blocklist{domains: make(map[string]struct{})} f, err := os.Open(path) if err != nil { if os.IsNotExist(err) { return bl, nil } return nil, err } defer f.Close() sc := bufio.NewScanner(f) for sc.Scan() { line := strings.TrimSpace(sc.Text()) if line == "" || line[0] == '#' || line[0] == '!' { continue } domain := parseLine(line) if domain != "" && !strings.HasPrefix(domain, "*.") { bl.domains[strings.ToLower(domain)] = struct {}{} } } return bl, sc.Err() } func parseLine(line string) string { line = strings.TrimSpace(line) // 1. Hosts: "0.0.0.0 domain" "127.0.0.1 domain # comment" if strings.HasPrefix(line, "0.0.0.0 ") { line = line[8:] } else if strings.HasPrefix(line, "127.0.0.1 ") { line = line[10:] } else if strings.HasPrefix(line, "||") { // 2. AdGuard / uBlock: "||domain^$attr" line = line[2:] // Cut at ^ or $ if idx := strings.IndexAny(line, "^$"); idx >= 0 { line = line[:idx] } } if idx := strings.IndexAny(line, " \t#"); idx >= 0 { line = line[:idx] } line = strings.TrimSpace(line) line = strings.TrimSuffix(line, ".") if strings.ContainsAny(line, "/:\\?") { return "" } return line } func (bl *Blocklist) Blocked(qname string) bool { qname = strings.ToLower(qname) qname = strings.TrimSuffix(qname, ".") _, ok := bl.domains[qname] return ok }