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  }