Files
OpenFlare/pkg/pagesarchive/inspect.go
T
ryan 65c02ef7a5 基准扩展 exhaustive(文档化)+ 12→0:枚举 switch 补显式 case(全部与现有 default 行为等价,fail-explicit 防未来枚举静默落入 default);source_tasks.go 为控制复杂度合并两个等价校验条件。
Result: {"status":"keep","total_issues":8,"golint_canonicalheader":0,"golint_errname":0,"golint_errorlint":1,"golint_exhaustive":0,"golint_forcetypeassert":0,"golint_gosec":0,"golint_intrange":0,"golint_modernize":3,"golint_nilnil":3,"golint_perfsprint":0,"golint_prealloc":0,"golint_recvcheck":1,"golint_usestdlibvars":0,"golint_wastedassign":0,"golint_total":8,"golint_test_testifylint":0,"golint_test_thelper":0,"golint_test_usetesting":0,"golint_test_total":0,"eslint_problems":0,"eslint_errors":0,"eslint_warnings":0,"tsc_errors":0,"measure_s":36}
2026-08-16 18:19:08 +08:00

304 lines
8.6 KiB
Go

// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package pagesarchive
import (
"archive/tar"
"bytes"
"errors"
"fmt"
"io"
"math"
"os"
"path"
)
// InspectOptions controls package inspection.
type InspectOptions struct {
// RootDir is an optional project root subdirectory that must contain EntryFile.
RootDir string
// EntryFile is the required entry file name (e.g. index.html).
EntryFile string
// Limits bounds files and actual extracted sizes.
Limits Limits
// VerifySizes is retained for source compatibility. Inspection now always
// streams regular members and verifies actual bytes against declared sizes.
VerifySizes bool
}
type measuredFile struct {
path string
size int64
}
type measuredArchive struct {
files []measuredFile
fileCount int
totalSize int64
}
// InspectFile opens path and inspects it as a Pages deployment package without
// loading the whole archive or any tar member body into memory.
func InspectFile(filePath string, format Format, opts InspectOptions) (*Manifest, error) {
file, err := os.Open(filePath) //nolint:gosec // filePath is a controlled temp upload path
if err != nil {
return nil, err
}
defer func() { _ = file.Close() }()
info, err := file.Stat()
if err != nil {
return nil, err
}
if format == "" {
head := make([]byte, formatDetectHeadBytes)
n, readErr := file.ReadAt(head, 0)
if readErr != nil && readErr != io.EOF {
return nil, readErr
}
format, err = DetectFormat(filePath, head[:n])
if err != nil {
return nil, err
}
}
return inspectFromReaderAt(file, info.Size(), format, opts)
}
// InspectBytes inspects an in-memory deployment package.
func InspectBytes(data []byte, format Format, opts InspectOptions) (*Manifest, error) {
if format == "" {
var err error
format, err = DetectFormat("", data)
if err != nil {
return nil, err
}
}
return inspectFromReaderAt(bytes.NewReader(data), int64(len(data)), format, opts)
}
func inspectFromReaderAt(ra io.ReaderAt, size int64, format Format, opts InspectOptions) (*Manifest, error) {
limits := normalizeLimits(opts.Limits)
var (
measured *measuredArchive
err error
)
if isTarFamily(format) {
measured, err = scanTarFamilyAt(ra, size, format, limits, true)
} else {
var entries []Entry
entries, err = listRandomAccessEntriesAt(ra, size, format)
if err == nil {
measured, err = inspectRandomAccessEntries(entries, limits)
}
}
if err != nil {
return nil, err
}
return buildMeasuredManifest(measured, opts)
}
func inspectRandomAccessEntries(entries []Entry, limits Limits) (*measuredArchive, error) {
measured := &measuredArchive{files: make([]measuredFile, 0, len(entries))}
for _, entry := range entries {
normalizedPath, skip, err := validateArchiveEntry(entry)
if err != nil {
return nil, err
}
if skip {
continue
}
if err := prepareMeasuredFile(measured, normalizedPath, entry.Size, limits, true); err != nil {
return nil, err
}
if entry.Open == nil {
return nil, fmt.Errorf("%s: pages archive member cannot be opened", normalizedPath)
}
src, err := entry.Open()
if err != nil {
return nil, fmt.Errorf("%s: %w", normalizedPath, err)
}
maxBytes := effectiveFileLimit(limits, measured.totalSize, true)
actual, copyErr := copyAndVerifySize(io.Discard, src, entry.Size, maxBytes)
closeErr := src.Close()
if err := errors.Join(copyErr, closeErr); err != nil {
return nil, fmt.Errorf("%s: %w", normalizedPath, err)
}
appendMeasuredFile(measured, normalizedPath, actual)
}
return measured, nil
}
func scanTarFamilyAt(
ra io.ReaderAt,
size int64,
format Format,
limits Limits,
enforceLimits bool,
) (*measuredArchive, error) {
if size < 0 {
return nil, errors.New("invalid pages package size")
}
tarReader, closeReader, err := openTarFamilyReader(io.NewSectionReader(ra, 0, size), format)
if err != nil {
return nil, err
}
measured, scanErr := scanTarReader(tarReader, limits, enforceLimits)
if closeErr := closeReader(); closeErr != nil {
scanErr = errors.Join(scanErr, closeErr)
}
return measured, scanErr
}
func scanTarReader(tarReader *tar.Reader, limits Limits, enforceLimits bool) (*measuredArchive, error) {
measured := &measuredArchive{files: make([]measuredFile, 0)}
for {
header, err := tarReader.Next()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return nil, fmt.Errorf("read tar pages package: %w", err)
}
entry := entryFromTarHeader(header)
normalizedPath, skip, err := validateArchiveEntry(entry)
if err != nil {
return nil, err
}
if skip {
continue
}
if err := prepareMeasuredFile(measured, normalizedPath, entry.Size, limits, enforceLimits); err != nil {
return nil, err
}
maxBytes := effectiveFileLimit(limits, measured.totalSize, enforceLimits)
actual, err := copyAndVerifySize(io.Discard, tarReader, entry.Size, maxBytes)
if err != nil {
return nil, fmt.Errorf("%s: %w", normalizedPath, err)
}
appendMeasuredFile(measured, normalizedPath, actual)
}
return measured, nil
}
func buildMeasuredManifest(measured *measuredArchive, opts InspectOptions) (*Manifest, error) {
if measured == nil || measured.fileCount == 0 {
return nil, errors.New("pages package is empty")
}
targetEntryPath, err := resolveTargetEntryPath(opts.RootDir, opts.EntryFile)
if err != nil {
return nil, err
}
paths := make([]string, 0, len(measured.files))
for _, file := range measured.files {
paths = append(paths, file.path)
}
commonPrefix := FindCommonRootPrefix(paths)
manifest := &Manifest{
Files: make([]FileEntry, 0, measured.fileCount),
FileCount: measured.fileCount,
TotalSize: measured.totalSize,
}
entrySeen := false
for _, file := range measured.files {
normalizedPath := StripPrefix(file.path, commonPrefix)
if normalizedPath == targetEntryPath {
entrySeen = true
}
manifest.Files = append(manifest.Files, FileEntry{
Path: normalizedPath,
Size: file.size,
})
}
if !entrySeen {
return nil, fmt.Errorf("pages package is missing entry file %s", targetEntryPath)
}
return manifest, nil
}
func prepareMeasuredFile(measured *measuredArchive, normalizedPath string, declaredSize uint64, limits Limits, enforceLimits bool) error {
if declaredSize > uint64(math.MaxInt64) {
return fmt.Errorf("%s: pages file size out of bounds", normalizedPath)
}
if !enforceLimits {
return nil
}
if measured.fileCount >= limits.MaxFiles {
return fmt.Errorf("pages deployment file count exceeds %d", limits.MaxFiles)
}
if exceedsFileByteLimit(declaredSize, limits.MaxFileBytes) {
return fmt.Errorf("pages file too large: %s", normalizedPath)
}
remaining := limits.MaxTotalBytes - measured.totalSize
if remaining < 0 || declaredSize > uint64(remaining) { //nolint:gosec // remaining is checked non-negative
return errors.New("pages extracted size exceeds limit")
}
return nil
}
func appendMeasuredFile(measured *measuredArchive, normalizedPath string, actual int64) {
measured.files = append(measured.files, measuredFile{path: normalizedPath, size: actual})
measured.fileCount++
measured.totalSize += actual
}
func effectiveFileLimit(limits Limits, totalSize int64, enforceLimits bool) int64 {
if !enforceLimits {
return -1
}
remaining := limits.MaxTotalBytes - totalSize
if remaining < limits.MaxFileBytes {
return remaining
}
return limits.MaxFileBytes
}
func resolveTargetEntryPath(rootDir, entryFile string) (string, error) {
normalizedRoot, err := NormalizeLogicalPath(rootDir, true)
if err != nil {
return "", fmt.Errorf("invalid pages root directory: %w", err)
}
if entryFile == "" {
entryFile = "index.html"
}
normalizedEntry, err := NormalizeLogicalPath(entryFile, false)
if err != nil {
return "", fmt.Errorf("invalid pages entry file: %w", err)
}
if normalizedRoot == "" {
return normalizedEntry, nil
}
return path.Join(normalizedRoot, normalizedEntry), nil
}
func validateArchiveEntry(entry Entry) (string, bool, error) {
normalizedPath, skip, err := NormalizeEntryPath(entry.Name)
if err != nil {
return "", false, err
}
if entry.IsSymlink {
return "", false, fmt.Errorf("pages package contains unsupported symlink: %s", normalizedPath)
}
if entry.IsHardlink {
return "", false, fmt.Errorf("pages package contains unsupported hardlink: %s", normalizedPath)
}
if entry.IsSpecial {
return "", false, fmt.Errorf("pages package contains unsupported special entry: %s", normalizedPath)
}
if skip || entry.IsDir {
return normalizedPath, true, nil
}
return normalizedPath, false, nil
}
func isTarFamily(format Format) bool {
switch format {
case FormatTar, FormatTarGz, FormatTarXz, FormatTarBz2:
return true
case FormatZip, FormatSevenZip:
return false
default:
return false
}
}