...
1
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
61
62
63
64 func ValidateNameAsPath(desc ocispec.Descriptor) error {
65
66 path, ok := orascontent.ResolveName(desc)
67 if !ok || path == "" {
68 return orascontent.ErrNoName
69 }
70
71
72 if target := filepath.ToSlash(filepath.Clean(path)); target != path {
73 return errors.Wrap(ErrDirtyPath, path)
74 }
75
76
77 if strings.Contains(path, "\\") {
78 return errors.Wrap(ErrPathNotSlashSeparated, path)
79 }
80
81
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
93 if strings.HasPrefix(path, "../") || path == ".." {
94 return errors.Wrap(ErrPathTraversalDisallowed, path)
95 }
96
97 return nil
98 }
99
100
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
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
121 return true
122 }
123
124
125
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
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
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
150 func WithPullByBFS(o *copyOpts) error {
151 o.dispatch = dispatchBFS
152 return nil
153 }
154
155
156
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
165
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
174
175 func WithContentStore(store orascontent.Store) CopyOpt {
176 return func(o *copyOpts) error {
177 o.contentProvideIngesterPusherFetcher = store
178 return nil
179 }
180 }
181
182
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
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
216
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
225 func WithUserAgent(agent string) CopyOpt {
226 return func(o *copyOpts) error {
227 o.userAgent = agent
228 return nil
229 }
230 }
231
232
233
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
245
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 }
255
View as plain text