storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/object-handlers-common.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2016, 2017, 2018 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 cmd
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net/http"
    23  	"regexp"
    24  	"strconv"
    25  	"time"
    26  
    27  	xhttp "storj.io/minio/cmd/http"
    28  	"storj.io/minio/pkg/bucket/lifecycle"
    29  )
    30  
    31  var (
    32  	etagRegex = regexp.MustCompile("\"*?([^\"]*?)\"*?$")
    33  )
    34  
    35  // Validates the preconditions for CopyObjectPart, returns true if CopyObjectPart
    36  // operation should not proceed. Preconditions supported are:
    37  //  x-amz-copy-source-if-modified-since
    38  //  x-amz-copy-source-if-unmodified-since
    39  //  x-amz-copy-source-if-match
    40  //  x-amz-copy-source-if-none-match
    41  func checkCopyObjectPartPreconditions(ctx context.Context, w http.ResponseWriter, r *http.Request, objInfo ObjectInfo) bool {
    42  	return checkCopyObjectPreconditions(ctx, w, r, objInfo)
    43  }
    44  
    45  // Validates the preconditions for CopyObject, returns true if CopyObject operation should not proceed.
    46  // Preconditions supported are:
    47  //  x-amz-copy-source-if-modified-since
    48  //  x-amz-copy-source-if-unmodified-since
    49  //  x-amz-copy-source-if-match
    50  //  x-amz-copy-source-if-none-match
    51  func checkCopyObjectPreconditions(ctx context.Context, w http.ResponseWriter, r *http.Request, objInfo ObjectInfo) bool {
    52  	// Return false for methods other than GET and HEAD.
    53  	if r.Method != http.MethodPut {
    54  		return false
    55  	}
    56  	// If the object doesn't have a modtime (IsZero), or the modtime
    57  	// is obviously garbage (Unix time == 0), then ignore modtimes
    58  	// and don't process the If-Modified-Since header.
    59  	if objInfo.ModTime.IsZero() || objInfo.ModTime.Equal(time.Unix(0, 0)) {
    60  		return false
    61  	}
    62  
    63  	// Headers to be set of object content is not going to be written to the client.
    64  	writeHeaders := func() {
    65  		// set common headers
    66  		setCommonHeaders(w)
    67  
    68  		// set object-related metadata headers
    69  		w.Header().Set(xhttp.LastModified, objInfo.ModTime.UTC().Format(http.TimeFormat))
    70  
    71  		if objInfo.ETag != "" {
    72  			w.Header()[xhttp.ETag] = []string{"\"" + objInfo.ETag + "\""}
    73  		}
    74  	}
    75  	// x-amz-copy-source-if-modified-since: Return the object only if it has been modified
    76  	// since the specified time otherwise return 412 (precondition failed).
    77  	ifModifiedSinceHeader := r.Header.Get(xhttp.AmzCopySourceIfModifiedSince)
    78  	if ifModifiedSinceHeader != "" {
    79  		if givenTime, err := time.Parse(http.TimeFormat, ifModifiedSinceHeader); err == nil {
    80  			if !ifModifiedSince(objInfo.ModTime, givenTime) {
    81  				// If the object is not modified since the specified time.
    82  				writeHeaders()
    83  				WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrPreconditionFailed), r.URL, guessIsBrowserReq(r))
    84  				return true
    85  			}
    86  		}
    87  	}
    88  
    89  	// x-amz-copy-source-if-unmodified-since : Return the object only if it has not been
    90  	// modified since the specified time, otherwise return a 412 (precondition failed).
    91  	ifUnmodifiedSinceHeader := r.Header.Get(xhttp.AmzCopySourceIfUnmodifiedSince)
    92  	if ifUnmodifiedSinceHeader != "" {
    93  		if givenTime, err := time.Parse(http.TimeFormat, ifUnmodifiedSinceHeader); err == nil {
    94  			if ifModifiedSince(objInfo.ModTime, givenTime) {
    95  				// If the object is modified since the specified time.
    96  				writeHeaders()
    97  				WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrPreconditionFailed), r.URL, guessIsBrowserReq(r))
    98  				return true
    99  			}
   100  		}
   101  	}
   102  
   103  	// x-amz-copy-source-if-match : Return the object only if its entity tag (ETag) is the
   104  	// same as the one specified; otherwise return a 412 (precondition failed).
   105  	ifMatchETagHeader := r.Header.Get(xhttp.AmzCopySourceIfMatch)
   106  	if ifMatchETagHeader != "" {
   107  		if !isETagEqual(objInfo.ETag, ifMatchETagHeader) {
   108  			// If the object ETag does not match with the specified ETag.
   109  			writeHeaders()
   110  			WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrPreconditionFailed), r.URL, guessIsBrowserReq(r))
   111  			return true
   112  		}
   113  	}
   114  
   115  	// If-None-Match : Return the object only if its entity tag (ETag) is different from the
   116  	// one specified otherwise, return a 304 (not modified).
   117  	ifNoneMatchETagHeader := r.Header.Get(xhttp.AmzCopySourceIfNoneMatch)
   118  	if ifNoneMatchETagHeader != "" {
   119  		if isETagEqual(objInfo.ETag, ifNoneMatchETagHeader) {
   120  			// If the object ETag matches with the specified ETag.
   121  			writeHeaders()
   122  			WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrPreconditionFailed), r.URL, guessIsBrowserReq(r))
   123  			return true
   124  		}
   125  	}
   126  	// Object content should be written to http.ResponseWriter
   127  	return false
   128  }
   129  
   130  // Validates the preconditions. Returns true if GET/HEAD operation should not proceed.
   131  // Preconditions supported are:
   132  //  If-Modified-Since
   133  //  If-Unmodified-Since
   134  //  If-Match
   135  //  If-None-Match
   136  func checkPreconditions(ctx context.Context, w http.ResponseWriter, r *http.Request, objInfo ObjectInfo, opts ObjectOptions) bool {
   137  	// Return false for methods other than GET and HEAD.
   138  	if r.Method != http.MethodGet && r.Method != http.MethodHead {
   139  		return false
   140  	}
   141  	// If the object doesn't have a modtime (IsZero), or the modtime
   142  	// is obviously garbage (Unix time == 0), then ignore modtimes
   143  	// and don't process the If-Modified-Since header.
   144  	if objInfo.ModTime.IsZero() || objInfo.ModTime.Equal(time.Unix(0, 0)) {
   145  		return false
   146  	}
   147  
   148  	// Headers to be set of object content is not going to be written to the client.
   149  	writeHeaders := func() {
   150  		// set common headers
   151  		setCommonHeaders(w)
   152  
   153  		// set object-related metadata headers
   154  		w.Header().Set(xhttp.LastModified, objInfo.ModTime.UTC().Format(http.TimeFormat))
   155  
   156  		if objInfo.ETag != "" {
   157  			w.Header()[xhttp.ETag] = []string{"\"" + objInfo.ETag + "\""}
   158  		}
   159  	}
   160  
   161  	// Check if the part number is correct.
   162  	if opts.PartNumber > 1 && opts.PartNumber > len(objInfo.Parts) {
   163  		// According to S3 we don't need to set any object information here.
   164  		WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidPartNumber), r.URL, guessIsBrowserReq(r))
   165  		return true
   166  	}
   167  
   168  	// If-Modified-Since : Return the object only if it has been modified since the specified time,
   169  	// otherwise return a 304 (not modified).
   170  	ifModifiedSinceHeader := r.Header.Get(xhttp.IfModifiedSince)
   171  	if ifModifiedSinceHeader != "" {
   172  		if givenTime, err := time.Parse(http.TimeFormat, ifModifiedSinceHeader); err == nil {
   173  			if !ifModifiedSince(objInfo.ModTime, givenTime) {
   174  				// If the object is not modified since the specified time.
   175  				writeHeaders()
   176  				w.WriteHeader(http.StatusNotModified)
   177  				return true
   178  			}
   179  		}
   180  	}
   181  
   182  	// If-Unmodified-Since : Return the object only if it has not been modified since the specified
   183  	// time, otherwise return a 412 (precondition failed).
   184  	ifUnmodifiedSinceHeader := r.Header.Get(xhttp.IfUnmodifiedSince)
   185  	if ifUnmodifiedSinceHeader != "" {
   186  		if givenTime, err := time.Parse(http.TimeFormat, ifUnmodifiedSinceHeader); err == nil {
   187  			if ifModifiedSince(objInfo.ModTime, givenTime) {
   188  				// If the object is modified since the specified time.
   189  				writeHeaders()
   190  				WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrPreconditionFailed), r.URL, guessIsBrowserReq(r))
   191  				return true
   192  			}
   193  		}
   194  	}
   195  
   196  	// If-Match : Return the object only if its entity tag (ETag) is the same as the one specified;
   197  	// otherwise return a 412 (precondition failed).
   198  	ifMatchETagHeader := r.Header.Get(xhttp.IfMatch)
   199  	if ifMatchETagHeader != "" {
   200  		if !isETagEqual(objInfo.ETag, ifMatchETagHeader) {
   201  			// If the object ETag does not match with the specified ETag.
   202  			writeHeaders()
   203  			WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrPreconditionFailed), r.URL, guessIsBrowserReq(r))
   204  			return true
   205  		}
   206  	}
   207  
   208  	// If-None-Match : Return the object only if its entity tag (ETag) is different from the
   209  	// one specified otherwise, return a 304 (not modified).
   210  	ifNoneMatchETagHeader := r.Header.Get(xhttp.IfNoneMatch)
   211  	if ifNoneMatchETagHeader != "" {
   212  		if isETagEqual(objInfo.ETag, ifNoneMatchETagHeader) {
   213  			// If the object ETag matches with the specified ETag.
   214  			writeHeaders()
   215  			w.WriteHeader(http.StatusNotModified)
   216  			return true
   217  		}
   218  	}
   219  	// Object content should be written to http.ResponseWriter
   220  	return false
   221  }
   222  
   223  // returns true if object was modified after givenTime.
   224  func ifModifiedSince(objTime time.Time, givenTime time.Time) bool {
   225  	// The Date-Modified header truncates sub-second precision, so
   226  	// use mtime < t+1s instead of mtime <= t to check for unmodified.
   227  	return objTime.After(givenTime.Add(1 * time.Second))
   228  }
   229  
   230  // canonicalizeETag returns ETag with leading and trailing double-quotes removed,
   231  // if any present
   232  func canonicalizeETag(etag string) string {
   233  	return etagRegex.ReplaceAllString(etag, "$1")
   234  }
   235  
   236  // isETagEqual return true if the canonical representations of two ETag strings
   237  // are equal, false otherwise
   238  func isETagEqual(left, right string) bool {
   239  	return canonicalizeETag(left) == canonicalizeETag(right)
   240  }
   241  
   242  // setPutObjHeaders sets all the necessary headers returned back
   243  // upon a success Put/Copy/CompleteMultipart/Delete requests
   244  // to activate delete only headers set delete as true
   245  func setPutObjHeaders(w http.ResponseWriter, objInfo ObjectInfo, delete bool) {
   246  	// We must not use the http.Header().Set method here because some (broken)
   247  	// clients expect the ETag header key to be literally "ETag" - not "Etag" (case-sensitive).
   248  	// Therefore, we have to set the ETag directly as map entry.
   249  	if objInfo.ETag != "" && !delete {
   250  		w.Header()[xhttp.ETag] = []string{`"` + objInfo.ETag + `"`}
   251  	}
   252  
   253  	// Set the relevant version ID as part of the response header.
   254  	if objInfo.VersionID != "" {
   255  		w.Header()[xhttp.AmzVersionID] = []string{objInfo.VersionID}
   256  		// If version is a deleted marker, set this header as well
   257  		if objInfo.DeleteMarker && delete { // only returned during delete object
   258  			w.Header()[xhttp.AmzDeleteMarker] = []string{strconv.FormatBool(objInfo.DeleteMarker)}
   259  		}
   260  	}
   261  
   262  	if objInfo.Bucket != "" && objInfo.Name != "" {
   263  		if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil && !delete {
   264  			ruleID, expiryTime := lc.PredictExpiryTime(lifecycle.ObjectOpts{
   265  				Name:         objInfo.Name,
   266  				UserTags:     objInfo.UserTags,
   267  				VersionID:    objInfo.VersionID,
   268  				ModTime:      objInfo.ModTime,
   269  				IsLatest:     objInfo.IsLatest,
   270  				DeleteMarker: objInfo.DeleteMarker,
   271  			})
   272  			if !expiryTime.IsZero() {
   273  				w.Header()[xhttp.AmzExpiration] = []string{
   274  					fmt.Sprintf(`expiry-date="%s", rule-id="%s"`, expiryTime.Format(http.TimeFormat), ruleID),
   275  				}
   276  			}
   277  		}
   278  	}
   279  }