From b62450b18522f33c27cd67ff0191a42908e88bb1 Mon Sep 17 00:00:00 2001 From: Max Resnick Date: Mon, 3 Mar 2025 21:38:25 -0800 Subject: doc: add comments --- go.mod | 3 +++ main.go | 51 ++++++++++++++++++++++++++++++++++++++++++++------- main_test.go | 12 ++++++++++-- 3 files changed, 57 insertions(+), 9 deletions(-) create mode 100644 go.mod diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..97a0960 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module go.ofmax.li/unbound-ads-generator + +go 1.24.0 diff --git a/main.go b/main.go index 5bb387d..09fc095 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,21 @@ +// Package main provides a tool to generate Unbound DNS server configuration +// for domain blocking. It fetches domain lists from URLs and converts them +// into Unbound local-zone configuration format. +// +// Usage: +// +// unbound-ads +// +// The url-list should contain HTTP(S) URLs, one per line, pointing to files +// containing domain lists. Comments (lines starting with #) are ignored. +// The output file will contain Unbound configuration in the format: +// +// local-zone: "domain.com" refuse package main import ( "bufio" + "flag" "fmt" "log/slog" "net/http" @@ -9,19 +23,33 @@ import ( "strings" ) +// Version is set during build using ldflags +var Version = "dev-" + strings.Split(strings.TrimPrefix(`$Format:%H$`, "$Format:"), "-")[0] + +// main processes a list of URLs containing domain lists and generates +// an Unbound DNS server configuration file for domain blocking. func main() { - if len(os.Args) != 3 { - slog.Error("usage: program ") + showVersion := flag.Bool("version", false, "Show version information") + flag.Parse() + + if *showVersion { + fmt.Println("unbound-ads version:", Version) + return + } + + args := flag.Args() + if len(args) != 2 { + slog.Error("usage: unbound-ads ") os.Exit(1) } - urls, err := fetchURLList(os.Args[1]) + urls, err := fetchURLList(args[0]) if err != nil { slog.Error("failed to fetch URL list", "error", err) os.Exit(1) } - f, err := os.Create(os.Args[2]) + f, err := os.Create(args[1]) if err != nil { slog.Error("failed to create output file", "error", err) os.Exit(1) @@ -42,6 +70,9 @@ func main() { slog.Info("completed", "total_domains", len(domains)) } +// fetchURLList retrieves a list of URLs from the specified URL. +// It ignores empty lines and comments (lines starting with #). +// Returns a slice of URLs or an error if the fetch fails. func fetchURLList(url string) ([]string, error) { resp, err := http.Get(url) if err != nil { @@ -61,6 +92,11 @@ func fetchURLList(url string) ([]string, error) { return urls, scanner.Err() } +// fetchDomainsAndWrite retrieves domains from the specified URL and writes them +// to the output in Unbound configuration format. It handles various domain list +// formats including "0.0.0.0 domain.com" and plain domain lists. +// Domains are deduplicated using the seen map. +// Returns an error if fetching or writing fails. func fetchDomainsAndWrite(url string, w *bufio.Writer, seen map[string]struct{}) error { resp, err := http.Get(url) if err != nil { @@ -72,12 +108,13 @@ func fetchDomainsAndWrite(url string, w *bufio.Writer, seen map[string]struct{}) scanner := bufio.NewScanner(resp.Body) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) + // Skip empty lines and comments if line == "" || strings.HasPrefix(line, "#") { continue } + // Handle both "0.0.0.0 domain.com" and plain domain formats var domain string - // Handle "0.0.0.0 domain.com" format if strings.Contains(line, " ") { parts := strings.Fields(line) if len(parts) >= 2 { @@ -87,13 +124,13 @@ func fetchDomainsAndWrite(url string, w *bufio.Writer, seen map[string]struct{}) domain = line } - // Basic domain validation and normalization + // Normalize and validate domain format domain = strings.ToLower(strings.TrimSpace(domain)) if domain == "" || !strings.Contains(domain, ".") || strings.HasPrefix(domain, ".") || strings.HasSuffix(domain, ".") { continue } - // Skip if we've seen this domain before + // Deduplicate domains if _, exists := seen[domain]; exists { continue } diff --git a/main_test.go b/main_test.go index 29a8341..e231051 100644 --- a/main_test.go +++ b/main_test.go @@ -43,7 +43,11 @@ https://example.com/2`, for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(tt.content)) + _, err := w.Write([]byte(tt.content)) + if err != nil { + t.Errorf("Couldn't write to content %v", err) + } + })) defer ts.Close() @@ -111,7 +115,11 @@ local-zone: "domain2.com" refuse for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(tt.content)) + _, err := w.Write([]byte(tt.content)) + if err != nil { + t.Errorf("Couldn't write to content %v", err) + } + })) defer ts.Close() -- cgit v1.2.3