diff options
Diffstat (limited to 'weed/s3api')
| -rw-r--r-- | weed/s3api/s3_constants/header.go | 5 | ||||
| -rw-r--r-- | weed/s3api/s3api_bucket_handlers_object_lock_config.go | 6 | ||||
| -rw-r--r-- | weed/s3api/s3api_object_retention.go | 39 | ||||
| -rw-r--r-- | weed/s3api/s3api_object_retention_test.go | 76 |
4 files changed, 110 insertions, 16 deletions
diff --git a/weed/s3api/s3_constants/header.go b/weed/s3api/s3_constants/header.go index e4c0ad77b..1ef6f62c5 100644 --- a/weed/s3api/s3_constants/header.go +++ b/weed/s3api/s3_constants/header.go @@ -23,6 +23,11 @@ import ( "github.com/gorilla/mux" ) +// S3 XML namespace +const ( + S3Namespace = "http://s3.amazonaws.com/doc/2006-03-01/" +) + // Standard S3 HTTP request constants const ( // S3 storage class diff --git a/weed/s3api/s3api_bucket_handlers_object_lock_config.go b/weed/s3api/s3api_bucket_handlers_object_lock_config.go index c779f80d7..23b52648e 100644 --- a/weed/s3api/s3api_bucket_handlers_object_lock_config.go +++ b/weed/s3api/s3api_bucket_handlers_object_lock_config.go @@ -86,6 +86,9 @@ func (s3a *S3ApiServer) GetObjectLockConfigurationHandler(w http.ResponseWriter, // Check if we have cached Object Lock configuration if bucketConfig.ObjectLockConfig != nil { + // Set namespace for S3 compatibility + bucketConfig.ObjectLockConfig.XMLNS = s3_constants.S3Namespace + // Use cached configuration and marshal it to XML for response marshaledXML, err := xml.Marshal(bucketConfig.ObjectLockConfig) if err != nil { @@ -139,6 +142,9 @@ func (s3a *S3ApiServer) GetObjectLockConfigurationHandler(w http.ResponseWriter, // not just ObjectLockConfig, before resetting the TTL s3a.updateBucketConfigCacheFromEntry(freshEntry) + // Set namespace for S3 compatibility + objectLockConfig.XMLNS = s3_constants.S3Namespace + // Marshal and return the configuration marshaledXML, err := xml.Marshal(objectLockConfig) if err != nil { diff --git a/weed/s3api/s3api_object_retention.go b/weed/s3api/s3api_object_retention.go index 5bb2faf54..ef298eb43 100644 --- a/weed/s3api/s3api_object_retention.go +++ b/weed/s3api/s3api_object_retention.go @@ -57,37 +57,40 @@ const ( // ObjectRetention represents S3 Object Retention configuration type ObjectRetention struct { - XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Retention"` - Mode string `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Mode,omitempty"` - RetainUntilDate *time.Time `xml:"http://s3.amazonaws.com/doc/2006-03-01/ RetainUntilDate,omitempty"` + XMLNS string `xml:"xmlns,attr,omitempty"` + XMLName xml.Name `xml:"Retention"` + Mode string `xml:"Mode,omitempty"` + RetainUntilDate *time.Time `xml:"RetainUntilDate,omitempty"` } // ObjectLegalHold represents S3 Object Legal Hold configuration type ObjectLegalHold struct { - XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ LegalHold"` - Status string `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Status,omitempty"` + XMLNS string `xml:"xmlns,attr,omitempty"` + XMLName xml.Name `xml:"LegalHold"` + Status string `xml:"Status,omitempty"` } // ObjectLockConfiguration represents S3 Object Lock Configuration type ObjectLockConfiguration struct { - XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ObjectLockConfiguration"` - ObjectLockEnabled string `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ObjectLockEnabled,omitempty"` - Rule *ObjectLockRule `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Rule,omitempty"` + XMLNS string `xml:"xmlns,attr,omitempty"` + XMLName xml.Name `xml:"ObjectLockConfiguration"` + ObjectLockEnabled string `xml:"ObjectLockEnabled,omitempty"` + Rule *ObjectLockRule `xml:"Rule,omitempty"` } // ObjectLockRule represents an Object Lock Rule type ObjectLockRule struct { - XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Rule"` - DefaultRetention *DefaultRetention `xml:"http://s3.amazonaws.com/doc/2006-03-01/ DefaultRetention,omitempty"` + XMLName xml.Name `xml:"Rule"` + DefaultRetention *DefaultRetention `xml:"DefaultRetention,omitempty"` } // DefaultRetention represents default retention settings // Implements custom XML unmarshal to track if Days/Years were present in XML type DefaultRetention struct { - XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ DefaultRetention"` - Mode string `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Mode,omitempty"` - Days int `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Days,omitempty"` - Years int `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Years,omitempty"` + XMLName xml.Name `xml:"DefaultRetention"` + Mode string `xml:"Mode,omitempty"` + Days int `xml:"Days,omitempty"` + Years int `xml:"Years,omitempty"` DaysSet bool `xml:"-"` YearsSet bool `xml:"-"` } @@ -102,8 +105,8 @@ func (dr *DefaultRetention) UnmarshalXML(d *xml.Decoder, start xml.StartElement) type Alias DefaultRetention aux := &struct { *Alias - Days *int `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Days,omitempty"` - Years *int `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Years,omitempty"` + Days *int `xml:"Days,omitempty"` + Years *int `xml:"Years,omitempty"` }{Alias: (*Alias)(dr)} if err := d.DecodeElement(aux, &start); err != nil { glog.V(2).Infof("DefaultRetention.UnmarshalXML: decode error: %v", err) @@ -245,6 +248,8 @@ func (s3a *S3ApiServer) getObjectRetention(bucket, object, versionId string) (*O return nil, ErrNoRetentionConfiguration } + // Set namespace for S3 compatibility + retention.XMLNS = s3_constants.S3Namespace return retention, nil } @@ -386,6 +391,8 @@ func (s3a *S3ApiServer) getObjectLegalHold(bucket, object, versionId string) (*O return nil, ErrNoLegalHoldConfiguration } + // Set namespace for S3 compatibility + legalHold.XMLNS = s3_constants.S3Namespace return legalHold, nil } diff --git a/weed/s3api/s3api_object_retention_test.go b/weed/s3api/s3api_object_retention_test.go index 20ccf60d9..34c772acd 100644 --- a/weed/s3api/s3api_object_retention_test.go +++ b/weed/s3api/s3api_object_retention_test.go @@ -202,6 +202,30 @@ func TestParseObjectRetention(t *testing.T) { }, }, { + name: "Valid retention XML without namespace (Veeam compatibility)", + xmlBody: `<Retention> + <Mode>GOVERNANCE</Mode> + <RetainUntilDate>2024-12-31T23:59:59Z</RetainUntilDate> + </Retention>`, + expectError: false, + expectedResult: &ObjectRetention{ + Mode: "GOVERNANCE", + RetainUntilDate: timePtr(time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC)), + }, + }, + { + name: "Valid compliance retention XML without namespace (Veeam compatibility)", + xmlBody: `<Retention> + <Mode>COMPLIANCE</Mode> + <RetainUntilDate>2025-01-01T00:00:00Z</RetainUntilDate> + </Retention>`, + expectError: false, + expectedResult: &ObjectRetention{ + Mode: "COMPLIANCE", + RetainUntilDate: timePtr(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)), + }, + }, + { name: "Empty XML body", xmlBody: "", expectError: true, @@ -312,6 +336,26 @@ func TestParseObjectLegalHold(t *testing.T) { }, }, { + name: "Valid legal hold ON without namespace", + xmlBody: `<LegalHold> + <Status>ON</Status> + </LegalHold>`, + expectError: false, + expectedResult: &ObjectLegalHold{ + Status: "ON", + }, + }, + { + name: "Valid legal hold OFF without namespace", + xmlBody: `<LegalHold> + <Status>OFF</Status> + </LegalHold>`, + expectError: false, + expectedResult: &ObjectLegalHold{ + Status: "OFF", + }, + }, + { name: "Empty XML body", xmlBody: "", expectError: true, @@ -406,6 +450,38 @@ func TestParseObjectLockConfiguration(t *testing.T) { }, }, { + name: "Valid object lock configuration without namespace", + xmlBody: `<ObjectLockConfiguration> + <ObjectLockEnabled>Enabled</ObjectLockEnabled> + </ObjectLockConfiguration>`, + expectError: false, + expectedResult: &ObjectLockConfiguration{ + ObjectLockEnabled: "Enabled", + }, + }, + { + name: "Valid object lock configuration with rule without namespace", + xmlBody: `<ObjectLockConfiguration> + <ObjectLockEnabled>Enabled</ObjectLockEnabled> + <Rule> + <DefaultRetention> + <Mode>GOVERNANCE</Mode> + <Days>30</Days> + </DefaultRetention> + </Rule> + </ObjectLockConfiguration>`, + expectError: false, + expectedResult: &ObjectLockConfiguration{ + ObjectLockEnabled: "Enabled", + Rule: &ObjectLockRule{ + DefaultRetention: &DefaultRetention{ + Mode: "GOVERNANCE", + Days: 30, + }, + }, + }, + }, + { name: "Empty XML body", xmlBody: "", expectError: true, |
