From 264e40388269fa518c971fc3ab67389213df164c Mon Sep 17 00:00:00 2001 From: "Alexander \"Arav\" Andreev" Date: Mon, 7 Feb 2022 04:49:21 +0400 Subject: [PATCH] Handlers. --- internal/handlers/handlers.go | 191 ++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 internal/handlers/handlers.go diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go new file mode 100644 index 0000000..bd6a7a4 --- /dev/null +++ b/internal/handlers/handlers.go @@ -0,0 +1,191 @@ +package handlers + +import ( + "crypto/sha256" + "dwelling-upload/internal/configuration" + "dwelling-upload/pkg/server" + "dwelling-upload/pkg/utils" + "encoding/base64" + "encoding/hex" + "fmt" + "html/template" + "io" + "io/fs" + "log" + "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 +} + +func NewUploadHandlers(conf *configuration.Configuration) *UploadHandlers { + compiledTemplates = amber.MustCompileDir(conf.WebDir+"/templates", + amber.DefaultDirOptions, defaultAmberOptions) + return &UploadHandlers{ + conf: conf} +} + +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) + log.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 { + log.Println("failed to parse form:", err) + http.Error(w, "request too big", http.StatusExpectationFailed) + return + } + + f, fHandler, err := r.FormFile("file") + if err != nil { + log.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 { + log.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 { + log.Println("failed to open file for writing", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + n, err := io.Copy(fDst, f) + if err != nil { + log.Println("failed to copy uploaded file to destination:", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + fmt.Println(n, err) + fDst.Sync() + fDst.Close() + + log.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) + log.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 { + log.Println("failed to open file to read:", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + defer fd.Close() + + http.ServeContent(w, r, path, stat.ModTime(), fd) +}