diff --git a/api/handler/get.go b/api/handler/get.go index 5113ad85b..2450062c0 100644 --- a/api/handler/get.go +++ b/api/handler/get.go @@ -5,6 +5,7 @@ import ( "net/http" "strconv" "strings" + "time" "github.com/gorilla/mux" "github.com/nspcc-dev/neofs-s3-gw/api" @@ -12,6 +13,11 @@ import ( "go.uber.org/zap" ) +type getObjectArgs struct { + IfModifiedSince time.Time + IfUnmodifiedSince time.Time +} + func fetchRangeHeader(headers http.Header, fullSize uint64) (*layer.RangeParams, error) { const prefix = "bytes=" rangeHeader := headers.Get("Range") @@ -73,10 +79,25 @@ func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) { rid = api.GetRequestID(r.Context()) ) + args, err := parseGetObjectArgs(r.Header) + if err != nil { + return + } + if inf, err = h.obj.GetObjectInfo(r.Context(), bkt, obj); err != nil { writeError(w, r, h.log, "could not find object", rid, bkt, obj, err) return } + + if !args.IfModifiedSince.IsZero() && inf.Created.Before(args.IfModifiedSince) { + w.WriteHeader(http.StatusNotModified) + return + } + if !args.IfUnmodifiedSince.IsZero() && inf.Created.After(args.IfUnmodifiedSince) { + w.WriteHeader(http.StatusPreconditionFailed) + return + } + if params, err = fetchRangeHeader(r.Header, uint64(inf.Size)); err != nil { writeError(w, r, h.log, "could not parse range header", rid, bkt, obj, err) return @@ -97,6 +118,27 @@ func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) { } } +func parseGetObjectArgs(headers http.Header) (*getObjectArgs, error) { + var err error + args := &getObjectArgs{} + + ifModifiedSince := headers.Get(api.IfModifiedSince) + if len(ifModifiedSince) > 0 { + if args.IfModifiedSince, err = time.Parse(http.TimeFormat, ifModifiedSince); err != nil { + return nil, fmt.Errorf("couldn't parse %s header: %w", api.IfModifiedSince, err) + } + } + + ifUnmodifiedSince := headers.Get(api.IfUnmodifiedSince) + if len(ifUnmodifiedSince) > 0 { + if args.IfUnmodifiedSince, err = time.Parse(http.TimeFormat, ifUnmodifiedSince); err != nil { + return nil, fmt.Errorf("couldn't parse %s header: %w", api.IfUnmodifiedSince, err) + } + } + + return args, nil +} + func writeRangeHeaders(w http.ResponseWriter, params *layer.RangeParams, size int64) { w.Header().Set("Accept-Ranges", "bytes") w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", params.Start, params.End, size)) diff --git a/api/headers.go b/api/headers.go index 085e3f773..4e8380f4e 100644 --- a/api/headers.go +++ b/api/headers.go @@ -1,6 +1,6 @@ package api -// Standard S3 HTTP response constants. +// Standard S3 HTTP request/response constants. const ( LastModified = "Last-Modified" Date = "Date" @@ -22,4 +22,6 @@ const ( ContentDisposition = "Content-Disposition" Authorization = "Authorization" Action = "Action" + IfModifiedSince = "If-Modified-Since" + IfUnmodifiedSince = "If-Unmodified-Since" )