package main import ( "bytes" "embed" "encoding/json" "html/template" "log/slog" "net" "net/http" "net/url" "os" "strings" ) //go:embed countries.json var countriesJSON []byte func main() { log := slog.New(slog.NewTextHandler(os.Stderr, nil)) socketPath := os.Getenv("SOCKET_PATH") if socketPath == "" { log.Error("SOCKET_PATH must be set") os.Exit(1) } domain := os.Getenv("DOMAIN") if domain == "" { log.Error("DOMAIN environment variable must be set") os.Exit(1) } var handler = handler{logger: log, domain: domain, lawsByCC: map[string]map[string]law{}} err := json.Unmarshal(countriesJSON, &handler.countries) if err != nil { log.Error("failed to parse countries.json", Error(err)) os.Exit(1) } lawFiles, err := os.ReadDir("laws") if err != nil { log.Error("failed to read laws/ directory", Error(err)) os.Exit(1) } for _, file := range lawFiles { text, err := os.ReadFile("laws/" + file.Name()) if err != nil { log.Error("failed to read file", Error(err), "path", "laws/"+file.Name()) os.Exit(1) } var laws []law err = json.Unmarshal([]byte(text), &laws) if err != nil { log.Error("failed to parse file", Error(err), "path", file.Name()) os.Exit(1) } cc := strings.SplitN(file.Name(), ".", 2)[0] handler.lawsByCC[cc] = map[string]law{} for _, law := range laws { if law.Redir != "" { handler.lawsByCC[cc][law.Redir] = law } } } http.HandleFunc("/", handler.handle) listener, err := net.Listen("unix", socketPath) if err != nil { log.Error("failed to listen", Error(err)) os.Exit(1) } if err := os.Chmod(socketPath, 0666); err != nil { log.Error("failed to set socket permissions", Error(err), "path", socketPath) return } err = http.Serve(listener, nil) log.Error("failed to serve", Error(err)) } //go:embed templates var templates embed.FS var tpl, _ = template.New("").Funcs(template.FuncMap{ "ToUpper": strings.ToUpper, }).Option("missingkey=error").ParseFS(templates, "templates/*") type handler struct { logger *slog.Logger domain string countries map[string]country lawsByCC map[string]map[string]law } func (h *handler) handle(w http.ResponseWriter, r *http.Request) { if r.Host == h.domain { if r.URL.Path != "/" { w.WriteHeader(http.StatusNotFound) w.Write([]byte("page not found")) return } var html bytes.Buffer err := tpl.ExecuteTemplate(&html, "index.html.tmpl", map[string]any{ "Countries": h.countries, "Domain": r.Host, }) if err != nil { h.logger.Error("failed to execute index template", Error(err)) http.Error(w, "internal server error", http.StatusInternalServerError) return } w.Write(html.Bytes()) return } cc, isSubdomain := strings.CutSuffix(r.Host, "."+h.domain) if !isSubdomain { h.logger.Error("request for unknown hosts", "host", r.Host) w.Write([]byte("unknown host")) return } key := strings.TrimLeft(r.URL.Path, "/") if len(key) > 0 { val, ok := h.lawsByCC[cc][key] if !ok { w.WriteHeader(http.StatusNotFound) w.Write([]byte("unknown law")) return } http.Redirect(w, r, val.URL, 302) } else { query := r.URL.Query().Get("q") if query != "" { country, ok := h.countries[cc] if !ok { w.WriteHeader(http.StatusNotFound) w.Write([]byte("search not implemented for this country")) return } if country.HasPlaceholder() { http.Redirect(w, r, strings.Replace(country.SearchURL, "%s", url.QueryEscape(query), 1), 302) return } else { w.WriteHeader(http.StatusBadRequest) } } _, hasJSONLaws := h.lawsByCC[cc] var html bytes.Buffer err := tpl.ExecuteTemplate(&html, "search.html.tmpl", map[string]any{ "TLD": cc, "Domain": h.domain, "Country": h.countries[cc], "HasJSONLaws": hasJSONLaws, }) if err != nil { h.logger.Error("failed to execute search template", Error(err)) http.Error(w, "internal server error", http.StatusInternalServerError) return } w.Write(html.Bytes()) } } type law struct { URL string Title string Abbr string Redir string } type country struct { Name string SearchURL string `json:"search_url"` } func (c country) HasPlaceholder() bool { return strings.Contains(c.SearchURL, "%s") } func Error(value error) slog.Attr { return slog.String("error", value.Error()) }