github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/bucket/lifecycle/expiration.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package lifecycle 19 20 import ( 21 "encoding/xml" 22 "time" 23 ) 24 25 var ( 26 errLifecycleInvalidDate = Errorf("Date must be provided in ISO 8601 format") 27 errLifecycleInvalidDays = Errorf("Days must be positive integer when used with Expiration") 28 errLifecycleInvalidExpiration = Errorf("Exactly one of Days (positive integer) or Date (positive ISO 8601 format) should be present inside Expiration.") 29 errLifecycleInvalidDeleteMarker = Errorf("Delete marker cannot be specified with Days or Date in a Lifecycle Expiration Policy") 30 errLifecycleDateNotMidnight = Errorf("'Date' must be at midnight GMT") 31 errLifecycleInvalidDeleteAll = Errorf("Days (positive integer) should be present inside Expiration with ExpiredObjectAllVersions.") 32 ) 33 34 // ExpirationDays is a type alias to unmarshal Days in Expiration 35 type ExpirationDays int 36 37 // UnmarshalXML parses number of days from Expiration and validates if 38 // greater than zero 39 func (eDays *ExpirationDays) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error { 40 var numDays int 41 err := d.DecodeElement(&numDays, &startElement) 42 if err != nil { 43 return err 44 } 45 if numDays <= 0 { 46 return errLifecycleInvalidDays 47 } 48 *eDays = ExpirationDays(numDays) 49 return nil 50 } 51 52 // MarshalXML encodes number of days to expire if it is non-zero and 53 // encodes empty string otherwise 54 func (eDays ExpirationDays) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error { 55 if eDays == 0 { 56 return nil 57 } 58 return e.EncodeElement(int(eDays), startElement) 59 } 60 61 // ExpirationDate is a embedded type containing time.Time to unmarshal 62 // Date in Expiration 63 type ExpirationDate struct { 64 time.Time 65 } 66 67 // UnmarshalXML parses date from Expiration and validates date format 68 func (eDate *ExpirationDate) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error { 69 var dateStr string 70 err := d.DecodeElement(&dateStr, &startElement) 71 if err != nil { 72 return err 73 } 74 // While AWS documentation mentions that the date specified 75 // must be present in ISO 8601 format, in reality they allow 76 // users to provide RFC 3339 compliant dates. 77 expDate, err := time.Parse(time.RFC3339, dateStr) 78 if err != nil { 79 return errLifecycleInvalidDate 80 } 81 // Allow only date timestamp specifying midnight GMT 82 hr, min, sec := expDate.Clock() 83 nsec := expDate.Nanosecond() 84 loc := expDate.Location() 85 if !(hr == 0 && min == 0 && sec == 0 && nsec == 0 && loc.String() == time.UTC.String()) { 86 return errLifecycleDateNotMidnight 87 } 88 89 *eDate = ExpirationDate{expDate} 90 return nil 91 } 92 93 // MarshalXML encodes expiration date if it is non-zero and encodes 94 // empty string otherwise 95 func (eDate ExpirationDate) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error { 96 if eDate.Time.IsZero() { 97 return nil 98 } 99 return e.EncodeElement(eDate.Format(time.RFC3339), startElement) 100 } 101 102 // ExpireDeleteMarker represents value of ExpiredObjectDeleteMarker field in Expiration XML element. 103 type ExpireDeleteMarker struct { 104 Boolean 105 } 106 107 // Boolean signifies a boolean XML struct with custom marshaling 108 type Boolean struct { 109 val bool 110 set bool 111 Unused struct{} // Needed for GOB compatibility 112 } 113 114 // Expiration - expiration actions for a rule in lifecycle configuration. 115 type Expiration struct { 116 XMLName xml.Name `xml:"Expiration"` 117 Days ExpirationDays `xml:"Days,omitempty"` 118 Date ExpirationDate `xml:"Date,omitempty"` 119 DeleteMarker ExpireDeleteMarker `xml:"ExpiredObjectDeleteMarker"` 120 // Indicates whether MinIO will remove all versions. If set to true, all versions will be deleted; 121 // if set to false the policy takes no action. This action uses the Days/Date to expire objects. 122 // This check is verified for latest version of the object. 123 DeleteAll Boolean `xml:"ExpiredObjectAllVersions"` 124 125 set bool 126 } 127 128 // MarshalXML encodes delete marker boolean into an XML form. 129 func (b Boolean) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error { 130 if !b.set { 131 return nil 132 } 133 return e.EncodeElement(b.val, startElement) 134 } 135 136 // UnmarshalXML decodes delete marker boolean from the XML form. 137 func (b *Boolean) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error { 138 var exp bool 139 err := d.DecodeElement(&exp, &startElement) 140 if err != nil { 141 return err 142 } 143 b.val = exp 144 b.set = true 145 return nil 146 } 147 148 // MarshalXML encodes expiration field into an XML form. 149 func (e Expiration) MarshalXML(enc *xml.Encoder, startElement xml.StartElement) error { 150 if !e.set { 151 return nil 152 } 153 type expirationWrapper Expiration 154 return enc.EncodeElement(expirationWrapper(e), startElement) 155 } 156 157 // UnmarshalXML decodes expiration field from the XML form. 158 func (e *Expiration) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error { 159 type expirationWrapper Expiration 160 var exp expirationWrapper 161 err := d.DecodeElement(&exp, &startElement) 162 if err != nil { 163 return err 164 } 165 *e = Expiration(exp) 166 e.set = true 167 return nil 168 } 169 170 // Validate - validates the "Expiration" element 171 func (e Expiration) Validate() error { 172 if !e.set { 173 return nil 174 } 175 176 // DeleteMarker cannot be specified if date or dates are specified. 177 if (!e.IsDaysNull() || !e.IsDateNull()) && e.DeleteMarker.set { 178 return errLifecycleInvalidDeleteMarker 179 } 180 181 if !e.DeleteMarker.set && !e.DeleteAll.set && e.IsDaysNull() && e.IsDateNull() { 182 return errXMLNotWellFormed 183 } 184 185 // Both expiration days and date are specified 186 if !e.IsDaysNull() && !e.IsDateNull() { 187 return errLifecycleInvalidExpiration 188 } 189 190 // DeleteAll set without expiration days 191 if e.DeleteAll.set && e.IsDaysNull() { 192 return errLifecycleInvalidDeleteAll 193 } 194 195 return nil 196 } 197 198 // IsDaysNull returns true if days field is null 199 func (e Expiration) IsDaysNull() bool { 200 return e.Days == ExpirationDays(0) 201 } 202 203 // IsDateNull returns true if date field is null 204 func (e Expiration) IsDateNull() bool { 205 return e.Date.Time.IsZero() 206 } 207 208 // IsNull returns true if both date and days fields are null 209 func (e Expiration) IsNull() bool { 210 return e.IsDaysNull() && e.IsDateNull() 211 }