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 ©Opts{ 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 }