Merge pull request #739 from stevvooe/etags-must-be-quoted
Etags must be quoted according to http specmaster
						commit
						b49d77a42f
					
				|  | @ -254,13 +254,14 @@ func (ms *manifests) Get(dgst digest.Digest) (*manifest.SignedManifest, error) { | |||
| 	return ms.GetByTag(dgst.String()) | ||||
| } | ||||
| 
 | ||||
| // AddEtagToTag allows a client to supply an eTag to GetByTag which will
 | ||||
| // be used for a conditional HTTP request.  If the eTag matches, a nil
 | ||||
| // manifest and nil error will be returned.
 | ||||
| func AddEtagToTag(tagName, dgst string) distribution.ManifestServiceOption { | ||||
| // AddEtagToTag allows a client to supply an eTag to GetByTag which will be
 | ||||
| // used for a conditional HTTP request.  If the eTag matches, a nil manifest
 | ||||
| // and nil error will be returned. etag is automatically quoted when added to
 | ||||
| // this map.
 | ||||
| func AddEtagToTag(tag, etag string) distribution.ManifestServiceOption { | ||||
| 	return func(ms distribution.ManifestService) error { | ||||
| 		if ms, ok := ms.(*manifests); ok { | ||||
| 			ms.etags[tagName] = dgst | ||||
| 			ms.etags[tag] = fmt.Sprintf(`"%s"`, etag) | ||||
| 			return nil | ||||
| 		} | ||||
| 		return fmt.Errorf("etag options is a client-only option") | ||||
|  |  | |||
|  | @ -463,7 +463,7 @@ func addTestManifestWithEtag(repo, reference string, content []byte, m *testutil | |||
| 		Method: "GET", | ||||
| 		Route:  "/v2/" + repo + "/manifests/" + reference, | ||||
| 		Headers: http.Header(map[string][]string{ | ||||
| 			"Etag": {dgst}, | ||||
| 			"Etag": {fmt.Sprintf(`"%s"`, dgst)}, | ||||
| 		}), | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -488,7 +488,7 @@ func testBlobAPI(t *testing.T, env *testEnv, args blobArgs) *testEnv { | |||
| 	checkHeaders(t, resp, http.Header{ | ||||
| 		"Content-Length":        []string{fmt.Sprint(layerLength)}, | ||||
| 		"Docker-Content-Digest": []string{canonicalDigest.String()}, | ||||
| 		"ETag":                  []string{canonicalDigest.String()}, | ||||
| 		"ETag":                  []string{fmt.Sprintf(`"%s"`, canonicalDigest)}, | ||||
| 		"Cache-Control":         []string{"max-age=31536000"}, | ||||
| 	}) | ||||
| 
 | ||||
|  | @ -499,6 +499,7 @@ func testBlobAPI(t *testing.T, env *testEnv, args blobArgs) *testEnv { | |||
| 		t.Fatalf("Error constructing request: %s", err) | ||||
| 	} | ||||
| 	req.Header.Set("If-None-Match", etag) | ||||
| 
 | ||||
| 	resp, err = http.DefaultClient.Do(req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Error constructing request: %s", err) | ||||
|  | @ -835,7 +836,7 @@ func testManifestAPI(t *testing.T, env *testEnv, args manifestArgs) (*testEnv, m | |||
| 	checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK) | ||||
| 	checkHeaders(t, resp, http.Header{ | ||||
| 		"Docker-Content-Digest": []string{dgst.String()}, | ||||
| 		"ETag":                  []string{dgst.String()}, | ||||
| 		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)}, | ||||
| 	}) | ||||
| 
 | ||||
| 	var fetchedManifest manifest.SignedManifest | ||||
|  | @ -857,7 +858,7 @@ func testManifestAPI(t *testing.T, env *testEnv, args manifestArgs) (*testEnv, m | |||
| 	checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK) | ||||
| 	checkHeaders(t, resp, http.Header{ | ||||
| 		"Docker-Content-Digest": []string{dgst.String()}, | ||||
| 		"ETag":                  []string{dgst.String()}, | ||||
| 		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)}, | ||||
| 	}) | ||||
| 
 | ||||
| 	var fetchedManifestByDigest manifest.SignedManifest | ||||
|  | @ -1301,12 +1302,12 @@ func checkHeaders(t *testing.T, resp *http.Response, headers http.Header) { | |||
| 		for _, v := range vs { | ||||
| 			if v == "*" { | ||||
| 				// Just ensure there is some value.
 | ||||
| 				if len(resp.Header[k]) > 0 { | ||||
| 				if len(resp.Header[http.CanonicalHeaderKey(k)]) > 0 { | ||||
| 					continue | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			for _, hv := range resp.Header[k] { | ||||
| 			for _, hv := range resp.Header[http.CanonicalHeaderKey(k)] { | ||||
| 				if hv != v { | ||||
| 					t.Fatalf("%+v %v header value not matched in response: %q != %q", resp.Header, k, hv, v) | ||||
| 				} | ||||
|  |  | |||
|  | @ -90,13 +90,13 @@ func (imh *imageManifestHandler) GetImageManifest(w http.ResponseWriter, r *http | |||
| 	w.Header().Set("Content-Type", "application/json; charset=utf-8") | ||||
| 	w.Header().Set("Content-Length", fmt.Sprint(len(sm.Raw))) | ||||
| 	w.Header().Set("Docker-Content-Digest", imh.Digest.String()) | ||||
| 	w.Header().Set("Etag", imh.Digest.String()) | ||||
| 	w.Header().Set("Etag", fmt.Sprintf(`"%s"`, imh.Digest)) | ||||
| 	w.Write(sm.Raw) | ||||
| } | ||||
| 
 | ||||
| func etagMatch(r *http.Request, etag string) bool { | ||||
| 	for _, headerVal := range r.Header["If-None-Match"] { | ||||
| 		if headerVal == etag { | ||||
| 		if headerVal == etag || headerVal == fmt.Sprintf(`"%s"`, etag) { // allow quoted or unquoted
 | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -47,7 +47,7 @@ func (bs *blobServer) ServeBlob(ctx context.Context, w http.ResponseWriter, r *h | |||
| 		} | ||||
| 		defer br.Close() | ||||
| 
 | ||||
| 		w.Header().Set("ETag", desc.Digest.String()) // If-None-Match handled by ServeContent
 | ||||
| 		w.Header().Set("ETag", fmt.Sprintf(`"%s"`, desc.Digest)) // If-None-Match handled by ServeContent
 | ||||
| 		w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%.f", blobCacheControlMaxAge.Seconds())) | ||||
| 
 | ||||
| 		if w.Header().Get("Docker-Content-Digest") == "" { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue