- admin.go: replace local sha1Hex, sha256Hex, writeJSON, writeError with pkg/utils equivalents - auth.go: replace local writeJSON with utils.WriteJSON; rewrite writeError as thin wrapper - cas.go: remove local sha1Hex and unused writeJSON; use utils.SHA1Bytes - pkg/utils.go: add WriteJSON, WriteError; reorder imports
110 lines
2.8 KiB
Go
110 lines
2.8 KiB
Go
// package utils provides shared utility functions (hashing, HTTP helpers, ZIP).
|
|
|
|
package utils
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bytes"
|
|
"crypto/sha1"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// ── Hashing ────────────────────────────────────────────────────
|
|
|
|
// SHA1Bytes returns the SHA-1 hex string of the given data.
|
|
func SHA1Bytes(data []byte) string {
|
|
h := sha1.Sum(data)
|
|
return hex.EncodeToString(h[:])
|
|
}
|
|
|
|
// SHA256Bytes returns the SHA-256 hex string of the given data.
|
|
func SHA256Bytes(data []byte) string {
|
|
h := sha256.Sum256(data)
|
|
return hex.EncodeToString(h[:])
|
|
}
|
|
|
|
// SHA1File computes the SHA-1 hash of a file at the given path.
|
|
func SHA1File(path string) (string, error) {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer f.Close()
|
|
|
|
h := sha1.New()
|
|
if _, err := io.Copy(h, f); err != nil {
|
|
return "", err
|
|
}
|
|
return hex.EncodeToString(h.Sum(nil)), nil
|
|
}
|
|
|
|
// ── HTTP helpers ───────────────────────────────────────────────
|
|
|
|
// WriteJSON writes a JSON response with the given status code.
|
|
func WriteJSON(w http.ResponseWriter, status int, v any) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
_ = json.NewEncoder(w).Encode(v)
|
|
}
|
|
|
|
// WriteError writes a JSON error response.
|
|
func WriteError(w http.ResponseWriter, status int, msg string) {
|
|
WriteJSON(w, status, map[string]string{"error": msg})
|
|
}
|
|
|
|
// ── ZIP ────────────────────────────────────────────────────────
|
|
|
|
// Unzip extracts a ZIP archive to the destination directory.
|
|
// Returns the list of extracted file paths.
|
|
// Protects against zip-slip by validating that each entry's target path
|
|
// stays within the destination directory.
|
|
func Unzip(data []byte, dest string) ([]string, error) {
|
|
reader, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var extracted []string
|
|
for _, f := range reader.File {
|
|
if f.FileInfo().IsDir() {
|
|
continue
|
|
}
|
|
|
|
target := filepath.Join(dest, f.Name)
|
|
|
|
// Zip-slip protection.
|
|
if !strings.HasPrefix(target, filepath.Clean(dest)+string(os.PathSeparator)) {
|
|
continue
|
|
}
|
|
|
|
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
|
continue
|
|
}
|
|
|
|
rc, err := f.Open()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
out, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
|
if err != nil {
|
|
rc.Close()
|
|
continue
|
|
}
|
|
|
|
io.Copy(out, rc)
|
|
out.Close()
|
|
rc.Close()
|
|
|
|
extracted = append(extracted, f.Name)
|
|
}
|
|
return extracted, nil
|
|
}
|