diff --git a/internal/archive/traversal.go b/internal/archive/traversal.go index de8b0da62..ddb84a02d 100644 --- a/internal/archive/traversal.go +++ b/internal/archive/traversal.go @@ -90,11 +90,14 @@ func (t *Traversal) FullTraversalArchivePath() string { // ContainerPath returns the full path of the archive directly holding the file at c, or blank for a // file on the scanned filesystem, whether or not its FullPath is set. func ContainerPath(c file.Coordinates) string { - i := strings.LastIndex(c.FullPath, file.ArchivePathSeparator) - if i < 0 { + entries, ok := archiveEntries(c) + if !ok { return "" } - return c.FullPath[:i] + if i := strings.LastIndex(entries, file.ArchivePathSeparator); i >= 0 { + return c.RealPath + file.ArchivePathSeparator + entries[:i] + } + return c.RealPath } // archiveEntries is the "!/"-delimited chain of entry paths after RealPath, reporting false for a file diff --git a/internal/archive/traversal_test.go b/internal/archive/traversal_test.go index 15010c697..73d0306b0 100644 --- a/internal/archive/traversal_test.go +++ b/internal/archive/traversal_test.go @@ -32,6 +32,9 @@ func TestContainerPath(t *testing.T) { assert.Equal(t, "", ContainerPath(file.NewCoordinates("/app.war", ""))) assert.Equal(t, "", ContainerPath(file.Coordinates{RealPath: "/app.war", FullPath: "/app.war"}), "a top-level file whose FullPath is set") assert.Equal(t, "", ContainerPath(file.Coordinates{RealPath: "/odd!/name"}), "a separator in a top-level real path is not an archive boundary") + assert.Equal(t, "", ContainerPath(file.Coordinates{RealPath: "/odd!/name", FullPath: "/odd!/name"}), "nor when FullPath is set") + assert.Equal(t, "/odd!/name.zip", ContainerPath(locationIn("/odd!/name.zip", "a.txt").Coordinates)) + assert.Equal(t, "/odd!/name.zip!/b.jar", ContainerPath(locationIn("/odd!/name.zip", "b.jar!/a.txt").Coordinates)) assert.Equal(t, "/app.war", ContainerPath(locationIn("/app.war", "WEB-INF/lib/dep.jar").Coordinates)) assert.Equal(t, "/app.war!/WEB-INF/lib/dep.jar", ContainerPath(locationIn("/app.war", "WEB-INF/lib/dep.jar!/META-INF/MANIFEST.MF").Coordinates)) diff --git a/syft/file/coordinates.go b/syft/file/coordinates.go index c496aad67..3645c534d 100644 --- a/syft/file/coordinates.go +++ b/syft/file/coordinates.go @@ -30,14 +30,18 @@ func (c Coordinates) FullNestedPath() string { return c.FullPath } -// LocalPath is the rooted path within the resolver holding the file: the segment of FullNestedPath after -// the last archive separator, keeping the separator's slash, or RealPath for a file not within an archive. +// LocalPath is the rooted path within the resolver holding the file: the last entry path in FullPath, or +// RealPath for a file not within an archive. RealPath is cut off before looking for a separator, so a +// "!/" in a real path is never taken for an archive boundary. func (c Coordinates) LocalPath() string { - fullPath := c.FullNestedPath() - if i := strings.LastIndex(fullPath, ArchivePathSeparator); i >= 0 { - return fullPath[i+len(ArchivePathSeparator)-1:] + entries, ok := strings.CutPrefix(c.FullPath, c.RealPath+ArchivePathSeparator) + if !ok { + return c.RealPath } - return fullPath + if i := strings.LastIndex(entries, ArchivePathSeparator); i >= 0 { + entries = entries[i+len(ArchivePathSeparator):] + } + return "/" + entries } func NewCoordinates(realPath, fsID string) Coordinates { diff --git a/syft/file/location_test.go b/syft/file/location_test.go index 7ea14de63..34074c7fa 100644 --- a/syft/file/location_test.go +++ b/syft/file/location_test.go @@ -106,4 +106,8 @@ func TestCoordinates_LocalPath(t *testing.T) { assert.Equal(t, "/a.jar", NewCoordinates("/a.jar", "").LocalPath()) assert.Equal(t, "/a.jar", Coordinates{RealPath: "/a.jar"}.LocalPath()) assert.Equal(t, "/META-INF/MANIFEST.MF", Coordinates{RealPath: "/a.war", FullPath: "/a.war!/lib/b.jar!/META-INF/MANIFEST.MF"}.LocalPath()) + assert.Equal(t, "/data/Hello!/world.txt", NewCoordinates("/data/Hello!/world.txt", "").LocalPath(), "a separator in a real path is not an archive boundary") + assert.Equal(t, "/data/Hello!/world.txt", Coordinates{RealPath: "/data/Hello!/world.txt", FullPath: "/data/Hello!/world.txt"}.LocalPath()) + assert.Equal(t, "/x.txt", Coordinates{RealPath: "/data/Hello!/a.zip", FullPath: "/data/Hello!/a.zip!/x.txt"}.LocalPath()) + assert.Equal(t, "/x.txt", Coordinates{RealPath: "/data/Hello!/a.zip", FullPath: "/data/Hello!/a.zip!/b.jar!/x.txt"}.LocalPath()) }