From 891fe58a27929c0e1ec9b73de58a37be3a514ab6 Mon Sep 17 00:00:00 2001 From: Reed Chan Date: Mon, 16 Jan 2023 10:00:10 +0800 Subject: [PATCH] feat(#1755): GetObjectOptions support passing in query strings; This allows you to pass in the response headers that you need to override when calling the GetObject method. --- .gitignore | 3 +- api-get-object.go | 12 +-- api-get-options.go | 52 +++++++++++++ examples/s3/getobject-override-respheaders.go | 75 +++++++++++++++++++ utils.go | 17 +++++ 5 files changed, 147 insertions(+), 12 deletions(-) create mode 100644 examples/s3/getobject-override-respheaders.go diff --git a/.gitignore b/.gitignore index 12ecd6ae9..8ae0384eb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ *.test validator golangci-lint -functional_tests \ No newline at end of file +functional_tests +.idea \ No newline at end of file diff --git a/api-get-object.go b/api-get-object.go index b17f3146b..e31e4cf92 100644 --- a/api-get-object.go +++ b/api-get-object.go @@ -23,8 +23,6 @@ import ( "fmt" "io" "net/http" - "net/url" - "strconv" "sync" "github.com/minio/minio-go/v7/pkg/s3utils" @@ -654,19 +652,11 @@ func (c *Client) getObject(ctx context.Context, bucketName, objectName string, o return nil, ObjectInfo{}, nil, err } - urlValues := make(url.Values) - if opts.VersionID != "" { - urlValues.Set("versionId", opts.VersionID) - } - if opts.PartNumber > 0 { - urlValues.Set("partNumber", strconv.Itoa(opts.PartNumber)) - } - // Execute GET on objectName. resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{ bucketName: bucketName, objectName: objectName, - queryValues: urlValues, + queryValues: opts.toQueryValues(), customHeader: opts.Header(), contentSHA256Hex: emptySHA256Hex, }) diff --git a/api-get-options.go b/api-get-options.go index 0082c1fa7..bb86a5994 100644 --- a/api-get-options.go +++ b/api-get-options.go @@ -20,6 +20,8 @@ package minio import ( "fmt" "net/http" + "net/url" + "strconv" "time" "github.com/minio/minio-go/v7/pkg/encrypt" @@ -36,6 +38,7 @@ type AdvancedGetOptions struct { // during GET requests. type GetObjectOptions struct { headers map[string]string + reqParams url.Values ServerSideEncryption encrypt.ServerSide VersionID string PartNumber int @@ -83,6 +86,34 @@ func (o *GetObjectOptions) Set(key, value string) { o.headers[http.CanonicalHeaderKey(key)] = value } +// SetReqParam - set request query string parameter +// supported key: see supportedQueryValues. +// If an unsupported key is passed in, it will be ignored and nothing will be done. +func (o *GetObjectOptions) SetReqParam(key, value string) { + if !isStandardQueryValue(key) { + // do nothing + return + } + if o.reqParams == nil { + o.reqParams = make(url.Values) + } + o.reqParams.Set(key, value) +} + +// AddReqParam - add request query string parameter +// supported key: see supportedQueryValues. +// If an unsupported key is passed in, it will be ignored and nothing will be done. +func (o *GetObjectOptions) AddReqParam(key, value string) { + if !isStandardQueryValue(key) { + // do nothing + return + } + if o.reqParams == nil { + o.reqParams = make(url.Values) + } + o.reqParams.Add(key, value) +} + // SetMatchETag - set match etag. func (o *GetObjectOptions) SetMatchETag(etag string) error { if etag == "" { @@ -149,3 +180,24 @@ func (o *GetObjectOptions) SetRange(start, end int64) error { } return nil } + +// toQueryValues - Convert the versionId, partNumber, and reqParams in Options to query string parameters. +func (o *GetObjectOptions) toQueryValues() url.Values { + urlValues := make(url.Values) + if o.VersionID != "" { + urlValues.Set("versionId", o.VersionID) + } + if o.PartNumber > 0 { + urlValues.Set("partNumber", strconv.Itoa(o.PartNumber)) + } + + if o.reqParams != nil { + for key, values := range o.reqParams { + for _, value := range values { + urlValues.Add(key, value) + } + } + } + + return urlValues +} diff --git a/examples/s3/getobject-override-respheaders.go b/examples/s3/getobject-override-respheaders.go new file mode 100644 index 000000000..332c1efc3 --- /dev/null +++ b/examples/s3/getobject-override-respheaders.go @@ -0,0 +1,75 @@ +//go:build example +// +build example + +/* + * MinIO Go Library for Amazon S3 Compatible Cloud Storage + * Copyright 2015-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 s3 + +import ( + "os" +) + +func main() { + // Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY, my-bucketname, my-objectname and + // my-testfile are dummy values, please replace them with original values. + + // Requests are always secure (HTTPS) by default. Set secure=false to enable insecure (HTTP) access. + // This boolean value is the last argument for New(). + + // New returns an Amazon S3 compatible client object. API compatibility (v2 or v4) is automatically + // determined based on the Endpoint value. + s3Client, err := minio.New("s3.amazonaws.com", &minio.Options{ + Creds: credentials.NewStaticV4("YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", ""), + Secure: true, + }) + if err != nil { + log.Fatalln(err) + } + + // response-cache-control=ResponseCacheControl + // response-content-disposition=ResponseContentDisposition + // response-content-encoding=ResponseContentEncoding + // response-content-language=ResponseContentLanguage + // response-content-type=ResponseContentType + // response-expires=ResponseExpires + opt := minio.GetObjectOptions{} + opt.SetReqParam("response-content-disposition", `attachment; filename="testing.txt"`) + opt.SetReqParam("response-cache-control", "No-cache") + opt.SetReqParam("response-content-encoding", "x-gzip") + opt.SetReqParam("response-expires", "Mon, 02 Jan 2006 15:04:05 GMT") + reader, err := s3Client.GetObject(context.Background(), "my-bucketname", "my-objectname", opt) + if err != nil { + log.Fatalln(err) + } + defer reader.Close() + + localFile, err := os.Create("my-testfile") + if err != nil { + log.Fatalln(err) + } + defer localFile.Close() + + stat, err := reader.Stat() + if err != nil { + log.Fatalln(err) + } + + if _, err := io.CopyN(localFile, reader, stat.Size); err != nil { + log.Fatalln(err) + } +} diff --git a/utils.go b/utils.go index 7ce8ec5ca..bd901cec6 100644 --- a/utils.go +++ b/utils.go @@ -511,6 +511,23 @@ func isAmzHeader(headerKey string) bool { return strings.HasPrefix(key, "x-amz-meta-") || strings.HasPrefix(key, "x-amz-grant-") || key == "x-amz-acl" || isSSEHeader(headerKey) || strings.HasPrefix(key, "x-amz-checksum-") } +// supportedQueryValues is a list of query strings that can be passed in when using GetObject. +var supportedQueryValues = map[string]bool{ + "partNumber": true, + "versionId": true, + "response-cache-control": true, + "response-content-disposition": true, + "response-content-encoding": true, + "response-content-language": true, + "response-content-type": true, + "response-expires": true, +} + +// isStandardQueryValue will return true when the passed in query string parameter is supported rather than customised. +func isStandardQueryValue(qsKey string) bool { + return supportedQueryValues[qsKey] +} + var ( md5Pool = sync.Pool{New: func() interface{} { return md5.New() }} sha256Pool = sync.Pool{New: func() interface{} { return sha256.New() }}