diff --git a/content/oci/readonlyoci_test.go b/content/oci/readonlyoci_test.go index d7b4fd0e..c1cbd20d 100644 --- a/content/oci/readonlyoci_test.go +++ b/content/oci/readonlyoci_test.go @@ -416,91 +416,117 @@ func TestReadOnlyStore_DirFS(t *testing.T) { } /* -testdata/hello-world.tar contains: - - blobs/ - sha256/ - 2db29710123e3e53a794f2694094b9b4338aa9ee5c40b930cb8063a1be392c54 // image layer - f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4 // image manifest - faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af // manifest list - feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412 // config - index.json - manifest.json - oci-layout +=== Contents of testdata/hello-world.tar === + +blobs/ + + blobs/sha256/ + blobs/sha256/2db29710123e3e53a794f2694094b9b4338aa9ee5c40b930cb8063a1be392c54 // image layer + blobs/sha256/f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4 // image manifest + blobs/sha256/faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af // manifest list + blobs/sha256/feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412 // config + +index.json +manifest.json +oci-layout + +=== Contents of testdata/hello-world-prefixed-path.tar === + +./ +./blobs/ + + ./blobs/sha256/ + ./blobs/sha256/2db29710123e3e53a794f2694094b9b4338aa9ee5c40b930cb8063a1be392c54 // image layer + ./blobs/sha256/f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4 // image manifest + ./blobs/sha256/faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af // manifest list + ./blobs/sha256/feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412 // config + +./index.json +./manifest.json +./oci-layout */ + func TestReadOnlyStore_TarFS(t *testing.T) { - ctx := context.Background() - s, err := NewFromTar(ctx, "testdata/hello-world.tar") - if err != nil { - t.Fatal("New() error =", err) - } + tarPaths := []string{ + "testdata/hello-world.tar", + "testdata/hello-world-prefixed-path.tar", + } + for _, tarPath := range tarPaths { + t.Run(tarPath, func(t *testing.T) { + ctx := context.Background() + s, err := NewFromTar(ctx, tarPath) + if err != nil { + t.Fatal("New() error =", err) + } - // test data in testdata/hello-world.tar - descs := []ocispec.Descriptor{ - // desc 0: config - { - MediaType: "application/vnd.docker.container.image.v1+json", - Size: 1469, - Digest: "sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412", - }, - // desc 1: layer - { - MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", - Size: 2479, - Digest: "sha256:2db29710123e3e53a794f2694094b9b4338aa9ee5c40b930cb8063a1be392c54", - }, - // desc 2: image manifest - { - MediaType: "application/vnd.docker.distribution.manifest.v2+json", - Digest: "sha256:f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4", - Size: 525, - Platform: &ocispec.Platform{ - Architecture: "amd64", - OS: "linux", - }, - }, - // desc 3: manifest list - { - MediaType: docker.MediaTypeManifestList, - Size: 2561, - Digest: "sha256:faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af", - }, - } + // test data in testdata/hello-world.tar + descs := []ocispec.Descriptor{ + // desc 0: config + { + MediaType: "application/vnd.docker.container.image.v1+json", + Size: 1469, + Digest: "sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412", + }, + // desc 1: layer + { + MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", + Size: 2479, + Digest: "sha256:2db29710123e3e53a794f2694094b9b4338aa9ee5c40b930cb8063a1be392c54", + }, + // desc 2: image manifest + { + MediaType: "application/vnd.docker.distribution.manifest.v2+json", + Digest: "sha256:f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4", + Size: 525, + Platform: &ocispec.Platform{ + Architecture: "amd64", + OS: "linux", + }, + }, + // desc 3: manifest list + { + MediaType: docker.MediaTypeManifestList, + Size: 2561, + Digest: "sha256:faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af", + }, + } - // test resolving by tag - for _, desc := range descs { - gotDesc, err := s.Resolve(ctx, desc.Digest.String()) - if err != nil { - t.Fatal("ReadOnlyStore: Resolve() error =", err) - } - if want := desc; gotDesc.Size != want.Size || gotDesc.Digest != want.Digest { - t.Errorf("ReadOnlyStore.Resolve() = %v, want %v", gotDesc, want) - } - } - // test resolving by tag - gotDesc, err := s.Resolve(ctx, "latest") - if err != nil { - t.Fatal("ReadOnlyStore: Resolve() error =", err) - } - if want := descs[3]; gotDesc.Size != want.Size || gotDesc.Digest != want.Digest { - t.Errorf("ReadOnlyStore.Resolve() = %v, want %v", gotDesc, want) - } + // test resolving by tag + for _, desc := range descs { + gotDesc, err := s.Resolve(ctx, desc.Digest.String()) + if err != nil { + t.Fatal("ReadOnlyStore: Resolve() error =", err) + } + if want := desc; gotDesc.Size != want.Size || gotDesc.Digest != want.Digest { + t.Errorf("ReadOnlyStore.Resolve() = %v, want %v", gotDesc, want) + } + } + // test resolving by tag + gotDesc, err := s.Resolve(ctx, "latest") + if err != nil { + t.Fatal("ReadOnlyStore: Resolve() error =", err) + } + if want := descs[3]; gotDesc.Size != want.Size || gotDesc.Digest != want.Digest { + t.Errorf("ReadOnlyStore.Resolve() = %v, want %v", gotDesc, want) + } - // test Predecessors - wantPredecessors := [][]ocispec.Descriptor{ - {descs[2]}, // desc 0 - {descs[2]}, // desc 1 - {descs[3]}, // desc 2 - {}, // desc 3 - } - for i, want := range wantPredecessors { - predecessors, err := s.Predecessors(ctx, descs[i]) - if err != nil { - t.Errorf("ReadOnlyStore.Predecessors(%d) error = %v", i, err) - } - if !equalDescriptorSet(predecessors, want) { - t.Errorf("ReadOnlyStore.Predecessors(%d) = %v, want %v", i, predecessors, want) - } + // test Predecessors + wantPredecessors := [][]ocispec.Descriptor{ + {descs[2]}, // desc 0 + {descs[2]}, // desc 1 + {descs[3]}, // desc 2 + {}, // desc 3 + } + for i, want := range wantPredecessors { + predecessors, err := s.Predecessors(ctx, descs[i]) + if err != nil { + t.Errorf("ReadOnlyStore.Predecessors(%d) error = %v", i, err) + } + if !equalDescriptorSet(predecessors, want) { + t.Errorf("ReadOnlyStore.Predecessors(%d) = %v, want %v", i, predecessors, want) + } + } + }) } } diff --git a/content/oci/readonlystorage_test.go b/content/oci/readonlystorage_test.go index b1d1906a..623dc5a7 100644 --- a/content/oci/readonlystorage_test.go +++ b/content/oci/readonlystorage_test.go @@ -161,57 +161,93 @@ func TestReadOnlyStorage_DirFS(t *testing.T) { } } -func TestReadOnlyStorage_TarFS(t *testing.T) { - s, err := NewStorageFromTar("testdata/hello-world.tar") - if err != nil { - t.Fatal("NewStorageFromTar() error =", err) - } - ctx := context.Background() - - // test data in testdata/hello-world.tar - blob := []byte(`{"architecture":"amd64","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"],"Image":"sha256:b9935d4e8431fb1a7f0989304ec86b3329a99a25f5efdc7f09f3f8c41434ca6d","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"8746661ca3c2f215da94e6d3f7dfdcafaff5ec0b21c9aff6af3dc379a82fbc72","container_config":{"Hostname":"8746661ca3c2","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"/hello\"]"],"Image":"sha256:b9935d4e8431fb1a7f0989304ec86b3329a99a25f5efdc7f09f3f8c41434ca6d","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"created":"2021-09-23T23:47:57.442225064Z","docker_version":"20.10.7","history":[{"created":"2021-09-23T23:47:57.098990892Z","created_by":"/bin/sh -c #(nop) COPY file:50563a97010fd7ce1ceebd1fa4f4891ac3decdf428333fb2683696f4358af6c2 in / "},{"created":"2021-09-23T23:47:57.442225064Z","created_by":"/bin/sh -c #(nop) CMD [\"/hello\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:e07ee1baac5fae6a26f30cabfe54a36d3402f96afda318fe0a96cec4ca393359"]}}`) - desc := content.NewDescriptorFromBytes(docker.MediaTypeManifest, blob) - - // test Exists - exists, err := s.Exists(ctx, desc) - if err != nil { - t.Fatal("ReadOnlyStorage.Exists() error =", err) - } - if want := true; exists != want { - t.Errorf("ReadOnlyStorage.Exists() = %v, want %v", exists, want) - } - - // test Fetch - rc, err := s.Fetch(ctx, desc) - if err != nil { - t.Fatal("ReadOnlyStorage.Fetch() error =", err) - } - got, err := io.ReadAll(rc) - if err != nil { - t.Fatal("ReadOnlyStorage.Fetch().Read() error =", err) - } - err = rc.Close() - if err != nil { - t.Error("ReadOnlyStorage.Fetch().Close() error =", err) - } - if !bytes.Equal(got, blob) { - t.Errorf("ReadOnlyStorage.Fetch() = %v, want %v", got, blob) - } +/* +=== Contents of testdata/hello-world.tar === + +blobs/ + blobs/sha256/ + blobs/sha256/2db29710123e3e53a794f2694094b9b4338aa9ee5c40b930cb8063a1be392c54 + blobs/sha256/f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4 + blobs/sha256/faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af + blobs/sha256/feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412 +index.json +manifest.json +oci-layout + +=== Contents of testdata/hello-world-prefixed-path.tar === + +./ +./blobs/ + ./blobs/sha256/ + ./blobs/sha256/2db29710123e3e53a794f2694094b9b4338aa9ee5c40b930cb8063a1be392c54 + ./blobs/sha256/f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4 + ./blobs/sha256/faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af + ./blobs/sha256/feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412 +./index.json +./manifest.json +./oci-layout - // test Exists against a non-existing content - blob = []byte("whatever") - desc = content.NewDescriptorFromBytes("", blob) - exists, err = s.Exists(ctx, desc) - if err != nil { - t.Fatal("ReadOnlyStorage.Exists() error =", err) - } - if want := false; exists != want { - t.Errorf("ReadOnlyStorage.Exists() = %v, want %v", exists, want) - } +*/ - // test Fetch against a non-existing content - _, err = s.Fetch(ctx, desc) - if want := errdef.ErrNotFound; !errors.Is(err, want) { - t.Errorf("ReadOnlyStorage.Fetch() error = %v, wantErr %v", err, want) +func TestReadOnlyStorage_TarFS(t *testing.T) { + tarPaths := []string{ + "testdata/hello-world.tar", + "testdata/hello-world-prefixed-path.tar", + } + for _, tarPath := range tarPaths { + t.Run(tarPath, func(t *testing.T) { + s, err := NewStorageFromTar(tarPath) + if err != nil { + t.Fatal("NewStorageFromTar() error =", err) + } + ctx := context.Background() + + // test data in testdata/hello-world.tar and testdata/hello-world-prefixed-path.tar + blob := []byte(`{"architecture":"amd64","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"],"Image":"sha256:b9935d4e8431fb1a7f0989304ec86b3329a99a25f5efdc7f09f3f8c41434ca6d","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"8746661ca3c2f215da94e6d3f7dfdcafaff5ec0b21c9aff6af3dc379a82fbc72","container_config":{"Hostname":"8746661ca3c2","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"/hello\"]"],"Image":"sha256:b9935d4e8431fb1a7f0989304ec86b3329a99a25f5efdc7f09f3f8c41434ca6d","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"created":"2021-09-23T23:47:57.442225064Z","docker_version":"20.10.7","history":[{"created":"2021-09-23T23:47:57.098990892Z","created_by":"/bin/sh -c #(nop) COPY file:50563a97010fd7ce1ceebd1fa4f4891ac3decdf428333fb2683696f4358af6c2 in / "},{"created":"2021-09-23T23:47:57.442225064Z","created_by":"/bin/sh -c #(nop) CMD [\"/hello\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:e07ee1baac5fae6a26f30cabfe54a36d3402f96afda318fe0a96cec4ca393359"]}}`) + desc := content.NewDescriptorFromBytes(docker.MediaTypeManifest, blob) + + // test Exists + exists, err := s.Exists(ctx, desc) + if err != nil { + t.Fatal("ReadOnlyStorage.Exists() error =", err) + } + if want := true; exists != want { + t.Errorf("ReadOnlyStorage.Exists() = %v, want %v", exists, want) + } + + // test Fetch + rc, err := s.Fetch(ctx, desc) + if err != nil { + t.Fatal("ReadOnlyStorage.Fetch() error =", err) + } + got, err := io.ReadAll(rc) + if err != nil { + t.Fatal("ReadOnlyStorage.Fetch().Read() error =", err) + } + err = rc.Close() + if err != nil { + t.Error("ReadOnlyStorage.Fetch().Close() error =", err) + } + if !bytes.Equal(got, blob) { + t.Errorf("ReadOnlyStorage.Fetch() = %v, want %v", got, blob) + } + + // test Exists against a non-existing content + blob = []byte("whatever") + desc = content.NewDescriptorFromBytes("", blob) + exists, err = s.Exists(ctx, desc) + if err != nil { + t.Fatal("ReadOnlyStorage.Exists() error =", err) + } + if want := false; exists != want { + t.Errorf("ReadOnlyStorage.Exists() = %v, want %v", exists, want) + } + + // test Fetch against a non-existing content + _, err = s.Fetch(ctx, desc) + if want := errdef.ErrNotFound; !errors.Is(err, want) { + t.Errorf("ReadOnlyStorage.Fetch() error = %v, wantErr %v", err, want) + } + }) } } diff --git a/content/oci/testdata/hello-world-prefixed-path.tar b/content/oci/testdata/hello-world-prefixed-path.tar new file mode 100644 index 00000000..b2466a42 Binary files /dev/null and b/content/oci/testdata/hello-world-prefixed-path.tar differ diff --git a/internal/fs/tarfs/tarfs.go b/internal/fs/tarfs/tarfs.go index ecaa6bdd..303881c6 100644 --- a/internal/fs/tarfs/tarfs.go +++ b/internal/fs/tarfs/tarfs.go @@ -22,6 +22,7 @@ import ( "io" "io/fs" "os" + "path" "path/filepath" "oras.land/oras-go/v2/errdef" @@ -143,12 +144,13 @@ func (tfs *TarFS) indexEntries() error { if err != nil { return err } - tfs.entries[header.Name] = &entry{ + + name := path.Clean(header.Name) + tfs.entries[name] = &entry{ header: header, pos: pos - blockSize, } } - return nil } diff --git a/internal/fs/tarfs/tarfs_test.go b/internal/fs/tarfs/tarfs_test.go index 3a67b88c..e80df155 100644 --- a/internal/fs/tarfs/tarfs_test.go +++ b/internal/fs/tarfs/tarfs_test.go @@ -27,96 +27,127 @@ import ( ) /* -testdata/test.tar contains: - - foobar - foobar_link - foobar_symlink - dir/ - hello - subdir/ - world +=== Contents of testdata/cleaned_path.tar === + +dir/ + dir/hello + dir/subdir/ + dir/subdir/world +foobar +foobar_link +foobar_symlink + +=== Contents of testdata/prefixed_path.tar === + +./ +./dir/ + ./dir/hello + ./dir/subdir/ + ./dir/subdir/world +./foobar +./foobar_link +./foobar_symlink + */ + func TestTarFS_Open_Success(t *testing.T) { testFiles := map[string][]byte{ "foobar": []byte("foobar"), "dir/hello": []byte("hello"), "dir/subdir/world": []byte("world"), } - tarPath := "testdata/test.tar" - tfs, err := New(tarPath) - if err != nil { - t.Fatalf("New() error = %v, wantErr %v", err, nil) - } - tarPathAbs, err := filepath.Abs(tarPath) - if err != nil { - t.Fatal("error calling filepath.Abs(), error =", err) - } - if tfs.path != tarPathAbs { - t.Fatalf("TarFS.path = %s, want %s", tfs.path, tarPathAbs) + tarPaths := []string{ + "testdata/cleaned_path.tar", + "testdata/prefixed_path.tar", } - for name, data := range testFiles { - f, err := tfs.Open(name) - if err != nil { - t.Fatalf("TarFS.Open(%s) error = %v, wantErr %v", name, err, nil) - continue - } - - got, err := io.ReadAll(f) - if err != nil { - t.Fatalf("failed to read %s: %v", name, err) - } - if err = f.Close(); err != nil { - t.Errorf("TarFS.Open(%s).Close() error = %v", name, err) - } - if want := data; !bytes.Equal(got, want) { - t.Errorf("TarFS.Open(%s) = %v, want %v", name, string(got), string(want)) - } + for _, tarPath := range tarPaths { + t.Run(tarPath, func(t *testing.T) { + tfs, err := New(tarPath) + if err != nil { + t.Fatalf("New() error = %v, wantErr %v", err, nil) + } + tarPathAbs, err := filepath.Abs(tarPath) + if err != nil { + t.Fatal("error calling filepath.Abs(), error =", err) + } + if tfs.path != tarPathAbs { + t.Fatalf("TarFS.path = %s, want %s", tfs.path, tarPathAbs) + } + + for name, data := range testFiles { + t.Run(name, func(t *testing.T) { + f, err := tfs.Open(name) + if err != nil { + t.Fatalf("TarFS.Open(%s) error = %v, wantErr %v", name, err, nil) + } + + got, err := io.ReadAll(f) + if err != nil { + t.Fatalf("failed to read %s: %v", name, err) + } + if err = f.Close(); err != nil { + t.Errorf("TarFS.Open(%s).Close() error = %v", name, err) + } + if want := data; !bytes.Equal(got, want) { + t.Errorf("TarFS.Open(%s) = %v, want %v", name, string(got), string(want)) + } + }) + } + }) } } func TestTarFS_Open_MoreThanOnce(t *testing.T) { - tfs, err := New("testdata/test.tar") - if err != nil { - t.Fatalf("New() error = %v, wantErr %v", err, nil) + tarPaths := []string{ + "testdata/cleaned_path.tar", + "testdata/prefixed_path.tar", } - name := "foobar" - data := []byte("foobar") - // open once - f1, err := tfs.Open(name) - if err != nil { - t.Fatalf("1st: TarFS.Open(%s) error = %v, wantErr %v", name, err, nil) - } + for _, tarPath := range tarPaths { + t.Run(tarPath, func(t *testing.T) { + tfs, err := New(tarPath) + if err != nil { + t.Fatalf("New() error = %v, wantErr %v", err, nil) + } - got, err := io.ReadAll(f1) - if err != nil { - t.Fatalf("1st: failed to read %s: %v", name, err) - } - if want := data; !bytes.Equal(got, want) { - t.Errorf("1st: TarFS.Open(%s) = %v, want %v", name, string(got), string(want)) - } + name := "foobar" + data := []byte("foobar") + // open once + f1, err := tfs.Open(name) + if err != nil { + t.Fatalf("1st: TarFS.Open(%s) error = %v, wantErr %v", name, err, nil) + } - // open twice - f2, err := tfs.Open(name) - if err != nil { - t.Fatalf("2nd: TarFS.Open(%s) error = %v, wantErr %v", name, err, nil) - } - got, err = io.ReadAll(f2) - if err != nil { - t.Fatalf("2nd: failed to read %s: %v", name, err) - } - if want := data; !bytes.Equal(got, want) { - t.Errorf("2nd: TarFS.Open(%s) = %v, want %v", name, string(got), string(want)) - } + got, err := io.ReadAll(f1) + if err != nil { + t.Fatalf("1st: failed to read %s: %v", name, err) + } + if want := data; !bytes.Equal(got, want) { + t.Errorf("1st: TarFS.Open(%s) = %v, want %v", name, string(got), string(want)) + } - // close - if err = f1.Close(); err != nil { - t.Errorf("1st TarFS.Open(%s).Close() error = %v", name, err) - } - if err = f2.Close(); err != nil { - t.Errorf("2nd TarFS.Open(%s).Close() error = %v", name, err) + // open twice + f2, err := tfs.Open(name) + if err != nil { + t.Fatalf("2nd: TarFS.Open(%s) error = %v, wantErr %v", name, err, nil) + } + got, err = io.ReadAll(f2) + if err != nil { + t.Fatalf("2nd: failed to read %s: %v", name, err) + } + if want := data; !bytes.Equal(got, want) { + t.Errorf("2nd: TarFS.Open(%s) = %v, want %v", name, string(got), string(want)) + } + + // close + if err = f1.Close(); err != nil { + t.Errorf("1st TarFS.Open(%s).Close() error = %v", name, err) + } + if err = f2.Close(); err != nil { + t.Errorf("2nd TarFS.Open(%s).Close() error = %v", name, err) + } + }) } } @@ -126,33 +157,63 @@ func TestTarFS_Open_NotExist(t *testing.T) { "subdir/bar", "barfoo", } - tfs, err := New("testdata/test.tar") - if err != nil { - t.Fatalf("New() error = %v, wantErr %v", err, nil) + tarPaths := []string{ + "testdata/cleaned_path.tar", + "testdata/prefixed_path.tar", } - for _, name := range testFiles { - _, err := tfs.Open(name) - if want := fs.ErrNotExist; !errors.Is(err, want) { - t.Errorf("TarFS.Open(%s) error = %v, wantErr %v", name, err, want) - } + + for _, tarPath := range tarPaths { + t.Run(tarPath, func(t *testing.T) { + tfs, err := New(tarPath) + if err != nil { + t.Fatalf("New() error = %v, wantErr %v", err, nil) + } + for _, name := range testFiles { + t.Run(name, func(t *testing.T) { + _, err := tfs.Open(name) + if want := fs.ErrNotExist; !errors.Is(err, want) { + t.Errorf("TarFS.Open(%s) error = %v, wantErr %v", name, err, want) + } + }) + } + }) } + } func TestTarFS_Open_InvalidPath(t *testing.T) { testFiles := []string{ + "..", + "../outside", + "./dir", "dir/", - "subdir/", "dir/subdir/", + "/absolute/path", + "dir/../invalid", + "dir/./invalid", + "dir//double_slash", + "dir/subdir/../../invalid", } - tfs, err := New("testdata/test.tar") - if err != nil { - t.Fatalf("New() error = %v, wantErr %v", err, nil) + tarPaths := []string{ + "testdata/cleaned_path.tar", + "testdata/prefixed_path.tar", } - for _, name := range testFiles { - _, err := tfs.Open(name) - if want := fs.ErrInvalid; !errors.Is(err, want) { - t.Errorf("TarFS.Open(%s) error = %v, wantErr %v", name, err, want) - } + + for _, tarPath := range tarPaths { + t.Run(tarPath, func(t *testing.T) { + tfs, err := New(tarPath) + if err != nil { + t.Fatalf("New() error = %v, wantErr %v", err, nil) + } + for _, name := range testFiles { + t.Run(name, func(t *testing.T) { + _, err := tfs.Open(name) + if want := fs.ErrInvalid; !errors.Is(err, want) { + t.Errorf("TarFS.Open(%s) error = %v, wantErr %v", name, err, want) + } + }) + } + }) } } @@ -161,58 +222,84 @@ func TestTarFS_Open_Unsupported(t *testing.T) { "foobar_link", "foobar_symlink", } - tfs, err := New("testdata/test.tar") - if err != nil { - t.Fatalf("New() error = %v, wantErr %v", err, nil) + tarPaths := []string{ + "testdata/cleaned_path.tar", + "testdata/prefixed_path.tar", } - for _, name := range testFiles { - _, err := tfs.Open(name) - if want := errdef.ErrUnsupported; !errors.Is(err, want) { - t.Errorf("TarFS.Open(%s) error = %v, wantErr %v", name, err, want) - } + + for _, tarPath := range tarPaths { + t.Run(tarPath, func(t *testing.T) { + tfs, err := New(tarPath) + if err != nil { + t.Fatalf("New() error = %v, wantErr %v", err, nil) + } + for _, name := range testFiles { + t.Run(name, func(t *testing.T) { + _, err := tfs.Open(name) + if want := errdef.ErrUnsupported; !errors.Is(err, want) { + t.Errorf("TarFS.Open(%s) error = %v, wantErr %v", name, err, want) + } + }) + } + }) } } func TestTarFS_Stat(t *testing.T) { - tfs, err := New("testdata/test.tar") - if err != nil { - t.Fatalf("New() error = %v, wantErr %v", err, nil) + tarPaths := []string{ + "testdata/cleaned_path.tar", + "testdata/prefixed_path.tar", } - name := "foobar" - fi, err := tfs.Stat(name) - if err != nil { - t.Fatal("Stat() error =", err) - } - if got, want := fi.Name(), "foobar"; got != want { - t.Errorf("Stat().want() = %v, want %v", got, want) - } - if got, want := fi.Size(), int64(6); got != want { - t.Errorf("Stat().Size() = %v, want %v", got, want) - } + for _, tarPath := range tarPaths { + t.Run(tarPath, func(t *testing.T) { + tfs, err := New(tarPath) + if err != nil { + t.Fatalf("New() error = %v, wantErr %v", err, nil) + } - name = "dir/hello" - fi, err = tfs.Stat(name) - if err != nil { - t.Fatal("Stat() error =", err) - } - if got, want := fi.Name(), "hello"; got != want { - t.Errorf("Stat().want() = %v, want %v", got, want) - } - if got, want := fi.Size(), int64(5); got != want { - t.Errorf("Stat().Size() = %v, want %v", got, want) - } + name := "foobar" + t.Run(name, func(t *testing.T) { + fi, err := tfs.Stat(name) + if err != nil { + t.Fatal("Stat() error =", err) + } + if got, want := fi.Name(), "foobar"; got != want { + t.Errorf("Stat().want() = %v, want %v", got, want) + } + if got, want := fi.Size(), int64(6); got != want { + t.Errorf("Stat().Size() = %v, want %v", got, want) + } + }) - name = "dir/subdir/world" - fi, err = tfs.Stat(name) - if err != nil { - t.Fatal("Stat() error =", err) - } - if got, want := fi.Name(), "world"; got != want { - t.Errorf("Stat().want() = %v, want %v", got, want) - } - if got, want := fi.Size(), int64(5); got != want { - t.Errorf("Stat().Size() = %v, want %v", got, want) + name = "dir/hello" + t.Run(name, func(t *testing.T) { + fi, err := tfs.Stat(name) + if err != nil { + t.Fatal("Stat() error =", err) + } + if got, want := fi.Name(), "hello"; got != want { + t.Errorf("Stat().want() = %v, want %v", got, want) + } + if got, want := fi.Size(), int64(5); got != want { + t.Errorf("Stat().Size() = %v, want %v", got, want) + } + }) + + name = "dir/subdir/world" + t.Run(name, func(t *testing.T) { + fi, err := tfs.Stat(name) + if err != nil { + t.Fatal("Stat() error =", err) + } + if got, want := fi.Name(), "world"; got != want { + t.Errorf("Stat().want() = %v, want %v", got, want) + } + if got, want := fi.Size(), int64(5); got != want { + t.Errorf("Stat().Size() = %v, want %v", got, want) + } + }) + }) } } @@ -222,49 +309,91 @@ func TestTarFS_Stat_NotExist(t *testing.T) { "subdir/bar", "barfoo", } - tfs, err := New("testdata/test.tar") - if err != nil { - t.Fatalf("New() error = %v, wantErr %v", err, nil) + tarPaths := []string{ + "testdata/cleaned_path.tar", + "testdata/prefixed_path.tar", } - for _, name := range testFiles { - _, err := tfs.Stat(name) - if want := fs.ErrNotExist; !errors.Is(err, want) { - t.Errorf("TarFS.Stat(%s) error = %v, wantErr %v", name, err, want) - } + + for _, tarPath := range tarPaths { + t.Run(tarPath, func(t *testing.T) { + tfs, err := New(tarPath) + if err != nil { + t.Fatalf("New() error = %v, wantErr %v", err, nil) + } + for _, name := range testFiles { + t.Run(name, func(t *testing.T) { + _, err := tfs.Stat(name) + if want := fs.ErrNotExist; !errors.Is(err, want) { + t.Errorf("TarFS.Stat(%s) error = %v, wantErr %v", name, err, want) + } + }) + } + }) } } func TestTarFS_Stat_InvalidPath(t *testing.T) { testFiles := []string{ + "..", + "../outside", + "./dir", "dir/", - "subdir/", "dir/subdir/", + "/absolute/path", + "dir/../invalid", + "dir/./invalid", + "dir//double_slash", + "dir/subdir/../../invalid", } - tfs, err := New("testdata/test.tar") - if err != nil { - t.Fatalf("New() error = %v, wantErr %v", err, nil) + tarPaths := []string{ + "testdata/cleaned_path.tar", + "testdata/prefixed_path.tar", } - for _, name := range testFiles { - _, err := tfs.Stat(name) - if want := fs.ErrInvalid; !errors.Is(err, want) { - t.Errorf("TarFS.Stat(%s) error = %v, wantErr %v", name, err, want) - } + + for _, tarPath := range tarPaths { + t.Run(tarPath, func(t *testing.T) { + tfs, err := New(tarPath) + if err != nil { + t.Fatalf("New() error = %v, wantErr %v", err, nil) + } + for _, name := range testFiles { + t.Run(name, func(t *testing.T) { + _, err := tfs.Stat(name) + if want := fs.ErrInvalid; !errors.Is(err, want) { + t.Errorf("TarFS.Stat(%s) error = %v, wantErr %v", name, err, want) + } + }) + } + }) } } func TestTarFS_Stat_Unsupported(t *testing.T) { testFiles := []string{ + "dir", + "dir/subdir", "foobar_link", "foobar_symlink", } - tfs, err := New("testdata/test.tar") - if err != nil { - t.Fatalf("New() error = %v, wantErr %v", err, nil) + tarPaths := []string{ + "testdata/cleaned_path.tar", + "testdata/prefixed_path.tar", } - for _, name := range testFiles { - _, err := tfs.Stat(name) - if want := errdef.ErrUnsupported; !errors.Is(err, want) { - t.Errorf("TarFS.Stat(%s) error = %v, wantErr %v", name, err, want) - } + + for _, tarPath := range tarPaths { + t.Run(tarPath, func(t *testing.T) { + tfs, err := New(tarPath) + if err != nil { + t.Fatalf("New() error = %v, wantErr %v", err, nil) + } + for _, name := range testFiles { + t.Run(name, func(t *testing.T) { + _, err := tfs.Stat(name) + if want := errdef.ErrUnsupported; !errors.Is(err, want) { + t.Errorf("TarFS.Stat(%s) error = %v, wantErr %v", name, err, want) + } + }) + } + }) } } diff --git a/internal/fs/tarfs/testdata/test.tar b/internal/fs/tarfs/testdata/cleaned_path.tar similarity index 100% rename from internal/fs/tarfs/testdata/test.tar rename to internal/fs/tarfs/testdata/cleaned_path.tar diff --git a/internal/fs/tarfs/testdata/prefixed_path.tar b/internal/fs/tarfs/testdata/prefixed_path.tar new file mode 100644 index 00000000..a3c20730 Binary files /dev/null and b/internal/fs/tarfs/testdata/prefixed_path.tar differ