1/*
2 * MinIO Go Library for Amazon S3 Compatible Cloud Storage
3 * Copyright 2020 MinIO, Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package minio
19
20import (
21	"bytes"
22	"context"
23	"encoding/xml"
24	"io/ioutil"
25	"net/http"
26	"net/url"
27
28	"github.com/minio/minio-go/v6/pkg/s3utils"
29	"github.com/minio/minio-go/v6/pkg/tags"
30)
31
32// PutObjectTagging replaces or creates object tag(s)
33func (c Client) PutObjectTagging(bucketName, objectName string, objectTags map[string]string) error {
34	return c.PutObjectTaggingWithContext(context.Background(), bucketName, objectName, objectTags)
35}
36
37// PutObjectTaggingWithContext replaces or creates object tag(s) with a context to control cancellations
38// and timeouts.
39func (c Client) PutObjectTaggingWithContext(ctx context.Context, bucketName, objectName string, objectTags map[string]string) error {
40	// Input validation.
41	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
42		return err
43	}
44
45	// Get resources properly escaped and lined up before
46	// using them in http request.
47	urlValues := make(url.Values)
48	urlValues.Set("tagging", "")
49
50	tags, err := tags.NewTags(objectTags, true)
51	if err != nil {
52		return err
53	}
54
55	reqBytes, err := xml.Marshal(tags)
56	if err != nil {
57		return err
58	}
59
60	reqMetadata := requestMetadata{
61		bucketName:       bucketName,
62		objectName:       objectName,
63		queryValues:      urlValues,
64		contentBody:      bytes.NewReader(reqBytes),
65		contentLength:    int64(len(reqBytes)),
66		contentMD5Base64: sumMD5Base64(reqBytes),
67	}
68
69	// Execute PUT to set a object tagging.
70	resp, err := c.executeMethod(ctx, "PUT", reqMetadata)
71	defer closeResponse(resp)
72	if err != nil {
73		return err
74	}
75	if resp != nil {
76		if resp.StatusCode != http.StatusOK {
77			return httpRespToErrorResponse(resp, bucketName, objectName)
78		}
79	}
80	return nil
81}
82
83// GetObjectTagging fetches object tag(s)
84func (c Client) GetObjectTagging(bucketName, objectName string) (string, error) {
85	return c.GetObjectTaggingWithContext(context.Background(), bucketName, objectName)
86}
87
88// GetObjectTaggingWithContext fetches object tag(s) with a context to control cancellations
89// and timeouts.
90func (c Client) GetObjectTaggingWithContext(ctx context.Context, bucketName, objectName string) (string, error) {
91	// Get resources properly escaped and lined up before
92	// using them in http request.
93	urlValues := make(url.Values)
94	urlValues.Set("tagging", "")
95
96	// Execute GET on object to get object tag(s)
97	resp, err := c.executeMethod(ctx, "GET", requestMetadata{
98		bucketName:  bucketName,
99		objectName:  objectName,
100		queryValues: urlValues,
101	})
102
103	defer closeResponse(resp)
104	if err != nil {
105		return "", err
106	}
107
108	if resp != nil {
109		if resp.StatusCode != http.StatusOK {
110			return "", httpRespToErrorResponse(resp, bucketName, objectName)
111		}
112	}
113
114	tagBuf, err := ioutil.ReadAll(resp.Body)
115	if err != nil {
116		return "", err
117	}
118
119	return string(tagBuf), err
120}
121
122// RemoveObjectTagging deletes object tag(s)
123func (c Client) RemoveObjectTagging(bucketName, objectName string) error {
124	return c.RemoveObjectTaggingWithContext(context.Background(), bucketName, objectName)
125}
126
127// RemoveObjectTaggingWithContext removes object tag(s) with a context to control cancellations
128// and timeouts.
129func (c Client) RemoveObjectTaggingWithContext(ctx context.Context, bucketName, objectName string) error {
130	// Get resources properly escaped and lined up before
131	// using them in http request.
132	urlValues := make(url.Values)
133	urlValues.Set("tagging", "")
134
135	// Execute DELETE on object to remove object tag(s)
136	resp, err := c.executeMethod(ctx, "DELETE", requestMetadata{
137		bucketName:  bucketName,
138		objectName:  objectName,
139		queryValues: urlValues,
140	})
141
142	defer closeResponse(resp)
143	if err != nil {
144		return err
145	}
146
147	if resp != nil {
148		// S3 returns "204 No content" after Object tag deletion.
149		if resp.StatusCode != http.StatusNoContent {
150			return httpRespToErrorResponse(resp, bucketName, objectName)
151		}
152	}
153	return err
154}
155