package ws import ( "crypto/rand" "encoding/base64" "fmt" "io" "net/http" "os" "path/filepath" "strings" "sectorinf.com/emilis/fupie/ws/route" ) var ( base64Stripper = strings.NewReplacer("/", "", "+", "", "=", "") ) func Upload(rootDir, redirect string, maxBytes int64) (route.HandlerFunc, error) { stat, err := os.Stat(rootDir) if err != nil { return nil, fmt.Errorf("stat [%s]: %w", rootDir, err) } if !stat.IsDir() { return nil, fmt.Errorf("root path [%s] is not a directory", rootDir) } rootDir, err = filepath.Abs(rootDir) if err != nil { return nil, fmt.Errorf("abs filepath [%s]: %w", rootDir, err) } redirect = strings.TrimRight(redirect, "/") return func(ctx route.Context) { if err := ctx.Request.ParseMultipartForm(maxBytes); err != nil { ctx.Status(http.StatusBadRequest) return } uploads := ctx.Request.MultipartForm.File["upload"] if len(uploads) != 1 { ctx.Status(http.StatusBadRequest) return } header := uploads[0] file, err := header.Open() if err != nil { ctx.InternalServerErrorf("header.Open: %s", err.Error()) return } newFilename := rename(header.Filename) path := filepath.Join(rootDir, newFilename) fsFile, err := os.Create(path) if err != nil { ctx.InternalServerErrorf("creating file at [%s]: %s", path, err.Error()) return } if _, err := io.CopyBuffer(fsFile, file, nil); err != nil { ctx.InternalServerErrorf("writing file at [%s]: %s", path, err.Error()) return } fsFile.Close() ctx.SeeOther(fmt.Sprintf("%s/%s", redirect, newFilename)) }, nil } func rename(name string) string { return fmt.Sprintf("%s_%s", randName(4), name) } func randName(bytes int) string { buffer := make([]byte, bytes) _, err := rand.Read(buffer) if err != nil { panic(err) } return base64Stripper.Replace(base64.StdEncoding.EncodeToString(buffer)) }