forked from TrueCloudLab/frostfs-s3-gw
Denis Kirillov
14ef9ff091
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
|
|
}
|