aboutsummaryrefslogtreecommitdiff
path: root/weed/s3api/s3api_object_handlers_retention.go
blob: 9a6e7be32a88618b20947c9d931e03fae52b3509 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package s3api

import (
	"encoding/xml"
	"errors"
	"net/http"

	"github.com/seaweedfs/seaweedfs/weed/glog"
	"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
	"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
	stats_collect "github.com/seaweedfs/seaweedfs/weed/stats"
)

// PutObjectRetentionHandler Put object Retention
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectRetention.html
func (s3a *S3ApiServer) PutObjectRetentionHandler(w http.ResponseWriter, r *http.Request) {
	bucket, object := s3_constants.GetBucketAndObject(r)
	glog.V(3).Infof("PutObjectRetentionHandler %s %s", bucket, object)

	// Check if Object Lock is available for this bucket (requires versioning)
	if !s3a.handleObjectLockAvailabilityCheck(w, r, bucket, "PutObjectRetentionHandler") {
		return
	}

	// Get version ID from query parameters
	versionId := r.URL.Query().Get("versionId")

	// Check for bypass governance retention header
	bypassGovernance := r.Header.Get("x-amz-bypass-governance-retention") == "true"

	// Parse retention configuration from request body
	retention, err := parseObjectRetention(r)
	if err != nil {
		glog.Errorf("PutObjectRetentionHandler: failed to parse retention config: %v", err)
		s3err.WriteErrorResponse(w, r, s3err.ErrMalformedXML)
		return
	}

	// Validate retention configuration
	if err := validateRetention(retention); err != nil {
		glog.Errorf("PutObjectRetentionHandler: invalid retention config: %v", err)
		s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
		return
	}

	// Set retention on the object
	if err := s3a.setObjectRetention(bucket, object, versionId, retention, bypassGovernance); err != nil {
		glog.Errorf("PutObjectRetentionHandler: failed to set retention: %v", err)

		// Handle specific error cases
		if errors.Is(err, ErrObjectNotFound) || errors.Is(err, ErrVersionNotFound) || errors.Is(err, ErrLatestVersionNotFound) {
			s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey)
			return
		}

		if errors.Is(err, ErrComplianceModeActive) || errors.Is(err, ErrGovernanceModeActive) {
			// Return 403 Forbidden for retention mode changes without proper permissions
			s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
			return
		}

		s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
		return
	}

	// Add VersionId to response headers if available (expected by s3-tests)
	if versionId != "" {
		w.Header().Set("x-amz-version-id", versionId)
	}

	// Record metrics
	stats_collect.RecordBucketActiveTime(bucket)

	// Return success (HTTP 200 with no body)
	w.WriteHeader(http.StatusOK)
	glog.V(3).Infof("PutObjectRetentionHandler: successfully set retention for %s/%s", bucket, object)
}

// GetObjectRetentionHandler Get object Retention
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectRetention.html
func (s3a *S3ApiServer) GetObjectRetentionHandler(w http.ResponseWriter, r *http.Request) {
	bucket, object := s3_constants.GetBucketAndObject(r)
	glog.V(3).Infof("GetObjectRetentionHandler %s %s", bucket, object)

	// Check if Object Lock is available for this bucket (requires versioning)
	if !s3a.handleObjectLockAvailabilityCheck(w, r, bucket, "GetObjectRetentionHandler") {
		return
	}

	// Get version ID from query parameters
	versionId := r.URL.Query().Get("versionId")

	// Get retention configuration for the object
	retention, err := s3a.getObjectRetention(bucket, object, versionId)
	if err != nil {
		glog.Errorf("GetObjectRetentionHandler: failed to get retention: %v", err)

		// Handle specific error cases
		if errors.Is(err, ErrObjectNotFound) || errors.Is(err, ErrVersionNotFound) {
			s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey)
			return
		}

		if errors.Is(err, ErrNoRetentionConfiguration) {
			s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchObjectLockConfiguration)
			return
		}

		s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
		return
	}

	// Marshal retention configuration to XML
	retentionXML, err := xml.Marshal(retention)
	if err != nil {
		glog.Errorf("GetObjectRetentionHandler: failed to marshal retention: %v", err)
		s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
		return
	}

	// Set response headers
	w.Header().Set("Content-Type", "application/xml")
	w.WriteHeader(http.StatusOK)

	// Write XML response
	if _, err := w.Write([]byte(xml.Header)); err != nil {
		glog.Errorf("GetObjectRetentionHandler: failed to write XML header: %v", err)
		return
	}

	if _, err := w.Write(retentionXML); err != nil {
		glog.Errorf("GetObjectRetentionHandler: failed to write retention XML: %v", err)
		return
	}

	// Record metrics
	stats_collect.RecordBucketActiveTime(bucket)

	glog.V(3).Infof("GetObjectRetentionHandler: successfully retrieved retention for %s/%s", bucket, object)
}