@ -18,10 +18,10 @@ package docker
import (
"context"
"io"
"net/http"
"net/url"
"path"
"strconv"
"strings"
"github.com/containerd/containerd/errdefs"
@ -29,6 +29,7 @@ import (
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/reference"
"github.com/containerd/containerd/remotes"
"github.com/containerd/containerd/remotes/docker/schema1"
"github.com/containerd/containerd/version"
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@ -150,6 +151,32 @@ func NewResolver(options ResolverOptions) remotes.Resolver {
}
}
func getManifestMediaType ( resp * http . Response ) string {
// Strip encoding data (manifests should always be ascii JSON)
contentType := resp . Header . Get ( "Content-Type" )
if sp := strings . IndexByte ( contentType , ';' ) ; sp != - 1 {
contentType = contentType [ 0 : sp ]
}
// As of Apr 30 2019 the registry.access.redhat.com registry does not specify
// the content type of any data but uses schema1 manifests.
if contentType == "text/plain" {
contentType = images . MediaTypeDockerSchema1Manifest
}
return contentType
}
type countingReader struct {
reader io . Reader
bytesRead int64
}
func ( r * countingReader ) Read ( p [ ] byte ) ( int , error ) {
n , err := r . reader . Read ( p )
r . bytesRead += int64 ( n )
return n , err
}
var _ remotes . Resolver = & dockerResolver { }
func ( r * dockerResolver ) Resolve ( ctx context . Context , ref string ) ( string , ocispec . Descriptor , error ) {
@ -220,40 +247,56 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp
}
return "" , ocispec . Descriptor { } , errors . Errorf ( "unexpected status code %v: %v" , u , resp . Status )
}
size := resp . ContentLength
// this is the only point at which we trust the registry. we use the
// content headers to assemble a descriptor for the name. when this becomes
// more robust, we mostly get this information from a secure trust store.
dgstHeader := digest . Digest ( resp . Header . Get ( "Docker-Content-Digest" ) )
contentType := getManifestMediaType ( resp )
if dgstHeader != "" {
if dgstHeader != "" && size != - 1 {
if err := dgstHeader . Validate ( ) ; err != nil {
return "" , ocispec . Descriptor { } , errors . Wrapf ( err , "%q in header not a valid digest" , dgstHeader )
}
dgst = dgstHeader
} else {
log . G ( ctx ) . Debug ( "no Docker-Content-Digest header, fetching manifest instead" )
req , err := http . NewRequest ( http . MethodGet , u , nil )
if err != nil {
return "" , ocispec . Descriptor { } , err
}
req . Header = r . headers
if dgst == "" {
return "" , ocispec . Descriptor { } , errors . Errorf ( "could not resolve digest for %v" , ref )
resp , err := fetcher . doRequestWithRetries ( ctx , req , nil )
if err != nil {
return "" , ocispec . Descriptor { } , err
}
defer resp . Body . Close ( )
var (
size int64
sizeHeader = resp . Header . Get ( "Content-Length" )
)
bodyReader := countingReader { reader : resp . Body }
size , err = strconv . ParseInt ( sizeHeader , 10 , 64 )
contentType = getManifestMediaType ( resp )
if contentType == images . MediaTypeDockerSchema1Manifest {
b , err := schema1 . ReadStripSignature ( & bodyReader )
if err != nil {
return "" , ocispec . Descriptor { } , err
}
return "" , ocispec . Descriptor { } , errors . Wrapf ( err , "invalid size header: %q" , sizeHeader )
dgst = digest . FromBytes ( b )
} else {
dgst , err = digest . FromReader ( & bodyReader )
if err != nil {
return "" , ocispec . Descriptor { } , err
}
}
if size < 0 {
return "" , ocispec . Descriptor { } , errors . Errorf ( "%q in header not a valid size" , sizeHeader )
size = bodyReader . bytesRead
}
desc := ocispec . Descriptor {
Digest : dgst ,
MediaType : resp. Header . Get ( "Content-Type" ) , // need to strip disposition?
MediaType : contentType,
Size : size ,
}