Add support for file uploading #16
3
assets/templates/html/deletePasteSuccess.html.tmpl
Normal file
3
assets/templates/html/deletePasteSuccess.html.tmpl
Normal file
@ -0,0 +1,3 @@
|
||||
{{define "title"}}
|
||||
Success - rushlink
|
||||
{{end}}
|
1
assets/templates/txt/deletePasteSuccess.txt.tmpl
Normal file
1
assets/templates/txt/deletePasteSuccess.txt.tmpl
Normal file
@ -0,0 +1 @@
|
||||
<{{.Request.Host}}/{{.Paste.Key}}> was succesfully deleted
|
@ -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 -}}
|
||||
|
@ -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 -}}
|
||||
|
@ -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 {
|
||||
|
70
handlers.go
70
handlers.go
@ -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")
|
||||
}
|
||||
errorCode = http.StatusForbidden
|
||||
return errors.New("invalid delete token")
|
||||
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
|
||||
|
30
paste.go
30
paste.go
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user