Normalize archive member names.

This commit is contained in:
miyuko
2026-02-07 13:05:34 +00:00
parent f7067b939b
commit 7e293d6ef9
3 changed files with 23 additions and 14 deletions

View File

@@ -11,6 +11,7 @@ import (
"io"
"math"
"os"
"path"
"strings"
"github.com/c2h5oh/datasize"
@@ -61,6 +62,16 @@ func (err UnresolvedRefError) Error() string {
return fmt.Sprintf("%d unresolved blob references", len(err.missing))
}
func normalizeArchiveMemberName(fileName string) string {
// Strip the leading slash and any extraneous path segments.
fileName = path.Clean(fileName)
fileName = strings.TrimPrefix(fileName, "/")
if fileName == "." {
fileName = ""
}
return fileName
}
// Returns a map of git hash to entry. If `manifest` is nil, returns an empty map.
func indexManifestByGitHash(manifest *Manifest) map[string]*Entry {
index := map[string]*Entry{}
@@ -110,15 +121,10 @@ func ExtractTar(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*
return nil, err
}
// For some reason, GNU tar includes any leading `.` path segments in archive filenames,
// unless there is a `..` path segment anywhere in the input filenames.
fileName := header.Name
for {
if strippedName, found := strings.CutPrefix(fileName, "./"); found {
fileName = strippedName
} else {
break
}
fileName := normalizeArchiveMemberName(header.Name)
if fileName == "" {
// This must be the root directory. It will be filled in by EnsureLeadingDirectories.
continue
}
switch header.Typeflag {
@@ -200,8 +206,9 @@ func ExtractZip(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*
missing := []string{}
manifest := NewManifest()
for _, file := range archive.File {
normalizedName := normalizeArchiveMemberName(file.Name)
if strings.HasSuffix(file.Name, "/") {
AddDirectory(manifest, file.Name)
AddDirectory(manifest, normalizedName)
} else {
fileReader, err := file.Open()
if err != nil {
@@ -216,10 +223,10 @@ func ExtractZip(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*
if file.Mode()&os.ModeSymlink != 0 {
entry := addSymlinkOrBlobReference(
manifest, file.Name, string(fileData), index, &missing)
manifest, normalizedName, string(fileData), index, &missing)
dataBytesRecycled += entry.GetOriginalSize()
} else {
AddFile(manifest, file.Name, fileData)
AddFile(manifest, normalizedName, fileData)
dataBytesTransferred += int64(len(fileData))
}
}
@@ -240,4 +247,3 @@ func ExtractZip(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*
return manifest, nil
}

View File

@@ -151,6 +151,9 @@ func AddProblem(manifest *Manifest, pathName, format string, args ...any) error
func EnsureLeadingDirectories(manifest *Manifest) {
for name := range manifest.Contents {
for dir := path.Dir(name); dir != "." && dir != ""; dir = path.Dir(dir) {
if dir == "/" {
panic("malformed manifest (paths must not be rooted in /)")
}
if _, exists := manifest.Contents[dir]; !exists {
AddDirectory(manifest, dir)
}

View File

@@ -70,7 +70,7 @@ func ApplyTarPatch(manifest *Manifest, reader io.Reader, parents CreateParentsMo
return err
}
segments := strings.Split(strings.TrimRight(header.Name, "/"), "/")
segments := strings.Split(normalizeArchiveMemberName(header.Name), "/")
fileName := segments[len(segments)-1]
node := root
for index, segment := range segments[:len(segments)-1] {