diff --git a/cmd/api-headers.go b/cmd/api-headers.go index 599b7a0fc..91e571b3b 100644 --- a/cmd/api-headers.go +++ b/cmd/api-headers.go @@ -20,6 +20,7 @@ package cmd import ( "bytes" "encoding/json" + "encoding/xml" "fmt" "net/http" "net/url" @@ -64,15 +65,31 @@ func setCommonHeaders(w http.ResponseWriter) { // Encodes the response headers into XML format. func encodeResponse(response interface{}) []byte { - var bytesBuffer bytes.Buffer - bytesBuffer.WriteString(xxml.Header) - buf, err := xxml.Marshal(response) - if err != nil { + var buf bytes.Buffer + buf.WriteString(xml.Header) + if err := xml.NewEncoder(&buf).Encode(response); err != nil { logger.LogIf(GlobalContext, err) return nil } - bytesBuffer.Write(buf) - return bytesBuffer.Bytes() + return buf.Bytes() +} + +// Use this encodeResponseList() to support control characters +// this function must be used by only ListObjects() for objects +// with control characters, this is a specialized extension +// to support AWS S3 compatible behavior. +// +// Do not use this function for anything other than ListObjects() +// variants, please open a github discussion if you wish to use +// this in other places. +func encodeResponseList(response interface{}) []byte { + var buf bytes.Buffer + buf.WriteString(xxml.Header) + if err := xxml.NewEncoder(&buf).Encode(response); err != nil { + logger.LogIf(GlobalContext, err) + return nil + } + return buf.Bytes() } // Encodes the response headers into JSON format. diff --git a/cmd/api-response.go b/cmd/api-response.go index dc819294a..866ebdf8a 100644 --- a/cmd/api-response.go +++ b/cmd/api-response.go @@ -314,12 +314,12 @@ func (s *Metadata) Set(k, v string) { } type xmlKeyEntry struct { - XMLName xml.Name + XMLName xxml.Name Value string `xml:",chardata"` } // MarshalXML - StringMap marshals into XML. -func (s *Metadata) MarshalXML(e *xml.Encoder, start xml.StartElement) error { +func (s *Metadata) MarshalXML(e *xxml.Encoder, start xxml.StartElement) error { if s == nil { return nil } @@ -334,7 +334,7 @@ func (s *Metadata) MarshalXML(e *xml.Encoder, start xml.StartElement) error { for _, item := range s.Items { if err := e.Encode(xmlKeyEntry{ - XMLName: xml.Name{Local: item.Key}, + XMLName: xxml.Name{Local: item.Key}, Value: item.Value, }); err != nil { return err diff --git a/cmd/bucket-listobjects-handlers.go b/cmd/bucket-listobjects-handlers.go index 33dfe4f5a..c4bfc4586 100644 --- a/cmd/bucket-listobjects-handlers.go +++ b/cmd/bucket-listobjects-handlers.go @@ -115,7 +115,7 @@ func (api objectAPIHandlers) ListObjectVersionsHandler(w http.ResponseWriter, r response := generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType, maxkeys, listObjectVersionsInfo) // Write success response. - writeSuccessResponseXML(w, encodeResponse(response)) + writeSuccessResponseXML(w, encodeResponseList(response)) } // ListObjectsV2MHandler - GET Bucket (List Objects) Version 2 with metadata. @@ -185,7 +185,7 @@ func (api objectAPIHandlers) ListObjectsV2MHandler(w http.ResponseWriter, r *htt maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, true) // Write success response. - writeSuccessResponseXML(w, encodeResponse(response)) + writeSuccessResponseXML(w, encodeResponseList(response)) } // ListObjectsV2Handler - GET Bucket (List Objects) Version 2. @@ -260,7 +260,7 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, false) // Write success response. - writeSuccessResponseXML(w, encodeResponse(response)) + writeSuccessResponseXML(w, encodeResponseList(response)) } func parseRequestToken(token string) (subToken string, nodeIndex int) { @@ -357,5 +357,5 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http response := generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType, maxKeys, listObjectsInfo) // Write success response. - writeSuccessResponseXML(w, encodeResponse(response)) + writeSuccessResponseXML(w, encodeResponseList(response)) } diff --git a/internal/amztime/iso8601_time.go b/internal/amztime/iso8601_time.go index 885d0bbda..d12f9b92a 100644 --- a/internal/amztime/iso8601_time.go +++ b/internal/amztime/iso8601_time.go @@ -23,7 +23,10 @@ import ( ) // RFC3339 a subset of the ISO8601 timestamp format. e.g 2014-04-29T18:30:38Z -const iso8601TimeFormat = "2006-01-02T15:04:05.000Z" // Reply date format with nanosecond precision. +const ( + iso8601TimeFormat = "2006-01-02T15:04:05.000Z" // Reply date format with millisecond precision. + iso8601TimeFormatLong = "2006-01-02T15:04:05.000000Z" // Reply date format with nanosecond precision. +) // ISO8601Format converts time 't' into ISO8601 time format expected in AWS S3 spec. // @@ -43,6 +46,7 @@ func ISO8601Format(t time.Time) string { func ISO8601Parse(iso8601 string) (t time.Time, err error) { for _, layout := range []string{ iso8601TimeFormat, + iso8601TimeFormatLong, time.RFC3339, } { t, err = time.Parse(layout, iso8601) diff --git a/internal/bucket/object/lock/lock.go b/internal/bucket/object/lock/lock.go index b718f28e8..e09983746 100644 --- a/internal/bucket/object/lock/lock.go +++ b/internal/bucket/object/lock/lock.go @@ -320,7 +320,7 @@ func (rDate *RetentionDate) UnmarshalXML(d *xml.Decoder, startElement xml.StartE // MarshalXML encodes expiration date if it is non-zero and encodes // empty string otherwise -func (rDate *RetentionDate) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error { +func (rDate RetentionDate) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error { if rDate.IsZero() { return nil }