diff options
Diffstat (limited to 'vendor/cloud.google.com/go/storage/reader.go')
-rw-r--r-- | vendor/cloud.google.com/go/storage/reader.go | 48 |
1 files changed, 36 insertions, 12 deletions
diff --git a/vendor/cloud.google.com/go/storage/reader.go b/vendor/cloud.google.com/go/storage/reader.go index c0f26bf..1fdc197 100644 --- a/vendor/cloud.google.com/go/storage/reader.go +++ b/vendor/cloud.google.com/go/storage/reader.go @@ -25,6 +25,7 @@ import ( "reflect" "strconv" "strings" + "time" "cloud.google.com/go/internal/trace" "golang.org/x/net/context" @@ -130,7 +131,11 @@ func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64) if err != nil { return nil, err } - var size int64 // total size of object, even if a range was requested. + var ( + size int64 // total size of object, even if a range was requested. + checkCRC bool + crc uint32 + ) if res.StatusCode == http.StatusPartialContent { cr := strings.TrimSpace(res.Header.Get("Content-Range")) if !strings.HasPrefix(cr, "bytes ") || !strings.Contains(cr, "/") { @@ -143,6 +148,18 @@ func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64) } } else { size = res.ContentLength + // Check the CRC iff all of the following hold: + // - We asked for content (length != 0). + // - We got all the content (status != PartialContent). + // - The server sent a CRC header. + // - The Go http stack did not uncompress the file. + // - We were not served compressed data that was uncompressed on download. + // The problem with the last two cases is that the CRC will not match -- GCS + // computes it on the compressed contents, but we compute it on the + // uncompressed contents. + if length != 0 && !goHTTPUncompressed(res) && !uncompressedByServer(res) { + crc, checkCRC = parseCRC32c(res) + } } remain := res.ContentLength @@ -152,14 +169,6 @@ func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64) body.Close() body = emptyBody } - var ( - checkCRC bool - crc uint32 - ) - // Even if there is a CRC header, we can't compute the hash on partial data. - if remain == size { - crc, checkCRC = parseCRC32c(res) - } return &Reader{ body: body, size: size, @@ -167,12 +176,20 @@ func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64) contentType: res.Header.Get("Content-Type"), contentEncoding: res.Header.Get("Content-Encoding"), cacheControl: res.Header.Get("Cache-Control"), + lastModified: res.Header.Get("Last-Modified"), wantCRC: crc, checkCRC: checkCRC, reopen: reopen, }, nil } +func uncompressedByServer(res *http.Response) bool { + // If the data is stored as gzip but is not encoded as gzip, then it + // was uncompressed by the server. + return res.Header.Get("X-Goog-Stored-Content-Encoding") == "gzip" && + res.Header.Get("Content-Encoding") != "gzip" +} + func parseCRC32c(res *http.Response) (uint32, bool) { const prefix = "crc32c=" for _, spec := range res.Header["X-Goog-Hash"] { @@ -200,10 +217,10 @@ type Reader struct { contentType string contentEncoding string cacheControl string + lastModified string checkCRC bool // should we check the CRC? wantCRC uint32 // the CRC32c value the server sent in the header gotCRC uint32 // running crc - checkedCRC bool // did we check the CRC? (For tests.) reopen func(seen int64) (*http.Response, error) } @@ -222,8 +239,7 @@ func (r *Reader) Read(p []byte) (int, error) { // Check CRC here. It would be natural to check it in Close, but // everybody defers Close on the assumption that it doesn't return // anything worth looking at. - if r.remain == 0 { // Only check if we have Content-Length. - r.checkedCRC = true + if err == io.EOF { if r.gotCRC != r.wantCRC { return n, fmt.Errorf("storage: bad CRC on read: got %d, want %d", r.gotCRC, r.wantCRC) @@ -288,3 +304,11 @@ func (r *Reader) ContentEncoding() string { func (r *Reader) CacheControl() string { return r.cacheControl } + +// LastModified returns the value of the Last-Modified header. +func (r *Reader) LastModified() (time.Time, error) { + if r.lastModified == "" { + return time.Time{}, errors.New("storage: no Last-Modified header") + } + return http.ParseTime(r.lastModified) +} |