From dff56286a744d805bf953ada296e6076c335258b Mon Sep 17 00:00:00 2001 From: "Lixia (Sylvia) Lei" Date: Mon, 20 Jan 2025 11:36:50 +0800 Subject: [PATCH] fix: handle dot-prefixed path for tarfs (#875) 1. Handle dot-prefixed paths such as `./index.json` when indexing and reading entries in tarfs 2. Add corresponding tests 3. Refactor original tests Fixes: #851 Signed-off-by: Lixia (Sylvia) Lei --- content/oci/readonlyoci_test.go | 186 ++++---- content/oci/readonlystorage_test.go | 136 ++++-- .../testdata/hello-world-prefixed-path.tar | Bin 0 -> 20480 bytes internal/fs/tarfs/tarfs.go | 6 +- internal/fs/tarfs/tarfs_test.go | 443 +++++++++++------- .../testdata/{test.tar => cleaned_path.tar} | Bin internal/fs/tarfs/testdata/prefixed_path.tar | Bin 0 -> 10240 bytes 7 files changed, 482 insertions(+), 289 deletions(-) create mode 100644 content/oci/testdata/hello-world-prefixed-path.tar rename internal/fs/tarfs/testdata/{test.tar => cleaned_path.tar} (100%) create mode 100644 internal/fs/tarfs/testdata/prefixed_path.tar 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 0000000000000000000000000000000000000000..b2466a42398a61279cbdaaccac065eb3bfd0a633 GIT binary patch literal 20480 zcmeHOc|c8h`%hk5CNWZkoEh<+GhGY=M7+b`cv8zzF zVfbaso~2>NQewz5WGTw~Jr$E>c;`p+n|FTq{L?w#bMAA#&;C50=OlMAsq+H@2T779 z;15jVQ0>3cE$J|UF%m;Cm_!lG1cD))z)kG%I^P+1L6km{(b?IVC>7(Bg5r$I4|_C4`UFVs2eko^!n%Yj}VAa z|9^%0Of9bLlXF5EMo+6Fcaw)IRd!|MU3YA*pe-K%bWc zCMa8;Yo9PHrwH=6`rXQ}_HW;v(R*O;O@+-5&rW5nS69Tj=J%Xyo-E5gZ>>D@XjRPJ zPcwXNPTG$M@t%MFhecTrXAk))KVj>d1e3Kh5<7N2gKddS*tFro(7;idS#uK2Oj9nF z-cjE4|1Rt18pQ=e<-v1zf4=^p;^8luQ740k4_V!+^yrFC`6o9kPWR2fl9sD(tTKyA z8|wF+zISMs{F4u%!Wi{U&+Njs$Bultp+$eM_91S*s)*(s)LC2JaH9rV3b zGA_IO?0refpOUN!#s<54erFk(yXy88uSdy9#kQ!Vs2%b-eJZT+O$}3Z(IIw;_94wT zuF6~Mf81~Sl^>@zwU!5EYimx%k1Q?e>SQzdw}_^OA^xM|&L<81{&dpNE+Jzd$oPyy z9r8Qn*Uu^bweOT!hI6~0xbD!q@2yN(oVcq&V}ogFr-Q0z*RHj+YTY3(Re8L)^TCQa zcbgsEKcnp1r$08H;ZX2%56h$_mC*Knx0>6yn&zlH%w_+g?=%p%=FJnk6ss!urA}Lp zkv+6ue`O{#%~8!gdMSIs!f93$kL^7)@WPd%oz~q?-#cp>F!I{OZO1I|9?dI)62@Jy zik`Ni(bK)>A2Ab8-e{1~E>A_8HcqXK`*q650i)*QNAU(N?w2^`@BiV+Iw4@{=rq!^ zjFZiqop7;`Y2xm1+0msp9AY-D#n6N<7RC3wZn_WA=?P{z_9J@c#LdqvFrAms@#vU~ zL2lbE^K5OyF70SNb7#UM1LQEp`u56fKhv3R9mmWbF`)j?I}VS6d~Vb`yz4*@@6l)O zHh3^{`Tq==6rXWu>EzrS?JOU-JI%YAZ|kd`vctpK|9a?{xu9h6z-9UH zvW-oYCkKQ(9btO~tlwnct=*}+q5C>L(t3_tx<%cl^~NUG{n3Nyfm+l_jw(&-(RpS#tplVy&L zyWi|XNScPwjnVJvHZkJ#+63t6;Psy$tY5g=e@7#;U8_zwANhh^p5;Ds%(LQ)kDycI zZrMU+&q}UM@?7b0$~rya=G_KynT@WV?&+PBQ)(H~&40d^OHyeES;dLCMOFjPd1S8Z z=dph^SL*9gv~TV@y7rah3gYB0UszUa1n7__KQy(FyLl$cW} zeEgH9=UTw`yBC#Dsoyj4gjsQGi(<5LiP&<-&Cu;uP|TJ-3ZKiJa>r{UN0ROZgCDdg z{H()+#fig*T@Lk|A^P0An^*5RTb}-HmwRr5KZ_G(xeqTlZuD)JCti9*r$$-+-;9ah zLv5L+Dp}NX+k#{H9W9r~7S255zR)8pQ1f-M>~`bW8E!R(>v^1*WR>Qo?Q-0zUtZVoD>rtyvHq9fF}ic-I)3&| zxCYt~`}}EMQD++>aDJ;DkJKf5N0j7e%_!}@?!rm4Z*!AM(_7!}JLXY2m#eTn>waNW zd0V$e#WxGK?Jb&B&}&C>zvBnOMqPf~tR$&>s{+3pcbhFSKU!L8yL?~Rwy-;zCQDi! zbqGxF9J*!mk~sDCqEF~C(XE?4bago8aeM<|b+g~(%_n-=E>V4XHS57OU8>Tg+}poV zWx>|prUqO0^9-q9tPM|D`la)NzIUen*1)Rc{TYpHmSumMuFo8uHUbWBxYPS`!Lk_d z#rEdI4lh*Y6$G3zFXmeBy!5z!@a9X$7Q2m!xw_f6i_ZGO;)FJ)*P`7r+xN_IxNRG; zzF9>~-n^U@r*|J)zjd(Q4PA?sI(yy=eFob5`4q zmN}2qDObP#ttBhy%n|{&uxV6mG&~(V?($SMb+T6vP z-?6`CULn@I@nfQ^hv(-B%mK&G_5@{|ExUarrq$Sl=D{yQY2|^-_30(P_vWnOwLXa+ z&TB2o%jb4A59J5XYj)UwV7GLe&ixZF!(M(7-PX3~x@3O80f$({IRE)R<}Djo93JO0 zY}pXO;%fGaHM+$s5194{S@wm+;edeD=`;JMUV5a%23cNH44RAB4nL(3Qhvo{7U6*g z%5smPe~#zO)gyHCFK5rSAHMitlR%%{^XC-JoNN9>dtH`@el_i3NlDqWYqWT*7;uSzp`Wk zws3~S<~5-K*20C&vg!ZeMAqNVM15#T-W3o)+>DcinBKDT0A%Rr-icUzI`Hm7aSbhIW_gvvBNE* zJ_#Stw{!okPHntWH>b22l$^36{C4!05nYcA*f-qjvBk}R6@EC2dCd(p`0r`r;99ue zyTv-U{k{4REFZtCzwgSQyRL6P&~+H1T)SyKmg3vDAc(j!MXcO3WtvY}bg#`fd^-&J z6j`S-Z#b`f%$_E#raLZ7JN`I(h3kN_IVo+19Qo$9W9l6H_3dY`%(DD`xI;>|ud_#& zc@J%pePj+_^ypyLxhQ#zulMRB*Ts8!CsPggoWbY2^mdnxz! z<0kD2*Izl;LvzU|{qcf_m4~0!H%}_7w5c%Q~CMx-Md3 zcxiZe>7pN6$^%0$j@-9#S0{7X6kp$Ku}=+CFE1$@|5b&p=XaY2c=(+?Zs=dp^4c@U z^0JHxmGW}4{Wk7vN-o`xcXW@9kOG> zQn!-iu@#ft?n&No_15K(dL|~%pBuW)^fqz*$fd7D?zQ?~#4!e^SPo_w4i^|0G=)4# z6N~`kIL{#n#iAl4z%UQ0c1A!*f<gvC`{wGMnSpV07 zy5ArfZ)Rs_C)0Cbf{O7Kbb5tGEpxR)oT`>p0v9#NR)~T=QXY*sj@1KF)pR(GT2zDrB2{mbUihC-2EU4A6l(Ae zg^CFk@sLc#l`{Z=CyuA;!=JS_?g!QcW-Km_PtXmHi&D1@h@Y{%kcSu1%b?| zc3`ZXM1uZA7LjR{Or)sMsbsG4GDgRRDIx_fGD-)+zl>4w1O{HC0Sw5L3U$;tnZcMJZc$)XvZf>DFiCX2@3%ONPoAeKjXf)ogWMkt8k2p(bxjzTG%WJN|K zftO(dC5XVk35p~HjuTlPg=i9G;s0|E$@g-Ip@>LR2!Voxp1{H|3o|enD~J?u zHQ;`*Ad1rT025#Yv>G_dLL!Ti6vR_3gi?q|(qI|Jc^o`d_bjOtT0Ma&g+Ip^OQ8hI zA{c{^U@*dQGz#iUgr`8ugu^6IA|%2DNR|RcQ8dbf+k(ikKol4!O~skI=S!>Q{ybYW z1quNW`w)Z`2#n@I%9j*_imZSG6>@^48xTlXsT@E^f#GNjG>H@j;Sfh*e}M(+6nZZD zbw7Kb6>=mCW?C2x!xTnwV0Oh~n7|VR#M8LQ;uufREDP)n<^>YLIFb_xk)uTtLpg}0 z2)ORGrAKLK+@CW+kwrl|qd@mAVz|hGjRUhod6?%w3yXNsi+|Woy}<0^W&JezNHwF9dMywv`)E`El>7a9q+ZZf-{}<@$#7vKB6$UX z5Jg6*7o21-*ECU}b9sGH(1D-UEc!&ozC8EV3hGzsR9$(iIk1VE=e^X?pkb8_?d8+Y z-6cw|b5UwIM(Ltw6>8U7*Do)BzfrYReF;uwAiDNe@e(bUFhQx*NDKWXz!7j&gCVlC zls`cyfuXPns#gGw(4dL~y#+@Bln_N|fGGl~T$IXnPP!|iB0$4o3E^3Xos~tG&J-CluU{H;T1j&N`-c}NCkZeRJ zFp&bVAQ`|lU}uPSMo=FFb;U?moRkAx4?%EWbLl7m8w1h>LK{h-kS z-vr>$yD3UOL4&Rnkto(Y8p48K%3P|YYUj+^)kw6r_s{@4QK1xEaqz7#%FrbE=vU-H zp$!A1k>z2L!9U`9rD@GaO)RyDN%{X>-+$upYxpmW;0R>Q|FuL|XQ%ORbOfN;sF~tP z<_hrP--m#;d&2jlx36^`FQ!*796r; z=3#>&_+$8DohkVpgyk+Tv|V>0;C=p&qObcuiWuwvcPQ;6-U1HZyJk8stNu@rn*Rj& z;JLYzY`CD+_%NYWS+Ac{)Ny%L_oWdCtDxg8D zD?TOve@FihK`8O+{8!5Vh%x`yl|E|I1({Onb%7%S19`@C;_7cj1n+Pz$gKu1dI|eO28-qqXdi+FiOBEf&Uf>{2K)< BN1^}# literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a3c20730b3b75349a1b793319e4bc4a4f32447c2 GIT binary patch literal 10240 zcmeH~eQv@q48?PloS=>Kc^m|SY1Oh-YKOMne#uIwP6(m_t}6BApCY2z;rIMpQ`Lff zskA}}B0VTLHGSr-yad@`HCgY;3q{HWC8T4&SxRv1-?yEVA~v7#&|J<~+P{2q(K}VW zZ#w20$O4!3H`aJ<$)q|VGFC(XISOac|M?JO%a*RJzf1lf^*2d>ok~GS#df1f;FbHI zSNRENd|V`f!THjYGe-qM3 zi2nb0Db6A?Nq&|GUo4YARX?@uZrgF*uIzt5_J0$!&hvk8ssCgC<5Xpc&96THZ$O6I z35@>V#OCeg_ApmZmi_-b|DF7A4H+S6yxi63cW$oy|KEss(|^5kmB7FB|C#@6AS