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 -}}
https://{{.Request.Host}}/{{.Paste.Key}}?deleteToken={{.Paste.DeleteToken | urlquery}}
{{else -}}
https://{{.Request.Host}}/{{.Paste.Key}}
{{end -}}

View File

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

View File

@ -79,7 +79,7 @@ func (t fileUploadState) String() string {
func OpenFileStore(path string) error {
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
@ -158,11 +158,23 @@ func (fu *fileUpload) save(tx *bolt.Tx) error {
}
func (fu *fileUpload) delete(tx *bolt.Tx) error {
// Replace the old paste with a new empty paste
return (&fileUpload{
// Remove the file in the backend
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,
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 {

View File

@ -173,47 +173,34 @@ func viewPasteHandlerInner(w http.ResponseWriter, r *http.Request, flags viewPas
}
func newPasteHandler(w http.ResponseWriter, r *http.Request) {
newPasteHandlerMultipart(w, r, func(w http.ResponseWriter, r *http.Request) {
newPasteHandlerURLEncoded(w, r, func(w http.ResponseWriter, r *http.Request) {
renderError(w, r, http.StatusBadRequest, "no form data in request\n")
})
})
}
// 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)
file, fileHeader, err := r.FormFile("file")
if err == nil {
newFileUploadPasteHandler(w, r, file, *fileHeader)
return
}
part, err := reader.NextPart()
if err != nil {
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())
} else if err == http.ErrMissingFile {
// Fallthrough
} else {
msg := fmt.Sprintf("could not parse form: %v\n", err)
renderError(w, r, http.StatusBadRequest, msg)
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 paste *paste
if err := DB.Update(func(tx *bolt.Tx) error {
var err error
// 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 {
panic(errors.Wrap(err, "creating fileUpload"))
}
@ -240,8 +227,7 @@ func newPasteHandlerURLEncoded(w http.ResponseWriter, r *http.Request, next http
newRedirectPasteHandler(w, r, shorten)
}
func newRedirectPasteHandler(w http.ResponseWriter, r *http.Request, shorten string) {
rawurl := r.PostForm.Get("shorten")
func newRedirectPasteHandler(w http.ResponseWriter, r *http.Request, rawurl string) {
userURL, err := url.ParseRequestURI(rawurl)
if err != nil {
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
if err := DB.Update(func(tx *bolt.Tx) error {
// Generate a new delete token for this paste
var err error
paste, err = shortenURL(tx, userURL)
return err
@ -282,22 +267,35 @@ func deletePasteHandler(w http.ResponseWriter, r *http.Request) {
}
var errorCode int
var paste paste
if err := DB.Update(func(tx *bolt.Tx) error {
p, err := getPaste(tx, key)
if err != nil {
errorCode = http.StatusNotFound
return err
}
if subtle.ConstantTimeCompare([]byte(deleteToken), []byte(p.DeleteToken)) == 1 {
p.delete(tx)
if p.State == pasteStateDeleted {
errorCode = http.StatusGone
return errors.New("already deleted")
}
if subtle.ConstantTimeCompare([]byte(deleteToken), []byte(p.DeleteToken)) == 0 {
errorCode = http.StatusForbidden
return errors.New("invalid delete token")
}
if err := p.delete(tx); err != nil {
errorCode = http.StatusInternalServerError
return err
}
paste = *p
return nil
}); err != nil {
log.Printf("error: %v\n", err)
renderError(w, r, errorCode, fmt.Sprintf("error: %v\n", err))
return
}
data := map[string]interface{}{"Paste": paste}
render(w, r, "deletePasteSuccess", data)
}
// Add a new fileUpload redirect to the database

View File

@ -7,6 +7,7 @@ import (
"strings"
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
)
@ -44,6 +45,8 @@ func (t pasteType) String() string {
return "paste"
case pasteTypeRedirect:
return "redirect"
case pasteTypeFileUpload:
return "file"
default:
return "invalid"
}
@ -94,12 +97,29 @@ func (p *paste) save(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
return (&paste{
Key: p.Key,
State: pasteStateDeleted,
DeleteToken: p.DeleteToken,
}).save(tx)
p.Type = pasteTypeUndef
p.State = pasteStateDeleted
p.Content = []byte{}
if err := p.save(tx); err != nil {
return errors.Wrap(err, "failed to delete paste in database")
}
return nil
}
// Get the URL from this paste.