mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-12 10:16: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,187 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pagesarchive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ulikunitz/xz"
|
||||
)
|
||||
|
||||
func TestDetectFormatFromName(t *testing.T) {
|
||||
cases := map[string]Format{
|
||||
"site.zip": FormatZip,
|
||||
"site.TAR.GZ": FormatTarGz,
|
||||
"site.tgz": FormatTarGz,
|
||||
"site.tar.xz": FormatTarXz,
|
||||
"site.txz": FormatTarXz,
|
||||
"site.tar.bz2": FormatTarBz2,
|
||||
"site.tar": FormatTar,
|
||||
"site.7z": FormatSevenZip,
|
||||
}
|
||||
for name, want := range cases {
|
||||
got, ok := DetectFormatFromName(name)
|
||||
assert.True(t, ok, name)
|
||||
assert.Equal(t, want, got, name)
|
||||
}
|
||||
_, ok := DetectFormatFromName("site.rar")
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
func TestInspectAndExtractZip(t *testing.T) {
|
||||
data := testZip(t, map[string]string{
|
||||
"dist/index.html": "<html>ok</html>",
|
||||
"dist/app.js": "console.log(1)",
|
||||
})
|
||||
manifest, err := InspectBytes(data, FormatZip, InspectOptions{
|
||||
EntryFile: "index.html",
|
||||
Limits: Limits{MaxFiles: 100, MaxFileBytes: 1 << 20, MaxTotalBytes: 1 << 20},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, manifest.FileCount)
|
||||
paths := make(map[string]struct{}, len(manifest.Files))
|
||||
for _, file := range manifest.Files {
|
||||
paths[file.Path] = struct{}{}
|
||||
}
|
||||
assert.Contains(t, paths, "index.html")
|
||||
assert.Contains(t, paths, "app.js")
|
||||
|
||||
dest := t.TempDir()
|
||||
require.NoError(t, ExtractBytes(data, FormatZip, dest, ExtractOptions{
|
||||
StripCommonRoot: true,
|
||||
EnforceLimits: true,
|
||||
Limits: Limits{MaxFiles: 100, MaxFileBytes: 1 << 20, MaxTotalBytes: 1 << 20},
|
||||
}))
|
||||
body, err := os.ReadFile(filepath.Join(dest, "index.html")) //nolint:gosec
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "<html>ok</html>", string(body))
|
||||
}
|
||||
|
||||
func TestExtractTrustedSkipsSizeLimits(t *testing.T) {
|
||||
// Content larger than a tiny limit would fail if limits were enforced.
|
||||
large := strings.Repeat("x", 64)
|
||||
data := testZip(t, map[string]string{
|
||||
"index.html": large,
|
||||
})
|
||||
dest := t.TempDir()
|
||||
require.NoError(t, ExtractBytes(data, FormatZip, dest, ExtractOptions{
|
||||
// Agent trusts control-plane validation: no size/count re-check.
|
||||
EnforceLimits: false,
|
||||
}))
|
||||
body, err := os.ReadFile(filepath.Join(dest, "index.html")) //nolint:gosec
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, large, string(body))
|
||||
}
|
||||
|
||||
func TestInspectAndExtractTarGz(t *testing.T) {
|
||||
data := testTarGz(t, map[string]string{
|
||||
"index.html": "<html>tar</html>",
|
||||
"style.css": "body{}",
|
||||
})
|
||||
format, err := DetectFormat("site.tar.gz", data)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, FormatTarGz, format)
|
||||
|
||||
manifest, err := InspectBytes(data, format, InspectOptions{
|
||||
EntryFile: "index.html",
|
||||
Limits: Limits{MaxFiles: 100, MaxFileBytes: 1 << 20, MaxTotalBytes: 1 << 20},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, manifest.FileCount)
|
||||
|
||||
dest := t.TempDir()
|
||||
require.NoError(t, ExtractBytes(data, format, dest, ExtractOptions{
|
||||
EnforceLimits: true,
|
||||
Limits: Limits{MaxFiles: 100, MaxFileBytes: 1 << 20, MaxTotalBytes: 1 << 20},
|
||||
}))
|
||||
body, err := os.ReadFile(filepath.Join(dest, "index.html")) //nolint:gosec
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "<html>tar</html>", string(body))
|
||||
}
|
||||
|
||||
func TestInspectTarXz(t *testing.T) {
|
||||
data := testTarXz(t, map[string]string{
|
||||
"index.html": "<html>xz</html>",
|
||||
})
|
||||
manifest, err := InspectBytes(data, FormatTarXz, InspectOptions{
|
||||
EntryFile: "index.html",
|
||||
Limits: Limits{MaxFiles: 10, MaxFileBytes: 1 << 20, MaxTotalBytes: 1 << 20},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, manifest.FileCount)
|
||||
}
|
||||
|
||||
func TestRejectZipSlip(t *testing.T) {
|
||||
data := testZip(t, map[string]string{
|
||||
"../evil.txt": "x",
|
||||
"index.html": "ok",
|
||||
})
|
||||
_, err := InspectBytes(data, FormatZip, InspectOptions{
|
||||
EntryFile: "index.html",
|
||||
Limits: Limits{MaxFiles: 10, MaxFileBytes: 1 << 20, MaxTotalBytes: 1 << 20},
|
||||
})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func testZip(t *testing.T, files map[string]string) []byte {
|
||||
t.Helper()
|
||||
var buffer bytes.Buffer
|
||||
writer := zip.NewWriter(&buffer)
|
||||
for name, content := range files {
|
||||
file, err := writer.Create(name)
|
||||
require.NoError(t, err)
|
||||
_, err = file.Write([]byte(content))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.NoError(t, writer.Close())
|
||||
return buffer.Bytes()
|
||||
}
|
||||
|
||||
func testTarGz(t *testing.T, files map[string]string) []byte {
|
||||
t.Helper()
|
||||
var buffer bytes.Buffer
|
||||
gzWriter := gzip.NewWriter(&buffer)
|
||||
tarWriter := tar.NewWriter(gzWriter)
|
||||
for name, content := range files {
|
||||
require.NoError(t, tarWriter.WriteHeader(&tar.Header{
|
||||
Name: name,
|
||||
Mode: 0o644,
|
||||
Size: int64(len(content)),
|
||||
}))
|
||||
_, err := tarWriter.Write([]byte(content))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.NoError(t, tarWriter.Close())
|
||||
require.NoError(t, gzWriter.Close())
|
||||
return buffer.Bytes()
|
||||
}
|
||||
|
||||
func testTarXz(t *testing.T, files map[string]string) []byte {
|
||||
t.Helper()
|
||||
var buffer bytes.Buffer
|
||||
xzWriter, err := xz.NewWriter(&buffer)
|
||||
require.NoError(t, err)
|
||||
tarWriter := tar.NewWriter(xzWriter)
|
||||
for name, content := range files {
|
||||
require.NoError(t, tarWriter.WriteHeader(&tar.Header{
|
||||
Name: name,
|
||||
Mode: 0o644,
|
||||
Size: int64(len(content)),
|
||||
}))
|
||||
_, err := tarWriter.Write([]byte(content))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.NoError(t, tarWriter.Close())
|
||||
require.NoError(t, xzWriter.Close())
|
||||
return buffer.Bytes()
|
||||
}
|
||||
Reference in New Issue
Block a user