mirror of
https://github.com/versity/versitygw.git
synced 2026-07-02 16:54:25 +00:00
f08f76fea4
Integrate x-amz-website-redirect-location across object metadata flows so uploads, copies, multipart creation, HEAD, and GET preserve and return redirect locations, and website hosting applies object-level redirects from the stored value.
470 lines
15 KiB
Go
470 lines
15 KiB
Go
// 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.
|
|
|
|
package integration
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
|
"github.com/versity/versitygw/s3err"
|
|
)
|
|
|
|
const maxWebsiteConfigSize = 131072
|
|
|
|
func PutBucketWebsite_non_existing_bucket(s *S3Conf) error {
|
|
testName := "PutBucketWebsite_non_existing_bucket"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.PutBucketWebsite(ctx, &s3.PutBucketWebsiteInput{
|
|
Bucket: getPtr("non-existing-bucket"),
|
|
WebsiteConfiguration: &types.WebsiteConfiguration{
|
|
IndexDocument: &types.IndexDocument{
|
|
Suffix: getPtr("index.html"),
|
|
},
|
|
},
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket))
|
|
})
|
|
}
|
|
|
|
func PutBucketWebsite_empty_suffix(s *S3Conf) error {
|
|
testName := "PutBucketWebsite_empty_suffix"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.PutBucketWebsite(ctx, &s3.PutBucketWebsiteInput{
|
|
Bucket: &bucket,
|
|
WebsiteConfiguration: &types.WebsiteConfiguration{
|
|
IndexDocument: &types.IndexDocument{
|
|
Suffix: getPtr(""),
|
|
},
|
|
},
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgIndexDocumentSuffix, ""))
|
|
})
|
|
}
|
|
|
|
func PutBucketWebsite_suffix_with_slash(s *S3Conf) error {
|
|
testName := "PutBucketWebsite_suffix_with_slash"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.PutBucketWebsite(ctx, &s3.PutBucketWebsiteInput{
|
|
Bucket: &bucket,
|
|
WebsiteConfiguration: &types.WebsiteConfiguration{
|
|
IndexDocument: &types.IndexDocument{
|
|
Suffix: getPtr("/index.html"),
|
|
},
|
|
},
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgIndexDocumentSuffix, "/index.html"))
|
|
})
|
|
}
|
|
|
|
func PutBucketWebsite_invalid_redirect_protocol(s *S3Conf) error {
|
|
testName := "PutBucketWebsite_invalid_redirect_protocol"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.PutBucketWebsite(ctx, &s3.PutBucketWebsiteInput{
|
|
Bucket: &bucket,
|
|
WebsiteConfiguration: &types.WebsiteConfiguration{
|
|
RedirectAllRequestsTo: &types.RedirectAllRequestsTo{
|
|
HostName: getPtr("example.com"),
|
|
Protocol: types.Protocol("ftp"),
|
|
},
|
|
},
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidWebsiteRedirectProtocol))
|
|
})
|
|
}
|
|
|
|
func PutBucketWebsite_redirectAll_index_error_routingRules(s *S3Conf) error {
|
|
testName := "PutBucketWebsite_redirectAll_index_error_routingRules"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
for _, test := range []struct {
|
|
name string
|
|
config *types.WebsiteConfiguration
|
|
}{
|
|
{
|
|
name: "index document",
|
|
config: &types.WebsiteConfiguration{
|
|
RedirectAllRequestsTo: &types.RedirectAllRequestsTo{
|
|
HostName: getPtr("example.com"),
|
|
},
|
|
IndexDocument: &types.IndexDocument{
|
|
Suffix: getPtr("index.html"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "error document",
|
|
config: &types.WebsiteConfiguration{
|
|
RedirectAllRequestsTo: &types.RedirectAllRequestsTo{
|
|
HostName: getPtr("example.com"),
|
|
},
|
|
ErrorDocument: &types.ErrorDocument{
|
|
Key: getPtr("error.html"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "routing rules",
|
|
config: &types.WebsiteConfiguration{
|
|
RedirectAllRequestsTo: &types.RedirectAllRequestsTo{
|
|
HostName: getPtr("example.com"),
|
|
},
|
|
RoutingRules: []types.RoutingRule{
|
|
{
|
|
Redirect: &types.Redirect{
|
|
HostName: getPtr("redirect.example.com"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
} {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.PutBucketWebsite(ctx, &s3.PutBucketWebsiteInput{
|
|
Bucket: &bucket,
|
|
WebsiteConfiguration: test.config,
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedXML)); err != nil {
|
|
return fmt.Errorf("%s: %w", test.name, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func PutBucketWebsite_invalid_routing_rule_protocol(s *S3Conf) error {
|
|
testName := "PutBucketWebsite_invalid_routing_rule_protocol"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.PutBucketWebsite(ctx, &s3.PutBucketWebsiteInput{
|
|
Bucket: &bucket,
|
|
WebsiteConfiguration: &types.WebsiteConfiguration{
|
|
IndexDocument: &types.IndexDocument{
|
|
Suffix: getPtr("index.html"),
|
|
},
|
|
RoutingRules: []types.RoutingRule{
|
|
{
|
|
Redirect: &types.Redirect{
|
|
HostName: getPtr("example.com"),
|
|
Protocol: types.Protocol("ftp"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidWebsiteRedirectProtocol))
|
|
})
|
|
}
|
|
|
|
func PutBucketWebsite_empty_routing_rule_condition(s *S3Conf) error {
|
|
testName := "PutBucketWebsite_empty_routing_rule_condition"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.PutBucketWebsite(ctx, &s3.PutBucketWebsiteInput{
|
|
Bucket: &bucket,
|
|
WebsiteConfiguration: &types.WebsiteConfiguration{
|
|
IndexDocument: &types.IndexDocument{
|
|
Suffix: getPtr("index.html"),
|
|
},
|
|
RoutingRules: []types.RoutingRule{
|
|
{
|
|
Condition: &types.Condition{},
|
|
Redirect: &types.Redirect{
|
|
HostName: getPtr("example.com"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedXML))
|
|
})
|
|
}
|
|
|
|
func PutBucketWebsite_empty_routing_rule_redirect(s *S3Conf) error {
|
|
testName := "PutBucketWebsite_empty_routing_rule_redirect"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.PutBucketWebsite(ctx, &s3.PutBucketWebsiteInput{
|
|
Bucket: &bucket,
|
|
WebsiteConfiguration: &types.WebsiteConfiguration{
|
|
IndexDocument: &types.IndexDocument{
|
|
Suffix: getPtr("index.html"),
|
|
},
|
|
RoutingRules: []types.RoutingRule{
|
|
{
|
|
Condition: &types.Condition{
|
|
KeyPrefixEquals: getPtr("docs/"),
|
|
},
|
|
Redirect: &types.Redirect{},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedXML))
|
|
})
|
|
}
|
|
|
|
func PutBucketWebsite_empty_error_document_key(s *S3Conf) error {
|
|
testName := "PutBucketWebsite_empty_error_document_key"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.PutBucketWebsite(ctx, &s3.PutBucketWebsiteInput{
|
|
Bucket: &bucket,
|
|
WebsiteConfiguration: &types.WebsiteConfiguration{
|
|
IndexDocument: &types.IndexDocument{
|
|
Suffix: getPtr("index.html"),
|
|
},
|
|
ErrorDocument: &types.ErrorDocument{
|
|
Key: getPtr(""),
|
|
},
|
|
},
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgErrorDocumentKey, ""))
|
|
})
|
|
}
|
|
|
|
func PutBucketWebsite_too_many_routing_rules(s *S3Conf) error {
|
|
testName := "PutBucketWebsite_too_many_routing_rules"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
routingRules := make([]types.RoutingRule, 51)
|
|
for i := range routingRules {
|
|
routingRules[i] = types.RoutingRule{
|
|
Condition: &types.Condition{
|
|
KeyPrefixEquals: getPtr(fmt.Sprintf("prefix-%d/", i)),
|
|
},
|
|
Redirect: &types.Redirect{
|
|
ReplaceKeyPrefixWith: getPtr(fmt.Sprintf("replacement-%d/", i)),
|
|
},
|
|
}
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.PutBucketWebsite(ctx, &s3.PutBucketWebsiteInput{
|
|
Bucket: &bucket,
|
|
WebsiteConfiguration: &types.WebsiteConfiguration{
|
|
IndexDocument: &types.IndexDocument{
|
|
Suffix: getPtr("index.html"),
|
|
},
|
|
RoutingRules: routingRules,
|
|
},
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetWebsiteRoutingRulesLimitedErr(51))
|
|
})
|
|
}
|
|
|
|
func PutBucketWebsite_routing_rule_replace_key_and_prefix(s *S3Conf) error {
|
|
testName := "PutBucketWebsite_routing_rule_replace_key_and_prefix"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.PutBucketWebsite(ctx, &s3.PutBucketWebsiteInput{
|
|
Bucket: &bucket,
|
|
WebsiteConfiguration: &types.WebsiteConfiguration{
|
|
IndexDocument: &types.IndexDocument{
|
|
Suffix: getPtr("index.html"),
|
|
},
|
|
RoutingRules: []types.RoutingRule{
|
|
{
|
|
Redirect: &types.Redirect{
|
|
ReplaceKeyWith: getPtr("replacement.html"),
|
|
ReplaceKeyPrefixWith: getPtr("replacement-prefix/"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetAPIError(s3err.ErrBothReplaceKeyAndPrefix))
|
|
})
|
|
}
|
|
|
|
func PutBucketWebsite_invalid_http_redirect_code(s *S3Conf) error {
|
|
testName := "PutBucketWebsite_invalid_http_redirect_code"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
for _, test := range []struct {
|
|
code string
|
|
expectedErr s3err.S3Error
|
|
}{
|
|
{code: "300", expectedErr: s3err.GetInvalidRedirectCodeErr(300)},
|
|
{code: "306", expectedErr: s3err.GetInvalidRedirectCodeErr(306)},
|
|
{code: "309", expectedErr: s3err.GetInvalidRedirectCodeErr(309)},
|
|
{code: "399", expectedErr: s3err.GetInvalidRedirectCodeErr(399)},
|
|
{code: "jibberish", expectedErr: s3err.GetAPIError(s3err.ErrMalformedXML)},
|
|
{code: "3xx", expectedErr: s3err.GetAPIError(s3err.ErrMalformedXML)},
|
|
} {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.PutBucketWebsite(ctx, &s3.PutBucketWebsiteInput{
|
|
Bucket: &bucket,
|
|
WebsiteConfiguration: &types.WebsiteConfiguration{
|
|
IndexDocument: &types.IndexDocument{
|
|
Suffix: getPtr("index.html"),
|
|
},
|
|
RoutingRules: []types.RoutingRule{
|
|
{
|
|
Redirect: &types.Redirect{
|
|
HostName: getPtr("example.com"),
|
|
HttpRedirectCode: getPtr(test.code),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, test.expectedErr); err != nil {
|
|
return fmt.Errorf("code %q: %w", test.code, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func PutBucketWebsite_invalid_http_error_code(s *S3Conf) error {
|
|
testName := "PutBucketWebsite_invalid_http_error_code"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
for _, test := range []struct {
|
|
code string
|
|
expectedErr s3err.S3Error
|
|
}{
|
|
{code: "399", expectedErr: s3err.GetInvalidHTTPErrorCodeErr(399)},
|
|
{code: "418", expectedErr: s3err.GetInvalidHTTPErrorCodeErr(418)},
|
|
{code: "499", expectedErr: s3err.GetInvalidHTTPErrorCodeErr(499)},
|
|
{code: "506", expectedErr: s3err.GetInvalidHTTPErrorCodeErr(506)},
|
|
{code: "jibberish", expectedErr: s3err.GetAPIError(s3err.ErrMalformedXML)},
|
|
{code: "4xx", expectedErr: s3err.GetAPIError(s3err.ErrMalformedXML)},
|
|
} {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.PutBucketWebsite(ctx, &s3.PutBucketWebsiteInput{
|
|
Bucket: &bucket,
|
|
WebsiteConfiguration: &types.WebsiteConfiguration{
|
|
IndexDocument: &types.IndexDocument{
|
|
Suffix: getPtr("index.html"),
|
|
},
|
|
RoutingRules: []types.RoutingRule{
|
|
{
|
|
Condition: &types.Condition{
|
|
HttpErrorCodeReturnedEquals: getPtr(test.code),
|
|
},
|
|
Redirect: &types.Redirect{
|
|
HostName: getPtr("example.com"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, test.expectedErr); err != nil {
|
|
return fmt.Errorf("code %q: %w", test.code, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func PutBucketWebsite_request_too_large(s *S3Conf) error {
|
|
testName := "PutBucketWebsite_request_too_large"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
longValue := strings.Repeat("a", 2048)
|
|
routingRules := make([]types.RoutingRule, 50)
|
|
for i := range routingRules {
|
|
routingRules[i] = types.RoutingRule{
|
|
Condition: &types.Condition{
|
|
KeyPrefixEquals: getPtr(fmt.Sprintf("prefix-%d-%s", i, longValue)),
|
|
},
|
|
Redirect: &types.Redirect{
|
|
HostName: getPtr("example.com"),
|
|
ReplaceKeyWith: getPtr(fmt.Sprintf("replacement-%d-%s", i, longValue)),
|
|
HttpRedirectCode: getPtr("301"),
|
|
},
|
|
}
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.PutBucketWebsite(ctx, &s3.PutBucketWebsiteInput{
|
|
Bucket: &bucket,
|
|
WebsiteConfiguration: &types.WebsiteConfiguration{
|
|
IndexDocument: &types.IndexDocument{
|
|
Suffix: getPtr("index.html"),
|
|
},
|
|
RoutingRules: routingRules,
|
|
},
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetMaxMessageLengthExceeded(maxWebsiteConfigSize))
|
|
})
|
|
}
|
|
|
|
func PutBucketWebsite_success(s *S3Conf) error {
|
|
testName := "PutBucketWebsite_success"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.PutBucketWebsite(ctx, &s3.PutBucketWebsiteInput{
|
|
Bucket: &bucket,
|
|
WebsiteConfiguration: &types.WebsiteConfiguration{
|
|
IndexDocument: &types.IndexDocument{
|
|
Suffix: getPtr("index.html"),
|
|
},
|
|
ErrorDocument: &types.ErrorDocument{
|
|
Key: getPtr("error.html"),
|
|
},
|
|
},
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func PutBucketWebsite_success_redirect_all(s *S3Conf) error {
|
|
testName := "PutBucketWebsite_success_redirect_all"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.PutBucketWebsite(ctx, &s3.PutBucketWebsiteInput{
|
|
Bucket: &bucket,
|
|
WebsiteConfiguration: &types.WebsiteConfiguration{
|
|
RedirectAllRequestsTo: &types.RedirectAllRequestsTo{
|
|
HostName: getPtr("example.com"),
|
|
Protocol: types.ProtocolHttps,
|
|
},
|
|
},
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|