Files
kooltab-update-server/internal/routes/files.go
T

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)
}