aboutsummaryrefslogtreecommitdiff
path: root/weed/s3api/cors/middleware.go
blob: 7aa40e84f7a978b846459cadeddd3e2245c78951 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
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.
	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) {
		// 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
		}

		// Get CORS configuration (bucket-specific or fallback)
		config, found := m.getCORSConfig(bucket)
		if !found {
			// No CORS configuration at all, 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
	}

	// Get CORS configuration (bucket-specific or fallback)
	config, found := m.getCORSConfig(bucket)
	if !found {
		// No CORS configuration at all 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)
}