1/*
2 * MinIO Go Library for Amazon S3 Compatible Cloud Storage
3 * Copyright 2019-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	"fmt"
25	"net/http"
26	"net/url"
27	"time"
28
29	"github.com/minio/minio-go/v7/pkg/s3utils"
30)
31
32// objectRetention - object retention specified in
33// https://docs.aws.amazon.com/AmazonS3/latest/API/Type_API_ObjectLockConfiguration.html
34type objectRetention struct {
35	XMLNS           string        `xml:"xmlns,attr,omitempty"`
36	XMLName         xml.Name      `xml:"Retention"`
37	Mode            RetentionMode `xml:"Mode,omitempty"`
38	RetainUntilDate *time.Time    `type:"timestamp" timestampFormat:"iso8601" xml:"RetainUntilDate,omitempty"`
39}
40
41func newObjectRetention(mode *RetentionMode, date *time.Time) (*objectRetention, error) {
42	objectRetention := &objectRetention{}
43
44	if date != nil && !date.IsZero() {
45		objectRetention.RetainUntilDate = date
46	}
47	if mode != nil {
48		if !mode.IsValid() {
49			return nil, fmt.Errorf("invalid retention mode `%v`", mode)
50		}
51		objectRetention.Mode = *mode
52	}
53
54	return objectRetention, nil
55}
56
57// PutObjectRetentionOptions represents options specified by user for PutObject call
58type PutObjectRetentionOptions struct {
59	GovernanceBypass bool
60	Mode             *RetentionMode
61	RetainUntilDate  *time.Time
62	VersionID        string
63}
64
65// PutObjectRetention sets object retention for a given object and versionID.
66func (c Client) PutObjectRetention(ctx context.Context, bucketName, objectName string, opts PutObjectRetentionOptions) error {
67	// Input validation.
68	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
69		return err
70	}
71
72	if err := s3utils.CheckValidObjectName(objectName); err != nil {
73		return err
74	}
75
76	// Get resources properly escaped and lined up before
77	// using them in http request.
78	urlValues := make(url.Values)
79	urlValues.Set("retention", "")
80
81	if opts.VersionID != "" {
82		urlValues.Set("versionId", opts.VersionID)
83	}
84
85	retention, err := newObjectRetention(opts.Mode, opts.RetainUntilDate)
86	if err != nil {
87		return err
88	}
89
90	retentionData, err := xml.Marshal(retention)
91	if err != nil {
92		return err
93	}
94
95	// Build headers.
96	headers := make(http.Header)
97
98	if opts.GovernanceBypass {
99		// Set the bypass goverenance retention header
100		headers.Set(amzBypassGovernance, "true")
101	}
102
103	reqMetadata := requestMetadata{
104		bucketName:       bucketName,
105		objectName:       objectName,
106		queryValues:      urlValues,
107		contentBody:      bytes.NewReader(retentionData),
108		contentLength:    int64(len(retentionData)),
109		contentMD5Base64: sumMD5Base64(retentionData),
110		contentSHA256Hex: sum256Hex(retentionData),
111		customHeader:     headers,
112	}
113
114	// Execute PUT Object Retention.
115	resp, err := c.executeMethod(ctx, http.MethodPut, reqMetadata)
116	defer closeResponse(resp)
117	if err != nil {
118		return err
119	}
120	if resp != nil {
121		if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
122			return httpRespToErrorResponse(resp, bucketName, objectName)
123		}
124	}
125	return nil
126}
127
128// GetObjectRetention gets retention of given object.
129func (c Client) GetObjectRetention(ctx context.Context, bucketName, objectName, versionID string) (mode *RetentionMode, retainUntilDate *time.Time, err error) {
130	// Input validation.
131	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
132		return nil, nil, err
133	}
134
135	if err := s3utils.CheckValidObjectName(objectName); err != nil {
136		return nil, nil, err
137	}
138	urlValues := make(url.Values)
139	urlValues.Set("retention", "")
140	if versionID != "" {
141		urlValues.Set("versionId", versionID)
142	}
143	// Execute GET on bucket to list objects.
144	resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
145		bucketName:       bucketName,
146		objectName:       objectName,
147		queryValues:      urlValues,
148		contentSHA256Hex: emptySHA256Hex,
149	})
150	defer closeResponse(resp)
151	if err != nil {
152		return nil, nil, err
153	}
154	if resp != nil {
155		if resp.StatusCode != http.StatusOK {
156			return nil, nil, httpRespToErrorResponse(resp, bucketName, objectName)
157		}
158	}
159	retention := &objectRetention{}
160	if err = xml.NewDecoder(resp.Body).Decode(retention); err != nil {
161		return nil, nil, err
162	}
163
164	return &retention.Mode, retention.RetainUntilDate, nil
165}
166