aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Resnick <max@ofmax.li>2025-03-03 21:38:25 -0800
committerMax Resnick <max@ofmax.li>2025-03-03 21:38:25 -0800
commitb62450b18522f33c27cd67ff0191a42908e88bb1 (patch)
tree43bd80dce6a6bbcd3b105c821c8b815e68d6f196
parent5680113281aa58b63b1bdd7445a17e281007df23 (diff)
downloadunbound-adblock-config-b62450b18522f33c27cd67ff0191a42908e88bb1.tar.gz
doc: add comments
-rw-r--r--go.mod3
-rw-r--r--main.go51
-rw-r--r--main_test.go12
3 files changed, 57 insertions, 9 deletions
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 <url-list> <output-file>
+//
+// 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 <url-list> <output-file>")
+ 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 <url-list> <output-file>")
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()