1
0
dwelling-upload/internal/handlers/handlers.go

201 lines
5.2 KiB
Go

package handlers
import (
"crypto/sha256"
"dwelling-upload/internal/configuration"
"dwelling-upload/pkg/logging"
"dwelling-upload/pkg/server"
"dwelling-upload/pkg/utils"
"encoding/base64"
"encoding/hex"
"fmt"
"html/template"
"io"
"io/fs"
"net/http"
"os"
"path"
"path/filepath"
"time"
"github.com/eknkc/amber"
)
var defaultAmberOptions = amber.Options{PrettyPrint: false, LineNumbers: false}
var compiledTemplates map[string]*template.Template
type IndexData struct {
MainSite string
FileMaxSz string
StorageCapacity int64
StorageUsed int64
StorageAvailable int64
StorageCapStr string
StorageUsedStr string
StorageAvailStr string
}
type UploadedData struct {
MainSite string
DownloadURL string
KeepForHours int
}
type UploadHandlers struct {
conf *configuration.Configuration
logErr *logging.Logger
logUpload *logging.Logger
logDownload *logging.Logger
}
func NewUploadHandlers(conf *configuration.Configuration, lErr, lUp, lDown *logging.Logger) *UploadHandlers {
compiledTemplates = amber.MustCompileDir(conf.WebDir+"/templates",
amber.DefaultDirOptions, defaultAmberOptions)
return &UploadHandlers{
conf: conf,
logErr: lErr,
logUpload: lUp,
logDownload: lDown}
}
func (h *UploadHandlers) Index(w http.ResponseWriter, r *http.Request) {
var storCapacity int64 = h.conf.Uploads.Limits.Storage << 20
var fMaxSize int64 = h.conf.Uploads.Limits.FileSize << 20
var storSize int64 = 0
filepath.Walk(h.conf.Uploads.Directory, func(_ string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
storSize += info.Size()
return nil
})
_, _, capStr := utils.ConvertFileSize(storCapacity)
_, _, usedStr := utils.ConvertFileSize(storSize)
_, _, availStr := utils.ConvertFileSize(storCapacity - storSize)
_, _, fMaxSzStr := utils.ConvertFileSize(fMaxSize)
if err := compiledTemplates["index"].Execute(w, &IndexData{
MainSite: utils.MainSite(r.Host),
FileMaxSz: fMaxSzStr,
StorageCapacity: storCapacity,
StorageCapStr: capStr,
StorageAvailable: storCapacity - storSize,
StorageAvailStr: availStr,
StorageUsed: storSize,
StorageUsedStr: usedStr,
}); err != nil {
w.WriteHeader(http.StatusInternalServerError)
h.logErr.Fatalln("failed to execute Index template:", err)
}
}
func (h *UploadHandlers) Upload(w http.ResponseWriter, r *http.Request) {
var fMaxSizeBytes int64 = h.conf.Uploads.Limits.FileSize << 20
r.Body = http.MaxBytesReader(w, r.Body, fMaxSizeBytes)
if err := r.ParseMultipartForm(fMaxSizeBytes); err != nil {
h.logErr.Println("failed to parse form:", err)
http.Error(w, "request too big", http.StatusExpectationFailed)
return
}
f, fHandler, err := r.FormFile("file")
if err != nil {
h.logErr.Println("failed to open incoming file:", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
defer func() {
os.Remove(fHandler.Filename)
f.Close()
}()
s256 := sha256.New()
if _, err := io.Copy(s256, f); err != nil {
h.logErr.Println("failed to compute SHA-256 hash:", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
fSha256 := hex.EncodeToString(s256.Sum(nil))
s256.Write([]byte(h.conf.HashSalt))
fSaltedHash := base64.RawURLEncoding.EncodeToString(s256.Sum(nil))
f.Seek(0, io.SeekStart)
fPath := path.Join(h.conf.Uploads.Directory, fSaltedHash)
_, err = os.Stat(fPath)
if os.IsNotExist(err) {
fDst, err := os.Create(fPath)
if err != nil {
h.logErr.Println("failed to open file for writing", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
n, err := io.Copy(fDst, f)
if err != nil {
h.logErr.Println("failed to copy uploaded file to destination:", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
fmt.Println(n, err)
fDst.Sync()
fDst.Close()
h.logUpload.Printf("| %s | %s | %s | SHA256 %s | %s | %d", r.RemoteAddr, utils.NetworkType(r.Host),
fHandler.Filename, fSha256, fSaltedHash, fHandler.Size)
w.WriteHeader(http.StatusCreated)
} else {
os.Chtimes(fPath, time.Now(), time.Now())
}
downloadURL := path.Join("/f", fSaltedHash, fHandler.Filename)
if err := compiledTemplates["uploaded"].Execute(w, &UploadedData{
MainSite: utils.MainSite(r.Host),
DownloadURL: downloadURL,
KeepForHours: h.conf.Uploads.Limits.KeepForHours,
}); err != nil {
w.WriteHeader(http.StatusInternalServerError)
h.logErr.Fatalln("failed to execute Index template:", err)
}
}
func (h *UploadHandlers) Download(w http.ResponseWriter, r *http.Request) {
saltedHash := server.GetURLParam(r, "hash")
name := server.GetURLParam(r, "name")
path := path.Join(h.conf.Uploads.Directory, saltedHash)
stat, err := os.Stat(path)
if os.IsNotExist(err) {
w.WriteHeader(http.StatusNotFound)
return
}
w.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", name))
// w.Header().Add("Content-Type", "application/octet-stream")
fd, err := os.Open(path)
if err != nil {
h.logErr.Println("failed to open file to read:", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
defer fd.Close()
h.logDownload.Printf("| %s | %s | %s | %s", r.RemoteAddr, utils.NetworkType(r.Host), name, saltedHash)
http.ServeContent(w, r, path, stat.ModTime(), fd)
}