oras.land/oras-go/v2@v2.5.1-0.20240520045656-aef90e4d04c4/registry/remote/repository.go (about)

     1  /*
     2  Copyright The ORAS Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7  http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package remote
    17  
    18  import (
    19  	"bytes"
    20  	"context"
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"mime"
    26  	"net/http"
    27  	"slices"
    28  	"strconv"
    29  	"strings"
    30  	"sync"
    31  	"sync/atomic"
    32  
    33  	"github.com/opencontainers/go-digest"
    34  	specs "github.com/opencontainers/image-spec/specs-go"
    35  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    36  	"oras.land/oras-go/v2/content"
    37  	"oras.land/oras-go/v2/errdef"
    38  	"oras.land/oras-go/v2/internal/cas"
    39  	"oras.land/oras-go/v2/internal/httputil"
    40  	"oras.land/oras-go/v2/internal/ioutil"
    41  	"oras.land/oras-go/v2/internal/spec"
    42  	"oras.land/oras-go/v2/internal/syncutil"
    43  	"oras.land/oras-go/v2/registry"
    44  	"oras.land/oras-go/v2/registry/remote/auth"
    45  	"oras.land/oras-go/v2/registry/remote/errcode"
    46  	"oras.land/oras-go/v2/registry/remote/internal/errutil"
    47  )
    48  
    49  const (
    50  	// headerDockerContentDigest is the "Docker-Content-Digest" header.
    51  	// If present on the response, it contains the canonical digest of the
    52  	// uploaded blob.
    53  	//
    54  	// References:
    55  	//   - https://docs.docker.com/registry/spec/api/#digest-header
    56  	//   - https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#pull
    57  	headerDockerContentDigest = "Docker-Content-Digest"
    58  
    59  	// headerOCIFiltersApplied is the "OCI-Filters-Applied" header.
    60  	// If present on the response, it contains a comma-separated list of the
    61  	// applied filters.
    62  	//
    63  	// Reference:
    64  	//   - https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers
    65  	headerOCIFiltersApplied = "OCI-Filters-Applied"
    66  
    67  	// headerOCISubject is the "OCI-Subject" header.
    68  	// If present on the response, it contains the digest of the subject,
    69  	// indicating that Referrers API is supported by the registry.
    70  	headerOCISubject = "OCI-Subject"
    71  )
    72  
    73  // filterTypeArtifactType is the "artifactType" filter applied on the list of
    74  // referrers.
    75  //
    76  // References:
    77  //   - Latest spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers
    78  //   - Compatible spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#listing-referrers
    79  const filterTypeArtifactType = "artifactType"
    80  
    81  // Client is an interface for a HTTP client.
    82  type Client interface {
    83  	// Do sends an HTTP request and returns an HTTP response.
    84  	//
    85  	// Unlike http.RoundTripper, Client can attempt to interpret the response
    86  	// and handle higher-level protocol details such as redirects and
    87  	// authentication.
    88  	//
    89  	// Like http.RoundTripper, Client should not modify the request, and must
    90  	// always close the request body.
    91  	Do(*http.Request) (*http.Response, error)
    92  }
    93  
    94  // Repository is an HTTP client to a remote repository.
    95  type Repository struct {
    96  	// Client is the underlying HTTP client used to access the remote registry.
    97  	// If nil, auth.DefaultClient is used.
    98  	Client Client
    99  
   100  	// Reference references the remote repository.
   101  	Reference registry.Reference
   102  
   103  	// PlainHTTP signals the transport to access the remote repository via HTTP
   104  	// instead of HTTPS.
   105  	PlainHTTP bool
   106  
   107  	// ManifestMediaTypes is used in `Accept` header for resolving manifests
   108  	// from references. It is also used in identifying manifests and blobs from
   109  	// descriptors. If an empty list is present, default manifest media types
   110  	// are used.
   111  	ManifestMediaTypes []string
   112  
   113  	// TagListPageSize specifies the page size when invoking the tag list API.
   114  	// If zero, the page size is determined by the remote registry.
   115  	// Reference: https://docs.docker.com/registry/spec/api/#tags
   116  	TagListPageSize int
   117  
   118  	// ReferrerListPageSize specifies the page size when invoking the Referrers
   119  	// API.
   120  	// If zero, the page size is determined by the remote registry.
   121  	// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers
   122  	ReferrerListPageSize int
   123  
   124  	// MaxMetadataBytes specifies a limit on how many response bytes are allowed
   125  	// in the server's response to the metadata APIs, such as catalog list, tag
   126  	// list, and referrers list.
   127  	// If less than or equal to zero, a default (currently 4MiB) is used.
   128  	MaxMetadataBytes int64
   129  
   130  	// SkipReferrersGC specifies whether to delete the dangling referrers
   131  	// index when referrers tag schema is utilized.
   132  	//  - If false, the old referrers index will be deleted after the new one
   133  	//    is successfully uploaded.
   134  	//  - If true, the old referrers index is kept.
   135  	// By default, it is disabled (set to false). See also:
   136  	//  - https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#referrers-tag-schema
   137  	//  - https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#pushing-manifests-with-subject
   138  	//  - https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#deleting-manifests
   139  	SkipReferrersGC bool
   140  
   141  	// HandleWarning handles the warning returned by the remote server.
   142  	// Callers SHOULD deduplicate warnings from multiple associated responses.
   143  	//
   144  	// References:
   145  	//   - https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#warnings
   146  	//   - https://www.rfc-editor.org/rfc/rfc7234#section-5.5
   147  	HandleWarning func(warning Warning)
   148  
   149  	// NOTE: Must keep fields in sync with clone().
   150  
   151  	// referrersState represents that if the repository supports Referrers API.
   152  	// default: referrersStateUnknown
   153  	referrersState referrersState
   154  
   155  	// referrersPingLock locks the pingReferrers() method and allows only
   156  	// one go-routine to send the request.
   157  	referrersPingLock sync.Mutex
   158  
   159  	// referrersMergePool provides a way to manage concurrent updates to a
   160  	// referrers index tagged by referrers tag schema.
   161  	referrersMergePool syncutil.Pool[syncutil.Merge[referrerChange]]
   162  }
   163  
   164  // NewRepository creates a client to the remote repository identified by a
   165  // reference.
   166  // Example: localhost:5000/hello-world
   167  func NewRepository(reference string) (*Repository, error) {
   168  	ref, err := registry.ParseReference(reference)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  	return &Repository{
   173  		Reference: ref,
   174  	}, nil
   175  }
   176  
   177  // newRepositoryWithOptions returns a Repository with the given Reference and
   178  // RepositoryOptions.
   179  //
   180  // RepositoryOptions are part of the Registry struct and set its defaults.
   181  // RepositoryOptions shares the same struct definition as Repository, which
   182  // contains unexported state that must not be copied to multiple Repositories.
   183  // To handle this we explicitly copy only the fields that we want to reproduce.
   184  func newRepositoryWithOptions(ref registry.Reference, opts *RepositoryOptions) (*Repository, error) {
   185  	if err := ref.ValidateRepository(); err != nil {
   186  		return nil, err
   187  	}
   188  	repo := (*Repository)(opts).clone()
   189  	repo.Reference = ref
   190  	return repo, nil
   191  }
   192  
   193  // clone makes a copy of the Repository being careful not to copy non-copyable fields (sync.Mutex and syncutil.Pool types)
   194  func (r *Repository) clone() *Repository {
   195  	return &Repository{
   196  		Client:               r.Client,
   197  		Reference:            r.Reference,
   198  		PlainHTTP:            r.PlainHTTP,
   199  		ManifestMediaTypes:   slices.Clone(r.ManifestMediaTypes),
   200  		TagListPageSize:      r.TagListPageSize,
   201  		ReferrerListPageSize: r.ReferrerListPageSize,
   202  		MaxMetadataBytes:     r.MaxMetadataBytes,
   203  		SkipReferrersGC:      r.SkipReferrersGC,
   204  		HandleWarning:        r.HandleWarning,
   205  	}
   206  }
   207  
   208  // SetReferrersCapability indicates the Referrers API capability of the remote
   209  // repository. true: capable; false: not capable.
   210  //
   211  // SetReferrersCapability is valid only when it is called for the first time.
   212  // SetReferrersCapability returns ErrReferrersCapabilityAlreadySet if the
   213  // Referrers API capability has been already set.
   214  //   - When the capability is set to true, the Referrers() function will always
   215  //     request the Referrers API. Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers
   216  //   - When the capability is set to false, the Referrers() function will always
   217  //     request the Referrers Tag. Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#referrers-tag-schema
   218  //   - When the capability is not set, the Referrers() function will automatically
   219  //     determine which API to use.
   220  func (r *Repository) SetReferrersCapability(capable bool) error {
   221  	var state referrersState
   222  	if capable {
   223  		state = referrersStateSupported
   224  	} else {
   225  		state = referrersStateUnsupported
   226  	}
   227  	if swapped := atomic.CompareAndSwapInt32(&r.referrersState, referrersStateUnknown, state); !swapped {
   228  		if fact := r.loadReferrersState(); fact != state {
   229  			return fmt.Errorf("%w: current capability = %v, new capability = %v",
   230  				ErrReferrersCapabilityAlreadySet,
   231  				fact == referrersStateSupported,
   232  				capable)
   233  		}
   234  	}
   235  	return nil
   236  }
   237  
   238  // setReferrersState atomically loads r.referrersState.
   239  func (r *Repository) loadReferrersState() referrersState {
   240  	return atomic.LoadInt32(&r.referrersState)
   241  }
   242  
   243  // client returns an HTTP client used to access the remote repository.
   244  // A default HTTP client is return if the client is not configured.
   245  func (r *Repository) client() Client {
   246  	if r.Client == nil {
   247  		return auth.DefaultClient
   248  	}
   249  	return r.Client
   250  }
   251  
   252  // do sends an HTTP request and returns an HTTP response using the HTTP client
   253  // returned by r.client().
   254  func (r *Repository) do(req *http.Request) (*http.Response, error) {
   255  	if r.HandleWarning == nil {
   256  		return r.client().Do(req)
   257  	}
   258  
   259  	resp, err := r.client().Do(req)
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  	handleWarningHeaders(resp.Header.Values(headerWarning), r.HandleWarning)
   264  	return resp, nil
   265  }
   266  
   267  // blobStore detects the blob store for the given descriptor.
   268  func (r *Repository) blobStore(desc ocispec.Descriptor) registry.BlobStore {
   269  	if isManifest(r.ManifestMediaTypes, desc) {
   270  		return r.Manifests()
   271  	}
   272  	return r.Blobs()
   273  }
   274  
   275  // Fetch fetches the content identified by the descriptor.
   276  func (r *Repository) Fetch(ctx context.Context, target ocispec.Descriptor) (io.ReadCloser, error) {
   277  	return r.blobStore(target).Fetch(ctx, target)
   278  }
   279  
   280  // Push pushes the content, matching the expected descriptor.
   281  func (r *Repository) Push(ctx context.Context, expected ocispec.Descriptor, content io.Reader) error {
   282  	return r.blobStore(expected).Push(ctx, expected, content)
   283  }
   284  
   285  // Mount makes the blob with the given digest in fromRepo
   286  // available in the repository signified by the receiver.
   287  //
   288  // This avoids the need to pull content down from fromRepo only to push it to r.
   289  //
   290  // If the registry does not implement mounting, getContent will be used to get the
   291  // content to push. If getContent is nil, the content will be pulled from the source
   292  // repository. If getContent returns an error, it will be wrapped inside the error
   293  // returned from Mount.
   294  func (r *Repository) Mount(ctx context.Context, desc ocispec.Descriptor, fromRepo string, getContent func() (io.ReadCloser, error)) error {
   295  	return r.Blobs().(registry.Mounter).Mount(ctx, desc, fromRepo, getContent)
   296  }
   297  
   298  // Exists returns true if the described content exists.
   299  func (r *Repository) Exists(ctx context.Context, target ocispec.Descriptor) (bool, error) {
   300  	return r.blobStore(target).Exists(ctx, target)
   301  }
   302  
   303  // Delete removes the content identified by the descriptor.
   304  func (r *Repository) Delete(ctx context.Context, target ocispec.Descriptor) error {
   305  	return r.blobStore(target).Delete(ctx, target)
   306  }
   307  
   308  // Blobs provides access to the blob CAS only, which contains config blobs,
   309  // layers, and other generic blobs.
   310  func (r *Repository) Blobs() registry.BlobStore {
   311  	return &blobStore{repo: r}
   312  }
   313  
   314  // Manifests provides access to the manifest CAS only.
   315  func (r *Repository) Manifests() registry.ManifestStore {
   316  	return &manifestStore{repo: r}
   317  }
   318  
   319  // Resolve resolves a reference to a manifest descriptor.
   320  // See also `ManifestMediaTypes`.
   321  func (r *Repository) Resolve(ctx context.Context, reference string) (ocispec.Descriptor, error) {
   322  	return r.Manifests().Resolve(ctx, reference)
   323  }
   324  
   325  // Tag tags a manifest descriptor with a reference string.
   326  func (r *Repository) Tag(ctx context.Context, desc ocispec.Descriptor, reference string) error {
   327  	return r.Manifests().Tag(ctx, desc, reference)
   328  }
   329  
   330  // PushReference pushes the manifest with a reference tag.
   331  func (r *Repository) PushReference(ctx context.Context, expected ocispec.Descriptor, content io.Reader, reference string) error {
   332  	return r.Manifests().PushReference(ctx, expected, content, reference)
   333  }
   334  
   335  // FetchReference fetches the manifest identified by the reference.
   336  // The reference can be a tag or digest.
   337  func (r *Repository) FetchReference(ctx context.Context, reference string) (ocispec.Descriptor, io.ReadCloser, error) {
   338  	return r.Manifests().FetchReference(ctx, reference)
   339  }
   340  
   341  // ParseReference resolves a tag or a digest reference to a fully qualified
   342  // reference from a base reference r.Reference.
   343  // Tag, digest, or fully qualified references are accepted as input.
   344  //
   345  // If reference is a fully qualified reference, then ParseReference parses it
   346  // and returns the parsed reference. If the parsed reference does not share
   347  // the same base reference with the Repository r, ParseReference returns a
   348  // wrapped error ErrInvalidReference.
   349  func (r *Repository) ParseReference(reference string) (registry.Reference, error) {
   350  	ref, err := registry.ParseReference(reference)
   351  	if err != nil {
   352  		ref = registry.Reference{
   353  			Registry:   r.Reference.Registry,
   354  			Repository: r.Reference.Repository,
   355  			Reference:  reference,
   356  		}
   357  
   358  		// reference is not a FQDN
   359  		if index := strings.IndexByte(reference, '@'); index != -1 {
   360  			// `@` implies *digest*, so drop the *tag* (irrespective of what it is).
   361  			ref.Reference = reference[index+1:]
   362  			err = ref.ValidateReferenceAsDigest()
   363  		} else {
   364  			err = ref.ValidateReference()
   365  		}
   366  
   367  		if err != nil {
   368  			return registry.Reference{}, err
   369  		}
   370  	} else if ref.Registry != r.Reference.Registry || ref.Repository != r.Reference.Repository {
   371  		return registry.Reference{}, fmt.Errorf(
   372  			"%w: mismatch between received %q and expected %q",
   373  			errdef.ErrInvalidReference, ref, r.Reference,
   374  		)
   375  	}
   376  
   377  	if len(ref.Reference) == 0 {
   378  		return registry.Reference{}, errdef.ErrInvalidReference
   379  	}
   380  
   381  	return ref, nil
   382  }
   383  
   384  // Tags lists the tags available in the repository.
   385  // See also `TagListPageSize`.
   386  // If `last` is NOT empty, the entries in the response start after the
   387  // tag specified by `last`. Otherwise, the response starts from the top
   388  // of the Tags list.
   389  //
   390  // References:
   391  //   - https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#content-discovery
   392  //   - https://docs.docker.com/registry/spec/api/#tags
   393  func (r *Repository) Tags(ctx context.Context, last string, fn func(tags []string) error) error {
   394  	ctx = auth.AppendRepositoryScope(ctx, r.Reference, auth.ActionPull)
   395  	url := buildRepositoryTagListURL(r.PlainHTTP, r.Reference)
   396  	var err error
   397  	for err == nil {
   398  		url, err = r.tags(ctx, last, fn, url)
   399  		// clear `last` for subsequent pages
   400  		last = ""
   401  	}
   402  	if err != errNoLink {
   403  		return err
   404  	}
   405  	return nil
   406  }
   407  
   408  // tags returns a single page of tag list with the next link.
   409  func (r *Repository) tags(ctx context.Context, last string, fn func(tags []string) error, url string) (string, error) {
   410  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
   411  	if err != nil {
   412  		return "", err
   413  	}
   414  	if r.TagListPageSize > 0 || last != "" {
   415  		q := req.URL.Query()
   416  		if r.TagListPageSize > 0 {
   417  			q.Set("n", strconv.Itoa(r.TagListPageSize))
   418  		}
   419  		if last != "" {
   420  			q.Set("last", last)
   421  		}
   422  		req.URL.RawQuery = q.Encode()
   423  	}
   424  	resp, err := r.do(req)
   425  	if err != nil {
   426  		return "", err
   427  	}
   428  	defer resp.Body.Close()
   429  
   430  	if resp.StatusCode != http.StatusOK {
   431  		return "", errutil.ParseErrorResponse(resp)
   432  	}
   433  	var page struct {
   434  		Tags []string `json:"tags"`
   435  	}
   436  	lr := limitReader(resp.Body, r.MaxMetadataBytes)
   437  	if err := json.NewDecoder(lr).Decode(&page); err != nil {
   438  		return "", fmt.Errorf("%s %q: failed to decode response: %w", resp.Request.Method, resp.Request.URL, err)
   439  	}
   440  	if err := fn(page.Tags); err != nil {
   441  		return "", err
   442  	}
   443  
   444  	return parseLink(resp)
   445  }
   446  
   447  // Predecessors returns the descriptors of image or artifact manifests directly
   448  // referencing the given manifest descriptor.
   449  // Predecessors internally leverages Referrers.
   450  // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers
   451  func (r *Repository) Predecessors(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   452  	var res []ocispec.Descriptor
   453  	if err := r.Referrers(ctx, desc, "", func(referrers []ocispec.Descriptor) error {
   454  		res = append(res, referrers...)
   455  		return nil
   456  	}); err != nil {
   457  		return nil, err
   458  	}
   459  	return res, nil
   460  }
   461  
   462  // Referrers lists the descriptors of image or artifact manifests directly
   463  // referencing the given manifest descriptor.
   464  //
   465  // fn is called for each page of the referrers result.
   466  // If artifactType is not empty, only referrers of the same artifact type are
   467  // fed to fn.
   468  //
   469  // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers
   470  func (r *Repository) Referrers(ctx context.Context, desc ocispec.Descriptor, artifactType string, fn func(referrers []ocispec.Descriptor) error) error {
   471  	state := r.loadReferrersState()
   472  	if state == referrersStateUnsupported {
   473  		// The repository is known to not support Referrers API, fallback to
   474  		// referrers tag schema.
   475  		return r.referrersByTagSchema(ctx, desc, artifactType, fn)
   476  	}
   477  
   478  	err := r.referrersByAPI(ctx, desc, artifactType, fn)
   479  	if state == referrersStateSupported {
   480  		// The repository is known to support Referrers API, no fallback.
   481  		return err
   482  	}
   483  
   484  	// The referrers state is unknown.
   485  	if err != nil {
   486  		if errors.Is(err, errdef.ErrUnsupported) {
   487  			// Referrers API is not supported, fallback to referrers tag schema.
   488  			r.SetReferrersCapability(false)
   489  			return r.referrersByTagSchema(ctx, desc, artifactType, fn)
   490  		}
   491  		return err
   492  	}
   493  
   494  	r.SetReferrersCapability(true)
   495  	return nil
   496  }
   497  
   498  // referrersByAPI lists the descriptors of manifests directly referencing
   499  // the given manifest descriptor by requesting Referrers API.
   500  // fn is called for the referrers result. If artifactType is not empty,
   501  // only referrers of the same artifact type are fed to fn.
   502  func (r *Repository) referrersByAPI(ctx context.Context, desc ocispec.Descriptor, artifactType string, fn func(referrers []ocispec.Descriptor) error) error {
   503  	ref := r.Reference
   504  	ref.Reference = desc.Digest.String()
   505  	ctx = auth.AppendRepositoryScope(ctx, ref, auth.ActionPull)
   506  
   507  	url := buildReferrersURL(r.PlainHTTP, ref, artifactType)
   508  	var err error
   509  	for err == nil {
   510  		url, err = r.referrersPageByAPI(ctx, artifactType, fn, url)
   511  	}
   512  	if err == errNoLink {
   513  		return nil
   514  	}
   515  	return err
   516  }
   517  
   518  // referrersPageByAPI lists a single page of the descriptors of manifests
   519  // directly referencing the given manifest descriptor. fn is called for
   520  // a page of referrersPageByAPI result.
   521  // If artifactType is not empty, only referrersPageByAPI of the same
   522  // artifact type are fed to fn.
   523  // referrersPageByAPI returns the link url for the next page.
   524  func (r *Repository) referrersPageByAPI(ctx context.Context, artifactType string, fn func(referrers []ocispec.Descriptor) error, url string) (string, error) {
   525  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
   526  	if err != nil {
   527  		return "", err
   528  	}
   529  	if r.ReferrerListPageSize > 0 {
   530  		q := req.URL.Query()
   531  		q.Set("n", strconv.Itoa(r.ReferrerListPageSize))
   532  		req.URL.RawQuery = q.Encode()
   533  	}
   534  
   535  	resp, err := r.do(req)
   536  	if err != nil {
   537  		return "", err
   538  	}
   539  	defer resp.Body.Close()
   540  
   541  	switch resp.StatusCode {
   542  	case http.StatusOK:
   543  	case http.StatusNotFound:
   544  		if errResp := errutil.ParseErrorResponse(resp); errutil.IsErrorCode(errResp, errcode.ErrorCodeNameUnknown) {
   545  			// The repository is not found, Referrers API status is unknown
   546  			return "", errResp
   547  		}
   548  		// Referrers API is not supported.
   549  		return "", fmt.Errorf("failed to query referrers API: %w", errdef.ErrUnsupported)
   550  	default:
   551  		return "", errutil.ParseErrorResponse(resp)
   552  	}
   553  
   554  	// also check the content type
   555  	if ct := resp.Header.Get("Content-Type"); ct != ocispec.MediaTypeImageIndex {
   556  		return "", fmt.Errorf("unknown content returned (%s), expecting image index: %w", ct, errdef.ErrUnsupported)
   557  	}
   558  
   559  	var index ocispec.Index
   560  	lr := limitReader(resp.Body, r.MaxMetadataBytes)
   561  	if err := json.NewDecoder(lr).Decode(&index); err != nil {
   562  		return "", fmt.Errorf("%s %q: failed to decode response: %w", resp.Request.Method, resp.Request.URL, err)
   563  	}
   564  
   565  	referrers := index.Manifests
   566  	if artifactType != "" {
   567  		// check both filters header and filters annotations for compatibility
   568  		// latest spec for filters header: https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers
   569  		// older spec for filters annotations: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#listing-referrers
   570  		filtersHeader := resp.Header.Get(headerOCIFiltersApplied)
   571  		filtersAnnotation := index.Annotations[spec.AnnotationReferrersFiltersApplied]
   572  		if !isReferrersFilterApplied(filtersHeader, filterTypeArtifactType) &&
   573  			!isReferrersFilterApplied(filtersAnnotation, filterTypeArtifactType) {
   574  			// perform client side filtering if the filter is not applied on the server side
   575  			referrers = filterReferrers(referrers, artifactType)
   576  		}
   577  	}
   578  	if len(referrers) > 0 {
   579  		if err := fn(referrers); err != nil {
   580  			return "", err
   581  		}
   582  	}
   583  	return parseLink(resp)
   584  }
   585  
   586  // referrersByTagSchema lists the descriptors of manifests directly
   587  // referencing the given manifest descriptor by requesting referrers tag.
   588  // fn is called for the referrers result. If artifactType is not empty,
   589  // only referrers of the same artifact type are fed to fn.
   590  // reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#backwards-compatibility
   591  func (r *Repository) referrersByTagSchema(ctx context.Context, desc ocispec.Descriptor, artifactType string, fn func(referrers []ocispec.Descriptor) error) error {
   592  	referrersTag := buildReferrersTag(desc)
   593  	_, referrers, err := r.referrersFromIndex(ctx, referrersTag)
   594  	if err != nil {
   595  		if errors.Is(err, errdef.ErrNotFound) {
   596  			// no referrers to the manifest
   597  			return nil
   598  		}
   599  		return err
   600  	}
   601  
   602  	filtered := filterReferrers(referrers, artifactType)
   603  	if len(filtered) == 0 {
   604  		return nil
   605  	}
   606  	return fn(filtered)
   607  }
   608  
   609  // referrersFromIndex queries the referrers index using the the given referrers
   610  // tag. If Succeeded, returns the descriptor of referrers index and the
   611  // referrers list.
   612  func (r *Repository) referrersFromIndex(ctx context.Context, referrersTag string) (ocispec.Descriptor, []ocispec.Descriptor, error) {
   613  	desc, rc, err := r.FetchReference(ctx, referrersTag)
   614  	if err != nil {
   615  		return ocispec.Descriptor{}, nil, err
   616  	}
   617  	defer rc.Close()
   618  
   619  	if err := limitSize(desc, r.MaxMetadataBytes); err != nil {
   620  		return ocispec.Descriptor{}, nil, fmt.Errorf("failed to read referrers index from referrers tag %s: %w", referrersTag, err)
   621  	}
   622  	var index ocispec.Index
   623  	if err := decodeJSON(rc, desc, &index); err != nil {
   624  		return ocispec.Descriptor{}, nil, fmt.Errorf("failed to decode referrers index from referrers tag %s: %w", referrersTag, err)
   625  	}
   626  
   627  	return desc, index.Manifests, nil
   628  }
   629  
   630  // pingReferrers returns true if the Referrers API is available for r.
   631  func (r *Repository) pingReferrers(ctx context.Context) (bool, error) {
   632  	switch r.loadReferrersState() {
   633  	case referrersStateSupported:
   634  		return true, nil
   635  	case referrersStateUnsupported:
   636  		return false, nil
   637  	}
   638  
   639  	// referrers state is unknown
   640  	// limit the rate of pinging referrers API
   641  	r.referrersPingLock.Lock()
   642  	defer r.referrersPingLock.Unlock()
   643  
   644  	switch r.loadReferrersState() {
   645  	case referrersStateSupported:
   646  		return true, nil
   647  	case referrersStateUnsupported:
   648  		return false, nil
   649  	}
   650  
   651  	ref := r.Reference
   652  	ref.Reference = zeroDigest
   653  	ctx = auth.AppendRepositoryScope(ctx, ref, auth.ActionPull)
   654  
   655  	url := buildReferrersURL(r.PlainHTTP, ref, "")
   656  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
   657  	if err != nil {
   658  		return false, err
   659  	}
   660  	resp, err := r.do(req)
   661  	if err != nil {
   662  		return false, err
   663  	}
   664  	defer resp.Body.Close()
   665  
   666  	switch resp.StatusCode {
   667  	case http.StatusOK:
   668  		supported := resp.Header.Get("Content-Type") == ocispec.MediaTypeImageIndex
   669  		r.SetReferrersCapability(supported)
   670  		return supported, nil
   671  	case http.StatusNotFound:
   672  		if err := errutil.ParseErrorResponse(resp); errutil.IsErrorCode(err, errcode.ErrorCodeNameUnknown) {
   673  			// repository not found
   674  			return false, err
   675  		}
   676  		r.SetReferrersCapability(false)
   677  		return false, nil
   678  	default:
   679  		return false, errutil.ParseErrorResponse(resp)
   680  	}
   681  }
   682  
   683  // delete removes the content identified by the descriptor in the entity "blobs"
   684  // or "manifests".
   685  func (r *Repository) delete(ctx context.Context, target ocispec.Descriptor, isManifest bool) error {
   686  	ref := r.Reference
   687  	ref.Reference = target.Digest.String()
   688  	ctx = auth.AppendRepositoryScope(ctx, ref, auth.ActionDelete)
   689  	buildURL := buildRepositoryBlobURL
   690  	if isManifest {
   691  		buildURL = buildRepositoryManifestURL
   692  	}
   693  	url := buildURL(r.PlainHTTP, ref)
   694  	req, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, nil)
   695  	if err != nil {
   696  		return err
   697  	}
   698  
   699  	resp, err := r.do(req)
   700  	if err != nil {
   701  		return err
   702  	}
   703  	defer resp.Body.Close()
   704  
   705  	switch resp.StatusCode {
   706  	case http.StatusAccepted:
   707  		return verifyContentDigest(resp, target.Digest)
   708  	case http.StatusNotFound:
   709  		return fmt.Errorf("%s: %w", target.Digest, errdef.ErrNotFound)
   710  	default:
   711  		return errutil.ParseErrorResponse(resp)
   712  	}
   713  }
   714  
   715  // blobStore accesses the blob part of the repository.
   716  type blobStore struct {
   717  	repo *Repository
   718  }
   719  
   720  // Fetch fetches the content identified by the descriptor.
   721  func (s *blobStore) Fetch(ctx context.Context, target ocispec.Descriptor) (rc io.ReadCloser, err error) {
   722  	ref := s.repo.Reference
   723  	ref.Reference = target.Digest.String()
   724  	ctx = auth.AppendRepositoryScope(ctx, ref, auth.ActionPull)
   725  	url := buildRepositoryBlobURL(s.repo.PlainHTTP, ref)
   726  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
   727  	if err != nil {
   728  		return nil, err
   729  	}
   730  
   731  	resp, err := s.repo.do(req)
   732  	if err != nil {
   733  		return nil, err
   734  	}
   735  	defer func() {
   736  		if err != nil {
   737  			resp.Body.Close()
   738  		}
   739  	}()
   740  
   741  	switch resp.StatusCode {
   742  	case http.StatusOK: // server does not support seek as `Range` was ignored.
   743  		if size := resp.ContentLength; size != -1 && size != target.Size {
   744  			return nil, fmt.Errorf("%s %q: mismatch Content-Length", resp.Request.Method, resp.Request.URL)
   745  		}
   746  
   747  		// check server range request capability.
   748  		// Docker spec allows range header form of "Range: bytes=<start>-<end>".
   749  		// However, the remote server may still not RFC 7233 compliant.
   750  		// Reference: https://docs.docker.com/registry/spec/api/#blob
   751  		if rangeUnit := resp.Header.Get("Accept-Ranges"); rangeUnit == "bytes" {
   752  			return httputil.NewReadSeekCloser(s.repo.client(), req, resp.Body, target.Size), nil
   753  		}
   754  		return resp.Body, nil
   755  	case http.StatusNotFound:
   756  		return nil, fmt.Errorf("%s: %w", target.Digest, errdef.ErrNotFound)
   757  	default:
   758  		return nil, errutil.ParseErrorResponse(resp)
   759  	}
   760  }
   761  
   762  // Mount mounts the given descriptor from fromRepo into s.
   763  func (s *blobStore) Mount(ctx context.Context, desc ocispec.Descriptor, fromRepo string, getContent func() (io.ReadCloser, error)) error {
   764  	// pushing usually requires both pull and push actions.
   765  	// Reference: https://github.com/distribution/distribution/blob/v2.7.1/registry/handlers/app.go#L921-L930
   766  	ctx = auth.AppendRepositoryScope(ctx, s.repo.Reference, auth.ActionPull, auth.ActionPush)
   767  
   768  	// We also need pull access to the source repo.
   769  	fromRef := s.repo.Reference
   770  	fromRef.Repository = fromRepo
   771  	ctx = auth.AppendRepositoryScope(ctx, fromRef, auth.ActionPull)
   772  
   773  	url := buildRepositoryBlobMountURL(s.repo.PlainHTTP, s.repo.Reference, desc.Digest, fromRepo)
   774  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)
   775  	if err != nil {
   776  		return err
   777  	}
   778  	resp, err := s.repo.do(req)
   779  	if err != nil {
   780  		return err
   781  	}
   782  	if resp.StatusCode == http.StatusCreated {
   783  		defer resp.Body.Close()
   784  		// Check the server seems to be behaving.
   785  		return verifyContentDigest(resp, desc.Digest)
   786  	}
   787  	if resp.StatusCode != http.StatusAccepted {
   788  		defer resp.Body.Close()
   789  		return errutil.ParseErrorResponse(resp)
   790  	}
   791  	resp.Body.Close()
   792  	// From the [spec]:
   793  	//
   794  	// "If a registry does not support cross-repository mounting
   795  	// or is unable to mount the requested blob,
   796  	// it SHOULD return a 202.
   797  	// This indicates that the upload session has begun
   798  	// and that the client MAY proceed with the upload."
   799  	//
   800  	// So we need to get the content from somewhere in order to
   801  	// push it. If the caller has provided a getContent function, we
   802  	// can use that, otherwise pull the content from the source repository.
   803  	//
   804  	// [spec]: https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#mounting-a-blob-from-another-repository
   805  
   806  	var r io.ReadCloser
   807  	if getContent != nil {
   808  		r, err = getContent()
   809  	} else {
   810  		r, err = s.sibling(fromRepo).Fetch(ctx, desc)
   811  	}
   812  	if err != nil {
   813  		return fmt.Errorf("cannot read source blob: %w", err)
   814  	}
   815  	defer r.Close()
   816  	return s.completePushAfterInitialPost(ctx, req, resp, desc, r)
   817  }
   818  
   819  // sibling returns a blob store for another repository in the same
   820  // registry.
   821  func (s *blobStore) sibling(otherRepoName string) *blobStore {
   822  	otherRepo := s.repo.clone()
   823  	otherRepo.Reference.Repository = otherRepoName
   824  	return &blobStore{
   825  		repo: otherRepo,
   826  	}
   827  }
   828  
   829  // Push pushes the content, matching the expected descriptor.
   830  // Existing content is not checked by Push() to minimize the number of out-going
   831  // requests.
   832  // Push is done by conventional 2-step monolithic upload instead of a single
   833  // `POST` request for better overall performance. It also allows early fail on
   834  // authentication errors.
   835  //
   836  // References:
   837  //   - https://docs.docker.com/registry/spec/api/#pushing-an-image
   838  //   - https://docs.docker.com/registry/spec/api/#initiate-blob-upload
   839  //   - https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#pushing-a-blob-monolithically
   840  func (s *blobStore) Push(ctx context.Context, expected ocispec.Descriptor, content io.Reader) error {
   841  	// start an upload
   842  	// pushing usually requires both pull and push actions.
   843  	// Reference: https://github.com/distribution/distribution/blob/v2.7.1/registry/handlers/app.go#L921-L930
   844  	ctx = auth.AppendRepositoryScope(ctx, s.repo.Reference, auth.ActionPull, auth.ActionPush)
   845  	url := buildRepositoryBlobUploadURL(s.repo.PlainHTTP, s.repo.Reference)
   846  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)
   847  	if err != nil {
   848  		return err
   849  	}
   850  
   851  	resp, err := s.repo.do(req)
   852  	if err != nil {
   853  		return err
   854  	}
   855  
   856  	if resp.StatusCode != http.StatusAccepted {
   857  		defer resp.Body.Close()
   858  		return errutil.ParseErrorResponse(resp)
   859  	}
   860  	resp.Body.Close()
   861  	return s.completePushAfterInitialPost(ctx, req, resp, expected, content)
   862  }
   863  
   864  // completePushAfterInitialPost implements step 2 of the push protocol. This can be invoked either by
   865  // Push or by Mount when the receiving repository does not implement the
   866  // mount endpoint.
   867  func (s *blobStore) completePushAfterInitialPost(ctx context.Context, req *http.Request, resp *http.Response, expected ocispec.Descriptor, content io.Reader) error {
   868  	reqHostname := req.URL.Hostname()
   869  	reqPort := req.URL.Port()
   870  	// monolithic upload
   871  	location, err := resp.Location()
   872  	if err != nil {
   873  		return err
   874  	}
   875  	// work-around solution for https://github.com/oras-project/oras-go/issues/177
   876  	// For some registries, if the port 443 is explicitly set to the hostname
   877  	// like registry.wabbit-networks.io:443/myrepo, blob push will fail since
   878  	// the hostname of the Location header in the response is set to
   879  	// registry.wabbit-networks.io instead of registry.wabbit-networks.io:443.
   880  	locationHostname := location.Hostname()
   881  	locationPort := location.Port()
   882  	// if location port 443 is missing, add it back
   883  	if reqPort == "443" && locationHostname == reqHostname && locationPort == "" {
   884  		location.Host = locationHostname + ":" + reqPort
   885  	}
   886  	url := location.String()
   887  	req, err = http.NewRequestWithContext(ctx, http.MethodPut, url, content)
   888  	if err != nil {
   889  		return err
   890  	}
   891  	if req.GetBody != nil && req.ContentLength != expected.Size {
   892  		// short circuit a size mismatch for built-in types.
   893  		return fmt.Errorf("mismatch content length %d: expect %d", req.ContentLength, expected.Size)
   894  	}
   895  	req.ContentLength = expected.Size
   896  	// the expected media type is ignored as in the API doc.
   897  	req.Header.Set("Content-Type", "application/octet-stream")
   898  	q := req.URL.Query()
   899  	q.Set("digest", expected.Digest.String())
   900  	req.URL.RawQuery = q.Encode()
   901  
   902  	// reuse credential from previous POST request
   903  	if auth := resp.Request.Header.Get("Authorization"); auth != "" {
   904  		req.Header.Set("Authorization", auth)
   905  	}
   906  	resp, err = s.repo.do(req)
   907  	if err != nil {
   908  		return err
   909  	}
   910  	defer resp.Body.Close()
   911  
   912  	if resp.StatusCode != http.StatusCreated {
   913  		return errutil.ParseErrorResponse(resp)
   914  	}
   915  	return nil
   916  }
   917  
   918  // Exists returns true if the described content exists.
   919  func (s *blobStore) Exists(ctx context.Context, target ocispec.Descriptor) (bool, error) {
   920  	_, err := s.Resolve(ctx, target.Digest.String())
   921  	if err == nil {
   922  		return true, nil
   923  	}
   924  	if errors.Is(err, errdef.ErrNotFound) {
   925  		return false, nil
   926  	}
   927  	return false, err
   928  }
   929  
   930  // Delete removes the content identified by the descriptor.
   931  func (s *blobStore) Delete(ctx context.Context, target ocispec.Descriptor) error {
   932  	return s.repo.delete(ctx, target, false)
   933  }
   934  
   935  // Resolve resolves a reference to a descriptor.
   936  func (s *blobStore) Resolve(ctx context.Context, reference string) (ocispec.Descriptor, error) {
   937  	ref, err := s.repo.ParseReference(reference)
   938  	if err != nil {
   939  		return ocispec.Descriptor{}, err
   940  	}
   941  	refDigest, err := ref.Digest()
   942  	if err != nil {
   943  		return ocispec.Descriptor{}, err
   944  	}
   945  	ctx = auth.AppendRepositoryScope(ctx, ref, auth.ActionPull)
   946  	url := buildRepositoryBlobURL(s.repo.PlainHTTP, ref)
   947  	req, err := http.NewRequestWithContext(ctx, http.MethodHead, url, nil)
   948  	if err != nil {
   949  		return ocispec.Descriptor{}, err
   950  	}
   951  
   952  	resp, err := s.repo.do(req)
   953  	if err != nil {
   954  		return ocispec.Descriptor{}, err
   955  	}
   956  	defer resp.Body.Close()
   957  
   958  	switch resp.StatusCode {
   959  	case http.StatusOK:
   960  		return generateBlobDescriptor(resp, refDigest)
   961  	case http.StatusNotFound:
   962  		return ocispec.Descriptor{}, fmt.Errorf("%s: %w", ref, errdef.ErrNotFound)
   963  	default:
   964  		return ocispec.Descriptor{}, errutil.ParseErrorResponse(resp)
   965  	}
   966  }
   967  
   968  // FetchReference fetches the blob identified by the reference.
   969  // The reference must be a digest.
   970  func (s *blobStore) FetchReference(ctx context.Context, reference string) (desc ocispec.Descriptor, rc io.ReadCloser, err error) {
   971  	ref, err := s.repo.ParseReference(reference)
   972  	if err != nil {
   973  		return ocispec.Descriptor{}, nil, err
   974  	}
   975  	refDigest, err := ref.Digest()
   976  	if err != nil {
   977  		return ocispec.Descriptor{}, nil, err
   978  	}
   979  
   980  	ctx = auth.AppendRepositoryScope(ctx, ref, auth.ActionPull)
   981  	url := buildRepositoryBlobURL(s.repo.PlainHTTP, ref)
   982  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
   983  	if err != nil {
   984  		return ocispec.Descriptor{}, nil, err
   985  	}
   986  
   987  	resp, err := s.repo.do(req)
   988  	if err != nil {
   989  		return ocispec.Descriptor{}, nil, err
   990  	}
   991  	defer func() {
   992  		if err != nil {
   993  			resp.Body.Close()
   994  		}
   995  	}()
   996  
   997  	switch resp.StatusCode {
   998  	case http.StatusOK: // server does not support seek as `Range` was ignored.
   999  		if resp.ContentLength == -1 {
  1000  			desc, err = s.Resolve(ctx, reference)
  1001  		} else {
  1002  			desc, err = generateBlobDescriptor(resp, refDigest)
  1003  		}
  1004  		if err != nil {
  1005  			return ocispec.Descriptor{}, nil, err
  1006  		}
  1007  
  1008  		// check server range request capability.
  1009  		// Docker spec allows range header form of "Range: bytes=<start>-<end>".
  1010  		// However, the remote server may still not RFC 7233 compliant.
  1011  		// Reference: https://docs.docker.com/registry/spec/api/#blob
  1012  		if rangeUnit := resp.Header.Get("Accept-Ranges"); rangeUnit == "bytes" {
  1013  			return desc, httputil.NewReadSeekCloser(s.repo.client(), req, resp.Body, desc.Size), nil
  1014  		}
  1015  		return desc, resp.Body, nil
  1016  	case http.StatusNotFound:
  1017  		return ocispec.Descriptor{}, nil, fmt.Errorf("%s: %w", ref, errdef.ErrNotFound)
  1018  	default:
  1019  		return ocispec.Descriptor{}, nil, errutil.ParseErrorResponse(resp)
  1020  	}
  1021  }
  1022  
  1023  // generateBlobDescriptor returns a descriptor generated from the response.
  1024  func generateBlobDescriptor(resp *http.Response, refDigest digest.Digest) (ocispec.Descriptor, error) {
  1025  	mediaType, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type"))
  1026  	if mediaType == "" {
  1027  		mediaType = "application/octet-stream"
  1028  	}
  1029  
  1030  	size := resp.ContentLength
  1031  	if size == -1 {
  1032  		return ocispec.Descriptor{}, fmt.Errorf("%s %q: unknown response Content-Length", resp.Request.Method, resp.Request.URL)
  1033  	}
  1034  
  1035  	if err := verifyContentDigest(resp, refDigest); err != nil {
  1036  		return ocispec.Descriptor{}, err
  1037  	}
  1038  
  1039  	return ocispec.Descriptor{
  1040  		MediaType: mediaType,
  1041  		Digest:    refDigest,
  1042  		Size:      size,
  1043  	}, nil
  1044  }
  1045  
  1046  // manifestStore accesses the manifest part of the repository.
  1047  type manifestStore struct {
  1048  	repo *Repository
  1049  }
  1050  
  1051  // Fetch fetches the content identified by the descriptor.
  1052  func (s *manifestStore) Fetch(ctx context.Context, target ocispec.Descriptor) (rc io.ReadCloser, err error) {
  1053  	ref := s.repo.Reference
  1054  	ref.Reference = target.Digest.String()
  1055  	ctx = auth.AppendRepositoryScope(ctx, ref, auth.ActionPull)
  1056  	url := buildRepositoryManifestURL(s.repo.PlainHTTP, ref)
  1057  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
  1058  	if err != nil {
  1059  		return nil, err
  1060  	}
  1061  	req.Header.Set("Accept", target.MediaType)
  1062  
  1063  	resp, err := s.repo.do(req)
  1064  	if err != nil {
  1065  		return nil, err
  1066  	}
  1067  	defer func() {
  1068  		if err != nil {
  1069  			resp.Body.Close()
  1070  		}
  1071  	}()
  1072  
  1073  	switch resp.StatusCode {
  1074  	case http.StatusOK:
  1075  		// no-op
  1076  	case http.StatusNotFound:
  1077  		return nil, fmt.Errorf("%s: %w", target.Digest, errdef.ErrNotFound)
  1078  	default:
  1079  		return nil, errutil.ParseErrorResponse(resp)
  1080  	}
  1081  	mediaType, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
  1082  	if err != nil {
  1083  		return nil, fmt.Errorf("%s %q: invalid response Content-Type: %w", resp.Request.Method, resp.Request.URL, err)
  1084  	}
  1085  	if mediaType != target.MediaType {
  1086  		return nil, fmt.Errorf("%s %q: mismatch response Content-Type %q: expect %q", resp.Request.Method, resp.Request.URL, mediaType, target.MediaType)
  1087  	}
  1088  	if size := resp.ContentLength; size != -1 && size != target.Size {
  1089  		return nil, fmt.Errorf("%s %q: mismatch Content-Length", resp.Request.Method, resp.Request.URL)
  1090  	}
  1091  	if err := verifyContentDigest(resp, target.Digest); err != nil {
  1092  		return nil, err
  1093  	}
  1094  	return resp.Body, nil
  1095  }
  1096  
  1097  // Push pushes the content, matching the expected descriptor.
  1098  func (s *manifestStore) Push(ctx context.Context, expected ocispec.Descriptor, content io.Reader) error {
  1099  	return s.pushWithIndexing(ctx, expected, content, expected.Digest.String())
  1100  }
  1101  
  1102  // Exists returns true if the described content exists.
  1103  func (s *manifestStore) Exists(ctx context.Context, target ocispec.Descriptor) (bool, error) {
  1104  	_, err := s.Resolve(ctx, target.Digest.String())
  1105  	if err == nil {
  1106  		return true, nil
  1107  	}
  1108  	if errors.Is(err, errdef.ErrNotFound) {
  1109  		return false, nil
  1110  	}
  1111  	return false, err
  1112  }
  1113  
  1114  // Delete removes the manifest content identified by the descriptor.
  1115  func (s *manifestStore) Delete(ctx context.Context, target ocispec.Descriptor) error {
  1116  	return s.deleteWithIndexing(ctx, target)
  1117  }
  1118  
  1119  // deleteWithIndexing removes the manifest content identified by the descriptor,
  1120  // and indexes referrers for the manifest when needed.
  1121  func (s *manifestStore) deleteWithIndexing(ctx context.Context, target ocispec.Descriptor) error {
  1122  	switch target.MediaType {
  1123  	case spec.MediaTypeArtifactManifest, ocispec.MediaTypeImageManifest, ocispec.MediaTypeImageIndex:
  1124  		if state := s.repo.loadReferrersState(); state == referrersStateSupported {
  1125  			// referrers API is available, no client-side indexing needed
  1126  			return s.repo.delete(ctx, target, true)
  1127  		}
  1128  
  1129  		if err := limitSize(target, s.repo.MaxMetadataBytes); err != nil {
  1130  			return err
  1131  		}
  1132  		ctx = auth.AppendRepositoryScope(ctx, s.repo.Reference, auth.ActionPull, auth.ActionDelete)
  1133  		manifestJSON, err := content.FetchAll(ctx, s, target)
  1134  		if err != nil {
  1135  			return err
  1136  		}
  1137  		if err := s.indexReferrersForDelete(ctx, target, manifestJSON); err != nil {
  1138  			return err
  1139  		}
  1140  	}
  1141  
  1142  	return s.repo.delete(ctx, target, true)
  1143  }
  1144  
  1145  // indexReferrersForDelete indexes referrers for manifests with a subject field
  1146  // on manifest delete.
  1147  //
  1148  // References:
  1149  //   - Latest spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#deleting-manifests
  1150  //   - Compatible spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#deleting-manifests
  1151  func (s *manifestStore) indexReferrersForDelete(ctx context.Context, desc ocispec.Descriptor, manifestJSON []byte) error {
  1152  	var manifest struct {
  1153  		Subject *ocispec.Descriptor `json:"subject"`
  1154  	}
  1155  	if err := json.Unmarshal(manifestJSON, &manifest); err != nil {
  1156  		return fmt.Errorf("failed to decode manifest: %s: %s: %w", desc.Digest, desc.MediaType, err)
  1157  	}
  1158  	if manifest.Subject == nil {
  1159  		// no subject, no indexing needed
  1160  		return nil
  1161  	}
  1162  
  1163  	subject := *manifest.Subject
  1164  	ok, err := s.repo.pingReferrers(ctx)
  1165  	if err != nil {
  1166  		return err
  1167  	}
  1168  	if ok {
  1169  		// referrers API is available, no client-side indexing needed
  1170  		return nil
  1171  	}
  1172  	return s.updateReferrersIndex(ctx, subject, referrerChange{desc, referrerOperationRemove})
  1173  }
  1174  
  1175  // Resolve resolves a reference to a descriptor.
  1176  // See also `ManifestMediaTypes`.
  1177  func (s *manifestStore) Resolve(ctx context.Context, reference string) (ocispec.Descriptor, error) {
  1178  	ref, err := s.repo.ParseReference(reference)
  1179  	if err != nil {
  1180  		return ocispec.Descriptor{}, err
  1181  	}
  1182  	ctx = auth.AppendRepositoryScope(ctx, ref, auth.ActionPull)
  1183  	url := buildRepositoryManifestURL(s.repo.PlainHTTP, ref)
  1184  	req, err := http.NewRequestWithContext(ctx, http.MethodHead, url, nil)
  1185  	if err != nil {
  1186  		return ocispec.Descriptor{}, err
  1187  	}
  1188  	req.Header.Set("Accept", manifestAcceptHeader(s.repo.ManifestMediaTypes))
  1189  
  1190  	resp, err := s.repo.do(req)
  1191  	if err != nil {
  1192  		return ocispec.Descriptor{}, err
  1193  	}
  1194  	defer resp.Body.Close()
  1195  
  1196  	switch resp.StatusCode {
  1197  	case http.StatusOK:
  1198  		return s.generateDescriptor(resp, ref, req.Method)
  1199  	case http.StatusNotFound:
  1200  		return ocispec.Descriptor{}, fmt.Errorf("%s: %w", ref, errdef.ErrNotFound)
  1201  	default:
  1202  		return ocispec.Descriptor{}, errutil.ParseErrorResponse(resp)
  1203  	}
  1204  }
  1205  
  1206  // FetchReference fetches the manifest identified by the reference.
  1207  // The reference can be a tag or digest.
  1208  func (s *manifestStore) FetchReference(ctx context.Context, reference string) (desc ocispec.Descriptor, rc io.ReadCloser, err error) {
  1209  	ref, err := s.repo.ParseReference(reference)
  1210  	if err != nil {
  1211  		return ocispec.Descriptor{}, nil, err
  1212  	}
  1213  
  1214  	ctx = auth.AppendRepositoryScope(ctx, ref, auth.ActionPull)
  1215  	url := buildRepositoryManifestURL(s.repo.PlainHTTP, ref)
  1216  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
  1217  	if err != nil {
  1218  		return ocispec.Descriptor{}, nil, err
  1219  	}
  1220  	req.Header.Set("Accept", manifestAcceptHeader(s.repo.ManifestMediaTypes))
  1221  
  1222  	resp, err := s.repo.do(req)
  1223  	if err != nil {
  1224  		return ocispec.Descriptor{}, nil, err
  1225  	}
  1226  	defer func() {
  1227  		if err != nil {
  1228  			resp.Body.Close()
  1229  		}
  1230  	}()
  1231  
  1232  	switch resp.StatusCode {
  1233  	case http.StatusOK:
  1234  		if resp.ContentLength == -1 {
  1235  			desc, err = s.Resolve(ctx, reference)
  1236  		} else {
  1237  			desc, err = s.generateDescriptor(resp, ref, req.Method)
  1238  		}
  1239  		if err != nil {
  1240  			return ocispec.Descriptor{}, nil, err
  1241  		}
  1242  		return desc, resp.Body, nil
  1243  	case http.StatusNotFound:
  1244  		return ocispec.Descriptor{}, nil, fmt.Errorf("%s: %w", ref, errdef.ErrNotFound)
  1245  	default:
  1246  		return ocispec.Descriptor{}, nil, errutil.ParseErrorResponse(resp)
  1247  	}
  1248  }
  1249  
  1250  // Tag tags a manifest descriptor with a reference string.
  1251  func (s *manifestStore) Tag(ctx context.Context, desc ocispec.Descriptor, reference string) error {
  1252  	ref, err := s.repo.ParseReference(reference)
  1253  	if err != nil {
  1254  		return err
  1255  	}
  1256  
  1257  	ctx = auth.AppendRepositoryScope(ctx, ref, auth.ActionPull, auth.ActionPush)
  1258  	rc, err := s.Fetch(ctx, desc)
  1259  	if err != nil {
  1260  		return err
  1261  	}
  1262  	defer rc.Close()
  1263  
  1264  	return s.push(ctx, desc, rc, ref.Reference)
  1265  }
  1266  
  1267  // PushReference pushes the manifest with a reference tag.
  1268  func (s *manifestStore) PushReference(ctx context.Context, expected ocispec.Descriptor, content io.Reader, reference string) error {
  1269  	ref, err := s.repo.ParseReference(reference)
  1270  	if err != nil {
  1271  		return err
  1272  	}
  1273  	return s.pushWithIndexing(ctx, expected, content, ref.Reference)
  1274  }
  1275  
  1276  // push pushes the manifest content, matching the expected descriptor.
  1277  func (s *manifestStore) push(ctx context.Context, expected ocispec.Descriptor, content io.Reader, reference string) error {
  1278  	ref := s.repo.Reference
  1279  	ref.Reference = reference
  1280  	// pushing usually requires both pull and push actions.
  1281  	// Reference: https://github.com/distribution/distribution/blob/v2.7.1/registry/handlers/app.go#L921-L930
  1282  	ctx = auth.AppendRepositoryScope(ctx, ref, auth.ActionPull, auth.ActionPush)
  1283  	url := buildRepositoryManifestURL(s.repo.PlainHTTP, ref)
  1284  	// unwrap the content for optimizations of built-in types.
  1285  	body := ioutil.UnwrapNopCloser(content)
  1286  	if _, ok := body.(io.ReadCloser); ok {
  1287  		// undo unwrap if the nopCloser is intended.
  1288  		body = content
  1289  	}
  1290  	req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, body)
  1291  	if err != nil {
  1292  		return err
  1293  	}
  1294  	if req.GetBody != nil && req.ContentLength != expected.Size {
  1295  		// short circuit a size mismatch for built-in types.
  1296  		return fmt.Errorf("mismatch content length %d: expect %d", req.ContentLength, expected.Size)
  1297  	}
  1298  	req.ContentLength = expected.Size
  1299  	req.Header.Set("Content-Type", expected.MediaType)
  1300  
  1301  	// if the underlying client is an auth client, the content might be read
  1302  	// more than once for obtaining the auth challenge and the actual request.
  1303  	// To prevent double reading, the manifest is read and stored in the memory,
  1304  	// and serve from the memory.
  1305  	client := s.repo.client()
  1306  	if _, ok := client.(*auth.Client); ok && req.GetBody == nil {
  1307  		store := cas.NewMemory()
  1308  		err := store.Push(ctx, expected, content)
  1309  		if err != nil {
  1310  			return err
  1311  		}
  1312  		req.GetBody = func() (io.ReadCloser, error) {
  1313  			return store.Fetch(ctx, expected)
  1314  		}
  1315  		req.Body, err = req.GetBody()
  1316  		if err != nil {
  1317  			return err
  1318  		}
  1319  	}
  1320  	resp, err := s.repo.do(req)
  1321  	if err != nil {
  1322  		return err
  1323  	}
  1324  	defer resp.Body.Close()
  1325  
  1326  	if resp.StatusCode != http.StatusCreated {
  1327  		return errutil.ParseErrorResponse(resp)
  1328  	}
  1329  	s.checkOCISubjectHeader(resp)
  1330  	return verifyContentDigest(resp, expected.Digest)
  1331  }
  1332  
  1333  // checkOCISubjectHeader checks the "OCI-Subject" header in the response and
  1334  // sets referrers capability accordingly.
  1335  // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#pushing-manifests-with-subject
  1336  func (s *manifestStore) checkOCISubjectHeader(resp *http.Response) {
  1337  	// If the "OCI-Subject" header is set, it indicates that the registry
  1338  	// supports the Referrers API and has processed the subject of the manifest.
  1339  	if subjectHeader := resp.Header.Get(headerOCISubject); subjectHeader != "" {
  1340  		s.repo.SetReferrersCapability(true)
  1341  	}
  1342  
  1343  	// If the "OCI-Subject" header is NOT set, it means that either the manifest
  1344  	// has no subject OR the referrers API is NOT supported by the registry.
  1345  	//
  1346  	// Since we don't know whether the pushed manifest has a subject or not,
  1347  	// we do not set the referrers capability to false at here.
  1348  }
  1349  
  1350  // pushWithIndexing pushes the manifest content matching the expected descriptor,
  1351  // and indexes referrers for the manifest when needed.
  1352  func (s *manifestStore) pushWithIndexing(ctx context.Context, expected ocispec.Descriptor, r io.Reader, reference string) error {
  1353  	switch expected.MediaType {
  1354  	case spec.MediaTypeArtifactManifest, ocispec.MediaTypeImageManifest, ocispec.MediaTypeImageIndex:
  1355  		if state := s.repo.loadReferrersState(); state == referrersStateSupported {
  1356  			// referrers API is available, no client-side indexing needed
  1357  			return s.push(ctx, expected, r, reference)
  1358  		}
  1359  
  1360  		if err := limitSize(expected, s.repo.MaxMetadataBytes); err != nil {
  1361  			return err
  1362  		}
  1363  		manifestJSON, err := content.ReadAll(r, expected)
  1364  		if err != nil {
  1365  			return err
  1366  		}
  1367  		if err := s.push(ctx, expected, bytes.NewReader(manifestJSON), reference); err != nil {
  1368  			return err
  1369  		}
  1370  		// check referrers API availability again after push
  1371  		if state := s.repo.loadReferrersState(); state == referrersStateSupported {
  1372  			// the subject has been processed the registry, no client-side
  1373  			// indexing needed
  1374  			return nil
  1375  		}
  1376  		return s.indexReferrersForPush(ctx, expected, manifestJSON)
  1377  	default:
  1378  		return s.push(ctx, expected, r, reference)
  1379  	}
  1380  }
  1381  
  1382  // indexReferrersForPush indexes referrers for manifests with a subject field
  1383  // on manifest push.
  1384  //
  1385  // References:
  1386  //   - Latest spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#pushing-manifests-with-subject
  1387  //   - Compatible spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#pushing-manifests-with-subject
  1388  func (s *manifestStore) indexReferrersForPush(ctx context.Context, desc ocispec.Descriptor, manifestJSON []byte) error {
  1389  	var subject ocispec.Descriptor
  1390  	switch desc.MediaType {
  1391  	case spec.MediaTypeArtifactManifest:
  1392  		var manifest spec.Artifact
  1393  		if err := json.Unmarshal(manifestJSON, &manifest); err != nil {
  1394  			return fmt.Errorf("failed to decode manifest: %s: %s: %w", desc.Digest, desc.MediaType, err)
  1395  		}
  1396  		if manifest.Subject == nil {
  1397  			// no subject, no indexing needed
  1398  			return nil
  1399  		}
  1400  		subject = *manifest.Subject
  1401  		desc.ArtifactType = manifest.ArtifactType
  1402  		desc.Annotations = manifest.Annotations
  1403  	case ocispec.MediaTypeImageManifest:
  1404  		var manifest ocispec.Manifest
  1405  		if err := json.Unmarshal(manifestJSON, &manifest); err != nil {
  1406  			return fmt.Errorf("failed to decode manifest: %s: %s: %w", desc.Digest, desc.MediaType, err)
  1407  		}
  1408  		if manifest.Subject == nil {
  1409  			// no subject, no indexing needed
  1410  			return nil
  1411  		}
  1412  		subject = *manifest.Subject
  1413  		desc.ArtifactType = manifest.ArtifactType
  1414  		if desc.ArtifactType == "" {
  1415  			desc.ArtifactType = manifest.Config.MediaType
  1416  		}
  1417  		desc.Annotations = manifest.Annotations
  1418  	case ocispec.MediaTypeImageIndex:
  1419  		var manifest ocispec.Index
  1420  		if err := json.Unmarshal(manifestJSON, &manifest); err != nil {
  1421  			return fmt.Errorf("failed to decode manifest: %s: %s: %w", desc.Digest, desc.MediaType, err)
  1422  		}
  1423  		if manifest.Subject == nil {
  1424  			// no subject, no indexing needed
  1425  			return nil
  1426  		}
  1427  		subject = *manifest.Subject
  1428  		desc.ArtifactType = manifest.ArtifactType
  1429  		desc.Annotations = manifest.Annotations
  1430  	default:
  1431  		return nil
  1432  	}
  1433  
  1434  	// if the manifest has a subject but the remote registry does not process it,
  1435  	// it means that the Referrers API is not supported by the registry.
  1436  	s.repo.SetReferrersCapability(false)
  1437  	return s.updateReferrersIndex(ctx, subject, referrerChange{desc, referrerOperationAdd})
  1438  }
  1439  
  1440  // updateReferrersIndex updates the referrers index for desc referencing subject
  1441  // on manifest push and manifest delete.
  1442  // References:
  1443  //   - https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#pushing-manifests-with-subject
  1444  //   - https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#deleting-manifests
  1445  func (s *manifestStore) updateReferrersIndex(ctx context.Context, subject ocispec.Descriptor, change referrerChange) (err error) {
  1446  	referrersTag := buildReferrersTag(subject)
  1447  
  1448  	var oldIndexDesc *ocispec.Descriptor
  1449  	var oldReferrers []ocispec.Descriptor
  1450  	prepare := func() error {
  1451  		// 1. pull the original referrers list using the referrers tag schema
  1452  		indexDesc, referrers, err := s.repo.referrersFromIndex(ctx, referrersTag)
  1453  		if err != nil {
  1454  			if errors.Is(err, errdef.ErrNotFound) {
  1455  				// valid case: no old referrers index
  1456  				return nil
  1457  			}
  1458  			return err
  1459  		}
  1460  		oldIndexDesc = &indexDesc
  1461  		oldReferrers = referrers
  1462  		return nil
  1463  	}
  1464  	update := func(referrerChanges []referrerChange) error {
  1465  		// 2. apply the referrer changes on the referrers list
  1466  		updatedReferrers, err := applyReferrerChanges(oldReferrers, referrerChanges)
  1467  		if err != nil {
  1468  			if err == errNoReferrerUpdate {
  1469  				return nil
  1470  			}
  1471  			return err
  1472  		}
  1473  
  1474  		// 3. push the updated referrers list using referrers tag schema
  1475  		if len(updatedReferrers) > 0 || s.repo.SkipReferrersGC {
  1476  			// push a new index in either case:
  1477  			// 1. the referrers list has been updated with a non-zero size
  1478  			// 2. OR the updated referrers list is empty but referrers GC
  1479  			//    is skipped, in this case an empty index should still be pushed
  1480  			//    as the old index won't get deleted
  1481  			newIndexDesc, newIndex, err := generateIndex(updatedReferrers)
  1482  			if err != nil {
  1483  				return fmt.Errorf("failed to generate referrers index for referrers tag %s: %w", referrersTag, err)
  1484  			}
  1485  			if err := s.push(ctx, newIndexDesc, bytes.NewReader(newIndex), referrersTag); err != nil {
  1486  				return fmt.Errorf("failed to push referrers index tagged by %s: %w", referrersTag, err)
  1487  			}
  1488  		}
  1489  
  1490  		// 4. delete the dangling original referrers index, if applicable
  1491  		if s.repo.SkipReferrersGC || oldIndexDesc == nil {
  1492  			return nil
  1493  		}
  1494  		if err := s.repo.delete(ctx, *oldIndexDesc, true); err != nil {
  1495  			return &ReferrersError{
  1496  				Op:      opDeleteReferrersIndex,
  1497  				Err:     fmt.Errorf("failed to delete dangling referrers index %s for referrers tag %s: %w", oldIndexDesc.Digest.String(), referrersTag, err),
  1498  				Subject: subject,
  1499  			}
  1500  		}
  1501  		return nil
  1502  	}
  1503  
  1504  	merge, done := s.repo.referrersMergePool.Get(referrersTag)
  1505  	defer done()
  1506  	return merge.Do(change, prepare, update)
  1507  }
  1508  
  1509  // ParseReference parses a reference to a fully qualified reference.
  1510  func (s *manifestStore) ParseReference(reference string) (registry.Reference, error) {
  1511  	return s.repo.ParseReference(reference)
  1512  }
  1513  
  1514  // generateDescriptor returns a descriptor generated from the response.
  1515  // See the truth table at the top of `repository_test.go`
  1516  func (s *manifestStore) generateDescriptor(resp *http.Response, ref registry.Reference, httpMethod string) (ocispec.Descriptor, error) {
  1517  	// 1. Validate Content-Type
  1518  	mediaType, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
  1519  	if err != nil {
  1520  		return ocispec.Descriptor{}, fmt.Errorf(
  1521  			"%s %q: invalid response `Content-Type` header; %w",
  1522  			resp.Request.Method,
  1523  			resp.Request.URL,
  1524  			err,
  1525  		)
  1526  	}
  1527  
  1528  	// 2. Validate Size
  1529  	if resp.ContentLength == -1 {
  1530  		return ocispec.Descriptor{}, fmt.Errorf(
  1531  			"%s %q: unknown response Content-Length",
  1532  			resp.Request.Method,
  1533  			resp.Request.URL,
  1534  		)
  1535  	}
  1536  
  1537  	// 3. Validate Client Reference
  1538  	var refDigest digest.Digest
  1539  	if d, err := ref.Digest(); err == nil {
  1540  		refDigest = d
  1541  	}
  1542  
  1543  	// 4. Validate Server Digest (if present)
  1544  	var serverHeaderDigest digest.Digest
  1545  	if serverHeaderDigestStr := resp.Header.Get(headerDockerContentDigest); serverHeaderDigestStr != "" {
  1546  		if serverHeaderDigest, err = digest.Parse(serverHeaderDigestStr); err != nil {
  1547  			return ocispec.Descriptor{}, fmt.Errorf(
  1548  				"%s %q: invalid response header value: `%s: %s`; %w",
  1549  				resp.Request.Method,
  1550  				resp.Request.URL,
  1551  				headerDockerContentDigest,
  1552  				serverHeaderDigestStr,
  1553  				err,
  1554  			)
  1555  		}
  1556  	}
  1557  
  1558  	/* 5. Now, look for specific error conditions; see truth table in method docstring */
  1559  	var contentDigest digest.Digest
  1560  
  1561  	if len(serverHeaderDigest) == 0 {
  1562  		if httpMethod == http.MethodHead {
  1563  			if len(refDigest) == 0 {
  1564  				// HEAD without server `Docker-Content-Digest` header is an
  1565  				// immediate fail
  1566  				return ocispec.Descriptor{}, fmt.Errorf(
  1567  					"HTTP %s request missing required header %q",
  1568  					httpMethod, headerDockerContentDigest,
  1569  				)
  1570  			}
  1571  			// Otherwise, just trust the client-supplied digest
  1572  			contentDigest = refDigest
  1573  		} else {
  1574  			// GET without server `Docker-Content-Digest` header forces the
  1575  			// expensive calculation
  1576  			var calculatedDigest digest.Digest
  1577  			if calculatedDigest, err = calculateDigestFromResponse(resp, s.repo.MaxMetadataBytes); err != nil {
  1578  				return ocispec.Descriptor{}, fmt.Errorf("failed to calculate digest on response body; %w", err)
  1579  			}
  1580  			contentDigest = calculatedDigest
  1581  		}
  1582  	} else {
  1583  		contentDigest = serverHeaderDigest
  1584  	}
  1585  
  1586  	if len(refDigest) > 0 && refDigest != contentDigest {
  1587  		return ocispec.Descriptor{}, fmt.Errorf(
  1588  			"%s %q: invalid response; digest mismatch in %s: received %q when expecting %q",
  1589  			resp.Request.Method, resp.Request.URL,
  1590  			headerDockerContentDigest, contentDigest,
  1591  			refDigest,
  1592  		)
  1593  	}
  1594  
  1595  	// 6. Finally, if we made it this far, then all is good; return.
  1596  	return ocispec.Descriptor{
  1597  		MediaType: mediaType,
  1598  		Digest:    contentDigest,
  1599  		Size:      resp.ContentLength,
  1600  	}, nil
  1601  }
  1602  
  1603  // calculateDigestFromResponse calculates the actual digest of the response body
  1604  // taking care not to destroy it in the process.
  1605  func calculateDigestFromResponse(resp *http.Response, maxMetadataBytes int64) (digest.Digest, error) {
  1606  	defer resp.Body.Close()
  1607  
  1608  	body := limitReader(resp.Body, maxMetadataBytes)
  1609  	content, err := io.ReadAll(body)
  1610  	if err != nil {
  1611  		return "", fmt.Errorf("%s %q: failed to read response body: %w", resp.Request.Method, resp.Request.URL, err)
  1612  	}
  1613  	resp.Body = io.NopCloser(bytes.NewReader(content))
  1614  
  1615  	return digest.FromBytes(content), nil
  1616  }
  1617  
  1618  // verifyContentDigest verifies "Docker-Content-Digest" header if present.
  1619  // OCI distribution-spec states the Docker-Content-Digest header is optional.
  1620  // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.0.1/spec.md#legacy-docker-support-http-headers
  1621  func verifyContentDigest(resp *http.Response, expected digest.Digest) error {
  1622  	digestStr := resp.Header.Get(headerDockerContentDigest)
  1623  
  1624  	if len(digestStr) == 0 {
  1625  		return nil
  1626  	}
  1627  
  1628  	contentDigest, err := digest.Parse(digestStr)
  1629  	if err != nil {
  1630  		return fmt.Errorf(
  1631  			"%s %q: invalid response header: `%s: %s`",
  1632  			resp.Request.Method, resp.Request.URL,
  1633  			headerDockerContentDigest, digestStr,
  1634  		)
  1635  	}
  1636  
  1637  	if contentDigest != expected {
  1638  		return fmt.Errorf(
  1639  			"%s %q: invalid response; digest mismatch in %s: received %q when expecting %q",
  1640  			resp.Request.Method, resp.Request.URL,
  1641  			headerDockerContentDigest, contentDigest,
  1642  			expected,
  1643  		)
  1644  	}
  1645  
  1646  	return nil
  1647  }
  1648  
  1649  // generateIndex generates an image index containing the given manifests list.
  1650  func generateIndex(manifests []ocispec.Descriptor) (ocispec.Descriptor, []byte, error) {
  1651  	if manifests == nil {
  1652  		manifests = []ocispec.Descriptor{} // make it an empty array to prevent potential server-side bugs
  1653  	}
  1654  	index := ocispec.Index{
  1655  		Versioned: specs.Versioned{
  1656  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  1657  		},
  1658  		MediaType: ocispec.MediaTypeImageIndex,
  1659  		Manifests: manifests,
  1660  	}
  1661  	indexJSON, err := json.Marshal(index)
  1662  	if err != nil {
  1663  		return ocispec.Descriptor{}, nil, err
  1664  	}
  1665  	indexDesc := content.NewDescriptorFromBytes(index.MediaType, indexJSON)
  1666  	return indexDesc, indexJSON, nil
  1667  }