@@ -59,6 +59,7 @@ import (
5959 extconf "zotregistry.dev/zot/v2/pkg/extensions/config"
6060 "zotregistry.dev/zot/v2/pkg/log"
6161 "zotregistry.dev/zot/v2/pkg/meta"
62+ zreg "zotregistry.dev/zot/v2/pkg/regexp"
6263 "zotregistry.dev/zot/v2/pkg/storage"
6364 storageConstants "zotregistry.dev/zot/v2/pkg/storage/constants"
6465 "zotregistry.dev/zot/v2/pkg/storage/gc"
@@ -7808,6 +7809,142 @@ func TestManifestValidation(t *testing.T) {
78087809 })
78097810}
78107811
7812+ func TestManifestDigestQueryTags (t * testing.T ) {
7813+ Convey ("Manifest PUT with digest ?tag= query parameters" , t , func () {
7814+ port := test .GetFreePort ()
7815+ baseURL := test .GetBaseURL (port )
7816+
7817+ conf := config .New ()
7818+ conf .HTTP .Port = port
7819+
7820+ dir := t .TempDir ()
7821+ ctlr := makeController (conf , dir )
7822+ cm := test .NewControllerManager (ctlr )
7823+ cm .StartServer ()
7824+ time .Sleep (1000 * time .Millisecond )
7825+
7826+ defer cm .StopServer ()
7827+
7828+ repoName := "digest-query-tags"
7829+ img := CreateRandomImage ()
7830+ manifestBytes := img .ManifestDescriptor .Data
7831+ manifestDigest := img .ManifestDescriptor .Digest
7832+
7833+ err := UploadImage (img , baseURL , repoName , "initial" )
7834+ So (err , ShouldBeNil )
7835+
7836+ putManifestByDigest := func (rawQuery string ) * resty.Response {
7837+ t .Helper ()
7838+
7839+ manifestPutURL , perr := url .Parse (baseURL + fmt .Sprintf ("/v2/%s/manifests/%s" , repoName , manifestDigest .String ()))
7840+ So (perr , ShouldBeNil )
7841+ manifestPutURL .RawQuery = rawQuery
7842+
7843+ resp , rerr := resty .R ().
7844+ SetHeader ("Content-Type" , ispec .MediaTypeImageManifest ).
7845+ SetBody (manifestBytes ).
7846+ Put (manifestPutURL .String ())
7847+ So (rerr , ShouldBeNil )
7848+
7849+ return resp
7850+ }
7851+
7852+ Convey ("multiple tag query parameters add tags and return OCI-Tag headers" , func () {
7853+ manifestPutURL , err := url .Parse (baseURL + fmt .Sprintf ("/v2/%s/manifests/%s" , repoName , manifestDigest .String ()))
7854+ So (err , ShouldBeNil )
7855+
7856+ q := manifestPutURL .Query ()
7857+ q .Add ("tag" , "v1.0.0" )
7858+ q .Add ("tag" , "v1.0" )
7859+ q .Add ("tag" , "edge" )
7860+ manifestPutURL .RawQuery = q .Encode ()
7861+
7862+ resp , err := resty .R ().
7863+ SetHeader ("Content-Type" , ispec .MediaTypeImageManifest ).
7864+ SetBody (manifestBytes ).
7865+ Put (manifestPutURL .String ())
7866+ So (err , ShouldBeNil )
7867+ So (resp .StatusCode (), ShouldEqual , http .StatusCreated )
7868+ So (resp .Header ().Get (constants .DistContentDigestKey ), ShouldEqual , manifestDigest .String ())
7869+
7870+ ociTags := resp .Header ().Values (constants .OCITagResponseKey )
7871+ sort .Strings (ociTags )
7872+ So (ociTags , ShouldResemble , []string {"edge" , "v1.0" , "v1.0.0" })
7873+
7874+ for _ , tag := range []string {"v1.0.0" , "v1.0" , "edge" } {
7875+ gresp , gerr := resty .R ().Get (baseURL + fmt .Sprintf ("/v2/%s/manifests/%s" , repoName , tag ))
7876+ So (gerr , ShouldBeNil )
7877+ So (gresp .StatusCode (), ShouldEqual , http .StatusOK )
7878+ }
7879+ })
7880+
7881+ Convey ("tag query with non-digest path reference returns 400" , func () {
7882+ manifestPutURL , err := url .Parse (baseURL + fmt .Sprintf ("/v2/%s/manifests/initial" , repoName ))
7883+ So (err , ShouldBeNil )
7884+ manifestPutURL .RawQuery = "tag=notallowed"
7885+
7886+ resp , err := resty .R ().
7887+ SetHeader ("Content-Type" , ispec .MediaTypeImageManifest ).
7888+ SetBody (manifestBytes ).
7889+ Put (manifestPutURL .String ())
7890+ So (err , ShouldBeNil )
7891+ So (resp .StatusCode (), ShouldEqual , http .StatusBadRequest )
7892+ })
7893+
7894+ Convey ("empty tag query parameter returns 400" , func () {
7895+ resp := putManifestByDigest ("tag=" )
7896+ So (resp .StatusCode (), ShouldEqual , http .StatusBadRequest )
7897+ })
7898+
7899+ Convey ("more than max tag query parameters returns 414" , func () {
7900+ q := url.Values {}
7901+ for i := range constants .MaxManifestDigestQueryTags + 1 {
7902+ q .Add ("tag" , fmt .Sprintf ("t%d" , i ))
7903+ }
7904+
7905+ resp := putManifestByDigest (q .Encode ())
7906+ So (resp .StatusCode (), ShouldEqual , http .StatusRequestURITooLong )
7907+ })
7908+
7909+ Convey ("more than max raw tag parameters returns 414 even when values are duplicates" , func () {
7910+ q := url.Values {}
7911+ for range constants .MaxManifestDigestQueryTags + 1 {
7912+ q .Add ("tag" , "same" )
7913+ }
7914+
7915+ resp := putManifestByDigest (q .Encode ())
7916+ So (resp .StatusCode (), ShouldEqual , http .StatusRequestURITooLong )
7917+ })
7918+
7919+ Convey ("invalid tag query value returns 400" , func () {
7920+ q := url.Values {}
7921+ q .Set ("tag" , "bad/ref" )
7922+
7923+ resp := putManifestByDigest (q .Encode ())
7924+ So (resp .StatusCode (), ShouldEqual , http .StatusBadRequest )
7925+ })
7926+
7927+ Convey ("tag query value longer than distribution-spec max length returns 400" , func () {
7928+ longTag := strings .Repeat ("a" , zreg .TagMaxLen + 1 )
7929+ q := url.Values {}
7930+ q .Set ("tag" , longTag )
7931+
7932+ resp := putManifestByDigest (q .Encode ())
7933+ So (resp .StatusCode (), ShouldEqual , http .StatusBadRequest )
7934+ })
7935+
7936+ Convey ("duplicate tag query values are deduplicated in response headers" , func () {
7937+ q := url.Values {}
7938+ q .Add ("tag" , "dup" )
7939+ q .Add ("tag" , "dup" )
7940+
7941+ resp := putManifestByDigest (q .Encode ())
7942+ So (resp .StatusCode (), ShouldEqual , http .StatusCreated )
7943+ So (resp .Header ().Values (constants .OCITagResponseKey ), ShouldResemble , []string {"dup" })
7944+ })
7945+ })
7946+ }
7947+
78117948func TestArtifactReferences (t * testing.T ) {
78127949 Convey ("Validate Artifact References" , t , func () {
78137950 // start a new server
0 commit comments