You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

142 lines
4.0 KiB

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
fallbackConfig *CORSConfiguration // Global CORS configuration as fallback
}
// NewMiddleware creates a new CORS middleware instance with optional global fallback config
func NewMiddleware(bucketChecker BucketChecker, corsConfigGetter CORSConfigGetter, fallbackConfig *CORSConfiguration) *Middleware {
return &Middleware{
bucketChecker: bucketChecker,
corsConfigGetter: corsConfigGetter,
fallbackConfig: fallbackConfig,
}
}
// getCORSConfig retrieves the applicable CORS configuration, trying bucket-specific first, then fallback.
// Returns the configuration and a boolean indicating if any configuration was found.
// Only falls back to global config when there's explicitly no bucket-level config.
// For other errors (e.g., access denied), returns false to let the handler deny the request.
func (m *Middleware) getCORSConfig(bucket string) (*CORSConfiguration, bool) {
config, errCode := m.corsConfigGetter.GetCORSConfiguration(bucket)
switch errCode {
case s3err.ErrNone:
if config != nil {
// Found a bucket-specific config, use it.
return config, true
}
// No bucket config, proceed to fallback.
case s3err.ErrNoSuchCORSConfiguration:
// No bucket config, proceed to fallback.
case s3err.ErrNoSuchBucket:
// Bucket doesn't exist, proceed to fallback.
// This ensures we don't leak existence information by returning 403 vs 200.
default:
// Any other error means we should not proceed.
return nil, false
}
// No bucket-level config found, try global fallback
if m.fallbackConfig != nil {
return m.fallbackConfig, true
}
return nil, 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) {
m.processCORS(w, r, func() {
next.ServeHTTP(w, r)
})
})
}
// HandleOptionsRequest handles OPTIONS requests for CORS preflight
func (m *Middleware) HandleOptionsRequest(w http.ResponseWriter, r *http.Request) {
m.processCORS(w, r, func() {
w.WriteHeader(http.StatusOK)
})
}
// processCORS handles the common CORS logic for both regular and preflight requests.
// It takes a next callback which is executed if the request flow proceeds (i.e., not short-circuited by CORS errors or preflight responses).
func (m *Middleware) processCORS(w http.ResponseWriter, r *http.Request, next func()) {
corsReq := ParseRequest(r)
bucket, _ := s3_constants.GetBucketAndObject(r)
// 1. Basic Validation
if bucket == "" {
next()
return
}
// 2. Load Configuration
config, hasConfig := m.getCORSConfig(bucket)
// 3. Apply Vary Header (Always applied if config exists)
if hasConfig {
w.Header().Add("Vary", "Origin")
}
// 4. Handle Non-CORS Requests
if corsReq.Origin == "" {
next()
return
}
// 5. Handle Missing Configuration
if !hasConfig {
if corsReq.IsPreflightRequest {
s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
return
}
next()
return
}
// 6. 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
}
next()
return
}
// 7. Success Case
ApplyHeaders(w, corsResp)
if corsReq.IsPreflightRequest {
w.WriteHeader(http.StatusOK)
return
}
next()
}