fix listing objects (#7008)

* fix listing objects

* add more list testing

* address comments

* fix next marker

* fix isTruncated in listing

* fix tests

* address tests

* Update s3api_object_handlers_multipart.go

* fixes

* store json into bucket content, for tagging and cors

* switch bucket metadata from json to proto

* fix

* Update s3api_bucket_config.go

* fix test issue

* fix test_bucket_listv2_delimiter_prefix

* Update cors.go

* skip special characters

* passing listing

* fix test_bucket_list_delimiter_prefix

* ok. fix the xsd generated go code now

* fix cors tests

* fix test

* fix test_bucket_list_unordered and test_bucket_listv2_unordered

do not accept the allow-unordered and delimiter parameter combination

* fix test_bucket_list_objects_anonymous and test_bucket_listv2_objects_anonymous

The tests test_bucket_list_objects_anonymous and test_bucket_listv2_objects_anonymous were failing because they try to set bucket ACL to public-read, but SeaweedFS only supported private ACL.

Updated PutBucketAclHandler to use the existing ExtractAcl function which already supports all standard S3 canned ACLs
Replaced the hardcoded check for only private ACL with proper ACL parsing that handles public-read, public-read-write, authenticated-read, bucket-owner-read, bucket-owner-full-control, etc.
Added unit tests to verify all standard canned ACLs are accepted

* fix list unordered

The test is expecting the error code to be InvalidArgument instead of InvalidRequest

* allow anonymous listing( and head, get)

* fix test_bucket_list_maxkeys_invalid

Invalid values: max-keys=blah → Returns ErrInvalidMaxKeys (HTTP 400)

* updating IsPublicRead when parsing acl

* more logs

* CORS Test Fix

* fix test_bucket_list_return_data

* default to private

* fix test_bucket_list_delimiter_not_skip_special

* default no acl

* add debug logging

* more logs

* use basic http client

remove logs also

* fixes

* debug

* Update stats.go

* debugging

* fix anonymous test expectation

anonymous user can read, as configured in s3 json.
This commit is contained in:
Chris Lu
2025-07-22 01:07:15 -07:00
committed by GitHub
parent 632029fd8b
commit 33b9017b48
28 changed files with 2150 additions and 708 deletions

View File

@@ -20,94 +20,73 @@ type CORSConfigGetter interface {
// Middleware handles CORS evaluation for all S3 API requests
type Middleware struct {
storage *Storage
bucketChecker BucketChecker
corsConfigGetter CORSConfigGetter
}
// NewMiddleware creates a new CORS middleware instance
func NewMiddleware(storage *Storage, bucketChecker BucketChecker, corsConfigGetter CORSConfigGetter) *Middleware {
func NewMiddleware(bucketChecker BucketChecker, corsConfigGetter CORSConfigGetter) *Middleware {
return &Middleware{
storage: storage,
bucketChecker: bucketChecker,
corsConfigGetter: corsConfigGetter,
}
}
// evaluateCORSRequest performs the common CORS request evaluation logic
// Returns: (corsResponse, responseWritten, shouldContinue)
// - corsResponse: the CORS response if evaluation succeeded
// - responseWritten: true if an error response was already written
// - shouldContinue: true if the request should continue to the next handler
func (m *Middleware) evaluateCORSRequest(w http.ResponseWriter, r *http.Request) (*CORSResponse, bool, bool) {
// Parse CORS request
corsReq := ParseRequest(r)
if corsReq.Origin == "" {
// Not a CORS request
return nil, false, true
}
// Extract bucket from request
bucket, _ := s3_constants.GetBucketAndObject(r)
if bucket == "" {
return nil, false, true
}
// Check if bucket exists
if err := m.bucketChecker.CheckBucket(r, bucket); err != s3err.ErrNone {
// For non-existent buckets, let the normal handler deal with it
return nil, false, true
}
// Load CORS configuration from cache
config, errCode := m.corsConfigGetter.GetCORSConfiguration(bucket)
if errCode != s3err.ErrNone || config == nil {
// No CORS configuration, handle based on request type
if corsReq.IsPreflightRequest {
// Preflight request without CORS config should fail
s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
return nil, true, false // Response written, don't continue
}
// Non-preflight request, continue normally
return nil, false, true
}
// Evaluate CORS request
corsResp, err := EvaluateRequest(config, corsReq)
if err != nil {
glog.V(3).Infof("CORS evaluation failed for bucket %s: %v", bucket, err)
if corsReq.IsPreflightRequest {
// Preflight request that doesn't match CORS rules should fail
s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
return nil, true, false // Response written, don't continue
}
// Non-preflight request, continue normally but without CORS headers
return nil, false, true
}
return corsResp, false, false
}
// Handler returns the CORS middleware handler
func (m *Middleware) Handler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Use the common evaluation logic
corsResp, responseWritten, shouldContinue := m.evaluateCORSRequest(w, r)
if responseWritten {
// Response was already written (error case)
return
}
// Parse CORS request
corsReq := ParseRequest(r)
if shouldContinue {
// Continue with normal request processing
// If not a CORS request, continue normally
if corsReq.Origin == "" {
next.ServeHTTP(w, r)
return
}
// Parse request to check if it's a preflight request
corsReq := ParseRequest(r)
// Extract bucket from request
bucket, _ := s3_constants.GetBucketAndObject(r)
if bucket == "" {
next.ServeHTTP(w, r)
return
}
// Apply CORS headers to response
// Check if bucket exists
if err := m.bucketChecker.CheckBucket(r, bucket); err != s3err.ErrNone {
// For non-existent buckets, let the normal handler deal with it
next.ServeHTTP(w, r)
return
}
// Load CORS configuration from cache
config, errCode := m.corsConfigGetter.GetCORSConfiguration(bucket)
if errCode != s3err.ErrNone || config == nil {
// No CORS configuration, handle based on request type
if corsReq.IsPreflightRequest {
// Preflight request without CORS config should fail
s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
return
}
// Non-preflight request, continue normally
next.ServeHTTP(w, r)
return
}
// Evaluate CORS request
corsResp, err := EvaluateRequest(config, corsReq)
if err != nil {
glog.V(3).Infof("CORS evaluation failed for bucket %s: %v", bucket, err)
if corsReq.IsPreflightRequest {
// Preflight request that doesn't match CORS rules should fail
s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
return
}
// Non-preflight request, continue normally but without CORS headers
next.ServeHTTP(w, r)
return
}
// Apply CORS headers
ApplyHeaders(w, corsResp)
// Handle preflight requests
@@ -117,22 +96,56 @@ func (m *Middleware) Handler(next http.Handler) http.Handler {
return
}
// Continue with normal request processing
// For actual requests, continue with normal processing
next.ServeHTTP(w, r)
})
}
// HandleOptionsRequest handles OPTIONS requests for CORS preflight
func (m *Middleware) HandleOptionsRequest(w http.ResponseWriter, r *http.Request) {
// Use the common evaluation logic
corsResp, responseWritten, shouldContinue := m.evaluateCORSRequest(w, r)
if responseWritten {
// Response was already written (error case)
// Parse CORS request
corsReq := ParseRequest(r)
// If not a CORS request, return OK
if corsReq.Origin == "" {
w.WriteHeader(http.StatusOK)
return
}
if shouldContinue || corsResp == nil {
// Not a CORS request or should continue normally
// Extract bucket from request
bucket, _ := s3_constants.GetBucketAndObject(r)
if bucket == "" {
w.WriteHeader(http.StatusOK)
return
}
// Check if bucket exists
if err := m.bucketChecker.CheckBucket(r, bucket); err != s3err.ErrNone {
// For non-existent buckets, return OK (let other handlers deal with bucket existence)
w.WriteHeader(http.StatusOK)
return
}
// Load CORS configuration from cache
config, errCode := m.corsConfigGetter.GetCORSConfiguration(bucket)
if errCode != s3err.ErrNone || config == nil {
// No CORS configuration for OPTIONS request should return access denied
if corsReq.IsPreflightRequest {
s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
return
}
w.WriteHeader(http.StatusOK)
return
}
// Evaluate CORS request
corsResp, err := EvaluateRequest(config, corsReq)
if err != nil {
glog.V(3).Infof("CORS evaluation failed for bucket %s: %v", bucket, err)
if corsReq.IsPreflightRequest {
s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
return
}
w.WriteHeader(http.StatusOK)
return
}