Denis Kirillov
14ef9ff091
All checks were successful
/ Vulncheck (pull_request) Successful in 1m40s
/ Builds (1.19) (pull_request) Successful in 2m54s
/ Builds (1.20) (pull_request) Successful in 2m39s
/ DCO (pull_request) Successful in 3m48s
/ Lint (pull_request) Successful in 3m32s
/ Tests (1.19) (pull_request) Successful in 2m38s
/ Tests (1.20) (pull_request) Successful in 2m55s
To be able to handle cases and return appropriate http status code when object missed in storage but gate cache contains its metadata we need write code after init object reader. So we separate init reader from actual reading. Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
197 lines
5.4 KiB
Go
197 lines
5.4 KiB
Go
package handler
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const sizeToDetectType = 512
|
|
|
|
func getRangeToDetectContentType(maxSize uint64) *layer.RangeParams {
|
|
end := maxSize
|
|
if sizeToDetectType < end {
|
|
end = sizeToDetectType
|
|
}
|
|
|
|
return &layer.RangeParams{
|
|
Start: 0,
|
|
End: end - 1,
|
|
}
|
|
}
|
|
|
|
func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) {
|
|
reqInfo := middleware.GetReqInfo(r.Context())
|
|
|
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
|
if err != nil {
|
|
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
|
return
|
|
}
|
|
|
|
conditional, err := parseConditionalHeaders(r.Header)
|
|
if err != nil {
|
|
h.logAndSendError(w, "could not parse request params", reqInfo, err)
|
|
return
|
|
}
|
|
|
|
p := &layer.HeadObjectParams{
|
|
BktInfo: bktInfo,
|
|
Object: reqInfo.ObjectName,
|
|
VersionID: reqInfo.URL.Query().Get(api.QueryVersionID),
|
|
}
|
|
|
|
extendedInfo, err := h.obj.GetExtendedObjectInfo(r.Context(), p)
|
|
if err != nil {
|
|
h.logAndSendError(w, "could not find object", reqInfo, err)
|
|
return
|
|
}
|
|
info := extendedInfo.ObjectInfo
|
|
|
|
encryptionParams, err := formEncryptionParams(r)
|
|
if err != nil {
|
|
h.logAndSendError(w, "invalid sse headers", reqInfo, err)
|
|
return
|
|
}
|
|
|
|
if err = encryptionParams.MatchObjectEncryption(layer.FormEncryptionInfo(info.Headers)); err != nil {
|
|
h.logAndSendError(w, "encryption doesn't match object", reqInfo, errors.GetAPIError(errors.ErrBadRequest), zap.Error(err))
|
|
return
|
|
}
|
|
|
|
if err = checkPreconditions(info, conditional); err != nil {
|
|
h.logAndSendError(w, "precondition failed", reqInfo, err)
|
|
return
|
|
}
|
|
|
|
t := &layer.ObjectVersion{
|
|
BktInfo: bktInfo,
|
|
ObjectName: info.Name,
|
|
VersionID: info.VersionID(),
|
|
}
|
|
|
|
tagSet, lockInfo, err := h.obj.GetObjectTaggingAndLock(r.Context(), t, extendedInfo.NodeVersion)
|
|
if err != nil && !errors.IsS3Error(err, errors.ErrNoSuchKey) {
|
|
h.logAndSendError(w, "could not get object meta data", reqInfo, err)
|
|
return
|
|
}
|
|
|
|
if len(info.ContentType) == 0 {
|
|
if info.ContentType = layer.MimeByFilePath(info.Name); len(info.ContentType) == 0 {
|
|
getParams := &layer.GetObjectParams{
|
|
ObjectInfo: info,
|
|
Versioned: p.Versioned(),
|
|
Range: getRangeToDetectContentType(info.Size),
|
|
BucketInfo: bktInfo,
|
|
}
|
|
|
|
objPayload, err := h.obj.GetObject(r.Context(), getParams)
|
|
if err != nil {
|
|
h.logAndSendError(w, "could not get object", reqInfo, err, zap.Stringer("oid", info.ID))
|
|
return
|
|
}
|
|
|
|
buffer, err := io.ReadAll(objPayload)
|
|
if err != nil {
|
|
h.logAndSendError(w, "could not partly read payload to detect content type", reqInfo, err, zap.Stringer("oid", info.ID))
|
|
return
|
|
}
|
|
|
|
info.ContentType = http.DetectContentType(buffer)
|
|
}
|
|
}
|
|
|
|
if err = h.setLockingHeaders(bktInfo, lockInfo, w.Header()); err != nil {
|
|
h.logAndSendError(w, "could not get locking info", reqInfo, err)
|
|
return
|
|
}
|
|
|
|
bktSettings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
|
if err != nil {
|
|
h.logAndSendError(w, "could not get bucket settings", reqInfo, err)
|
|
return
|
|
}
|
|
|
|
writeHeaders(w.Header(), r.Header, extendedInfo, len(tagSet), bktSettings.Unversioned())
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
func (h *handler) HeadBucketHandler(w http.ResponseWriter, r *http.Request) {
|
|
reqInfo := middleware.GetReqInfo(r.Context())
|
|
|
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
|
if err != nil {
|
|
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
|
return
|
|
}
|
|
|
|
w.Header().Set(api.OwnerID, bktInfo.Owner.EncodeToString())
|
|
w.Header().Set(api.ContainerID, bktInfo.CID.EncodeToString())
|
|
w.Header().Set(api.AmzBucketRegion, bktInfo.LocationConstraint)
|
|
|
|
if isAvailableToResolve(bktInfo.Zone, h.cfg.ResolveZoneList, h.cfg.IsResolveListAllow) {
|
|
w.Header().Set(api.ContainerName, bktInfo.Name)
|
|
w.Header().Set(api.ContainerZone, bktInfo.Zone)
|
|
}
|
|
|
|
middleware.WriteResponse(w, http.StatusOK, nil, middleware.MimeNone)
|
|
}
|
|
|
|
func (h *handler) setLockingHeaders(bktInfo *data.BucketInfo, lockInfo *data.LockInfo, header http.Header) error {
|
|
if !bktInfo.ObjectLockEnabled {
|
|
return nil
|
|
}
|
|
|
|
legalHold := &data.LegalHold{Status: legalHoldOff}
|
|
retention := &data.Retention{Mode: governanceMode}
|
|
|
|
if lockInfo.IsLegalHoldSet() {
|
|
legalHold.Status = legalHoldOn
|
|
}
|
|
if lockInfo.IsRetentionSet() {
|
|
retention.RetainUntilDate = lockInfo.UntilDate()
|
|
if lockInfo.IsCompliance() {
|
|
retention.Mode = complianceMode
|
|
}
|
|
}
|
|
|
|
writeLockHeaders(header, legalHold, retention)
|
|
return nil
|
|
}
|
|
|
|
func writeLockHeaders(h http.Header, legalHold *data.LegalHold, retention *data.Retention) {
|
|
h.Set(api.AmzObjectLockLegalHold, legalHold.Status)
|
|
|
|
if retention.RetainUntilDate != "" {
|
|
h.Set(api.AmzObjectLockRetainUntilDate, retention.RetainUntilDate)
|
|
h.Set(api.AmzObjectLockMode, retention.Mode)
|
|
}
|
|
}
|
|
|
|
func isAvailableToResolve(zone string, list []string, isAllowList bool) bool {
|
|
// empty zone means container doesn't have proper system name,
|
|
// so we don't have to resolve it
|
|
if len(zone) == 0 {
|
|
return false
|
|
}
|
|
|
|
var zoneInList bool
|
|
for _, t := range list {
|
|
if t == zone {
|
|
zoneInList = true
|
|
break
|
|
}
|
|
}
|
|
// InList | IsAllowList | Result
|
|
// 0 0 1
|
|
// 0 1 0
|
|
// 1 0 0
|
|
// 1 1 1
|
|
return zoneInList == isAllowList
|
|
}
|