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  }