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 -}} | {{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 -}} | ||||||
|  | |||||||
| @ -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 -}} | ||||||
|  | |||||||
| @ -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 { | ||||||
|  | |||||||
							
								
								
									
										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) { | 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") | ||||||
| 		} | 		} | ||||||
| 		errorCode = http.StatusForbidden | 		if subtle.ConstantTimeCompare([]byte(deleteToken), []byte(p.DeleteToken)) == 0 { | ||||||
| 		return errors.New("invalid delete token") | 			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 { | 	}); 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 | ||||||
|  | |||||||
							
								
								
									
										30
									
								
								paste.go
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								paste.go
									
									
									
									
									
								
							| @ -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. | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user