diff --git a/weed/s3api/cors/cors.go b/weed/s3api/cors/cors.go index f0cb88513..e6638c791 100644 --- a/weed/s3api/cors/cors.go +++ b/weed/s3api/cors/cors.go @@ -14,6 +14,9 @@ import ( "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" ) +// S3 metadata file name constant to avoid typos and reduce duplication +const S3MetadataFileName = ".s3metadata" + // CORSRule represents a single CORS rule type CORSRule struct { ID string `xml:"ID,omitempty" json:"ID,omitempty"` @@ -429,7 +432,7 @@ func NewStorage(filerClient FilerClient, entryGetter EntryGetter, bucketsPath st // Store stores CORS configuration in the filer func (s *Storage) Store(bucket string, config *CORSConfiguration) error { // Store in bucket metadata - bucketMetadataPath := fmt.Sprintf("%s/%s/.s3metadata", s.bucketsPath, bucket) + bucketMetadataPath := fmt.Sprintf("%s/%s/%s", s.bucketsPath, bucket, S3MetadataFileName) // Get existing metadata existingEntry, err := s.entryGetter.GetEntry("", bucketMetadataPath) @@ -456,7 +459,7 @@ func (s *Storage) Store(bucket string, config *CORSConfiguration) error { request := &filer_pb.CreateEntryRequest{ Directory: s.bucketsPath + "/" + bucket, Entry: &filer_pb.Entry{ - Name: ".s3metadata", + Name: S3MetadataFileName, IsDirectory: false, Attributes: &filer_pb.FuseAttributes{ Crtime: time.Now().Unix(), @@ -474,7 +477,7 @@ func (s *Storage) Store(bucket string, config *CORSConfiguration) error { // Load loads CORS configuration from the filer func (s *Storage) Load(bucket string) (*CORSConfiguration, error) { - bucketMetadataPath := fmt.Sprintf("%s/%s/.s3metadata", s.bucketsPath, bucket) + bucketMetadataPath := fmt.Sprintf("%s/%s/%s", s.bucketsPath, bucket, S3MetadataFileName) entry, err := s.entryGetter.GetEntry("", bucketMetadataPath) if err != nil || entry == nil { @@ -511,7 +514,7 @@ func (s *Storage) Load(bucket string) (*CORSConfiguration, error) { // Delete deletes CORS configuration from the filer func (s *Storage) Delete(bucket string) error { - bucketMetadataPath := fmt.Sprintf("%s/%s/.s3metadata", s.bucketsPath, bucket) + bucketMetadataPath := fmt.Sprintf("%s/%s/%s", s.bucketsPath, bucket, S3MetadataFileName) entry, err := s.entryGetter.GetEntry("", bucketMetadataPath) if err != nil || entry == nil { @@ -540,7 +543,7 @@ func (s *Storage) Delete(bucket string) error { request := &filer_pb.CreateEntryRequest{ Directory: s.bucketsPath + "/" + bucket, Entry: &filer_pb.Entry{ - Name: ".s3metadata", + Name: S3MetadataFileName, IsDirectory: false, Attributes: &filer_pb.FuseAttributes{ Crtime: time.Now().Unix(), diff --git a/weed/s3api/s3api_bucket_config.go b/weed/s3api/s3api_bucket_config.go index 2cd4e9a18..24c9c0458 100644 --- a/weed/s3api/s3api_bucket_config.go +++ b/weed/s3api/s3api_bucket_config.go @@ -269,7 +269,7 @@ func (s3a *S3ApiServer) loadCORSFromMetadata(bucket string) (*cors.CORSConfigura return nil, fmt.Errorf("invalid bucket name: %s", bucket) } - bucketMetadataPath := fmt.Sprintf("%s/%s/.s3metadata", s3a.option.BucketsPath, bucket) + bucketMetadataPath := fmt.Sprintf("%s/%s/%s", s3a.option.BucketsPath, bucket, cors.S3MetadataFileName) entry, err := s3a.getEntry("", bucketMetadataPath) if err != nil || entry == nil { diff --git a/weed/s3api/s3api_server.go b/weed/s3api/s3api_server.go index 9e9adcfca..f83775480 100644 --- a/weed/s3api/s3api_server.go +++ b/weed/s3api/s3api_server.go @@ -129,43 +129,6 @@ func (s3a *S3ApiServer) registerRouter(router *mux.Router) { apiRouter.Methods(http.MethodGet).Path("/status").HandlerFunc(s3a.StatusHandler) apiRouter.Methods(http.MethodGet).Path("/healthz").HandlerFunc(s3a.StatusHandler) - // Global OPTIONS handler for service-level requests (non-bucket requests) - // This handles requests like OPTIONS /, OPTIONS /status, OPTIONS /healthz - apiRouter.Methods(http.MethodOptions).PathPrefix("/").HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - // Only handle if this is not a bucket-specific request - vars := mux.Vars(r) - bucket := vars["bucket"] - if bucket != "" { - // This is a bucket-specific request, skip - return - } - - origin := r.Header.Get("Origin") - if origin != "" { - if len(s3a.option.AllowedOrigins) == 0 || s3a.option.AllowedOrigins[0] == "*" { - origin = "*" - } else { - originFound := false - for _, allowedOrigin := range s3a.option.AllowedOrigins { - if origin == allowedOrigin { - originFound = true - } - } - if !originFound { - writeFailureResponse(w, r, http.StatusForbidden) - return - } - } - } - - w.Header().Set("Access-Control-Allow-Origin", origin) - w.Header().Set("Access-Control-Expose-Headers", "*") - w.Header().Set("Access-Control-Allow-Methods", "*") - w.Header().Set("Access-Control-Allow-Headers", "*") - writeSuccessResponseEmpty(w, r) - }) - var routers []*mux.Router if s3a.option.DomainName != "" { domainNames := strings.Split(s3a.option.DomainName, ",") @@ -349,6 +312,45 @@ func (s3a *S3ApiServer) registerRouter(router *mux.Router) { } + // Global OPTIONS handler for service-level requests (non-bucket requests) + // This handles requests like OPTIONS /, OPTIONS /status, OPTIONS /healthz + // Place this after bucket handlers to avoid interfering with bucket CORS middleware + apiRouter.Methods(http.MethodOptions).PathPrefix("/").HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + // Only handle if this is not a bucket-specific request + vars := mux.Vars(r) + bucket := vars["bucket"] + if bucket != "" { + // This is a bucket-specific request, let bucket CORS middleware handle it + http.NotFound(w, r) + return + } + + origin := r.Header.Get("Origin") + if origin != "" { + if len(s3a.option.AllowedOrigins) == 0 || s3a.option.AllowedOrigins[0] == "*" { + origin = "*" + } else { + originFound := false + for _, allowedOrigin := range s3a.option.AllowedOrigins { + if origin == allowedOrigin { + originFound = true + } + } + if !originFound { + writeFailureResponse(w, r, http.StatusForbidden) + return + } + } + } + + w.Header().Set("Access-Control-Allow-Origin", origin) + w.Header().Set("Access-Control-Expose-Headers", "*") + w.Header().Set("Access-Control-Allow-Methods", "*") + w.Header().Set("Access-Control-Allow-Headers", "*") + writeSuccessResponseEmpty(w, r) + }) + // ListBuckets apiRouter.Methods(http.MethodGet).Path("/").HandlerFunc(track(s3a.ListBucketsHandler, "LIST"))