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" "net/http" "os" "path" "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 storSize, err := utils.DirectorySize(h.conf.Uploads.Directory) if err != nil { h.logErr.Printf("cannot compute storage size: %s", err) } _, _, 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) }