2020-10-27 17:17:41 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2022-06-21 17:28:07 +02:00
|
|
|
"errors"
|
2020-10-27 17:17:41 +01:00
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"math/rand"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
2022-08-16 00:23:29 +02:00
|
|
|
|
|
|
|
"github.com/leon-richardt/jaf/exifscrubber"
|
2022-10-17 20:36:24 +02:00
|
|
|
"github.com/leon-richardt/jaf/extdetect"
|
2020-10-27 17:17:41 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type uploadHandler struct {
|
2022-08-16 00:23:29 +02:00
|
|
|
config *Config
|
|
|
|
exifScrubber *exifscrubber.ExifScrubber
|
2020-10-27 17:17:41 +01:00
|
|
|
}
|
|
|
|
|
2023-07-14 14:11:38 +02:00
|
|
|
func (handler *uploadHandler) PostUploadRedirect(w http.ResponseWriter, r *http.Request) {
|
2020-10-27 17:17:41 +01:00
|
|
|
defer r.Body.Close()
|
|
|
|
|
2023-07-14 14:11:38 +02:00
|
|
|
// Limit size to 50Mb
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, 50*1024*1024)
|
|
|
|
uploadFile, header, err := r.FormFile("file")
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "could not read uploaded file: "+err.Error(), http.StatusBadRequest)
|
|
|
|
log.Println(" could not read uploaded file: " + err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fileData, err := io.ReadAll(uploadFile)
|
|
|
|
uploadFile.Close()
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "could not read attached file: "+err.Error(), http.StatusInternalServerError)
|
|
|
|
log.Println(" could not read attached file: " + err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Scrub EXIF, if requested and detectable by us
|
|
|
|
if handler.config.ScrubExif {
|
|
|
|
scrubbedData, err := handler.exifScrubber.ScrubExif(fileData[:])
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
// If scrubbing was successful, update what to write to file
|
|
|
|
fileData = scrubbedData
|
|
|
|
} else {
|
|
|
|
// Unknown file types (not PNG or JPEG) are allowed to contain EXIF, as we don't know
|
|
|
|
// how to handle them. Handling of other errors depends on configuration.
|
|
|
|
if err != exifscrubber.ErrUnknownFileType {
|
|
|
|
if handler.config.ExifAbortOnError {
|
|
|
|
log.Printf("could not scrub EXIF from file, aborting upload: %s", err.Error())
|
|
|
|
http.Error(
|
|
|
|
w,
|
|
|
|
"could not scrub EXIF from file: "+err.Error(),
|
|
|
|
http.StatusInternalServerError,
|
|
|
|
)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// An error occured but we are configured to proceed with the upload anyway
|
|
|
|
log.Printf(
|
|
|
|
"could not scrub EXIF from file but proceeding with upload as configured: %s",
|
|
|
|
err.Error(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
link, err := generateLink(handler, fileData[:], header.Filename)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "could not save file: "+err.Error(), http.StatusInternalServerError)
|
|
|
|
log.Println(" could not save file: " + err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implicitly means code 200
|
|
|
|
http.Redirect(w, r, link, http.StatusFound)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (handler *uploadHandler) PostUpload(w http.ResponseWriter, r *http.Request) {
|
|
|
|
defer r.Body.Close()
|
|
|
|
|
|
|
|
// Limit size to 50Mb
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, 50*1024*1024)
|
2020-10-27 17:17:41 +01:00
|
|
|
uploadFile, header, err := r.FormFile("file")
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "could not read uploaded file: "+err.Error(), http.StatusBadRequest)
|
|
|
|
log.Println(" could not read uploaded file: " + err.Error())
|
|
|
|
return
|
|
|
|
}
|
2022-08-16 00:23:29 +02:00
|
|
|
|
|
|
|
fileData, err := io.ReadAll(uploadFile)
|
|
|
|
uploadFile.Close()
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "could not read attached file: "+err.Error(), http.StatusInternalServerError)
|
|
|
|
log.Println(" could not read attached file: " + err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Scrub EXIF, if requested and detectable by us
|
|
|
|
if handler.config.ScrubExif {
|
|
|
|
scrubbedData, err := handler.exifScrubber.ScrubExif(fileData[:])
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
// If scrubbing was successful, update what to write to file
|
|
|
|
fileData = scrubbedData
|
|
|
|
} else {
|
|
|
|
// Unknown file types (not PNG or JPEG) are allowed to contain EXIF, as we don't know
|
|
|
|
// how to handle them. Handling of other errors depends on configuration.
|
|
|
|
if err != exifscrubber.ErrUnknownFileType {
|
|
|
|
if handler.config.ExifAbortOnError {
|
|
|
|
log.Printf("could not scrub EXIF from file, aborting upload: %s", err.Error())
|
|
|
|
http.Error(
|
|
|
|
w,
|
|
|
|
"could not scrub EXIF from file: "+err.Error(),
|
|
|
|
http.StatusInternalServerError,
|
|
|
|
)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// An error occured but we are configured to proceed with the upload anyway
|
|
|
|
log.Printf(
|
|
|
|
"could not scrub EXIF from file but proceeding with upload as configured: %s",
|
|
|
|
err.Error(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-10-27 17:17:41 +01:00
|
|
|
|
2022-10-17 20:36:24 +02:00
|
|
|
link, err := generateLink(handler, fileData[:], header.Filename)
|
2020-10-27 17:17:41 +01:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "could not save file: "+err.Error(), http.StatusInternalServerError)
|
|
|
|
log.Println(" could not save file: " + err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implicitly means code 200
|
|
|
|
w.Write([]byte(link))
|
|
|
|
}
|
|
|
|
|
2022-06-21 17:28:07 +02:00
|
|
|
// Generates a valid link to uploadFile with the specified file extension.
|
|
|
|
// Returns the link or an error in case of failure.
|
|
|
|
// Does not close the passed file pointer.
|
2022-10-17 20:36:24 +02:00
|
|
|
func generateLink(handler *uploadHandler, fileData []byte, fileName string) (string, error) {
|
|
|
|
ext := extdetect.BuildFileExtension(fileData, fileName)
|
|
|
|
|
2022-06-21 17:28:07 +02:00
|
|
|
// Find an unused file name
|
|
|
|
var fullFileName string
|
|
|
|
var savePath string
|
|
|
|
for {
|
|
|
|
fileStem := createRandomFileName(handler.config.LinkLength)
|
2022-10-17 20:36:24 +02:00
|
|
|
fullFileName = fileStem + ext
|
2022-06-21 17:28:07 +02:00
|
|
|
savePath = handler.config.FileDir + fullFileName
|
|
|
|
|
|
|
|
if !fileExists(savePath) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
link := handler.config.LinkPrefix + fullFileName
|
|
|
|
|
2022-08-16 00:23:29 +02:00
|
|
|
err := saveFile(fileData[:], savePath)
|
2022-06-21 17:28:07 +02:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return link, nil
|
|
|
|
}
|
|
|
|
|
2022-08-16 00:23:29 +02:00
|
|
|
func saveFile(fileData []byte, name string) error {
|
|
|
|
err := os.WriteFile(name, fileData, 0o644)
|
|
|
|
return err
|
2020-10-27 17:17:41 +01:00
|
|
|
}
|
|
|
|
|
2022-08-16 00:23:29 +02:00
|
|
|
func fileExists(fileName string) bool {
|
|
|
|
_, err := os.Stat(fileName)
|
2022-06-21 17:28:07 +02:00
|
|
|
|
|
|
|
return !errors.Is(err, os.ErrNotExist)
|
|
|
|
}
|
|
|
|
|
2020-10-27 17:17:41 +01:00
|
|
|
func createRandomFileName(length int) string {
|
|
|
|
chars := make([]byte, length)
|
|
|
|
|
|
|
|
for i := 0; i < length; i++ {
|
|
|
|
index := rand.Intn(len(allowedChars))
|
|
|
|
chars[i] = allowedChars[index]
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(chars)
|
|
|
|
}
|