package main import ( "embed" "encoding/json" "io/ioutil" "log" "net" "net/http" "net/url" "os" "strings" "text/template" ) //go:embed countries.json var countriesJSON []byte func main() { socketPath := os.Getenv("SOCKET_PATH") if socketPath == "" { log.Fatal("SOCKET_PATH must be set") } domain := os.Getenv("DOMAIN") if domain == "" { log.Fatal("DOMAIN environment variable must be set") } var handler = handler{domain: domain, lawsByCC: map[string]map[string]law{}} err := json.Unmarshal(countriesJSON, &handler.countries) if err != nil { log.Fatal("countries.json ", err) } lawFiles, err := ioutil.ReadDir("laws") if err != nil { log.Fatal(err) } for _, file := range lawFiles { text, err := ioutil.ReadFile("laws/" + file.Name()) if err != nil { log.Fatal(file.Name(), err) } var laws []law err = json.Unmarshal([]byte(text), &laws) if err != nil { log.Fatal(file.Name(), err) } 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.Fatal(err) } if err := os.Chmod(socketPath, 0666); err != nil { log.Fatalf("error setting socket permissions: %s", err) return } log.Fatal(http.Serve(listener, nil)) } //go:embed templates var templates embed.FS var tpl, _ = template.New("").Funcs(template.FuncMap{ "ToUpper": strings.ToUpper, }).ParseFS(templates, "templates/*") type handler struct { 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 } err := tpl.ExecuteTemplate(w, "index.html.tmpl", map[string]any{ "Countries": h.countries, "Domain": r.Host, }) if err != nil { log.Fatal(err) } return } cc, isSubdomain := strings.CutSuffix(r.Host, "."+h.domain) if !isSubdomain { 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] err := tpl.ExecuteTemplate(w, "search.html.tmpl", map[string]any{ "TLD": cc, "Domain": h.domain, "Country": h.countries[cc], "HasJSONLaws": hasJSONLaws, }) if err != nil { log.Fatal(err) } } } 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") }