mirror of
https://github.com/chrislusf/seaweedfs
synced 2025-07-26 13:32:46 +02:00
* 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.
156 lines
4.3 KiB
Go
156 lines
4.3 KiB
Go
package cors
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
|
)
|
|
|
|
// BucketChecker interface for checking bucket existence
|
|
type BucketChecker interface {
|
|
CheckBucket(r *http.Request, bucket string) s3err.ErrorCode
|
|
}
|
|
|
|
// CORSConfigGetter interface for getting CORS configuration
|
|
type CORSConfigGetter interface {
|
|
GetCORSConfiguration(bucket string) (*CORSConfiguration, s3err.ErrorCode)
|
|
}
|
|
|
|
// Middleware handles CORS evaluation for all S3 API requests
|
|
type Middleware struct {
|
|
bucketChecker BucketChecker
|
|
corsConfigGetter CORSConfigGetter
|
|
}
|
|
|
|
// NewMiddleware creates a new CORS middleware instance
|
|
func NewMiddleware(bucketChecker BucketChecker, corsConfigGetter CORSConfigGetter) *Middleware {
|
|
return &Middleware{
|
|
bucketChecker: bucketChecker,
|
|
corsConfigGetter: corsConfigGetter,
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
// Parse CORS request
|
|
corsReq := ParseRequest(r)
|
|
|
|
// If not a CORS request, continue normally
|
|
if corsReq.Origin == "" {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
// Extract bucket from request
|
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
|
if bucket == "" {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
// 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
|
|
if corsReq.IsPreflightRequest {
|
|
// Preflight request should return 200 OK with just CORS headers
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
|
|
// 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) {
|
|
// Parse CORS request
|
|
corsReq := ParseRequest(r)
|
|
|
|
// If not a CORS request, return OK
|
|
if corsReq.Origin == "" {
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// Apply CORS headers and return success
|
|
ApplyHeaders(w, corsResp)
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|