package middleware import ( "net/http" "net/url" "strconv" "strings" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" "go.uber.org/zap" ) const wildcardPlaceholder = "" 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) headerVHSEnabled := r.Header.Get(settings.VHSHeader()) if isVHSAddress(headerVHSEnabled, settings.GlobalVHS(), settings.VHSNamespacesEnabled(), reqInfo.Namespace) { prepareVHSAddress(reqInfo, r, settings) } else { preparePathStyleAddress(reqInfo, r, reqLogger) } h.ServeHTTP(w, r) }) } } func isVHSAddress(headerVHSEnabled string, enabledFlag bool, vhsNamespaces map[string]bool, namespace string) bool { if result, err := strconv.ParseBool(headerVHSEnabled); err == nil { return result } 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 } }