feat: initial commit
This commit is contained in:
188
pkg/wails3kit/updates/platform/archive.go
Normal file
188
pkg/wails3kit/updates/platform/archive.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Eriyc/rules_wails/pkg/wails3kit/updates"
|
||||
)
|
||||
|
||||
func extractBundle(_ context.Context, archivePath string, format updates.ArtifactFormat) (string, updates.BundleManifest, error) {
|
||||
stageDir, err := os.MkdirTemp("", "wails3kit-stage-*")
|
||||
if err != nil {
|
||||
return "", updates.BundleManifest{}, err
|
||||
}
|
||||
|
||||
switch format {
|
||||
case updates.ArtifactFormatZip:
|
||||
err = extractZip(archivePath, stageDir)
|
||||
case updates.ArtifactFormatTarGz:
|
||||
err = extractTarGz(archivePath, stageDir)
|
||||
default:
|
||||
err = fmt.Errorf("%w: unsupported format %s", updates.ErrInvalidArtifact, format)
|
||||
}
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(stageDir)
|
||||
return "", updates.BundleManifest{}, err
|
||||
}
|
||||
|
||||
manifestPath := filepath.Join(stageDir, "bundle.json")
|
||||
bytes, err := os.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(stageDir)
|
||||
return "", updates.BundleManifest{}, err
|
||||
}
|
||||
|
||||
var manifest updates.BundleManifest
|
||||
if err := json.Unmarshal(bytes, &manifest); err != nil {
|
||||
_ = os.RemoveAll(stageDir)
|
||||
return "", updates.BundleManifest{}, err
|
||||
}
|
||||
|
||||
if err := validateBundle(stageDir, manifest); err != nil {
|
||||
_ = os.RemoveAll(stageDir)
|
||||
return "", updates.BundleManifest{}, err
|
||||
}
|
||||
|
||||
return stageDir, manifest, nil
|
||||
}
|
||||
|
||||
func extractZip(archivePath string, targetDir string) error {
|
||||
reader, err := zip.OpenReader(archivePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
for _, file := range reader.File {
|
||||
path, err := safeArchivePath(targetDir, file.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if file.FileInfo().IsDir() {
|
||||
if err := os.MkdirAll(path, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
source, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeFile(path, source, file.Mode()); err != nil {
|
||||
_ = source.Close()
|
||||
return err
|
||||
}
|
||||
_ = source.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractTarGz(archivePath string, targetDir string) error {
|
||||
file, err := os.Open(archivePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
gzipReader, err := gzip.NewReader(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gzipReader.Close()
|
||||
|
||||
reader := tar.NewReader(gzipReader)
|
||||
for {
|
||||
header, err := reader.Next()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path, err := safeArchivePath(targetDir, header.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
if err := os.MkdirAll(path, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
case tar.TypeReg:
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeFile(path, reader, os.FileMode(header.Mode)); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("%w: unsupported tar entry %s", updates.ErrInvalidArtifact, header.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateBundle(stageDir string, manifest updates.BundleManifest) error {
|
||||
if manifest.SchemaVersion != 1 {
|
||||
return fmt.Errorf("%w: unsupported bundle schema %d", updates.ErrInvalidArtifact, manifest.SchemaVersion)
|
||||
}
|
||||
if !isSafeRelative(manifest.EntryPoint) {
|
||||
return fmt.Errorf("%w: invalid entrypoint", updates.ErrInvalidArtifact)
|
||||
}
|
||||
if len(manifest.Files) == 0 {
|
||||
return fmt.Errorf("%w: bundle contains no files", updates.ErrInvalidArtifact)
|
||||
}
|
||||
|
||||
for _, file := range manifest.Files {
|
||||
if !isSafeRelative(file.Path) {
|
||||
return fmt.Errorf("%w: invalid bundle path %s", updates.ErrInvalidArtifact, file.Path)
|
||||
}
|
||||
if _, err := strconv.ParseUint(file.Mode, 8, 32); err != nil {
|
||||
return fmt.Errorf("%w: invalid mode %s", updates.ErrInvalidArtifact, file.Mode)
|
||||
}
|
||||
info, err := os.Stat(filepath.Join(stageDir, filepath.FromSlash(file.Path)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return fmt.Errorf("%w: file entry %s is a directory", updates.ErrInvalidArtifact, file.Path)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func safeArchivePath(root string, name string) (string, error) {
|
||||
if !isSafeRelative(name) && strings.TrimSpace(name) != "bundle.json" {
|
||||
return "", fmt.Errorf("%w: unsafe archive path %s", updates.ErrInvalidArtifact, name)
|
||||
}
|
||||
return filepath.Join(root, filepath.FromSlash(name)), nil
|
||||
}
|
||||
|
||||
func isSafeRelative(path string) bool {
|
||||
cleaned := filepath.Clean(filepath.FromSlash(path))
|
||||
if cleaned == "." || cleaned == "" {
|
||||
return false
|
||||
}
|
||||
if filepath.IsAbs(cleaned) {
|
||||
return false
|
||||
}
|
||||
return !strings.HasPrefix(cleaned, "..")
|
||||
}
|
||||
Reference in New Issue
Block a user