mirror of
https://github.com/anchore/syft.git
synced 2026-10-30 14:02:26 +01:00
`:` was escaped as `%3A`, but `%` itself wasn't, so an entry named `a:b.jar` and one named `a%3Ab.jar` got the same `ArchivePath`. Their contents then collide in `FileMetadata` / `FileDigests` (last writer wins) and share artifact IDs. `%` is now escaped as `%25` before `:`, which makes the encoding injective. The encoding is documented on `Coordinates.ArchivePath` and in the schema. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
111 lines
3.5 KiB
Go
111 lines
3.5 KiB
Go
package archive
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/anchore/syft/syft/file"
|
|
)
|
|
|
|
func TestTraversalContextRoundTrip(t *testing.T) {
|
|
assert.Nil(t, TraversalFromContext(context.Background()))
|
|
|
|
trav := &Traversal{Location: file.NewLocation("app.war")}
|
|
ctx := WithTraversal(context.Background(), trav)
|
|
require.Same(t, trav, TraversalFromContext(ctx))
|
|
|
|
child := &Traversal{Location: locationIn("app.war", "/WEB-INF/lib/dep.jar")}
|
|
ctx = WithTraversal(ctx, child)
|
|
require.Same(t, child, TraversalFromContext(ctx))
|
|
}
|
|
|
|
// locationIn is a location as the Index returns it: an entry path stamped with the containing chain.
|
|
func locationIn(archivePath, entryPath string) file.Location {
|
|
loc := file.NewLocation(entryPath)
|
|
loc.ArchivePath = archivePath
|
|
return loc
|
|
}
|
|
|
|
func TestVirtualPath(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
location file.Location
|
|
expected string
|
|
}{
|
|
{
|
|
name: "outside an archive returns the path unchanged",
|
|
location: file.NewLocation("/WEB-INF/lib/dep.jar"),
|
|
expected: "/WEB-INF/lib/dep.jar",
|
|
},
|
|
{
|
|
name: "outside an archive preserves a relative path",
|
|
location: file.NewLocation("some/dir/example.jar"),
|
|
expected: "some/dir/example.jar",
|
|
},
|
|
{
|
|
name: "single level trims leading slash",
|
|
location: locationIn("app.war", "/WEB-INF/lib/dep.jar"),
|
|
expected: "app.war:WEB-INF/lib/dep.jar",
|
|
},
|
|
{
|
|
name: "single level with slash-less entry",
|
|
location: locationIn("app.war", "WEB-INF/lib/dep.jar"),
|
|
expected: "app.war:WEB-INF/lib/dep.jar",
|
|
},
|
|
{
|
|
name: "multi-level chain",
|
|
location: locationIn("app.war:WEB-INF/lib/dep.jar", "/inner/most.jar"),
|
|
expected: "app.war:WEB-INF/lib/dep.jar:inner/most.jar",
|
|
},
|
|
{
|
|
// a colon in the entry name must not be mistaken for a chain delimiter
|
|
name: "colon in entry path is escaped",
|
|
location: locationIn("app.war", "WEB-INF/lib/dep:special.jar"),
|
|
expected: "app.war:WEB-INF/lib/dep%3Aspecial.jar",
|
|
},
|
|
{
|
|
name: "every colon in the entry path is escaped",
|
|
location: locationIn("app.war", "lib/a:b:c.jar"),
|
|
expected: "app.war:lib/a%3Ab%3Ac.jar",
|
|
},
|
|
{
|
|
name: "access path is preferred over real path",
|
|
location: file.NewVirtualLocation("/real/dep.jar", "/link/dep.jar"),
|
|
expected: "/link/dep.jar",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equal(t, tt.expected, VirtualPath(tt.location))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestVirtualPath_theEscapeCharacterIsItselfEscaped(t *testing.T) {
|
|
// an entry path holding the literal text %3A must not collide with one holding a colon, or their
|
|
// contents share an ArchivePath and overwrite each other's file metadata
|
|
fromColon := VirtualPath(locationIn("outer.zip", "a:b.jar"))
|
|
fromLiteral := VirtualPath(locationIn("outer.zip", "a%3Ab.jar"))
|
|
|
|
assert.Equal(t, "outer.zip:a%3Ab.jar", fromColon)
|
|
assert.Equal(t, "outer.zip:a%253Ab.jar", fromLiteral)
|
|
}
|
|
|
|
func TestVirtualPath_escapingComposesAcrossNestingLevels(t *testing.T) {
|
|
level2 := VirtualPath(locationIn("outer.zip", "lib/mid:dle.war"))
|
|
require.Equal(t, "outer.zip:lib/mid%3Adle.war", level2)
|
|
|
|
final := VirtualPath(locationIn(level2, "WEB-INF/lib/inn:er.jar"))
|
|
require.Equal(t, "outer.zip:lib/mid%3Adle.war:WEB-INF/lib/inn%3Aer.jar", final)
|
|
|
|
assert.Equal(t,
|
|
[]string{"outer.zip", "lib/mid%3Adle.war", "WEB-INF/lib/inn%3Aer.jar"},
|
|
strings.Split(final, ":"),
|
|
"splitting on ':' must recover exactly the three real archive boundaries",
|
|
)
|
|
}
|