forked from TrueCloudLab/frostfs-s3-gw
Roman Loginov
a87c636b4c
It is assumed that the X-Frostfs-S3-VHS header will have the value enabled/disabled instead of true/false. Signed-off-by: Roman Loginov <r.loginov@yadro.com>
155 lines
3.8 KiB
Go
155 lines
3.8 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const (
|
|
wildcardPlaceholder = "<wildcard>"
|
|
|
|
enabledVHS = "enabled"
|
|
disabledVHS = "disabled"
|
|
)
|
|
|
|
type VHSSettings interface {
|
|
Domains() []string
|
|
GlobalVHS() bool
|
|
VHSHeader() string
|
|
ServernameHeader() string
|
|
VHSNamespacesEnabled() map[string]bool
|
|
}
|
|
|
|
func PrepareAddressStyle(settings VHSSettings, log *zap.Logger) Func {
|
|
return func(h http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
reqInfo := GetReqInfo(ctx)
|
|
reqLogger := reqLogOrDefault(ctx, log)
|
|
statusVHS := r.Header.Get(settings.VHSHeader())
|
|
|
|
if isVHSAddress(statusVHS, settings.GlobalVHS(), settings.VHSNamespacesEnabled(), reqInfo.Namespace) {
|
|
prepareVHSAddress(reqInfo, r, settings)
|
|
} else {
|
|
preparePathStyleAddress(reqInfo, r, reqLogger)
|
|
}
|
|
|
|
h.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
func isVHSAddress(statusVHS string, enabledFlag bool, vhsNamespaces map[string]bool, namespace string) bool {
|
|
switch statusVHS {
|
|
case enabledVHS:
|
|
return true
|
|
case disabledVHS:
|
|
return false
|
|
default:
|
|
result := enabledFlag
|
|
if v, ok := vhsNamespaces[namespace]; ok {
|
|
result = v
|
|
}
|
|
|
|
return result
|
|
}
|
|
}
|
|
|
|
func prepareVHSAddress(reqInfo *ReqInfo, r *http.Request, settings VHSSettings) {
|
|
reqInfo.RequestVHSEnabled = true
|
|
bktName, match := checkDomain(r.Host, getDomains(r, settings))
|
|
if match {
|
|
if bktName == "" {
|
|
reqInfo.RequestType = noneType
|
|
} else {
|
|
if objName := strings.TrimPrefix(r.URL.Path, "/"); objName != "" {
|
|
reqInfo.RequestType = objectType
|
|
reqInfo.ObjectName = objName
|
|
reqInfo.BucketName = bktName
|
|
} else {
|
|
reqInfo.RequestType = bucketType
|
|
reqInfo.BucketName = bktName
|
|
}
|
|
}
|
|
} else {
|
|
parts := strings.Split(r.Host, ".")
|
|
reqInfo.BucketName = parts[0]
|
|
|
|
if objName := strings.TrimPrefix(r.URL.Path, "/"); objName != "" {
|
|
reqInfo.RequestType = objectType
|
|
reqInfo.ObjectName = objName
|
|
} else {
|
|
reqInfo.RequestType = bucketType
|
|
}
|
|
}
|
|
}
|
|
|
|
func getDomains(r *http.Request, settings VHSSettings) []string {
|
|
if headerServername := r.Header.Get(settings.ServernameHeader()); headerServername != "" {
|
|
return []string{headerServername}
|
|
}
|
|
|
|
return settings.Domains()
|
|
}
|
|
|
|
func preparePathStyleAddress(reqInfo *ReqInfo, r *http.Request, reqLogger *zap.Logger) {
|
|
bktObj := strings.TrimPrefix(r.URL.Path, "/")
|
|
if bktObj == "" {
|
|
reqInfo.RequestType = noneType
|
|
} else if ind := strings.IndexByte(bktObj, '/'); ind != -1 && bktObj[ind+1:] != "" {
|
|
reqInfo.RequestType = objectType
|
|
reqInfo.BucketName = bktObj[:ind]
|
|
reqInfo.ObjectName = bktObj[ind+1:]
|
|
|
|
if r.URL.RawPath != "" {
|
|
// we have to do this because of
|
|
// https://github.com/go-chi/chi/issues/641
|
|
// https://github.com/go-chi/chi/issues/642
|
|
if obj, err := url.PathUnescape(reqInfo.ObjectName); err != nil {
|
|
reqLogger.Warn(logs.FailedToUnescapeObjectName, zap.Error(err))
|
|
} else {
|
|
reqInfo.ObjectName = obj
|
|
}
|
|
}
|
|
} else {
|
|
reqInfo.RequestType = bucketType
|
|
reqInfo.BucketName = strings.TrimSuffix(bktObj, "/")
|
|
}
|
|
}
|
|
|
|
func checkDomain(host string, domains []string) (bktName string, match bool) {
|
|
partsHost := strings.Split(host, ".")
|
|
for _, pattern := range domains {
|
|
partsPattern := strings.Split(pattern, ".")
|
|
bktName, match = compareMatch(partsHost, partsPattern)
|
|
if match {
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func compareMatch(host, pattern []string) (bktName string, match bool) {
|
|
if len(host) < len(pattern) {
|
|
return "", false
|
|
}
|
|
|
|
i, j := len(host)-1, len(pattern)-1
|
|
for j >= 0 && (pattern[j] == wildcardPlaceholder || host[i] == pattern[j]) {
|
|
i--
|
|
j--
|
|
}
|
|
|
|
switch {
|
|
case i == -1:
|
|
return "", true
|
|
case i == 0 && (j != 0 || host[i] == pattern[j]):
|
|
return host[0], true
|
|
default:
|
|
return "", false
|
|
}
|
|
}
|