Merge pull request #1903 from versity/test/rest_cors_two

test: CORS - response header tests
This commit is contained in:
Ben McClelland
2026-03-03 08:19:13 -08:00
committed by GitHub
7 changed files with 608 additions and 61 deletions
@@ -86,4 +86,4 @@ get_bucket_cors_check_valid_data() {
return 1
fi
return 0
}
}
+189 -5
View File
@@ -318,16 +318,31 @@ send_rest_go_command_callback() {
return 0
}
# return 0 for key match, 1 for no key match, 2 for value mismatch
check_key_and_value_pair_for_match() {
if ! check_param_count_v2 "read key, read value, expected key, expected value" 4 $#; then
return 2
fi
if [ "${1,,}" == "${3,,}" ]; then
if [ "$2" != "$4" ]; then
log 2 "expected value of '$4', was '$2'"
return 2
fi
return 0
fi
return 1
}
check_for_header_key_and_value() {
if ! check_param_count_v2 "data file, header key, header value" 3 $#; then
return 1
fi
while IFS=$': \r' read -r key value; do
if [ "$key" == "$2" ]; then
if [ "$value" != "$3" ]; then
log 2 "expected value of '$3', was '$value'"
return 1
fi
local check_result=0
check_key_and_value_pair_for_match "$key" "$value" "$2" "$3" || check_result=$?
if [ "$check_result" -eq 2 ]; then
return 1
elif [ "$check_result" -eq 0 ]; then
return 0
fi
done <<< "$(grep -E '^.+: .+$' "$1")"
@@ -446,3 +461,172 @@ send_rest_go_command_write_response_to_file() {
return 0
}
check_header_keys_and_values() {
if ! check_param_count_gt "data file, header/key value pairs" 1 $#; then
return 1
fi
local data_file="$1"
local pairs=("${@:2}")
local check_result=0
local remaining_pairs=""
local line=""
local key=""
local value=""
# Parse header lines until the blank line separator.
# - Split on the first ':'
# - Allow empty values (e.g. "X-Foo:")
# - Preserve spaces in values
while IFS= read -r line; do
line="${line%$'\r'}"
# End of headers
if [ -z "$line" ]; then
break
fi
# Require at least one ':' to treat as a header field
case "$line" in
*:*) ;;
*) continue ;;
esac
key="${line%%:*}"
value="${line#*:}"
value="${value# }"
check_result=0
if remaining_pairs=$(check_for_key_and_value_within_pairs "$key" "$value" "${pairs[@]}"); then
# match found; remaining_pairs contains the updated list
pairs=()
if [ -n "$remaining_pairs" ]; then
while IFS= read -r line; do
pairs+=("$line")
done <<< "$remaining_pairs"
fi
if [ "${#pairs[@]}" -eq 0 ]; then
return 0
fi
else
check_result=$?
if [ "$check_result" -eq 2 ]; then
log 2 "error checking pair"
return 1
fi
# check_result==1 means no match; keep current pairs
fi
done < "$data_file"
if [ "${#pairs[@]}" -eq 0 ]; then
return 0
fi
log 2 "missing expected header key '${pairs[0]}'"
return 1
}
# Check that a specific header key/value pair (or pairs) is NOT present.
# Returns:
# 0 - none of the specified pairs are present
# 1 - at least one specified pair is present
# 2 - other error (param count, malformed pairs)
check_header_keys_and_values_not_present() {
if ! check_param_count_gt "data file, header/key value pairs" 1 $#; then
return 2
fi
local data_file="$1"
shift 1
if [ $(( $# % 2 )) -ne 0 ]; then
log 2 "header key/value pairs must be even count"
return 2
fi
local expected_pairs=("$@")
local line=""
local key=""
local value=""
while IFS= read -r line; do
line="${line%$'\r'}"
# End of headers
if [ -z "$line" ]; then
break
fi
case "$line" in
*:*) ;;
*) continue ;;
esac
key="${line%%:*}"
value="${line#*:}"
value="${value# }"
local idx
for ((idx=0; idx<${#expected_pairs[@]}; idx+=2)); do
local exp_key="${expected_pairs[$idx]}"
local exp_val="${expected_pairs[$((idx+1))]}"
if [ "${key,,}" = "${exp_key,,}" ] && [ "$value" = "$exp_val" ]; then
log 2 "unexpected header pair present: '$exp_key: $exp_val'"
return 1
fi
done
done < "$data_file"
return 0
}
check_for_key_and_value_within_pairs() {
if ! check_param_count_gt "read key, read value, full set of key/value pairs" 2 $#; then
return 2
fi
local read_key="$1"
local read_value="$2"
shift 2
# Require an even number of remaining args (key/value pairs)
if [ $(( $# % 2 )) -ne 0 ]; then
log 2 "key/value pairs must be even count"
return 2
fi
local pairs=()
local omit_idx=-1
local idx=0
while [ $# -gt 0 ]; do
local key="$1"
local value="$2"
local check_result=0
pairs+=("$key" "$value")
check_key_and_value_pair_for_match "$read_key" "$read_value" "$key" "$value" || check_result=$?
if [ "$check_result" -eq 2 ]; then
return 2
elif [ "$check_result" -eq 0 ]; then
# Omit the last matching pair (preserves previous behavior)
omit_idx=$idx
fi
idx=$((idx + 2))
shift 2
done
if [ "$omit_idx" -lt 0 ]; then
return 1
fi
for ((idx=0; idx<${#pairs[@]}; idx+=2)); do
if [ "$idx" -eq "$omit_idx" ]; then
continue
fi
echo "${pairs[$idx]}"
echo "${pairs[$((idx+1))]}"
done
return 0
}
@@ -0,0 +1,80 @@
package command
import (
"encoding/xml"
"errors"
"fmt"
"strings"
)
const (
AllowedMethods = "allowedMethods"
AllowedOrigins = "allowedOrigins"
AllowedHeaders = "allowedHeaders"
ExposedHeaders = "exposedHeaders"
ID = "id"
MaxAgeSeconds = "maxAgeSeconds"
)
type CORSRule struct {
XMLName xml.Name `xml:"CORSRule"`
AllowedMethod []string `xml:"AllowedMethod"`
AllowedOrigin []string `xml:"AllowedOrigin"`
AllowedHeader []string `xml:"AllowedHeader,omitempty"`
ExposeHeader []string `xml:"ExposeHeader,omitempty"`
ID string `xml:"ID,omitempty"`
MaxAgeSeconds string `xml:"MaxAgeSeconds,omitempty"`
}
type CORSConfiguration struct {
XMLName xml.Name `xml:"CORSConfiguration"`
XMLNamespace string `xml:"xmlns,attr"`
CORSRules []*CORSRule
}
func NewPutBucketCORSCommand(command *S3Command, ruleStrings []string) (*S3Command, error) {
command.Method = "PUT"
command.Query = "cors"
corsConfiguration := &CORSConfiguration{
XMLNamespace: "https://s3.amazonaws.com/doc/2006-03-01/",
}
for _, ruleString := range ruleStrings {
var corsRule *CORSRule
var err error
if corsRule, err = assembleCORSRule(ruleString); err != nil {
return nil, fmt.Errorf("error assembling CORS rule: %w", err)
}
corsConfiguration.CORSRules = append(corsConfiguration.CORSRules, corsRule)
}
xmlData, err := xml.Marshal(corsConfiguration)
if err != nil {
return nil, fmt.Errorf("error marshalling XML: %w", err)
}
command.Payload = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + string(xmlData)
return command, nil
}
func assembleCORSRule(ruleString string) (*CORSRule, error) {
corsRule := &CORSRule{}
ruleComponents := strings.Split(ruleString, ";")
for _, component := range ruleComponents {
componentSegments := strings.Split(component, "=")
switch componentSegments[0] {
case AllowedMethods:
corsRule.AllowedMethod = strings.Split(componentSegments[1], ",")
case AllowedOrigins:
corsRule.AllowedOrigin = strings.Split(componentSegments[1], ",")
case AllowedHeaders:
corsRule.AllowedHeader = strings.Split(componentSegments[1], ",")
case ExposedHeaders:
corsRule.ExposeHeader = strings.Split(componentSegments[1], ",")
case ID:
corsRule.ID = componentSegments[1]
case MaxAgeSeconds:
corsRule.MaxAgeSeconds = componentSegments[1]
default:
return nil, errors.New("invalid CORSRule component type: " + componentSegments[0])
}
}
return corsRule, nil
}
+29 -38
View File
@@ -41,25 +41,10 @@ const (
const SHA256HashZeroBytes = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
type S3RESTCommand struct {
Method string
Url string
Queries map[string]string
SignedParams map[string]string
UnsignedParams map[string]string
DataSource DataSource
}
type S3CommandErrors struct {
IncorrectSignature bool
AuthorizationHeaderMalformed bool
IncorrectCredential string
IncorrectYearMonthDay bool
InvalidYearMonthDay bool
IncorrectContentMD5 bool
MissingHostParam bool
CustomHostParam string
CustomHostParamSet bool
type HeaderValue struct {
Key string
Value string
Signed bool
}
type S3Command struct {
@@ -74,6 +59,7 @@ type S3Command struct {
AwsSecretAccessKey string
ServiceName string
SignedParams map[string]string
UnsignedParams map[string]string
PayloadFile string
IncorrectSignature bool
AuthorizationHeaderMalformed bool
@@ -100,7 +86,7 @@ type S3Command struct {
currentDateTime string
host string
payloadHash string
headerValues [][]string
headerValues []*HeaderValue
canonicalRequestHash string
path string
signedParamString string
@@ -208,24 +194,24 @@ func (s *S3Command) initializeOpenSSLPayloadAndGetContentLength() error {
}
func (s *S3Command) addHeaderValues() error {
s.headerValues = [][]string{}
s.headerValues = []*HeaderValue{}
if s.MissingHostParam {
s.headerValues = append(s.headerValues, []string{"host", ""})
s.headerValues = append(s.headerValues, &HeaderValue{"host", "", true})
} else if s.CustomHostParamSet {
s.headerValues = append(s.headerValues, []string{"host", s.CustomHostParam})
s.headerValues = append(s.headerValues, &HeaderValue{"host", s.CustomHostParam, true})
} else {
s.headerValues = append(s.headerValues, []string{"host", s.host})
s.headerValues = append(s.headerValues, &HeaderValue{"host", s.host, true})
}
if s.PayloadType == StreamingAWS4HMACSHA256PayloadTrailer && s.ChecksumType != "" {
s.headerValues = append(s.headerValues, []string{"x-amz-trailer", fmt.Sprintf("x-amz-checksum-%s", s.ChecksumType)})
s.headerValues = append(s.headerValues, &HeaderValue{"x-amz-trailer", fmt.Sprintf("x-amz-checksum-%s", s.ChecksumType), true})
}
s.headerValues = append(s.headerValues,
[]string{"x-amz-content-sha256", s.payloadHash},
[]string{"x-amz-date", s.currentDateTime},
&HeaderValue{"x-amz-content-sha256", s.payloadHash, true},
&HeaderValue{"x-amz-date", s.currentDateTime, true},
)
if s.Client == OPENSSL && !s.OmitContentLength {
s.headerValues = append(s.headerValues,
[]string{"Content-Length", fmt.Sprintf("%d", s.contentLength)})
&HeaderValue{"Content-Length", fmt.Sprintf("%d", s.contentLength), true})
}
if s.dataSource != nil && s.PayloadType != UnsignedPayload {
payloadSize, err := s.dataSource.SourceDataByteSize()
@@ -233,19 +219,22 @@ func (s *S3Command) addHeaderValues() error {
return fmt.Errorf("error getting payload size: %w", err)
}
s.headerValues = append(s.headerValues,
[]string{"x-amz-decoded-content-length", fmt.Sprintf("%d", payloadSize)})
&HeaderValue{"x-amz-decoded-content-length", fmt.Sprintf("%d", payloadSize), true})
}
for key, value := range s.SignedParams {
s.headerValues = append(s.headerValues, []string{key, value})
s.headerValues = append(s.headerValues, &HeaderValue{key, value, true})
}
if s.ContentMD5 || s.IncorrectContentMD5 || s.CustomContentMD5 != "" {
if err := s.addContentMD5Header(); err != nil {
return fmt.Errorf("error adding Content-MD5 header: %w", err)
}
}
for key, value := range s.UnsignedParams {
s.headerValues = append(s.headerValues, &HeaderValue{key, value, false})
}
sort.Slice(s.headerValues,
func(i, j int) bool {
return strings.ToLower(s.headerValues[i][0]) < strings.ToLower(s.headerValues[j][0])
return strings.ToLower(s.headerValues[i].Key) < strings.ToLower(s.headerValues[j].Key)
})
return nil
}
@@ -283,7 +272,7 @@ func (s *S3Command) addContentMD5Header() error {
contentMD5 = base64.StdEncoding.EncodeToString(md5Hash)
}
s.headerValues = append(s.headerValues, []string{"Content-MD5", contentMD5})
s.headerValues = append(s.headerValues, &HeaderValue{"Content-MD5", contentMD5, true})
return nil
}
@@ -308,9 +297,11 @@ func (s *S3Command) generateCanonicalRequestString() {
var signedParams []string
for _, headerValue := range s.headerValues {
key := strings.ToLower(headerValue[0])
canonicalRequestLines = append(canonicalRequestLines, key+":"+headerValue[1])
signedParams = append(signedParams, key)
if headerValue.Signed {
key := strings.ToLower(headerValue.Key)
canonicalRequestLines = append(canonicalRequestLines, key+":"+headerValue.Value)
signedParams = append(signedParams, key)
}
}
canonicalRequestLines = append(canonicalRequestLines, "")
@@ -375,7 +366,7 @@ func (s *S3Command) buildCurlShellCommand() (string, error) {
authorizationString := s.buildAuthorizationString()
curlCommand = append(curlCommand, "-H", fmt.Sprintf("\"%s\"", authorizationString))
for _, headerValue := range s.headerValues {
headerString := fmt.Sprintf("\"%s: %s\"", headerValue[0], headerValue[1])
headerString := fmt.Sprintf("\"%s: %s\"", headerValue.Key, headerValue.Value)
curlCommand = append(curlCommand, "-H", headerString)
}
if s.PayloadFile != "" {
@@ -407,10 +398,10 @@ func (s *S3Command) buildOpenSSLCommand() error {
openSSLCommand := []string{fmt.Sprintf("%s %s HTTP/1.1", s.Method, s.path)}
openSSLCommand = append(openSSLCommand, s.buildAuthorizationString())
for _, headerValue := range s.headerValues {
if headerValue[0] == "host" && s.MissingHostParam {
if headerValue.Key == "host" && s.MissingHostParam {
continue
}
openSSLCommand = append(openSSLCommand, fmt.Sprintf("%s:%s", headerValue[0], headerValue[1]))
openSSLCommand = append(openSSLCommand, fmt.Sprintf("%s:%s", headerValue.Key, headerValue.Value))
}
file, err := os.Create(s.FilePath)
+19 -5
View File
@@ -13,6 +13,7 @@ import (
const (
CreateBucket = "createBucket"
PutBucketCors = "putBucketCORS"
PutBucketTagging = "putBucketTagging"
PutObject = "putObject"
PutObjectTagging = "putObjectTagging"
@@ -29,6 +30,7 @@ var awsSecretAccessKey *string
var serviceName *string
var signedParamsMap restParams
var unsignedParamsMap restParams
var payloadFile *string
var incorrectSignature *bool
var incorrectCredential *string
@@ -63,15 +65,19 @@ var omitContentLength *bool
var locationConstraint *string
var locationConstraintSet bool = false
var corsRules arrayFlags
type restParams map[string]string
var paramSeparator *string
func (r *restParams) String() string {
return fmt.Sprintf("%v", *r)
}
func (r *restParams) Set(value string) error {
*r = make(map[string]string)
pairs := strings.Split(value, ",")
pairs := strings.Split(value, *paramSeparator)
for _, pair := range pairs {
kv := strings.SplitN(pair, ":", 2)
if len(kv) != 2 {
@@ -111,6 +117,7 @@ func main() {
AwsSecretAccessKey: *awsSecretAccessKey,
ServiceName: *serviceName,
SignedParams: signedParamsMap,
UnsignedParams: unsignedParamsMap,
PayloadFile: *payloadFile,
IncorrectSignature: *incorrectSignature,
AuthorizationScheme: *authorizationScheme,
@@ -149,7 +156,11 @@ func getS3CommandType(baseCommand *command.S3Command) (command.S3CommandConverte
switch *commandType {
case CreateBucket:
if s3Command, err = command.NewCreateBucketCommand(baseCommand, *locationConstraint, locationConstraintSet); err != nil {
return nil, fmt.Errorf("error setting up CreateBucket command: %v", err)
return nil, fmt.Errorf("error setting up CreateBucket command: %w", err)
}
case PutBucketCors:
if s3Command, err = command.NewPutBucketCORSCommand(baseCommand, corsRules); err != nil {
return nil, fmt.Errorf("error setting up PutBucketCORS command: %w", err)
}
case PutBucketTagging:
fields := command.TaggingFields{
@@ -158,11 +169,11 @@ func getS3CommandType(baseCommand *command.S3Command) (command.S3CommandConverte
TagValues: tagValues,
}
if s3Command, err = command.NewPutBucketTaggingCommand(baseCommand, &fields); err != nil {
return nil, fmt.Errorf("error setting up PutBucketTagging command: %v", err)
return nil, fmt.Errorf("error setting up PutBucketTagging command: %w", err)
}
case PutObject:
if s3Command, err = command.NewPutObjectCommand(baseCommand); err != nil {
return nil, fmt.Errorf("error setting up PutObject command: %v", err)
return nil, fmt.Errorf("error setting up PutObject command: %w", err)
}
case PutObjectTagging:
fields := command.TaggingFields{
@@ -171,7 +182,7 @@ func getS3CommandType(baseCommand *command.S3Command) (command.S3CommandConverte
TagValues: tagValues,
}
if s3Command, err = command.NewPutObjectTaggingCommand(baseCommand, &fields); err != nil {
return nil, fmt.Errorf("error setting up PutBucketTagging command: %v", err)
return nil, fmt.Errorf("error setting up PutBucketTagging command: %w", err)
}
default:
s3Command = baseCommand
@@ -198,6 +209,7 @@ func buildCommand(s3Command command.S3CommandConverter) error {
}
func checkFlags() error {
paramSeparator = flag.String("paramSeparator", ",", "What character to use to separate signed and unsigned params")
method = flag.String("method", "GET", "HTTP method to use")
url = flag.String("url", "https://localhost:7070", "S3 server URL")
bucketName = flag.String("bucketName", "", "Bucket name")
@@ -210,6 +222,7 @@ func checkFlags() error {
logger.Debug = flag.Bool("debug", false, "Print debug statements")
logger.LogFile = flag.String("logFile", "", "Log file, if any")
flag.Var(&signedParamsMap, "signedParams", "Signed params, separated by comma")
flag.Var(&unsignedParamsMap, "unsignedParams", "Unsigned params, separated by comma")
payloadFile = flag.String("payloadFile", "", "Payload file path, if any")
incorrectSignature = flag.Bool("incorrectSignature", false, "Simulate an incorrect signature")
incorrectYearMonthDay = flag.Bool("incorrectYearMonthDay", false, "Simulate an incorrect year/month/day")
@@ -235,6 +248,7 @@ func checkFlags() error {
flag.Var(&tagKeys, "tagKey", "Tag key (can add multiple)")
flag.Var(&tagValues, "tagValue", "Tag value (can add multiple)")
locationConstraint = flag.String("locationConstraint", "", "Location constraint for bucket creation")
flag.Var(&corsRules, "corsRule", "CORS rule for PutBucketCORS command (can add multiple)")
// Parse the flags
flag.Parse()
+138
View File
@@ -0,0 +1,138 @@
#!/usr/bin/env bats
# Copyright 2026 Versity Software
# This file is licensed under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http:#www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
load ./bats-support/load
load ./bats-assert/load
source ./tests/drivers/file.sh
source ./tests/drivers/params.sh
source ./tests/drivers/rest.sh
source ./tests/logger.sh
source ./tests/setup_unit.sh
@test "check_key_and_value_pair_for_match" {
run check_key_and_value_pair_for_match "one" "two" "three"
assert_failure 2
run check_key_and_value_pair_for_match "one" "two" "one" "three"
assert_failure 2
run check_key_and_value_pair_for_match "one" "two" "three" "four"
assert_failure 1
run check_key_and_value_pair_for_match "one" "two two" "one" "two two"
assert_success
run check_key_and_value_pair_for_match "Content-Type" "application/xml" "content-type" "application/xml"
assert_success
run check_key_and_value_pair_for_match "one" "two" "one" "two"
assert_success
}
@test "check_for_key_and_value_within_pairs" {
# match in middle, omit it
run check_for_key_and_value_within_pairs "B" "2" "A" "1" "B" "2" "C" "3"
assert_success
assert_output $'A\n1\nC\n3'
# match first, omit it
run check_for_key_and_value_within_pairs "A" "1" "A" "1" "B" "2" "C" "3"
assert_success
assert_output $'B\n2\nC\n3'
# match last, omit it
run check_for_key_and_value_within_pairs "C" "3" "A" "1" "B" "2" "C" "3"
assert_success
assert_output $'A\n1\nB\n2'
# no match should fail
run check_for_key_and_value_within_pairs "D" "9" "A" "1" "B" "2" "C" "3"
assert_failure 1
# key match but value mismatch should fail
run check_for_key_and_value_within_pairs "B" "999" "A" "1" "B" "2" "C" "3"
assert_failure 2
# duplicate exact pairs; omit only the last matching pair
run check_for_key_and_value_within_pairs "A" "2" "A" "2" "A" "2" "B" "3"
assert_success
assert_output $'A\n2\nB\n3'
# odd number of pair args should fail
run check_for_key_and_value_within_pairs "A" "1" "A" "1" "B"
assert_failure 2
}
@test "check_header_keys_and_values" {
run get_file_name
assert_success
resp_file="$TEST_FILE_FOLDER/$output"
# 1) Exact match, single header
run bash -c "printf 'HTTP/1.1 200 OK\r\nContent-Type: application/xml\r\n\r\n<body/>' > '$resp_file'"
assert_success
run check_header_keys_and_values "$resp_file" "Content-Type" "application/xml"
assert_success
# 2) Header value contains spaces
run bash -c "printf 'HTTP/1.1 200 OK\r\nContent-Disposition: attachment; filename=\"a b.txt\"\r\n\r\n' > '$resp_file'"
assert_success
run check_header_keys_and_values "$resp_file" "Content-Disposition" 'attachment; filename="a b.txt"'
assert_success
# 3) Empty header value allowed
run bash -c "printf 'HTTP/1.1 200 OK\r\nX-Empty:\r\n\r\n' > '$resp_file'"
assert_success
run check_header_keys_and_values "$resp_file" "X-Empty" ""
assert_success
# 4) Missing expected header should fail
run bash -c "printf 'HTTP/1.1 200 OK\r\nContent-Type: application/xml\r\n\r\n' > '$resp_file'"
assert_success
run check_header_keys_and_values "$resp_file" "ETag" '"abc"'
assert_failure
# 5) Value mismatch should fail
run bash -c "printf 'HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n' > '$resp_file'"
assert_success
run check_header_keys_and_values "$resp_file" "Content-Type" "application/xml"
assert_failure
# 6) Multiple expected headers, any order
run bash -c "printf 'HTTP/1.1 200 OK\r\nETag: \"abc\"\r\nContent-Type: application/xml\r\n\r\n' > '$resp_file'"
assert_success
run check_header_keys_and_values "$resp_file" "Content-Type" "application/xml" "ETag" '"abc"'
assert_success
# 7) Stop parsing at blank line (body contains ':' should not be treated as header)
run bash -c "printf 'HTTP/1.1 200 OK\r\nContent-Type: application/xml\r\n\r\nNotAHeader: still body\n' > '$resp_file'"
assert_success
run check_header_keys_and_values "$resp_file" "Content-Type" "application/xml"
assert_success
# 8) Key case-insensitivity
run bash -c "printf 'HTTP/1.1 200 OK\r\ncontent-type: application/xml\r\n\r\n' > '$resp_file'"
assert_success
run check_header_keys_and_values "$resp_file" "Content-Type" "application/xml"
assert_success
# middle header key/value
run bash -c "printf 'HTTP/1.1 200 OK\r\ncontent-type: application/xml\r\ndummy-header: dummy-val\r\nanother-dummy-header: another-val\r\n\r\n' > '$resp_file'"
assert_success
run check_header_keys_and_values "$resp_file" "Dummy-Header" "dummy-val"
assert_success
}
+152 -12
View File
@@ -49,9 +49,6 @@ source ./tests/setup.sh
}
@test "REST - CORS - empty CORS rule" {
if [ "$DIRECT" != "true" ]; then
skip "https://github.com/versity/versitygw/issues/1863"
fi
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name=$output
@@ -65,9 +62,6 @@ source ./tests/setup.sh
}
@test "REST - CORS - missing allowed origin" {
if [ "$DIRECT" != "true" ]; then
skip "https://github.com/versity/versitygw/issues/1863"
fi
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name=$output
@@ -81,9 +75,6 @@ source ./tests/setup.sh
}
@test "REST - CORS - missing allowed method" {
if [ "$DIRECT" != "true" ]; then
skip "https://github.com/versity/versitygw/issues/1863"
fi
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name=$output
@@ -97,9 +88,6 @@ source ./tests/setup.sh
}
@test "REST - CORS - empty allowed method" {
if [ "$DIRECT" != "true" ]; then
skip "https://github.com/versity/versitygw/issues/1863"
fi
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name=$output
@@ -145,3 +133,155 @@ source ./tests/setup.sh
run send_rest_go_command_expect_error "404" "NoSuchCORSConfiguration" "does not exist" "-query" "cors" "-bucketName" "$bucket_name"
assert_success
}
@test "REST - CORS - origin - ETag not returned in exposed headers" {
if [ "$DIRECT" != "true" ]; then
skip "https://github.com/versity/versitygw/issues/1893"
fi
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name=$output
run setup_bucket_v2 "$bucket_name"
assert_success
run send_rest_go_command "200" "-commandType" "putBucketCORS" "-corsRule" "allowedMethods=GET,PUT;allowedOrigins=http://example.com" "-bucketName" "$bucket_name" "-contentMD5"
assert_success
run send_rest_go_command_callback "200" "check_header_keys_and_values_not_present" "-bucketName" "$bucket_name" "-signedParams" "origin:http://example.com" "--" "access-control-expose-headers" "ETag"
assert_success
}
@test "REST - CORS - non-allowed method" {
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name=$output
run setup_bucket_v2 "$bucket_name"
assert_success
run send_rest_go_command "200" "-commandType" "putBucketCORS" "-corsRule" "allowedOrigins=http://example.com;allowedMethods=GET,POST" "-bucketName" "$bucket_name" "-contentMD5"
assert_success
run send_rest_go_command_callback "200" "check_header_keys_and_values_not_present" "-bucketName" "$bucket_name" "-paramSeparator" ";" "-signedParams" "origin:http://example.com" "-unsignedParams" "Access-Control-Request-Methods:GET,PUT" "--" "access-control-allow-methods" "GET"
assert_success
}
@test "REST - CORS - allowed method" {
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name=$output
run setup_bucket_v2 "$bucket_name"
assert_success
run send_rest_go_command "200" "-commandType" "putBucketCORS" "-corsRule" "allowedOrigins=http://example.com;allowedMethods=GET,POST" "-bucketName" "$bucket_name" "-contentMD5"
assert_success
run send_rest_go_command_callback "200" "check_header_keys_and_values" "-bucketName" "$bucket_name" "-paramSeparator" ";" "-signedParams" "origin:http://example.com" "-unsignedParams" "Access-Control-Request-Methods:GET,POST" "--" \
"access-control-allow-methods" "GET, POST" "access-control-allow-origin" "http://example.com"
assert_success
}
@test "REST - CORS - non-allowed header" {
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name=$output
run setup_bucket_v2 "$bucket_name"
assert_success
run send_rest_go_command "200" "-commandType" "putBucketCORS" "-corsRule" "allowedOrigins=http://example.com;allowedMethods=GET;allowedHeaders=dummy-header" "-bucketName" "$bucket_name" "-contentMD5"
assert_success
run send_rest_go_command_callback "200" "check_header_keys_and_values_not_present" "-bucketName" "$bucket_name" "-unsignedParams" "Access-Control-Request-Headers:dummy-headers" "--" "access-control-expose-headers" "dummy-header"
assert_success
}
@test "REST - CORS - allowed headers" {
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name=$output
run setup_bucket_v2 "$bucket_name"
assert_success
run send_rest_go_command "200" "-commandType" "putBucketCORS" "-corsRule" "allowedOrigins=http://example.com;allowedMethods=GET;allowedHeaders=dummy-header-one,dummy-header-two" "-bucketName" "$bucket_name" "-contentMD5"
assert_success
run send_rest_go_command_callback "200" "check_header_keys_and_values" "-bucketName" "$bucket_name" "-unsignedParams" "origin:http://example.com,Access-Control-Request-Headers:dummy-header-one" "--" \
"access-control-allow-headers" "dummy-header-one"
assert_success
run send_rest_go_command_callback "200" "check_header_keys_and_values" "-bucketName" "$bucket_name" "-paramSeparator" ";" "-unsignedParams" "origin:http://example.com;Access-Control-Request-Headers:dummy-header-one,dummy-header-two" "--" \
"access-control-allow-headers" "dummy-header-one, dummy-header-two"
assert_success
}
@test "REST - CORS - invalid max age seconds" {
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name=$output
run setup_bucket_v2 "$bucket_name"
assert_success
run send_rest_go_command_expect_error "400" "MalformedXML" "did not validate" "-commandType" "putBucketCORS" "-corsRule" "allowedOrigins=http://example.com;allowedMethods=GET;maxAgeSeconds=a" "-bucketName" "$bucket_name" "-contentMD5"
assert_success
}
@test "REST - CORS - valid max age seconds" {
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name=$output
run setup_bucket_v2 "$bucket_name"
assert_success
run send_rest_go_command "200" "-commandType" "putBucketCORS" "-corsRule" "allowedOrigins=http://example.com;allowedMethods=GET;maxAgeSeconds=3000" "-bucketName" "$bucket_name" "-contentMD5"
assert_success
run send_rest_go_command_callback "200" "check_header_keys_and_values" "-bucketName" "$bucket_name" "-paramSeparator" ";" "-unsignedParams" "origin:http://example.com" "--" \
"access-control-max-age" "3000"
assert_success
}
@test "REST - CORS - all fields" {
if [ "$DIRECT" != "true" ]; then
skip "https://github.com/versity/versitygw/issues/1893"
fi
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name=$output
run setup_bucket_v2 "$bucket_name"
assert_success
run send_rest_go_command "200" "-commandType" "putBucketCORS" "-corsRule" "allowedOrigins=http://example.com;allowedMethods=GET;allowedHeaders=dummy-header;exposedHeaders=dummy-header-two;maxAgeSeconds=60" \
"-bucketName" "$bucket_name" "-contentMD5"
assert_success
run send_rest_go_command_callback "200" "check_header_keys_and_values" "-bucketName" "$bucket_name" "-unsignedParams" "origin:http://example.com,access-control-request-methods:GET,access-control-request-headers:dummy-header" \
"--" "access-control-allow-origin" "http://example.com" "access-control-allow-methods" "GET" "access-control-allow-headers" "dummy-header" "access-control-expose-headers" "dummy-header-two" "access-control-max-age" "60"
assert_success
}
@test "REST - CORS - two rules" {
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name=$output
run setup_bucket_v2 "$bucket_name"
assert_success
run send_rest_go_command "200" "-commandType" "putBucketCORS" "-paramSeparator" ";" "-corsRule" "allowedOrigins=http://example.com;allowedMethods=GET,POST" "-corsRule" "allowedOrigins=http://exampletwo.com;allowedMethods=GET,PUT" "-bucketName" "$bucket_name" "-contentMD5"
assert_success
run send_rest_go_command_callback "200" "check_header_keys_and_values" "-bucketName" "$bucket_name" "-paramSeparator" ";" "-unsignedParams" "origin:http://example.com;access-control-request-methods:GET,POST" \
"--" "access-control-allow-origin" "http://example.com" "access-control-allow-methods" "GET, POST"
assert_success
run send_rest_go_command_callback "200" "check_header_keys_and_values" "-bucketName" "$bucket_name" "-paramSeparator" ";" "-unsignedParams" "origin:http://exampletwo.com;access-control-request-methods:GET,PUT" \
"--" "access-control-allow-origin" "http://exampletwo.com" "access-control-allow-methods" "GET, PUT"
assert_success
}