storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/bucket/lifecycle/expiration.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2019 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package lifecycle 18 19 import ( 20 "encoding/xml" 21 "time" 22 ) 23 24 var ( 25 errLifecycleInvalidDate = Errorf("Date must be provided in ISO 8601 format") 26 errLifecycleInvalidDays = Errorf("Days must be positive integer when used with Expiration") 27 errLifecycleInvalidExpiration = Errorf("Exactly one of Days (positive integer) or Date (positive ISO 8601 format) should be present inside Expiration.") 28 errLifecycleInvalidDeleteMarker = Errorf("Delete marker cannot be specified with Days or Date in a Lifecycle Expiration Policy") 29 errLifecycleDateNotMidnight = Errorf("'Date' must be at midnight GMT") 30 ) 31 32 // ExpirationDays is a type alias to unmarshal Days in Expiration 33 type ExpirationDays int 34 35 // UnmarshalXML parses number of days from Expiration and validates if 36 // greater than zero 37 func (eDays *ExpirationDays) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error { 38 var numDays int 39 err := d.DecodeElement(&numDays, &startElement) 40 if err != nil { 41 return err 42 } 43 if numDays <= 0 { 44 return errLifecycleInvalidDays 45 } 46 *eDays = ExpirationDays(numDays) 47 return nil 48 } 49 50 // MarshalXML encodes number of days to expire if it is non-zero and 51 // encodes empty string otherwise 52 func (eDays ExpirationDays) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error { 53 if eDays == 0 { 54 return nil 55 } 56 return e.EncodeElement(int(eDays), startElement) 57 } 58 59 // ExpirationDate is a embedded type containing time.Time to unmarshal 60 // Date in Expiration 61 type ExpirationDate struct { 62 time.Time 63 } 64 65 // UnmarshalXML parses date from Expiration and validates date format 66 func (eDate *ExpirationDate) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error { 67 var dateStr string 68 err := d.DecodeElement(&dateStr, &startElement) 69 if err != nil { 70 return err 71 } 72 // While AWS documentation mentions that the date specified 73 // must be present in ISO 8601 format, in reality they allow 74 // users to provide RFC 3339 compliant dates. 75 expDate, err := time.Parse(time.RFC3339, dateStr) 76 if err != nil { 77 return errLifecycleInvalidDate 78 } 79 // Allow only date timestamp specifying midnight GMT 80 hr, min, sec := expDate.Clock() 81 nsec := expDate.Nanosecond() 82 loc := expDate.Location() 83 if !(hr == 0 && min == 0 && sec == 0 && nsec == 0 && loc.String() == time.UTC.String()) { 84 return errLifecycleDateNotMidnight 85 } 86 87 *eDate = ExpirationDate{expDate} 88 return nil 89 } 90 91 // MarshalXML encodes expiration date if it is non-zero and encodes 92 // empty string otherwise 93 func (eDate ExpirationDate) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error { 94 if eDate.Time.IsZero() { 95 return nil 96 } 97 return e.EncodeElement(eDate.Format(time.RFC3339), startElement) 98 } 99 100 // ExpireDeleteMarker represents value of ExpiredObjectDeleteMarker field in Expiration XML element. 101 type ExpireDeleteMarker struct { 102 val bool 103 set bool 104 } 105 106 // Expiration - expiration actions for a rule in lifecycle configuration. 107 type Expiration struct { 108 XMLName xml.Name `xml:"Expiration"` 109 Days ExpirationDays `xml:"Days,omitempty"` 110 Date ExpirationDate `xml:"Date,omitempty"` 111 DeleteMarker ExpireDeleteMarker `xml:"ExpiredObjectDeleteMarker"` 112 113 set bool 114 } 115 116 // MarshalXML encodes delete marker boolean into an XML form. 117 func (b ExpireDeleteMarker) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error { 118 if !b.set { 119 return nil 120 } 121 return e.EncodeElement(b.val, startElement) 122 } 123 124 // UnmarshalXML decodes delete marker boolean from the XML form. 125 func (b *ExpireDeleteMarker) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error { 126 var exp bool 127 err := d.DecodeElement(&exp, &startElement) 128 if err != nil { 129 return err 130 } 131 b.val = exp 132 b.set = true 133 return nil 134 } 135 136 // MarshalXML encodes expiration field into an XML form. 137 func (e Expiration) MarshalXML(enc *xml.Encoder, startElement xml.StartElement) error { 138 if !e.set { 139 return nil 140 } 141 type expirationWrapper Expiration 142 return enc.EncodeElement(expirationWrapper(e), startElement) 143 } 144 145 // UnmarshalXML decodes expiration field from the XML form. 146 func (e *Expiration) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error { 147 type expirationWrapper Expiration 148 var exp expirationWrapper 149 err := d.DecodeElement(&exp, &startElement) 150 if err != nil { 151 return err 152 } 153 *e = Expiration(exp) 154 e.set = true 155 return nil 156 } 157 158 // Validate - validates the "Expiration" element 159 func (e Expiration) Validate() error { 160 if !e.set { 161 return nil 162 } 163 164 // DeleteMarker cannot be specified if date or dates are specified. 165 if (!e.IsDaysNull() || !e.IsDateNull()) && e.DeleteMarker.set { 166 return errLifecycleInvalidDeleteMarker 167 } 168 169 if !e.DeleteMarker.set && e.IsDaysNull() && e.IsDateNull() { 170 return errXMLNotWellFormed 171 } 172 173 // Both expiration days and date are specified 174 if !e.IsDaysNull() && !e.IsDateNull() { 175 return errLifecycleInvalidExpiration 176 } 177 178 return nil 179 } 180 181 // IsDaysNull returns true if days field is null 182 func (e Expiration) IsDaysNull() bool { 183 return e.Days == ExpirationDays(0) 184 } 185 186 // IsDateNull returns true if date field is null 187 func (e Expiration) IsDateNull() bool { 188 return e.Date.Time.IsZero() 189 } 190 191 // IsNull returns true if both date and days fields are null 192 func (e Expiration) IsNull() bool { 193 return e.IsDaysNull() && e.IsDateNull() 194 }