mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-11-10 21:21:13 +08:00
feat: enhance ZIP entry normalization and path compatibility checks (#13733)
This commit is contained in:
2 files changed
+133
-6
No files matched your search
@@ -1267,12 +1267,12 @@ func (f FileOp) extractArchiveWithSDK(ctx context.Context, input io.Reader, dst
|
||||
fileName := archFile.NameInArchive
|
||||
var err error
|
||||
if header, ok := archFile.Header.(cZip.FileHeader); ok {
|
||||
if header.NonUTF8 && header.Flags == 0 {
|
||||
fileName, err = decodeGBK(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header, err = normalizeZipEntry(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileName = header.Name
|
||||
info = header.FileInfo()
|
||||
}
|
||||
header, hasTarHeader := getArchiveTarHeader(archFile)
|
||||
ownership, hasOwnership := getArchiveOwnership(archFile)
|
||||
@@ -1281,7 +1281,7 @@ func (f FileOp) extractArchiveWithSDK(ctx context.Context, input io.Reader, dst
|
||||
return err
|
||||
}
|
||||
extractionStarted = true
|
||||
if archFile.FileInfo.IsDir() {
|
||||
if info.IsDir() {
|
||||
if filePath == root {
|
||||
return ensureArchiveDirectory(root, constant.DirPerm)
|
||||
}
|
||||
@@ -1448,6 +1448,12 @@ func (f FileOp) decompressWithSDKState(ctx context.Context, srcFile string, dst
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if cType == Zip || cType == SdkZip {
|
||||
if _, err := inspectZipPaths(ctx, input); err != nil {
|
||||
_ = input.Close()
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
var extractor archiver.Extractor = getFormat(cType)
|
||||
if cType == X7z {
|
||||
extractor = archiver.SevenZip{Password: secret}
|
||||
@@ -1561,6 +1567,12 @@ func (f FileOp) Decompress(ctx context.Context, srcFile string, dst string, cTyp
|
||||
}
|
||||
|
||||
func (f FileOp) DecompressWithOptions(ctx context.Context, srcFile string, dst string, cType CompressType, secret string, options DecompressOptions) error {
|
||||
if cType == Zip {
|
||||
handled, err := f.decompressZipWithPathCompatibility(ctx, srcFile, dst, options)
|
||||
if handled || err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if cType == X7z && options.PreserveOwner {
|
||||
return f.decompressSevenZipWithFallback(ctx, srcFile, dst, secret, options)
|
||||
}
|
||||
|
||||
@@ -3,13 +3,17 @@ package files
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
cZip "github.com/klauspost/compress/zip"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
type ZipArchiver struct {
|
||||
@@ -60,3 +64,114 @@ func (z ZipArchiver) Compress(ctx context.Context, sourcePaths []string, dstFile
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeZipEntry(header cZip.FileHeader) (cZip.FileHeader, error) {
|
||||
if header.NonUTF8 && header.Flags == 0 {
|
||||
name, err := decodeGBK(header.Name)
|
||||
if err != nil {
|
||||
return header, err
|
||||
}
|
||||
header.Name = name
|
||||
}
|
||||
name := strings.ReplaceAll(header.Name, `\`, "/")
|
||||
if name == "" || strings.ContainsRune(name, 0) || strings.HasPrefix(name, "/") {
|
||||
return header, fmt.Errorf("invalid ZIP path: %q", header.Name)
|
||||
}
|
||||
for _, part := range strings.Split(name, "/") {
|
||||
if part == ".." || strings.Contains(part, ":") {
|
||||
return header, fmt.Errorf("invalid ZIP path: %q", header.Name)
|
||||
}
|
||||
}
|
||||
mode := header.Mode()
|
||||
if strings.HasSuffix(name, "/") && !mode.IsDir() {
|
||||
if mode&fs.ModeType != 0 || header.UncompressedSize64 != 0 {
|
||||
return header, fmt.Errorf("invalid ZIP directory: %q", header.Name)
|
||||
}
|
||||
header.SetMode(mode | fs.ModeDir | (mode.Perm()&0444)>>2)
|
||||
}
|
||||
header.Name = name
|
||||
isDir := header.Mode().IsDir()
|
||||
name = path.Clean(name)
|
||||
if name == "." && !isDir {
|
||||
return header, fmt.Errorf("invalid ZIP file path: %q", header.Name)
|
||||
}
|
||||
if isDir {
|
||||
name += "/"
|
||||
}
|
||||
header.Name = name
|
||||
return header, nil
|
||||
}
|
||||
|
||||
func inspectZipPaths(ctx context.Context, input afero.File) (bool, error) {
|
||||
info, err := input.Stat()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
reader, err := cZip.NewReader(input, info.Size())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
compatible := false
|
||||
for _, entry := range reader.File {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
name := entry.Name
|
||||
if entry.NonUTF8 && entry.Flags == 0 {
|
||||
name, err = decodeGBK(name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
compatible = compatible || strings.Contains(name, `\`)
|
||||
}
|
||||
if !compatible {
|
||||
return false, nil
|
||||
}
|
||||
type zipPathEntry struct {
|
||||
original string
|
||||
dir bool
|
||||
}
|
||||
entries := make(map[string]zipPathEntry, len(reader.File))
|
||||
for _, entry := range reader.File {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
header, err := normalizeZipEntry(entry.FileHeader)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
name := path.Clean(header.Name)
|
||||
if previous, exists := entries[name]; exists {
|
||||
return false, fmt.Errorf("conflicting ZIP paths: %q and %q", previous.original, entry.Name)
|
||||
}
|
||||
entries[name] = zipPathEntry{original: entry.Name, dir: header.Mode().IsDir()}
|
||||
}
|
||||
for name, entry := range entries {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
for parent := path.Dir(name); parent != "."; parent = path.Dir(parent) {
|
||||
if previous, exists := entries[parent]; exists && !previous.dir {
|
||||
return false, fmt.Errorf("conflicting ZIP paths: %q and %q", previous.original, entry.original)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (f FileOp) decompressZipWithPathCompatibility(ctx context.Context, srcFile, dst string, options DecompressOptions) (bool, error) {
|
||||
input, err := f.Fs.Open(srcFile)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
compatible, inspectErr := inspectZipPaths(ctx, input)
|
||||
if inspectErr == nil && compatible {
|
||||
_, inspectErr = f.extractArchiveWithSDK(ctx, input, dst, getFormat(Zip), options)
|
||||
}
|
||||
closeErr := input.Close()
|
||||
if inspectErr != nil {
|
||||
return false, inspectErr
|
||||
}
|
||||
return compatible, closeErr
|
||||
}
|
||||
Reference in new issue
Block a user