forked from TrueCloudLab/frostfs-http-gw
Compare commits
5 commits
Author | SHA1 | Date | |
---|---|---|---|
b7b08d9d82 | |||
b9f1f455f8 | |||
304dbdd4c8 | |||
273459e090 | |||
cb72d11515 |
16 changed files with 601 additions and 74 deletions
|
@ -1,4 +1,4 @@
|
|||
FROM golang:1.22-alpine AS basebuilder
|
||||
FROM golang:1.24-alpine AS basebuilder
|
||||
RUN apk add --update make bash ca-certificates
|
||||
|
||||
FROM basebuilder AS builder
|
||||
|
|
|
@ -10,7 +10,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go_versions: [ '1.22', '1.23' ]
|
||||
go_versions: [ '1.23', '1.24' ]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.23'
|
||||
go-version: '1.24'
|
||||
cache: true
|
||||
|
||||
- name: Install linters
|
||||
|
@ -28,7 +28,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go_versions: [ '1.22', '1.23' ]
|
||||
go_versions: [ '1.23', '1.24' ]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
@ -53,7 +53,7 @@ jobs:
|
|||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.23'
|
||||
go-version: '1.24'
|
||||
|
||||
- name: Run integration tests
|
||||
run: |-
|
||||
|
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.22'
|
||||
go-version: '1.23'
|
||||
check-latest: true
|
||||
|
||||
- name: Install govulncheck
|
||||
|
|
|
@ -22,9 +22,6 @@ linters-settings:
|
|||
# 'default' case is present, even if all enum members aren't listed in the
|
||||
# switch
|
||||
default-signifies-exhaustive: true
|
||||
govet:
|
||||
# report about shadowed variables
|
||||
check-shadowing: false
|
||||
custom:
|
||||
truecloudlab-linters:
|
||||
path: bin/external_linters.so
|
||||
|
|
|
@ -4,9 +4,12 @@ This document outlines major changes between releases.
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
- Update Go to 1.23 (#228)
|
||||
|
||||
### Added
|
||||
- Add handling quota limit reached error (#187)
|
||||
- Add slash clipping for FileName attribute (#174)
|
||||
- Add new format of tag names config
|
||||
|
||||
## [0.32.3] - 2025-02-05
|
||||
|
||||
|
|
35
Makefile
35
Makefile
|
@ -2,9 +2,9 @@
|
|||
|
||||
REPO ?= $(shell go list -m)
|
||||
VERSION ?= $(shell git describe --tags --match "v*" --dirty --always --abbrev=8 2>/dev/null || cat VERSION 2>/dev/null || echo "develop")
|
||||
GO_VERSION ?= 1.22
|
||||
LINT_VERSION ?= 1.60.3
|
||||
TRUECLOUDLAB_LINT_VERSION ?= 0.0.6
|
||||
GO_VERSION ?= 1.23
|
||||
LINT_VERSION ?= 1.64.8
|
||||
TRUECLOUDLAB_LINT_VERSION ?= 0.0.10
|
||||
BUILD ?= $(shell date -u --iso=seconds)
|
||||
|
||||
HUB_IMAGE ?= git.frostfs.info/truecloudlab/frostfs-http-gw
|
||||
|
@ -30,9 +30,10 @@ PKG_VERSION ?= $(shell echo $(VERSION) | sed "s/^v//" | \
|
|||
sed "s/-/~/")-${OS_RELEASE}
|
||||
.PHONY: debpackage debclean
|
||||
|
||||
FUZZ_NGFUZZ_DIR ?= ""
|
||||
FUZZING_DIR = $(shell pwd)/tests/fuzzing/files
|
||||
NGFUZZ_REPO = https://gitflic.ru/project/yadro/ngfuzz.git
|
||||
FUZZ_TIMEOUT ?= 30
|
||||
FUZZ_FUNCTIONS ?= "all"
|
||||
FUZZ_FUNCTIONS ?= ""
|
||||
FUZZ_AUX ?= ""
|
||||
|
||||
# Make all binaries
|
||||
|
@ -99,18 +100,22 @@ check-ngfuzz:
|
|||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: install-fuzzing-deps
|
||||
install-fuzzing-deps: check-clang check-ngfuzz
|
||||
.PHONY: install-ngfuzz
|
||||
install-ngfuzz:
|
||||
ifeq (,$(wildcard $(FUZZING_DIR)/ngfuzz))
|
||||
@rm -rf $(FUZZING_DIR)/ngfuzz
|
||||
@git clone $(NGFUZZ_REPO) $(FUZZING_DIR)/ngfuzz
|
||||
@cd $(FUZZING_DIR)/ngfuzz && make
|
||||
endif
|
||||
|
||||
.PHONY: fuzz
|
||||
fuzz: install-fuzzing-deps
|
||||
fuzz: check-clang install-ngfuzz
|
||||
@START_PATH=$$(pwd); \
|
||||
ROOT_PATH=$$(realpath --relative-to=$(FUZZ_NGFUZZ_DIR) $$START_PATH) ; \
|
||||
cd $(FUZZ_NGFUZZ_DIR) && \
|
||||
./ngfuzz -clean && \
|
||||
./ngfuzz -fuzz $(FUZZ_FUNCTIONS) -rootdir $$ROOT_PATH -timeout $(FUZZ_TIMEOUT) $(FUZZ_AUX) && \
|
||||
./ngfuzz -report
|
||||
|
||||
ROOT_PATH=$$(realpath --relative-to=$(FUZZING_DIR)/ngfuzz $$START_PATH) ; \
|
||||
cd $(FUZZING_DIR)/ngfuzz && \
|
||||
./bin/ngfuzz clean && \
|
||||
env CGO_ENABLED=1 ./bin/ngfuzz fuzz --funcs $(FUZZ_FUNCTIONS) --rootdir $$ROOT_PATH --timeout $(FUZZ_TIMEOUT) $(FUZZ_AUX) && \
|
||||
./bin/ngfuzz coverage --rootdir $$ROOT_PATH
|
||||
|
||||
# Reformat code
|
||||
fmt:
|
||||
|
@ -150,7 +155,7 @@ dirty-image:
|
|||
@@make -C $(TMP_DIR)/linters lib CGO_ENABLED=1 OUT_DIR=$(OUTPUT_LINT_DIR)
|
||||
@rm -rf $(TMP_DIR)/linters
|
||||
@rmdir $(TMP_DIR) 2>/dev/null || true
|
||||
@CGO_ENABLED=1 GOBIN=$(LINT_DIR) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v$(LINT_VERSION)
|
||||
@CGO_ENABLED=1 GOBIN=$(LINT_DIR) go install -trimpath github.com/golangci/golangci-lint/cmd/golangci-lint@v$(LINT_VERSION)
|
||||
|
||||
# Run linters
|
||||
lint:
|
||||
|
|
|
@ -115,6 +115,7 @@ type (
|
|||
|
||||
tagsConfig struct {
|
||||
tagLogs sync.Map
|
||||
defaultLvl zap.AtomicLevel
|
||||
}
|
||||
|
||||
logLevelConfig struct {
|
||||
|
@ -134,19 +135,34 @@ func newLogLevel(v *viper.Viper) zap.AtomicLevel {
|
|||
}
|
||||
|
||||
func newTagsConfig(v *viper.Viper, ll zapcore.Level) *tagsConfig {
|
||||
var t tagsConfig
|
||||
t := tagsConfig{defaultLvl: zap.NewAtomicLevelAt(ll)}
|
||||
if err := t.update(v, ll); err != nil {
|
||||
// panic here is analogue of the similar panic during common log level initialization.
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
return &t
|
||||
}
|
||||
|
||||
func newLogLevelConfig(lvl zap.AtomicLevel, tagsConfig *tagsConfig) *logLevelConfig {
|
||||
return &logLevelConfig{
|
||||
cfg := &logLevelConfig{
|
||||
logLevel: lvl,
|
||||
tagsConfig: tagsConfig,
|
||||
}
|
||||
|
||||
cfg.setMinLogLevel()
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (l *logLevelConfig) setMinLogLevel() {
|
||||
l.tagsConfig.tagLogs.Range(func(_, value any) bool {
|
||||
v := value.(zapcore.Level)
|
||||
if v < l.logLevel.Level() {
|
||||
l.logLevel.SetLevel(v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (l *logLevelConfig) update(cfg *viper.Viper, log *zap.Logger) {
|
||||
|
@ -159,34 +175,34 @@ func (l *logLevelConfig) update(cfg *viper.Viper, log *zap.Logger) {
|
|||
if err := l.tagsConfig.update(cfg, l.logLevel.Level()); err != nil {
|
||||
log.Warn(logs.TagsLogConfigWontBeUpdated, zap.Error(err), logs.TagField(logs.TagApp))
|
||||
}
|
||||
|
||||
l.setMinLogLevel()
|
||||
}
|
||||
|
||||
func (t *tagsConfig) LevelEnabled(tag string, tgtLevel zapcore.Level) bool {
|
||||
lvl, ok := t.tagLogs.Load(tag)
|
||||
if !ok {
|
||||
return false
|
||||
return t.defaultLvl.Enabled(tgtLevel)
|
||||
}
|
||||
|
||||
return lvl.(zapcore.Level).Enabled(tgtLevel)
|
||||
}
|
||||
|
||||
func (t *tagsConfig) DefaultEnabled(lvl zapcore.Level) bool {
|
||||
return t.defaultLvl.Enabled(lvl)
|
||||
}
|
||||
|
||||
func (t *tagsConfig) update(cfg *viper.Viper, ll zapcore.Level) error {
|
||||
tags, err := fetchLogTagsConfig(cfg, ll)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.tagLogs.Range(func(key, value any) bool {
|
||||
t.tagLogs.Range(func(key, _ any) bool {
|
||||
k := key.(string)
|
||||
v := value.(zapcore.Level)
|
||||
|
||||
if lvl, ok := tags[k]; ok {
|
||||
if lvl != v {
|
||||
t.tagLogs.Store(key, lvl)
|
||||
}
|
||||
} else {
|
||||
if _, ok := tags[k]; !ok {
|
||||
t.tagLogs.Delete(key)
|
||||
delete(tags, k)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
@ -194,6 +210,7 @@ func (t *tagsConfig) update(cfg *viper.Viper, ll zapcore.Level) error {
|
|||
for k, v := range tags {
|
||||
t.tagLogs.Store(k, v)
|
||||
}
|
||||
t.defaultLvl.SetLevel(ll)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -672,7 +689,7 @@ func (a *app) configReload(ctx context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
a.settings.logLevelConfig.update(a.cfg.settings, a.log)
|
||||
a.settings.logLevelConfig.update(a.cfg.config(), a.log)
|
||||
|
||||
if err := a.settings.dialerSource.Update(fetchMultinetConfig(a.config(), a.log)); err != nil {
|
||||
a.log.Warn(logs.MultinetConfigWontBeUpdated, zap.Error(err), logs.TagField(logs.TagApp))
|
||||
|
|
|
@ -41,6 +41,7 @@ type zapCoreTagFilterWrapper struct {
|
|||
|
||||
type TagFilterSettings interface {
|
||||
LevelEnabled(tag string, lvl zapcore.Level) bool
|
||||
DefaultEnabled(lvl zapcore.Level) bool
|
||||
}
|
||||
|
||||
func (c *zapCoreTagFilterWrapper) Enabled(level zapcore.Level) bool {
|
||||
|
@ -63,24 +64,26 @@ func (c *zapCoreTagFilterWrapper) Check(entry zapcore.Entry, checked *zapcore.Ch
|
|||
}
|
||||
|
||||
func (c *zapCoreTagFilterWrapper) Write(entry zapcore.Entry, fields []zapcore.Field) error {
|
||||
if c.shouldSkip(entry, fields) || c.shouldSkip(entry, c.extra) {
|
||||
if c.shouldSkip(entry, fields, c.extra) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.core.Write(entry, fields)
|
||||
}
|
||||
|
||||
func (c *zapCoreTagFilterWrapper) shouldSkip(entry zapcore.Entry, fields []zap.Field) bool {
|
||||
func (c *zapCoreTagFilterWrapper) shouldSkip(entry zapcore.Entry, fields []zap.Field, extra []zap.Field) bool {
|
||||
for _, field := range fields {
|
||||
if field.Key == logs.TagFieldName && field.Type == zapcore.StringType {
|
||||
if !c.settings.LevelEnabled(field.String, entry.Level) {
|
||||
return true
|
||||
return !c.settings.LevelEnabled(field.String, entry.Level)
|
||||
}
|
||||
break
|
||||
}
|
||||
for _, field := range extra {
|
||||
if field.Key == logs.TagFieldName && field.Type == zapcore.StringType {
|
||||
return !c.settings.LevelEnabled(field.String, entry.Level)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return !c.settings.DefaultEnabled(entry.Level)
|
||||
}
|
||||
|
||||
func (c *zapCoreTagFilterWrapper) Sync() error {
|
||||
|
@ -127,14 +130,13 @@ func newLogEncoder() zapcore.Encoder {
|
|||
//
|
||||
// See also zapcore.Level, zap.NewProductionConfig, zap.AddStacktrace.
|
||||
func newStdoutLogger(v *viper.Viper, lvl zap.AtomicLevel, loggerSettings LoggerAppSettings, tagSetting TagFilterSettings) *Logger {
|
||||
stdout := zapcore.AddSync(os.Stderr)
|
||||
stdout := zapcore.AddSync(os.Stdout)
|
||||
|
||||
consoleOutCore := zapcore.NewCore(newLogEncoder(), stdout, lvl)
|
||||
consoleOutCore = applyZapCoreMiddlewares(consoleOutCore, v, loggerSettings, tagSetting)
|
||||
|
||||
return &Logger{
|
||||
logger: zap.New(consoleOutCore, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel))),
|
||||
lvl: lvl,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,7 +154,6 @@ func newJournaldLogger(v *viper.Viper, lvl zap.AtomicLevel, loggerSettings Logge
|
|||
|
||||
return &Logger{
|
||||
logger: zap.New(coreWithContext, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel))),
|
||||
lvl: lvl,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -113,7 +113,7 @@ const (
|
|||
|
||||
cfgLoggerTags = "logger.tags"
|
||||
cfgLoggerTagsPrefixTmpl = cfgLoggerTags + ".%d."
|
||||
cfgLoggerTagsNameTmpl = cfgLoggerTagsPrefixTmpl + "name"
|
||||
cfgLoggerTagsNameTmpl = cfgLoggerTagsPrefixTmpl + "names"
|
||||
cfgLoggerTagsLevelTmpl = cfgLoggerTagsPrefixTmpl + "level"
|
||||
|
||||
// Wallet.
|
||||
|
@ -208,7 +208,6 @@ var defaultTags = []string{logs.TagApp, logs.TagDatapath, logs.TagExternalStorag
|
|||
|
||||
type Logger struct {
|
||||
logger *zap.Logger
|
||||
lvl zap.AtomicLevel
|
||||
}
|
||||
|
||||
type appCfg struct {
|
||||
|
@ -516,8 +515,8 @@ func fetchLogTagsConfig(v *viper.Viper, defaultLvl zapcore.Level) (map[string]za
|
|||
res := make(map[string]zapcore.Level)
|
||||
|
||||
for i := 0; ; i++ {
|
||||
name := v.GetString(fmt.Sprintf(cfgLoggerTagsNameTmpl, i))
|
||||
if name == "" {
|
||||
tagNames := v.GetString(fmt.Sprintf(cfgLoggerTagsNameTmpl, i))
|
||||
if tagNames == "" {
|
||||
break
|
||||
}
|
||||
|
||||
|
@ -529,7 +528,12 @@ func fetchLogTagsConfig(v *viper.Viper, defaultLvl zapcore.Level) (map[string]za
|
|||
}
|
||||
}
|
||||
|
||||
res[name] = lvl
|
||||
for _, tagName := range strings.Split(tagNames, ",") {
|
||||
tagName = strings.TrimSpace(tagName)
|
||||
if len(tagName) != 0 {
|
||||
res[tagName] = lvl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(res) == 0 && !v.IsSet(cfgLoggerTags) {
|
||||
|
|
|
@ -20,8 +20,9 @@ HTTP_GW_LOGGER_SAMPLING_ENABLED=false
|
|||
HTTP_GW_LOGGER_SAMPLING_INITIAL=100
|
||||
HTTP_GW_LOGGER_SAMPLING_THEREAFTER=100
|
||||
HTTP_GW_LOGGER_SAMPLING_INTERVAL=1s
|
||||
HTTP_GW_LOGGER_TAGS_0_NAME=app
|
||||
HTTP_GW_LOGGER_TAGS_1_NAME=datapath
|
||||
HTTP_GW_LOGGER_TAGS_0_NAMES=app,datapath
|
||||
HTTP_GW_LOGGER_TAGS_0_LEVEL=level
|
||||
HTTP_GW_LOGGER_TAGS_1_NAME=external_storage_tree
|
||||
|
||||
HTTP_GW_SERVER_0_ADDRESS=0.0.0.0:443
|
||||
HTTP_GW_SERVER_0_TLS_ENABLED=false
|
||||
|
|
|
@ -30,8 +30,7 @@ logger:
|
|||
thereafter: 100
|
||||
interval: 1s
|
||||
tags:
|
||||
- name: app
|
||||
- name: datapath
|
||||
- names: app,datapath
|
||||
level: debug
|
||||
|
||||
server:
|
||||
|
|
|
@ -176,10 +176,9 @@ logger:
|
|||
thereafter: 100
|
||||
interval: 1s
|
||||
tags:
|
||||
- name: "app"
|
||||
- names: "app,datapath"
|
||||
level: info
|
||||
- name: "datapath"
|
||||
- name: "external_storage_tree"
|
||||
- names: "external_storage_tree"
|
||||
```
|
||||
|
||||
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||
|
@ -199,13 +198,13 @@ parameter. Available tags:
|
|||
|
||||
```yaml
|
||||
tags:
|
||||
- name: "app"
|
||||
- names: "app,datapath"
|
||||
level: info
|
||||
```
|
||||
|
||||
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||
|-----------------------|------------|---------------|---------------------------|-------------------------------------------------------------------------------------------------------|
|
||||
| `name` | `string` | yes | | Tag name. Possible values see below in `Tag values` section. |
|
||||
|-----------|------------|---------------|---------------------------|-------------------------------------------------------------------------------------------------------|
|
||||
| `names` | `[]string` | yes | | Tag names separated by `,`. Possible values see below in `Tag values` section. |
|
||||
| `level` | `string` | yes | Value from `logger.level` | Logging level for specific tag. Possible values: `debug`, `info`, `warn`, `dpanic`, `panic`, `fatal`. |
|
||||
|
||||
### Tag values
|
||||
|
|
2
go.mod
2
go.mod
|
@ -1,6 +1,6 @@
|
|||
module git.frostfs.info/TrueCloudLab/frostfs-http-gw
|
||||
|
||||
go 1.22
|
||||
go 1.23
|
||||
|
||||
require (
|
||||
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20241125133852-37bd75821121
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -78,7 +80,7 @@ func (h *Handler) Preflight(req *fasthttp.RequestCtx) {
|
|||
|
||||
for _, rule := range corsConfig.CORSRules {
|
||||
for _, o := range rule.AllowedOrigins {
|
||||
if o == string(origin) || o == wildcard {
|
||||
if o == string(origin) || o == wildcard || (strings.Contains(o, "*") && match(o, string(origin))) {
|
||||
for _, m := range rule.AllowedMethods {
|
||||
if m == string(method) {
|
||||
if !checkSubslice(rule.AllowedHeaders, headers) {
|
||||
|
@ -117,6 +119,11 @@ func (h *Handler) SetCORSHeaders(req *fasthttp.RequestCtx) {
|
|||
return
|
||||
}
|
||||
|
||||
method := req.Request.Header.Peek(fasthttp.HeaderAccessControlRequestMethod)
|
||||
if len(method) == 0 {
|
||||
method = req.Method()
|
||||
}
|
||||
|
||||
ctx = qostagging.ContextWithIOTag(ctx, internalIOTag)
|
||||
cidParam, _ := req.UserValue("cid").(string)
|
||||
reqLog := h.reqLogger(ctx)
|
||||
|
@ -141,9 +148,9 @@ func (h *Handler) SetCORSHeaders(req *fasthttp.RequestCtx) {
|
|||
|
||||
for _, rule := range corsConfig.CORSRules {
|
||||
for _, o := range rule.AllowedOrigins {
|
||||
if o == string(origin) {
|
||||
if o == string(origin) || (strings.Contains(o, "*") && len(o) > 1 && match(o, string(origin))) {
|
||||
for _, m := range rule.AllowedMethods {
|
||||
if m == string(req.Method()) {
|
||||
if m == string(method) {
|
||||
req.Response.Header.Set(fasthttp.HeaderAccessControlAllowOrigin, string(origin))
|
||||
req.Response.Header.Set(fasthttp.HeaderAccessControlAllowMethods, strings.Join(rule.AllowedMethods, ", "))
|
||||
req.Response.Header.Set(fasthttp.HeaderAccessControlAllowCredentials, "true")
|
||||
|
@ -154,7 +161,7 @@ func (h *Handler) SetCORSHeaders(req *fasthttp.RequestCtx) {
|
|||
}
|
||||
if o == wildcard {
|
||||
for _, m := range rule.AllowedMethods {
|
||||
if m == string(req.Method()) {
|
||||
if m == string(method) {
|
||||
if withCredentials {
|
||||
req.Response.Header.Set(fasthttp.HeaderAccessControlAllowOrigin, string(origin))
|
||||
req.Response.Header.Set(fasthttp.HeaderAccessControlAllowCredentials, "true")
|
||||
|
@ -318,12 +325,9 @@ func setCORSHeadersFromRule(c *fasthttp.RequestCtx, cors *data.CORSRule) {
|
|||
}
|
||||
|
||||
func checkSubslice(slice []string, subSlice []string) bool {
|
||||
if sliceContains(slice, wildcard) {
|
||||
if slices.Contains(slice, wildcard) {
|
||||
return true
|
||||
}
|
||||
if len(subSlice) > len(slice) {
|
||||
return false
|
||||
}
|
||||
for _, r := range subSlice {
|
||||
if !sliceContains(slice, r) {
|
||||
return false
|
||||
|
@ -334,9 +338,16 @@ func checkSubslice(slice []string, subSlice []string) bool {
|
|||
|
||||
func sliceContains(slice []string, str string) bool {
|
||||
for _, s := range slice {
|
||||
if s == str {
|
||||
if s == str || (strings.Contains(s, "*") && match(s, str)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func match(tmpl, str string) bool {
|
||||
regexpStr := "^" + regexp.QuoteMeta(tmpl) + "$"
|
||||
regexpStr = regexpStr[:strings.Index(regexpStr, "*")-1] + "." + regexpStr[strings.Index(regexpStr, "*"):]
|
||||
reg := regexp.MustCompile(regexpStr)
|
||||
return reg.Match([]byte(str))
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/data"
|
||||
|
@ -407,6 +408,12 @@ func TestCheckSubslice(t *testing.T) {
|
|||
actual: []string{"str1", "str5"},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "wildcard in allowed",
|
||||
allowed: []string{"str*"},
|
||||
actual: []string{"str", "str5"},
|
||||
expected: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require.Equal(t, tc.expected, checkSubslice(tc.allowed, tc.actual))
|
||||
|
@ -414,6 +421,489 @@ func TestCheckSubslice(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAllowedOriginWildcards(t *testing.T) {
|
||||
hc := prepareHandlerContext(t)
|
||||
bktName := "bucket-allowed-origin-wildcards"
|
||||
cnrID, cnr, err := hc.prepareContainer(bktName, acl.Private)
|
||||
require.NoError(t, err)
|
||||
hc.frostfs.SetContainer(cnrID, cnr)
|
||||
|
||||
cfg := &data.CORSConfiguration{
|
||||
CORSRules: []data.CORSRule{
|
||||
{
|
||||
AllowedOrigins: []string{"*suffix.example"},
|
||||
AllowedMethods: []string{"GET"},
|
||||
},
|
||||
{
|
||||
AllowedOrigins: []string{"https://*example"},
|
||||
AllowedMethods: []string{"GET"},
|
||||
},
|
||||
{
|
||||
AllowedOrigins: []string{"prefix.example*"},
|
||||
AllowedMethods: []string{"GET"},
|
||||
},
|
||||
},
|
||||
}
|
||||
setCORSObject(t, hc, cnrID, cfg, 1)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
handler func(*fasthttp.RequestCtx)
|
||||
requestHeaders map[string]string
|
||||
expectedHeaders map[string]string
|
||||
expectedStatus int
|
||||
}{
|
||||
{
|
||||
name: "set cors headers, empty request cors headers",
|
||||
handler: hc.Handler().SetCORSHeaders,
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set cors headers, invalid origin",
|
||||
handler: hc.Handler().SetCORSHeaders,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "https://origin.com",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set cors headers, first rule, no symbols in place of wildcard",
|
||||
handler: hc.Handler().SetCORSHeaders,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "suffix.example",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "suffix.example",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "GET",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set cors headers, first rule, valid origin",
|
||||
handler: hc.Handler().SetCORSHeaders,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "http://suffix.example",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "http://suffix.example",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "GET",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set cors headers, first rule, invalid origin",
|
||||
handler: hc.Handler().SetCORSHeaders,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "http://suffix-example",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set cors headers, second rule, no symbols in place of wildcard",
|
||||
handler: hc.Handler().SetCORSHeaders,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "https://example",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "https://example",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "GET",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set cors headers, second rule, valid origin",
|
||||
handler: hc.Handler().SetCORSHeaders,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "https://www.example",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "https://www.example",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "GET",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set cors headers, second rule, invalid origin",
|
||||
handler: hc.Handler().SetCORSHeaders,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "https://www.example.com",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set cors headers, third rule, no symbols in place of wildcard",
|
||||
handler: hc.Handler().SetCORSHeaders,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "prefix.example",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "prefix.example",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "GET",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set cors headers, third rule, valid origin",
|
||||
handler: hc.Handler().SetCORSHeaders,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "prefix.example.com",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "prefix.example.com",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "GET",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set cors headers, third rule, invalid origin",
|
||||
handler: hc.Handler().SetCORSHeaders,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "www.prefix.example",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set cors headers, third rule, invalid request method in header",
|
||||
handler: hc.Handler().SetCORSHeaders,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "prefix.example.com",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "PUT",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set cors headers, third rule, valid request method in header",
|
||||
handler: hc.Handler().SetCORSHeaders,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "prefix.example.com",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "GET",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "prefix.example.com",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "GET",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "preflight, empty request cors headers",
|
||||
handler: hc.Handler().Preflight,
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "",
|
||||
},
|
||||
expectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "preflight, invalid origin",
|
||||
handler: hc.Handler().Preflight,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "https://origin.com",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "GET",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "",
|
||||
},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "preflight, first rule, no symbols in place of wildcard",
|
||||
handler: hc.Handler().Preflight,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "suffix.example",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "GET",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "suffix.example",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "GET",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prelight, first rule, valid origin",
|
||||
handler: hc.Handler().Preflight,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "http://suffix.example",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "GET",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "http://suffix.example",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "GET",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "preflight, first rule, invalid origin",
|
||||
handler: hc.Handler().Preflight,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "http://suffix-example",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "GET",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "",
|
||||
},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "preflight, second rule, no symbols in place of wildcard",
|
||||
handler: hc.Handler().Preflight,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "https://example",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "GET",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "https://example",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "GET",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "preflight, second rule, valid origin",
|
||||
handler: hc.Handler().Preflight,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "https://www.example",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "GET",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "https://www.example",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "GET",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "preflight, second rule, invalid origin",
|
||||
handler: hc.Handler().Preflight,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "https://www.example.com",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "GET",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "",
|
||||
},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "preflight, third rule, no symbols in place of wildcard",
|
||||
handler: hc.Handler().Preflight,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "prefix.example",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "GET",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "prefix.example",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "GET",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "preflight, third rule, valid origin",
|
||||
handler: hc.Handler().Preflight,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "prefix.example.com",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "GET",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "prefix.example.com",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "GET",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "preflight, third rule, invalid origin",
|
||||
handler: hc.Handler().Preflight,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "www.prefix.example",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "GET",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "",
|
||||
},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "preflight, third rule, invalid request method in header",
|
||||
handler: hc.Handler().Preflight,
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "prefix.example.com",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "PUT",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "",
|
||||
},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := prepareCORSRequest(t, bktName, tc.requestHeaders)
|
||||
tc.handler(r)
|
||||
|
||||
expectedStatus := fasthttp.StatusOK
|
||||
if tc.expectedStatus != 0 {
|
||||
expectedStatus = tc.expectedStatus
|
||||
}
|
||||
require.Equal(t, expectedStatus, r.Response.StatusCode())
|
||||
for k, v := range tc.expectedHeaders {
|
||||
require.Equal(t, v, string(r.Response.Header.Peek(k)))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowedHeaderWildcards(t *testing.T) {
|
||||
hc := prepareHandlerContext(t)
|
||||
bktName := "bucket-allowed-header-wildcards"
|
||||
cnrID, cnr, err := hc.prepareContainer(bktName, acl.Private)
|
||||
require.NoError(t, err)
|
||||
hc.frostfs.SetContainer(cnrID, cnr)
|
||||
|
||||
cfg := &data.CORSConfiguration{
|
||||
CORSRules: []data.CORSRule{
|
||||
{
|
||||
AllowedOrigins: []string{"https://www.example.com"},
|
||||
AllowedMethods: []string{"HEAD"},
|
||||
AllowedHeaders: []string{"*-suffix"},
|
||||
},
|
||||
{
|
||||
AllowedOrigins: []string{"https://www.example.com"},
|
||||
AllowedMethods: []string{"HEAD"},
|
||||
AllowedHeaders: []string{"start-*-end"},
|
||||
},
|
||||
{
|
||||
AllowedOrigins: []string{"https://www.example.com"},
|
||||
AllowedMethods: []string{"HEAD"},
|
||||
AllowedHeaders: []string{"X-Amz-*"},
|
||||
},
|
||||
},
|
||||
}
|
||||
setCORSObject(t, hc, cnrID, cfg, 1)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
requestHeaders map[string]string
|
||||
expectedHeaders map[string]string
|
||||
expectedStatus int
|
||||
}{
|
||||
{
|
||||
name: "first rule, valid headers",
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "https://www.example.com",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "HEAD",
|
||||
fasthttp.HeaderAccessControlRequestHeaders: "header-suffix, -suffix",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "https://www.example.com",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "HEAD",
|
||||
fasthttp.HeaderAccessControlAllowHeaders: "header-suffix, -suffix",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "first rule, invalid headers",
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "https://www.example.com",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "HEAD",
|
||||
fasthttp.HeaderAccessControlRequestHeaders: "header-suffix-*",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "",
|
||||
fasthttp.HeaderAccessControlAllowHeaders: "",
|
||||
},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "second rule, valid headers",
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "https://www.example.com",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "HEAD",
|
||||
fasthttp.HeaderAccessControlRequestHeaders: "start--end, start-header-end",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "https://www.example.com",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "HEAD",
|
||||
fasthttp.HeaderAccessControlAllowHeaders: "start--end, start-header-end",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "second rule, invalid header ending",
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "https://www.example.com",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "HEAD",
|
||||
fasthttp.HeaderAccessControlRequestHeaders: "start-header-end-*",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "",
|
||||
fasthttp.HeaderAccessControlAllowHeaders: "",
|
||||
},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "second rule, invalid header beginning",
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "https://www.example.com",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "HEAD",
|
||||
fasthttp.HeaderAccessControlRequestHeaders: "*-start-header-end",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "",
|
||||
fasthttp.HeaderAccessControlAllowHeaders: "",
|
||||
},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "third rule, valid headers",
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "https://www.example.com",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "HEAD",
|
||||
fasthttp.HeaderAccessControlRequestHeaders: "X-Amz-Date, X-Amz-Content-Sha256",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "https://www.example.com",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "HEAD",
|
||||
fasthttp.HeaderAccessControlAllowHeaders: "X-Amz-Date, X-Amz-Content-Sha256",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "third rule, invalid headers",
|
||||
requestHeaders: map[string]string{
|
||||
fasthttp.HeaderOrigin: "https://www.example.com",
|
||||
fasthttp.HeaderAccessControlRequestMethod: "HEAD",
|
||||
fasthttp.HeaderAccessControlRequestHeaders: "Authorization",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
fasthttp.HeaderAccessControlAllowOrigin: "",
|
||||
fasthttp.HeaderAccessControlAllowMethods: "",
|
||||
fasthttp.HeaderAccessControlAllowHeaders: "",
|
||||
},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := prepareCORSRequest(t, bktName, tc.requestHeaders)
|
||||
hc.Handler().Preflight(r)
|
||||
|
||||
expectedStatus := http.StatusOK
|
||||
if tc.expectedStatus != 0 {
|
||||
expectedStatus = tc.expectedStatus
|
||||
}
|
||||
require.Equal(t, expectedStatus, r.Response.StatusCode())
|
||||
for k, v := range tc.expectedHeaders {
|
||||
require.Equal(t, v, string(r.Response.Header.Peek(k)))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setCORSObject(t *testing.T, hc *handlerContext, cnrID cid.ID, corsConfig *data.CORSConfiguration, epoch uint64) {
|
||||
payload, err := xml.Marshal(corsConfig)
|
||||
require.NoError(t, err)
|
||||
|
|
Loading…
Add table
Reference in a new issue