mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 08:36:37 +08:00
feat(pages): configurable limits, multi-format packages, and dual versioning
Make Pages package size and history retention system-configurable, support zip/tar.gz/tar.xz/tar.bz2/tar/7z uploads, prune history with clear keep-N semantics, and rebind agent config to the live active Pages deployment so main-config rollback never depends on pruned packages.
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package pagesarchive provides multi-format archive detection, inspection and
|
||||
// extraction helpers for OpenFlare Pages deployment packages.
|
||||
package pagesarchive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Format identifies a supported Pages deployment package archive format.
|
||||
type Format string
|
||||
|
||||
const (
|
||||
// FormatZip is a ZIP archive.
|
||||
FormatZip Format = "zip"
|
||||
// FormatTar is an uncompressed tar archive.
|
||||
FormatTar Format = "tar"
|
||||
// FormatTarGz is a gzip-compressed tar archive.
|
||||
FormatTarGz Format = "tar.gz"
|
||||
// FormatTarXz is an xz-compressed tar archive.
|
||||
FormatTarXz Format = "tar.xz"
|
||||
// FormatTarBz2 is a bzip2-compressed tar archive.
|
||||
FormatTarBz2 Format = "tar.bz2"
|
||||
// FormatSevenZip is a 7z archive.
|
||||
FormatSevenZip Format = "7z"
|
||||
|
||||
ustarMagicOffset = 257
|
||||
ustarMagicMinLen = 262
|
||||
)
|
||||
|
||||
// DetectFormatFromName returns the archive format inferred from a file name.
|
||||
// Returns an empty Format and false when the extension is unsupported.
|
||||
func DetectFormatFromName(fileName string) (Format, bool) {
|
||||
name := strings.ToLower(strings.TrimSpace(fileName))
|
||||
switch {
|
||||
case strings.HasSuffix(name, ".tar.gz"), strings.HasSuffix(name, ".tgz"):
|
||||
return FormatTarGz, true
|
||||
case strings.HasSuffix(name, ".tar.xz"), strings.HasSuffix(name, ".txz"):
|
||||
return FormatTarXz, true
|
||||
case strings.HasSuffix(name, ".tar.bz2"), strings.HasSuffix(name, ".tbz2"), strings.HasSuffix(name, ".tbz"):
|
||||
return FormatTarBz2, true
|
||||
case strings.HasSuffix(name, ".tar"):
|
||||
return FormatTar, true
|
||||
case strings.HasSuffix(name, ".7z"):
|
||||
return FormatSevenZip, true
|
||||
case strings.HasSuffix(name, ".zip"):
|
||||
return FormatZip, true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
// DetectFormatFromBytes returns the archive format inferred from magic bytes.
|
||||
// Prefer DetectFormatFromName when a reliable file name is available.
|
||||
func DetectFormatFromBytes(data []byte) (Format, bool) {
|
||||
if isZipMagic(data) {
|
||||
return FormatZip, true
|
||||
}
|
||||
if isSevenZipMagic(data) {
|
||||
return FormatSevenZip, true
|
||||
}
|
||||
if isXZMagic(data) {
|
||||
return FormatTarXz, true
|
||||
}
|
||||
if isGzipMagic(data) {
|
||||
return FormatTarGz, true
|
||||
}
|
||||
if isBzip2Magic(data) {
|
||||
return FormatTarBz2, true
|
||||
}
|
||||
if looksLikeTar(data) {
|
||||
return FormatTar, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// DetectFormat prefers the file name when present, otherwise magic bytes.
|
||||
func DetectFormat(fileName string, data []byte) (Format, error) {
|
||||
if format, ok := DetectFormatFromName(fileName); ok {
|
||||
return format, nil
|
||||
}
|
||||
if format, ok := DetectFormatFromBytes(data); ok {
|
||||
return format, nil
|
||||
}
|
||||
return "", fmt.Errorf("unsupported pages package format")
|
||||
}
|
||||
|
||||
// Extension returns the canonical file extension for a format (without leading dot).
|
||||
func Extension(format Format) string {
|
||||
switch format {
|
||||
case FormatZip:
|
||||
return "zip"
|
||||
case FormatTar:
|
||||
return "tar"
|
||||
case FormatTarGz:
|
||||
return "tar.gz"
|
||||
case FormatTarXz:
|
||||
return "tar.xz"
|
||||
case FormatTarBz2:
|
||||
return "tar.bz2"
|
||||
case FormatSevenZip:
|
||||
return "7z"
|
||||
default:
|
||||
return "bin"
|
||||
}
|
||||
}
|
||||
|
||||
// MIMEType returns a reasonable content type for the archive format.
|
||||
func MIMEType(format Format) string {
|
||||
switch format {
|
||||
case FormatZip:
|
||||
return "application/zip"
|
||||
case FormatTar:
|
||||
return "application/x-tar"
|
||||
case FormatTarGz:
|
||||
return "application/gzip"
|
||||
case FormatTarXz:
|
||||
return "application/x-xz"
|
||||
case FormatTarBz2:
|
||||
return "application/x-bzip2"
|
||||
case FormatSevenZip:
|
||||
return "application/x-7z-compressed"
|
||||
default:
|
||||
return "application/octet-stream"
|
||||
}
|
||||
}
|
||||
|
||||
// SupportedExtensions lists human-readable extensions for UI copy and accept attributes.
|
||||
func SupportedExtensions() []string {
|
||||
return []string{".zip", ".tar.gz", ".tgz", ".tar.xz", ".txz", ".tar.bz2", ".tbz2", ".tar", ".7z"}
|
||||
}
|
||||
|
||||
// AcceptAttribute returns a comma-separated accept list for file inputs.
|
||||
func AcceptAttribute() string {
|
||||
return strings.Join(SupportedExtensions(), ",")
|
||||
}
|
||||
|
||||
// NormalizeNameExtension returns a storage-safe extension for the given format/name.
|
||||
func NormalizeNameExtension(fileName string, format Format) string {
|
||||
if format != "" {
|
||||
return Extension(format)
|
||||
}
|
||||
if formatFromName, ok := DetectFormatFromName(fileName); ok {
|
||||
return Extension(formatFromName)
|
||||
}
|
||||
ext := strings.TrimPrefix(filepath.Ext(fileName), ".")
|
||||
if ext == "" {
|
||||
return "bin"
|
||||
}
|
||||
return strings.ToLower(ext)
|
||||
}
|
||||
|
||||
func isZipMagic(data []byte) bool {
|
||||
return len(data) >= 4 &&
|
||||
data[0] == 0x50 && data[1] == 0x4b &&
|
||||
(data[2] == 0x03 || data[2] == 0x05 || data[2] == 0x07) &&
|
||||
(data[3] == 0x04 || data[3] == 0x06 || data[3] == 0x08)
|
||||
}
|
||||
|
||||
func isSevenZipMagic(data []byte) bool {
|
||||
return len(data) >= 6 &&
|
||||
data[0] == 0x37 && data[1] == 0x7a && data[2] == 0xbc &&
|
||||
data[3] == 0xaf && data[4] == 0x27 && data[5] == 0x1c
|
||||
}
|
||||
|
||||
func isXZMagic(data []byte) bool {
|
||||
return len(data) >= 6 &&
|
||||
data[0] == 0xfd && data[1] == 0x37 && data[2] == 0x7a &&
|
||||
data[3] == 0x58 && data[4] == 0x5a && data[5] == 0x00
|
||||
}
|
||||
|
||||
func isGzipMagic(data []byte) bool {
|
||||
return len(data) >= 2 && data[0] == 0x1f && data[1] == 0x8b
|
||||
}
|
||||
|
||||
func isBzip2Magic(data []byte) bool {
|
||||
return len(data) >= 3 && data[0] == 0x42 && data[1] == 0x5a && data[2] == 0x68
|
||||
}
|
||||
|
||||
func looksLikeTar(data []byte) bool {
|
||||
// POSIX ustar magic at offset 257 ("ustar\0" or "ustar ").
|
||||
if len(data) < ustarMagicMinLen {
|
||||
return false
|
||||
}
|
||||
return bytes.Equal(data[ustarMagicOffset:ustarMagicOffset+5], []byte("ustar"))
|
||||
}
|
||||
Reference in New Issue
Block a user