package routes import ( "crypto/sha256" "encoding/hex" "errors" "io" "io/fs" "mime/multipart" "os" ) // saveTemp writes an uploaded file to a new temp file in dir and returns its // path and SHA-256. func saveTemp(fh *multipart.FileHeader, dir string) (path, sum string, err error) { src, err := fh.Open() if err != nil { return "", "", err } defer src.Close() dst, err := os.CreateTemp(dir, "upload-*.apk") if err != nil { return "", "", err } hash := sha256.New() _, err = io.Copy(io.MultiWriter(dst, hash), src) if cerr := dst.Close(); err == nil { err = cerr } if err != nil { os.Remove(dst.Name()) return "", "", err } return dst.Name(), hex.EncodeToString(hash.Sum(nil)), nil } // moveFile moves src to dst without ever overwriting an existing dst. It // falls back to a copy when they are on different filesystems (the tmp and // uploads directories are separate volumes). func moveFile(src, dst string) error { // Unlike rename, link fails when dst exists, so a concurrent upload can never replace a published APK if err := os.Link(src, dst); err == nil { return os.Remove(src) } else if errors.Is(err, fs.ErrExist) { return err } in, err := os.Open(src) if err != nil { return err } defer in.Close() out, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) if err != nil { return err } _, err = io.Copy(out, in) if cerr := out.Close(); err == nil { err = cerr } if err != nil { os.Remove(dst) return err } return os.Remove(src) }