Implement file deletion

This commit is contained in:
Daan Sprenkels 2019-11-22 18:41:54 +01:00
parent c82a71f7d0
commit df86d5836a
7 changed files with 83 additions and 47 deletions

View File

@ -0,0 +1,3 @@
{{define "title"}}
Success - rushlink
{{end}}

View File

@ -0,0 +1 @@
<{{.Request.Host}}/{{.Paste.Key}}> was succesfully deleted

View File

@ -1,4 +1,5 @@
https://{{.Request.Host}}/{{.Paste.Key}}
{{if .Request.PostForm.deleteToken -}} {{if .Request.PostForm.deleteToken -}}
https://{{.Request.Host}}/{{.Paste.Key}}?deleteToken={{.Paste.DeleteToken | urlquery}} https://{{.Request.Host}}/{{.Paste.Key}}?deleteToken={{.Paste.DeleteToken | urlquery}}
{{else -}}
https://{{.Request.Host}}/{{.Paste.Key}}
{{end -}} {{end -}}

View File

@ -1,4 +1,5 @@
https://{{.Request.Host}}/{{.Paste.Key}}
{{if .Request.PostForm.deleteToken -}} {{if .Request.PostForm.deleteToken -}}
https://{{.Request.Host}}/{{.Paste.Key}}?deleteToken={{.Paste.DeleteToken | urlquery}} https://{{.Request.Host}}/{{.Paste.Key}}?deleteToken={{.Paste.DeleteToken | urlquery}}
{{else -}}
https://{{.Request.Host}}/{{.Paste.Key}}
{{end -}} {{end -}}

View File

