Skip to content

Commit 4dd6f6e

Browse files
committed
Don't silently ignore errors determining size in TryReuseBlob
When looking for inexact matches, this will cause the matches to be skipped. When checking for an exact match, this will cause an upload failure; we don't have any other way to handle pre-existing blobs on the destination. Signed-off-by: Miloslav Trmač <mitr@redhat.com>
1 parent 99996a1 commit 4dd6f6e

File tree

3 files changed

+63
-8
lines changed

3 files changed

+63
-8
lines changed

docker/docker_client.go

+23-7
Original file line numberDiff line numberDiff line change
@@ -998,20 +998,33 @@ func (c *dockerClient) getExternalBlob(ctx context.Context, urls []string) (io.R
998998
resp.Body.Close()
999999
continue
10001000
}
1001-
return resp.Body, getBlobSize(resp), nil
1001+
1002+
size, err := getBlobSize(resp)
1003+
if err != nil {
1004+
size = -1
1005+
}
1006+
return resp.Body, size, nil
10021007
}
10031008
if remoteErrors == nil {
10041009
return nil, 0, nil // fallback to non-external blob
10051010
}
10061011
return nil, 0, fmt.Errorf("failed fetching external blob from all urls: %w", multierr.Format("", ", ", "", remoteErrors))
10071012
}
10081013

1009-
func getBlobSize(resp *http.Response) int64 {
1010-
size, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
1011-
if err != nil {
1012-
size = -1
1014+
func getBlobSize(resp *http.Response) (int64, error) {
1015+
hdrs := resp.Header.Values("Content-Length")
1016+
if len(hdrs) == 0 {
1017+
return -1, errors.New(`Missing "Content-Length" header in response`)
10131018
}
1014-
return size
1019+
hdr := hdrs[0] // Equivalent to resp.Header.Get(…)
1020+
size, err := strconv.ParseInt(hdr, 10, 64)
1021+
if err != nil { // Go’s response reader should already reject such values.
1022+
return -1, err
1023+
}
1024+
if size < 0 { // '-' is not a valid character in Content-Length, so negative values are invalid. Go’s response reader should already reject such values.
1025+
return -1, fmt.Errorf(`Invalid negative "Content-Length" %q`, hdr)
1026+
}
1027+
return size, nil
10151028
}
10161029

10171030
// getBlob returns a stream for the specified blob in ref, and the blob’s size (or -1 if unknown).
@@ -1042,7 +1055,10 @@ func (c *dockerClient) getBlob(ctx context.Context, ref dockerReference, info ty
10421055
return nil, 0, fmt.Errorf("fetching blob: %w", err)
10431056
}
10441057
cache.RecordKnownLocation(ref.Transport(), bicTransportScope(ref), info.Digest, newBICLocationReference(ref))
1045-
blobSize := getBlobSize(res)
1058+
blobSize, err := getBlobSize(res)
1059+
if err != nil {
1060+
blobSize = -1
1061+
}
10461062

10471063
reconnectingReader, err := newBodyReader(ctx, c, path, res.Body)
10481064
if err != nil {

docker/docker_client_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,41 @@ func TestParseRegistryWarningHeader(t *testing.T) {
338338
}
339339
}
340340

341+
func TestGetBlobSize(t *testing.T) {
342+
for _, c := range []struct {
343+
headers []string
344+
expected int64 // -1 if error expected
345+
}{
346+
{[]string{}, -1},
347+
{[]string{"0"}, 0},
348+
{[]string{"1"}, 1},
349+
{[]string{"0777"}, 777}, // Not interpreted as octal
350+
{[]string{"x"}, -1}, // Not a number: Go's response reader rejects such responses.
351+
{[]string{"1", "2"}, -1}, // Ambiguous: Go's response reader rejects such responses.
352+
{[]string{""}, -1}, // Empty header: Go's response reader rejects such responses.
353+
{[]string{"-1"}, -1}, // Negative: Go's response reader rejects such responses.
354+
} {
355+
var buf bytes.Buffer
356+
buf.WriteString("HTTP/1.1 200 OK\r\n")
357+
for _, v := range c.headers {
358+
buf.WriteString("Content-Length: " + v + "\r\n")
359+
}
360+
buf.WriteString("\r\n")
361+
resp, err := http.ReadResponse(bufio.NewReader(&buf), nil)
362+
if err != nil {
363+
assert.Equal(t, int64(-1), c.expected)
364+
} else {
365+
res, err := getBlobSize(resp)
366+
if c.expected == -1 {
367+
assert.Error(t, err, c.headers)
368+
} else {
369+
require.NoError(t, err, c.headers)
370+
assert.Equal(t, c.expected, res)
371+
}
372+
}
373+
}
374+
}
375+
341376
func TestIsManifestUnknownError(t *testing.T) {
342377
// Mostly a smoke test; we can add more registries here if they need special handling.
343378

docker/docker_image_dest.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,12 @@ func (d *dockerImageDestination) blobExists(ctx context.Context, repo reference.
243243
defer res.Body.Close()
244244
switch res.StatusCode {
245245
case http.StatusOK:
246+
size, err := getBlobSize(res)
247+
if err != nil {
248+
return false, -1, fmt.Errorf("determining size of blob %s in %s: %w", digest, repo.Name(), err)
249+
}
246250
logrus.Debugf("... already exists")
247-
return true, getBlobSize(res), nil
251+
return true, size, nil
248252
case http.StatusUnauthorized:
249253
logrus.Debugf("... not authorized")
250254
return false, -1, fmt.Errorf("checking whether a blob %s exists in %s: %w", digest, repo.Name(), registryHTTPResponseToError(res))

0 commit comments

Comments
 (0)