package web import ( "fmt" "io" "net/http" "strings" "github.com/owen/vibedns/internal/app" "github.com/owen/vibedns/internal/validate" ) // recordInputFromForm assembles a record from either the type-specific editor // (field_* inputs) or the advanced raw editor. func recordInputFromForm(r *http.Request) app.RecordInput { enabled := formBool(r, "enabled") in := app.RecordInput{ Name: formString(r, "name"), Type: formString(r, "type"), TTL: formUint32Ptr(r, "ttl"), Enabled: &enabled, Comment: formString(r, "comment"), } if formBool(r, "advanced") { // The advanced editor supplies rdata verbatim, and may override the // record type with one that has no dedicated editor. in.Data = formString(r, "data") if t := formString(r, "field_rtype"); t != "" { in.Type = t } if d := formString(r, "field_rdata"); d != "" { in.Data = d } return in } fields := map[string]string{} for key, values := range r.Form { if !strings.HasPrefix(key, "field_") || len(values) == 0 { continue } fields[strings.TrimPrefix(key, "field_")] = values[0] } if len(fields) > 0 { in.Fields = fields } else { in.Data = formString(r, "data") } return in } func (s *Server) handleRecordCreate(w http.ResponseWriter, r *http.Request) error { zoneID, err := pathID(r, "id") if err != nil { return err } if err := parseForm(r); err != nil { return err } rec, err := s.app.CreateRecord(r.Context(), s.actor(r), zoneID, recordInputFromForm(r)) if err != nil { return err } setFlash(w, r, "success", fmt.Sprintf("Added %s %s record.", rec.Name, rec.Type)) s.redirectBack(w, r) return nil } func (s *Server) handleRecordUpdate(w http.ResponseWriter, r *http.Request) error { id, err := pathID(r, "id") if err != nil { return err } if err := parseForm(r); err != nil { return err } rec, err := s.app.UpdateRecord(r.Context(), s.actor(r), id, recordInputFromForm(r)) if err != nil { return err } setFlash(w, r, "success", fmt.Sprintf("Saved %s %s record.", rec.Name, rec.Type)) s.redirectBack(w, r) return nil } func (s *Server) handleRecordDelete(w http.ResponseWriter, r *http.Request) error { id, err := pathID(r, "id") if err != nil { return err } if err := parseForm(r); err != nil { return err } if err := s.app.DeleteRecord(r.Context(), s.actor(r), id); err != nil { return err } setFlash(w, r, "success", "Record deleted.") s.redirectBack(w, r) return nil } func (s *Server) handleRecordToggle(w http.ResponseWriter, r *http.Request) error { id, err := pathID(r, "id") if err != nil { return err } if err := parseForm(r); err != nil { return err } enabled := formBool(r, "enabled") if err := s.app.SetRecordEnabled(r.Context(), s.actor(r), id, enabled); err != nil { return err } state := "disabled" if enabled { state = "enabled" } setFlash(w, r, "success", "Record "+state+".") s.redirectBack(w, r) return nil } func (s *Server) handleRecordBulk(w http.ResponseWriter, r *http.Request) error { zoneID, err := pathID(r, "id") if err != nil { return err } if err := parseForm(r); err != nil { return err } ids := formInt64s(r, "record_id") action := app.BulkAction(formString(r, "action")) n, err := s.app.BulkRecords(r.Context(), s.actor(r), zoneID, ids, action) if err != nil { return err } verb := map[app.BulkAction]string{ app.BulkDelete: "deleted", app.BulkEnable: "enabled", app.BulkDisable: "disabled", }[action] setFlash(w, r, "success", fmt.Sprintf("%d record%s %s.", n, plural(n), verb)) s.redirectBack(w, r) return nil } func plural(n int) string { if n == 1 { return "" } return "s" } // recordFieldValues splits a stored record back into editor fields, so an // existing record opens in its dedicated form rather than as raw rdata. func recordFieldValues(rtype, data string) map[string]string { return validate.SplitRData(rtype, data) } // copyLimited copies at most n bytes, reporting an error past the limit. func copyLimited(dst io.Writer, src io.Reader, n int64) (int64, error) { return io.Copy(dst, io.LimitReader(src, n)) }