1 package handlers
2
3 import (
4 "fmt"
5 "net/http"
6 "net/url"
7
8 "github.com/distribution/reference"
9 "github.com/docker/distribution"
10 dcontext "github.com/docker/distribution/context"
11 "github.com/docker/distribution/registry/api/errcode"
12 v2 "github.com/docker/distribution/registry/api/v2"
13 "github.com/docker/distribution/registry/storage"
14 "github.com/gorilla/handlers"
15 "github.com/opencontainers/go-digest"
16 )
17
18
19
20 func blobUploadDispatcher(ctx *Context, r *http.Request) http.Handler {
21 buh := &blobUploadHandler{
22 Context: ctx,
23 UUID: getUploadUUID(ctx),
24 }
25
26 handler := handlers.MethodHandler{
27 "GET": http.HandlerFunc(buh.GetUploadStatus),
28 "HEAD": http.HandlerFunc(buh.GetUploadStatus),
29 }
30
31 if !ctx.readOnly {
32 handler["POST"] = http.HandlerFunc(buh.StartBlobUpload)
33 handler["PATCH"] = http.HandlerFunc(buh.PatchBlobData)
34 handler["PUT"] = http.HandlerFunc(buh.PutBlobUploadComplete)
35 handler["DELETE"] = http.HandlerFunc(buh.CancelBlobUpload)
36 }
37
38 if buh.UUID != "" {
39 state, err := hmacKey(ctx.Config.HTTP.Secret).unpackUploadState(r.FormValue("_state"))
40 if err != nil {
41 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
42 dcontext.GetLogger(ctx).Infof("error resolving upload: %v", err)
43 buh.Errors = append(buh.Errors, v2.ErrorCodeBlobUploadInvalid.WithDetail(err))
44 })
45 }
46 buh.State = state
47
48 if state.Name != ctx.Repository.Named().Name() {
49 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
50 dcontext.GetLogger(ctx).Infof("mismatched repository name in upload state: %q != %q", state.Name, buh.Repository.Named().Name())
51 buh.Errors = append(buh.Errors, v2.ErrorCodeBlobUploadInvalid.WithDetail(err))
52 })
53 }
54
55 if state.UUID != buh.UUID {
56 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
57 dcontext.GetLogger(ctx).Infof("mismatched uuid in upload state: %q != %q", state.UUID, buh.UUID)
58 buh.Errors = append(buh.Errors, v2.ErrorCodeBlobUploadInvalid.WithDetail(err))
59 })
60 }
61
62 blobs := ctx.Repository.Blobs(buh)
63 upload, err := blobs.Resume(buh, buh.UUID)
64 if err != nil {
65 dcontext.GetLogger(ctx).Errorf("error resolving upload: %v", err)
66 if err == distribution.ErrBlobUploadUnknown {
67 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
68 buh.Errors = append(buh.Errors, v2.ErrorCodeBlobUploadUnknown.WithDetail(err))
69 })
70 }
71
72 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
73 buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
74 })
75 }
76 buh.Upload = upload
77
78 if size := upload.Size(); size != buh.State.Offset {
79 defer upload.Close()
80 dcontext.GetLogger(ctx).Errorf("upload resumed at wrong offest: %d != %d", size, buh.State.Offset)
81 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
82 buh.Errors = append(buh.Errors, v2.ErrorCodeBlobUploadInvalid.WithDetail(err))
83 upload.Cancel(buh)
84 })
85 }
86 return closeResources(handler, buh.Upload)
87 }
88
89 return handler
90 }
91
92
93 type blobUploadHandler struct {
94 *Context
95
96
97
98 UUID string
99
100 Upload distribution.BlobWriter
101
102 State blobUploadState
103 }
104
105
106
107 func (buh *blobUploadHandler) StartBlobUpload(w http.ResponseWriter, r *http.Request) {
108 var options []distribution.BlobCreateOption
109
110 fromRepo := r.FormValue("from")
111 mountDigest := r.FormValue("mount")
112
113 if mountDigest != "" && fromRepo != "" {
114 opt, err := buh.createBlobMountOption(fromRepo, mountDigest)
115 if opt != nil && err == nil {
116 options = append(options, opt)
117 }
118 }
119
120 blobs := buh.Repository.Blobs(buh)
121 upload, err := blobs.Create(buh, options...)
122
123 if err != nil {
124 if ebm, ok := err.(distribution.ErrBlobMounted); ok {
125 if err := buh.writeBlobCreatedHeaders(w, ebm.Descriptor); err != nil {
126 buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
127 }
128 } else if err == distribution.ErrUnsupported {
129 buh.Errors = append(buh.Errors, errcode.ErrorCodeUnsupported)
130 } else {
131 buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
132 }
133 return
134 }
135
136 buh.Upload = upload
137
138 if err := buh.blobUploadResponse(w, r, true); err != nil {
139 buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
140 return
141 }
142
143 w.Header().Set("Docker-Upload-UUID", buh.Upload.ID())
144 w.WriteHeader(http.StatusAccepted)
145 }
146
147
148 func (buh *blobUploadHandler) GetUploadStatus(w http.ResponseWriter, r *http.Request) {
149 if buh.Upload == nil {
150 buh.Errors = append(buh.Errors, v2.ErrorCodeBlobUploadUnknown)
151 return
152 }
153
154
155
156
157 if err := buh.blobUploadResponse(w, r, true); err != nil {
158 buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
159 return
160 }
161
162 w.Header().Set("Docker-Upload-UUID", buh.UUID)
163 w.WriteHeader(http.StatusNoContent)
164 }
165
166
167 func (buh *blobUploadHandler) PatchBlobData(w http.ResponseWriter, r *http.Request) {
168 if buh.Upload == nil {
169 buh.Errors = append(buh.Errors, v2.ErrorCodeBlobUploadUnknown)
170 return
171 }
172
173 ct := r.Header.Get("Content-Type")
174 if ct != "" && ct != "application/octet-stream" {
175 buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(fmt.Errorf("bad Content-Type")))
176
177 return
178 }
179
180
181
182 if err := copyFullPayload(buh, w, r, buh.Upload, -1, "blob PATCH"); err != nil {
183 buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err.Error()))
184 return
185 }
186
187 if err := buh.blobUploadResponse(w, r, false); err != nil {
188 buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
189 return
190 }
191
192 w.WriteHeader(http.StatusAccepted)
193 }
194
195
196
197
198
199
200 func (buh *blobUploadHandler) PutBlobUploadComplete(w http.ResponseWriter, r *http.Request) {
201 if buh.Upload == nil {
202 buh.Errors = append(buh.Errors, v2.ErrorCodeBlobUploadUnknown)
203 return
204 }
205
206 dgstStr := r.FormValue("digest")
207
208 if dgstStr == "" {
209
210 buh.Errors = append(buh.Errors, v2.ErrorCodeDigestInvalid.WithDetail("digest missing"))
211 return
212 }
213
214 dgst, err := digest.Parse(dgstStr)
215 if err != nil {
216
217 buh.Errors = append(buh.Errors, v2.ErrorCodeDigestInvalid.WithDetail("digest parsing failed"))
218 return
219 }
220
221 if err := copyFullPayload(buh, w, r, buh.Upload, -1, "blob PUT"); err != nil {
222 buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err.Error()))
223 return
224 }
225
226 desc, err := buh.Upload.Commit(buh, distribution.Descriptor{
227 Digest: dgst,
228
229
230
231
232 })
233
234 if err != nil {
235 switch err := err.(type) {
236 case distribution.ErrBlobInvalidDigest:
237 buh.Errors = append(buh.Errors, v2.ErrorCodeDigestInvalid.WithDetail(err))
238 case errcode.Error:
239 buh.Errors = append(buh.Errors, err)
240 default:
241 switch err {
242 case distribution.ErrAccessDenied:
243 buh.Errors = append(buh.Errors, errcode.ErrorCodeDenied)
244 case distribution.ErrUnsupported:
245 buh.Errors = append(buh.Errors, errcode.ErrorCodeUnsupported)
246 case distribution.ErrBlobInvalidLength, distribution.ErrBlobDigestUnsupported:
247 buh.Errors = append(buh.Errors, v2.ErrorCodeBlobUploadInvalid.WithDetail(err))
248 default:
249 dcontext.GetLogger(buh).Errorf("unknown error completing upload: %v", err)
250 buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
251 }
252
253 }
254
255
256 if err := buh.Upload.Cancel(buh); err != nil {
257
258 dcontext.GetLogger(buh).Errorf("error canceling upload after error: %v", err)
259 }
260
261 return
262 }
263 if err := buh.writeBlobCreatedHeaders(w, desc); err != nil {
264 buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
265 return
266 }
267 }
268
269
270 func (buh *blobUploadHandler) CancelBlobUpload(w http.ResponseWriter, r *http.Request) {
271 if buh.Upload == nil {
272 buh.Errors = append(buh.Errors, v2.ErrorCodeBlobUploadUnknown)
273 return
274 }
275
276 w.Header().Set("Docker-Upload-UUID", buh.UUID)
277 if err := buh.Upload.Cancel(buh); err != nil {
278 dcontext.GetLogger(buh).Errorf("error encountered canceling upload: %v", err)
279 buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
280 }
281
282 w.WriteHeader(http.StatusNoContent)
283 }
284
285
286
287
288
289
290 func (buh *blobUploadHandler) blobUploadResponse(w http.ResponseWriter, r *http.Request, fresh bool) error {
291
292 buh.State.Name = buh.Repository.Named().Name()
293 buh.State.UUID = buh.Upload.ID()
294 buh.Upload.Close()
295 buh.State.Offset = buh.Upload.Size()
296 buh.State.StartedAt = buh.Upload.StartedAt()
297
298 token, err := hmacKey(buh.Config.HTTP.Secret).packUploadState(buh.State)
299 if err != nil {
300 dcontext.GetLogger(buh).Infof("error building upload state token: %s", err)
301 return err
302 }
303
304 uploadURL, err := buh.urlBuilder.BuildBlobUploadChunkURL(
305 buh.Repository.Named(), buh.Upload.ID(),
306 url.Values{
307 "_state": []string{token},
308 })
309 if err != nil {
310 dcontext.GetLogger(buh).Infof("error building upload url: %s", err)
311 return err
312 }
313
314 endRange := buh.Upload.Size()
315 if endRange > 0 {
316 endRange = endRange - 1
317 }
318
319 w.Header().Set("Docker-Upload-UUID", buh.UUID)
320 w.Header().Set("Location", uploadURL)
321
322 w.Header().Set("Content-Length", "0")
323 w.Header().Set("Range", fmt.Sprintf("0-%d", endRange))
324
325 return nil
326 }
327
328
329
330
331 func (buh *blobUploadHandler) createBlobMountOption(fromRepo, mountDigest string) (distribution.BlobCreateOption, error) {
332 dgst, err := digest.Parse(mountDigest)
333 if err != nil {
334 return nil, err
335 }
336
337 ref, err := reference.WithName(fromRepo)
338 if err != nil {
339 return nil, err
340 }
341
342 canonical, err := reference.WithDigest(ref, dgst)
343 if err != nil {
344 return nil, err
345 }
346
347 return storage.WithMountFrom(canonical), nil
348 }
349
350
351
352
353 func (buh *blobUploadHandler) writeBlobCreatedHeaders(w http.ResponseWriter, desc distribution.Descriptor) error {
354 ref, err := reference.WithDigest(buh.Repository.Named(), desc.Digest)
355 if err != nil {
356 return err
357 }
358 blobURL, err := buh.urlBuilder.BuildBlobURL(ref)
359 if err != nil {
360 return err
361 }
362
363 w.Header().Set("Location", blobURL)
364 w.Header().Set("Content-Length", "0")
365 w.Header().Set("Docker-Content-Digest", desc.Digest.String())
366 w.WriteHeader(http.StatusCreated)
367 return nil
368 }
369
View as plain text