package main import ( "context" "crypto/subtle" "embed" "fmt" "html/template" "log" "net/http" "strings" "git.wntrmute.dev/kyle/goutils/config" "git.wntrmute.dev/kyle/kls/links" "github.com/jackc/pgx/v4/pgxpool" ) type server struct { db *pgxpool.Pool } //go:embed templates/*.tpl var templateFiles embed.FS var templates = template.Must(template.ParseFS(templateFiles, "templates/*.tpl")) type page struct { Short string URLs []*links.URL } func (srv *server) servePage(w http.ResponseWriter, p page, tpl string) { err := templates.ExecuteTemplate(w, tpl, p) if err != nil { log.Printf("error executing template: %s", err) http.Error(w, fmt.Sprintf("template execution failed: %s", err.Error()), http.StatusInternalServerError) return } } func (srv *server) postURL(w http.ResponseWriter, r *http.Request) { err := r.ParseForm() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } isRawURL := boolean(r.FormValue("rawp")) url := r.FormValue("value") if len(url) == 0 { http.Error(w, "invalid URL", http.StatusBadRequest) return } url, err = links.CleanString(url, isRawURL) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } ctx := context.Background() short, err := links.StoreURL(ctx, srv.db, url) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } srv.servePage(w, page{Short: short}, "index.tpl") } func (srv *server) redirect(w http.ResponseWriter, r *http.Request) { short := strings.TrimPrefix(r.URL.Path, "/") u, err := links.RetrieveURL(context.Background(), srv.db, short) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } http.Redirect(w, r, u, http.StatusFound) } func (srv *server) listAll(w http.ResponseWriter, r *http.Request) { allPosts, err := links.FetchAll(context.Background(), srv.db) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } p := page{ URLs: allPosts, } srv.servePage(w, p, "list.tpl") } func (srv *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet && links.ValidShortCode.MatchString(r.URL.Path) { srv.redirect(w, r) return } user, pass, ok := r.BasicAuth() username := config.Get("HTTP_USER") password := config.Get("HTTP_PASS") if !ok || subtle.ConstantTimeCompare([]byte(user), []byte(username)) != 1 || subtle.ConstantTimeCompare([]byte(pass), []byte(password)) != 1 { w.Header().Set("WWW-Authenticate", `Basic realm="kls"`) w.WriteHeader(401) w.Write([]byte("Unauthorised.\n")) return } if r.Method == http.MethodPost { srv.postURL(w, r) return } if r.URL.Path == "/list" { srv.listAll(w, r) return } srv.servePage(w, page{}, "index.tpl") } func boolean(s string) bool { switch s { case "on", "true", "True", "yes", "Yes": return true default: return false } }