75 lines
1.5 KiB
Go
75 lines
1.5 KiB
Go
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)
|
|
}
|