@ -79,7 +79,7 @@ func (t fileUploadState) String() string {
func OpenFileStore(path string) error { func OpenFileStore(path string) error {
if path == "" { if path == "" {
return errors.New("file store path not set") return errors.New("file-store not set")
} }
// Try to create the file store directory if it does not yet exist // Try to create the file store directory if it does not yet exist
@ -158,11 +158,23 @@ func (fu *fileUpload) save(tx *bolt.Tx) error {
} }
func (fu *fileUpload) delete(tx *bolt.Tx) error { func (fu *fileUpload) delete(tx *bolt.Tx) error {
// Replace the old paste with a new empty paste // Remove the file in the backend
return (&fileUpload{ filePath := fileStorePath(fu.ID, fu.FileName)
if err := os.Remove(filePath); err != nil {
return err
}
// Update the file in the server
if err := (&fileUpload{
ID: fu.ID, ID: fu.ID,
State: fileUploadStateDeleted, State: fileUploadStateDeleted,
}).save(tx) }).save(tx); err != nil {
return err
}
// Cleanup the parent directory
wrap := "deletion succeeded, but removing the file directory has failed"
return errors.Wrap(os.Remove(path.Dir(filePath)), wrap)
} }
func (fu *fileUpload) url() *url.URL { func (fu *fileUpload) url() *url.URL {

View File

@ -173,47 +173,34 @@ func viewPasteHandlerInner(w http.ResponseWriter, r *http.Request, flags viewPas
} }
func newPasteHandler(w http.ResponseWriter, r *http.Request) { func newPasteHandler(w http.ResponseWriter, r *http.Request) {
newPasteHandlerMultipart(w, r, func(w http.ResponseWriter, r *http.Request) { file, fileHeader, err := r.FormFile("file")
newPasteHandlerURLEncoded(w, r, func(w http.ResponseWriter, r *http.Request) { if err == nil {
renderError(w, r, http.StatusBadRequest, "no form data in request\n") newFileUploadPasteHandler(w, r, file, *fileHeader)
})
})
}
// Try to parse the new-paste request as multi-part form.
//
// If there is no multi-part form in the request, this handler will call next.
func newPasteHandlerMultipart(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
reader, err := r.MultipartReader()
if err != nil {
next(w, r)
return return
} } else if err == http.ErrMissingFile {
// Fallthrough
part, err := reader.NextPart() } else {
if err != nil { msg := fmt.Sprintf("could not parse form: %v\n", err)
if err == io.EOF {
renderError(w, r, http.StatusBadRequest, "multipart form is empty\n")
return
}
panic(errors.Wrap(err, "multipart.Reader.NextPart"))
}
if part.FormName() != "file" {
msg := fmt.Sprintf("invalid multipart form name: %v", part.FormName())
renderError(w, r, http.StatusBadRequest, msg) renderError(w, r, http.StatusBadRequest, msg)
return return
} }
newFileUploadPasteHandler(w, r, *part) shorten := r.FormValue("shorten")
if shorten != "" {
newRedirectPasteHandler(w, r, shorten)
return
}
renderError(w, r, http.StatusBadRequest, "no 'file' and no 'shorten' fields given in form\n")
} }
func newFileUploadPasteHandler(w http.ResponseWriter, r *http.Request, part multipart.Part) { func newFileUploadPasteHandler(w http.ResponseWriter, r *http.Request, file multipart.File, header multipart.FileHeader) {
var fu *fileUpload var fu *fileUpload
var paste *paste var paste *paste
if err := DB.Update(func(tx *bolt.Tx) error { if err := DB.Update(func(tx *bolt.Tx) error {
var err error var err error
// Create the fileUpload in the database // Create the fileUpload in the database
fu, err = newFileUpload(tx, &part, part.FileName(), part.Header.Get("Content-Type")) fu, err = newFileUpload(tx, file, header.Filename, header.Header.Get("Content-Type"))
if err != nil { if err != nil {
panic(errors.Wrap(err, "creating fileUpload")) panic(errors.Wrap(err, "creating fileUpload"))
} }
@ -240,8 +227,7 @@ func newPasteHandlerURLEncoded(w http.ResponseWriter, r *http.Request, next http
newRedirectPasteHandler(w, r, shorten) newRedirectPasteHandler(w, r, shorten)
} }
func newRedirectPasteHandler(w http.ResponseWriter, r *http.Request, shorten string) { func newRedirectPasteHandler(w http.ResponseWriter, r *http.Request, rawurl string) {
rawurl := r.PostForm.Get("shorten")
userURL, err := url.ParseRequestURI(rawurl) userURL, err := url.ParseRequestURI(rawurl)
if err != nil { if err != nil {
msg := fmt.Sprintf("invalid url (%v): %v", err, rawurl) msg := fmt.Sprintf("invalid url (%v): %v", err, rawurl)
@ -259,7 +245,6 @@ func newRedirectPasteHandler(w http.ResponseWriter, r *http.Request, shorten str
var paste *paste var paste *paste
if err := DB.Update(func(tx *bolt.Tx) error { if err := DB.Update(func(tx *bolt.Tx) error {
// Generate a new delete token for this paste
var err error var err error
paste, err = shortenURL(tx, userURL) paste, err = shortenURL(tx, userURL)
return err return err
@ -282,22 +267,35 @@ func deletePasteHandler(w http.ResponseWriter, r *http.Request) {
} }
var errorCode int var errorCode int
var paste paste
if err := DB.Update(func(tx *bolt.Tx) error { if err := DB.Update(func(tx *bolt.Tx) error {
p, err := getPaste(tx, key) p, err := getPaste(tx, key)
if err != nil { if err != nil {
errorCode = http.StatusNotFound errorCode = http.StatusNotFound
return err return err
} }
if subtle.ConstantTimeCompare([]byte(deleteToken), []byte(p.DeleteToken)) == 1 { if p.State == pasteStateDeleted {
p.delete(tx) errorCode = http.StatusGone
return errors.New("already deleted")
} }
if subtle.ConstantTimeCompare([]byte(deleteToken), []byte(p.DeleteToken)) == 0 {
errorCode = http.StatusForbidden errorCode = http.StatusForbidden
return errors.New("invalid delete token") return errors.New("invalid delete token")
}
if err := p.delete(tx); err != nil {
errorCode = http.StatusInternalServerError
return err
}
paste = *p
return nil
}); err != nil { }); err != nil {
log.Printf("error: %v\n", err) log.Printf("error: %v\n", err)
renderError(w, r, errorCode, fmt.Sprintf("error: %v\n", err)) renderError(w, r, errorCode, fmt.Sprintf("error: %v\n", err))
return return
} }
data := map[string]interface{}{"Paste": paste}
render(w, r, "deletePasteSuccess", data)
} }
// Add a new fileUpload redirect to the database // Add a new fileUpload redirect to the database

View File

@ -7,6 +7,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/google/uuid"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt" bolt "go.etcd.io/bbolt"
) )
@ -44,6 +45,8 @@ func (t pasteType) String() string {
return "paste" return "paste"
case pasteTypeRedirect: case pasteTypeRedirect:
return "redirect" return "redirect"
case pasteTypeFileUpload:
return "file"
default: default:
return "invalid" return "invalid"
} }
@ -94,12 +97,29 @@ func (p *paste) save(tx *bolt.Tx) error {
} }
func (p *paste) delete(tx *bolt.Tx) error { func (p *paste) delete(tx *bolt.Tx) error {
// Remove the (maybe) attached file
if p.Type == pasteTypeFileUpload {
fuID, err := uuid.FromBytes(p.Content)
if err != nil {
return errors.Wrap(err, "failed to parse uuid")
}
fu, err := getFileUpload(tx, fuID)
if err != nil {
return errors.Wrap(err, "failed to find file in database")
}
if err := fu.delete(tx); err != nil {
return errors.Wrap(err, "failed to remove file")
}
}
// Replace the old paste with a new empty paste // Replace the old paste with a new empty paste
return (&paste{ p.Type = pasteTypeUndef
Key: p.Key, p.State = pasteStateDeleted
State: pasteStateDeleted, p.Content = []byte{}
DeleteToken: p.DeleteToken, if err := p.save(tx); err != nil {
}).save(tx) return errors.Wrap(err, "failed to delete paste in database")
}
return nil
} }
// Get the URL from this paste. // Get the URL from this paste.