package server import ( "net/http" "strings" "send/internal/auth" "send/internal/store" ) // handleDelete removes an object early. Three credentials are accepted: the // delete token handed to the uploader, the token that owns the object, and any // admin token. // // There is only one delete route, and it is a POST, so the success page's plain // form works with scripting disabled and no second code path is needed. func (s *Server) handleDelete(w http.ResponseWriter, r *http.Request) { id, err := store.CleanID(r.PathValue("id")) if err != nil { s.fail(w, r, http.StatusNotFound, "No such file.") return } m, err := s.store.Get(id, s.now()) if err != nil { s.fail(w, r, http.StatusNotFound, "No such file.") return } secret := bearer(r) if secret == "" { // A small form post; the 4 KiB cap keeps this from being a way to // stream a body into memory. r.Body = http.MaxBytesReader(w, r.Body, maxFieldBytes) if err := r.ParseForm(); err == nil { secret = strings.TrimSpace(r.PostFormValue("token")) } } if secret == "" { s.fail(w, r, http.StatusUnauthorized, "A delete token or an owning token is required.") return } if !s.mayDelete(m, secret) { s.fail(w, r, http.StatusForbidden, "That token cannot delete this file.") return } if err := s.store.Delete(id); err != nil { s.log.Error("deleting object", "id", id, "err", err) s.fail(w, r, http.StatusInternalServerError, "Could not delete the file.") return } s.log.Info("deleted", "id", id, "ip", clientIP(r, s.cfg)) if wantsJSON(r) { writeJSON(w, http.StatusOK, map[string]string{"status": "deleted", "id": id}) return } s.render(w, http.StatusOK, "error.html", errorPage{ page: s.page("Deleted", false), Status: "Deleted", Message: "The file is gone.", }) } // mayDelete checks the presented secret against the object's delete token // first, then against the token file. func (s *Server) mayDelete(m *store.Meta, secret string) bool { if auth.EqualHash(m.DeleteHash, auth.HashSecret(secret)) { return true } if err := s.tokens.MaybeReload(); err != nil { s.log.Error("reloading token file", "err", err) } t := s.tokens.Lookup(secret) if t == nil { return false } return t.Admin || (m.Owner != "" && t.Name == m.Owner) }