/* * Minio Cloud Storage, (C) 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package b2 import ( "fmt" "io" "net/http" "net/url" "strconv" "strings" "time" "github.com/minio/minio/pkg/errors" minio "github.com/minio/minio/cmd" ) // mkRange converts offset, size into Range header equivalent. func mkRange(offset, size int64) string { if offset == 0 && size == 0 { return "" } if size == 0 { return fmt.Sprintf("bytes=%d-", offset) } return fmt.Sprintf("bytes=%d-%d", offset, offset+size-1) } // AnonGetObject - performs a plain http GET request on a public resource, // fails if the resource is not public. func (l *b2Objects) AnonGetObject(bucket string, object string, startOffset int64, length int64, writer io.Writer) error { uri := fmt.Sprintf("%s/file/%s/%s", l.b2Client.DownloadURI, bucket, object) req, err := http.NewRequest("GET", uri, nil) if err != nil { return b2ToObjectError(errors.Trace(err), bucket, object) } rng := mkRange(startOffset, length) if rng != "" { req.Header.Set("Range", rng) } resp, err := l.anonClient.Do(req) if err != nil { return b2ToObjectError(errors.Trace(err), bucket, object) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return b2ToObjectError(errors.Trace(fmt.Errorf(resp.Status)), bucket, object) } _, err = io.Copy(writer, resp.Body) return b2ToObjectError(errors.Trace(err), bucket, object) } // Converts http Header into ObjectInfo. This function looks for all the // standard Backblaze B2 headers to convert into ObjectInfo. // // Content-Length is converted to Size. // X-Bz-Upload-Timestamp is converted to ModTime. // X-Bz-Info-
: is converted to
: // Content-Type is converted to ContentType. // X-Bz-Content-Sha1 is converted to ETag. func headerToObjectInfo(bucket, object string, header http.Header) (objInfo minio.ObjectInfo, err error) { clen, err := strconv.ParseInt(header.Get("Content-Length"), 10, 64) if err != nil { return objInfo, b2ToObjectError(errors.Trace(err), bucket, object) } // Converting upload timestamp in milliseconds to a time.Time value for ObjectInfo.ModTime. timeStamp, err := strconv.ParseInt(header.Get("X-Bz-Upload-Timestamp"), 10, 64) if err != nil { return objInfo, b2ToObjectError(errors.Trace(err), bucket, object) } // Populate user metadata by looking for all the X-Bz-Info- // HTTP headers, ignore other headers since they have their own // designated meaning, for more details refer B2 API documentation. userMetadata := make(map[string]string) for key := range header { if strings.HasPrefix(key, "X-Bz-Info-") { var name string name, err = url.QueryUnescape(strings.TrimPrefix(key, "X-Bz-Info-")) if err != nil { return objInfo, b2ToObjectError(errors.Trace(err), bucket, object) } var val string val, err = url.QueryUnescape(header.Get(key)) if err != nil { return objInfo, b2ToObjectError(errors.Trace(err), bucket, object) } userMetadata[name] = val } } return minio.ObjectInfo{ Bucket: bucket, Name: object, ContentType: header.Get("Content-Type"), ModTime: time.Unix(0, 0).Add(time.Duration(timeStamp) * time.Millisecond), Size: clen, ETag: header.Get("X-Bz-File-Id"), UserDefined: userMetadata, }, nil } // AnonGetObjectInfo - performs a plain http HEAD request on a public resource, // fails if the resource is not public. func (l *b2Objects) AnonGetObjectInfo(bucket string, object string) (objInfo minio.ObjectInfo, err error) { uri := fmt.Sprintf("%s/file/%s/%s", l.b2Client.DownloadURI, bucket, object) req, err := http.NewRequest("HEAD", uri, nil) if err != nil { return objInfo, b2ToObjectError(errors.Trace(err), bucket, object) } resp, err := l.anonClient.Do(req) if err != nil { return objInfo, b2ToObjectError(errors.Trace(err), bucket, object) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return objInfo, b2ToObjectError(errors.Trace(fmt.Errorf(resp.Status)), bucket, object) } return headerToObjectInfo(bucket, object, resp.Header) }