github.com/nikkelma/oras-project_oras-go@v1.1.1-0.20220201001104-a75f6a419090/pkg/oras/opts.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  package oras
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io"
    21  	"path/filepath"
    22  	"strings"
    23  	"sync"
    24  
    25  	"github.com/containerd/containerd/images"
    26  	"github.com/opencontainers/go-digest"
    27  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    28  	"github.com/pkg/errors"
    29  	"golang.org/x/sync/semaphore"
    30  	orascontent "oras.land/oras-go/pkg/content"
    31  )
    32  
    33  func copyOptsDefaults() *copyOpts {
    34  	return &copyOpts{
    35  		dispatch:         images.Dispatch,
    36  		filterName:       filterName,
    37  		cachedMediaTypes: []string{ocispec.MediaTypeImageManifest, ocispec.MediaTypeImageIndex},
    38  		validateName:     ValidateNameAsPath,
    39  	}
    40  }
    41  
    42  type CopyOpt func(o *copyOpts) error
    43  
    44  type copyOpts struct {
    45  	allowedMediaTypes                   []string
    46  	dispatch                            func(context.Context, images.Handler, *semaphore.Weighted, ...ocispec.Descriptor) error
    47  	baseHandlers                        []images.Handler
    48  	callbackHandlers                    []images.Handler
    49  	contentProvideIngesterPusherFetcher orascontent.Store
    50  	filterName                          func(ocispec.Descriptor) bool
    51  	cachedMediaTypes                    []string
    52  
    53  	saveManifest func([]byte)
    54  	saveLayers   func([]ocispec.Descriptor)
    55  	validateName func(desc ocispec.Descriptor) error
    56  
    57  	userAgent string
    58  }
    59  
    60  // ValidateNameAsPath validates name in the descriptor as file path in order
    61  // to generate good packages intended to be pulled using the FileStore or
    62  // the oras cli.
    63  // For cross-platform considerations, only unix paths are accepted.
    64  func ValidateNameAsPath(desc ocispec.Descriptor) error {
    65  	// no empty name
    66  	path, ok := orascontent.ResolveName(desc)
    67  	if !ok || path == "" {
    68  		return orascontent.ErrNoName
    69  	}
    70  
    71  	// path should be clean
    72  	if target := filepath.ToSlash(filepath.Clean(path)); target != path {
    73  		return errors.Wrap(ErrDirtyPath, path)
    74  	}
    75  
    76  	// path should be slash-separated
    77  	if strings.Contains(path, "\\") {
    78  		return errors.Wrap(ErrPathNotSlashSeparated, path)
    79  	}
    80  
    81  	// disallow absolute path: covers unix and windows format
    82  	if strings.HasPrefix(path, "/") {
    83  		return errors.Wrap(ErrAbsolutePathDisallowed, path)
    84  	}
    85  	if len(path) > 2 {
    86  		c := path[0]
    87  		if path[1] == ':' && path[2] == '/' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') {
    88  			return errors.Wrap(ErrAbsolutePathDisallowed, path)
    89  		}
    90  	}
    91  
    92  	// disallow path traversal
    93  	if strings.HasPrefix(path, "../") || path == ".." {
    94  		return errors.Wrap(ErrPathTraversalDisallowed, path)
    95  	}
    96  
    97  	return nil
    98  }
    99  
   100  // dispatchBFS behaves the same as images.Dispatch() but in sequence with breath-first search.
   101  func dispatchBFS(ctx context.Context, handler images.Handler, weighted *semaphore.Weighted, descs ...ocispec.Descriptor) error {
   102  	for i := 0; i < len(descs); i++ {
   103  		desc := descs[i]
   104  		children, err := handler.Handle(ctx, desc)
   105  		if err != nil {
   106  			switch err := errors.Cause(err); err {
   107  			case images.ErrSkipDesc:
   108  				continue // don't traverse the children.
   109  			case ErrStopProcessing:
   110  				return nil
   111  			}
   112  			return err
   113  		}
   114  		descs = append(descs, children...)
   115  	}
   116  	return nil
   117  }
   118  
   119  func filterName(desc ocispec.Descriptor) bool {
   120  	// needs to be filled in
   121  	return true
   122  }
   123  
   124  // WithAdditionalCachedMediaTypes adds media types normally cached in memory when pulling.
   125  // This does not replace the default media types, but appends to them
   126  func WithAdditionalCachedMediaTypes(cachedMediaTypes ...string) CopyOpt {
   127  	return func(o *copyOpts) error {
   128  		o.cachedMediaTypes = append(o.cachedMediaTypes, cachedMediaTypes...)
   129  		return nil
   130  	}
   131  }
   132  
   133  // WithAllowedMediaType sets the allowed media types
   134  func WithAllowedMediaType(allowedMediaTypes ...string) CopyOpt {
   135  	return func(o *copyOpts) error {
   136  		o.allowedMediaTypes = append(o.allowedMediaTypes, allowedMediaTypes...)
   137  		return nil
   138  	}
   139  }
   140  
   141  // WithAllowedMediaTypes sets the allowed media types
   142  func WithAllowedMediaTypes(allowedMediaTypes []string) CopyOpt {
   143  	return func(o *copyOpts) error {
   144  		o.allowedMediaTypes = append(o.allowedMediaTypes, allowedMediaTypes...)
   145  		return nil
   146  	}
   147  }
   148  
   149  // WithPullByBFS opt to pull in sequence with breath-first search
   150  func WithPullByBFS(o *copyOpts) error {
   151  	o.dispatch = dispatchBFS
   152  	return nil
   153  }
   154  
   155  // WithPullBaseHandler provides base handlers, which will be called before
   156  // any pull specific handlers.
   157  func WithPullBaseHandler(handlers ...images.Handler) CopyOpt {
   158  	return func(o *copyOpts) error {
   159  		o.baseHandlers = append(o.baseHandlers, handlers...)
   160  		return nil
   161  	}
   162  }
   163  
   164  // WithPullCallbackHandler provides callback handlers, which will be called after
   165  // any pull specific handlers.
   166  func WithPullCallbackHandler(handlers ...images.Handler) CopyOpt {
   167  	return func(o *copyOpts) error {
   168  		o.callbackHandlers = append(o.callbackHandlers, handlers...)
   169  		return nil
   170  	}
   171  }
   172  
   173  // WithContentProvideIngester opt to the provided Provider and Ingester
   174  // for file system I/O, including caches.
   175  func WithContentStore(store orascontent.Store) CopyOpt {
   176  	return func(o *copyOpts) error {
   177  		o.contentProvideIngesterPusherFetcher = store
   178  		return nil
   179  	}
   180  }
   181  
   182  // WithPullEmptyNameAllowed allows pulling blobs with empty name.
   183  func WithPullEmptyNameAllowed() CopyOpt {
   184  	return func(o *copyOpts) error {
   185  		o.filterName = func(ocispec.Descriptor) bool {
   186  			return true
   187  		}
   188  		return nil
   189  	}
   190  }
   191  
   192  // WithPullStatusTrack report results to stdout
   193  func WithPullStatusTrack(writer io.Writer) CopyOpt {
   194  	return WithPullCallbackHandler(pullStatusTrack(writer))
   195  }
   196  
   197  func pullStatusTrack(writer io.Writer) images.Handler {
   198  	var printLock sync.Mutex
   199  	return images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   200  		if name, ok := orascontent.ResolveName(desc); ok {
   201  			digestString := desc.Digest.String()
   202  			if err := desc.Digest.Validate(); err == nil {
   203  				if algo := desc.Digest.Algorithm(); algo == digest.SHA256 {
   204  					digestString = desc.Digest.Encoded()[:12]
   205  				}
   206  			}
   207  			printLock.Lock()
   208  			defer printLock.Unlock()
   209  			fmt.Fprintln(writer, "Downloaded", digestString, name)
   210  		}
   211  		return nil, nil
   212  	})
   213  }
   214  
   215  // WithNameValidation validates the image title in the descriptor.
   216  // Pass nil to disable name validation.
   217  func WithNameValidation(validate func(desc ocispec.Descriptor) error) CopyOpt {
   218  	return func(o *copyOpts) error {
   219  		o.validateName = validate
   220  		return nil
   221  	}
   222  }
   223  
   224  // WithUserAgent set the user agent string in http communications
   225  func WithUserAgent(agent string) CopyOpt {
   226  	return func(o *copyOpts) error {
   227  		o.userAgent = agent
   228  		return nil
   229  	}
   230  }
   231  
   232  // WithLayerDescriptors passes the slice of Descriptors for layers to the
   233  // provided func. If the passed parameter is nil, returns an error.
   234  func WithLayerDescriptors(save func([]ocispec.Descriptor)) CopyOpt {
   235  	return func(o *copyOpts) error {
   236  		if save == nil {
   237  			return errors.New("layers save func must be non-nil")
   238  		}
   239  		o.saveLayers = save
   240  		return nil
   241  	}
   242  }
   243  
   244  // WithRootManifest passes the root manifest for the artifacts to the provided
   245  // func. If the passed parameter is nil, returns an error.
   246  func WithRootManifest(save func(b []byte)) CopyOpt {
   247  	return func(o *copyOpts) error {
   248  		if save == nil {
   249  			return errors.New("manifest save func must be non-nil")
   250  		}
   251  		o.saveManifest = save
   252  		return nil
   253  	}
   254  }