aboutsummaryrefslogtreecommitdiff
path: root/weed/s3api/s3api_bucket_cors_handlers.go
blob: c45f86014c5bf7dd0ce680266a956307501dd710 (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
package s3api

import (
	"encoding/xml"
	"net/http"

	"github.com/seaweedfs/seaweedfs/weed/glog"
	"github.com/seaweedfs/seaweedfs/weed/s3api/cors"
	"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
	"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
)

// Default CORS configuration for global fallback
var (
	defaultFallbackAllowedMethods = []string{"GET", "PUT", "POST", "DELETE", "HEAD"}
	defaultFallbackExposeHeaders  = []string{
		"ETag",
		"Content-Length",
		"Content-Type",
		"Last-Modified",
		"x-amz-request-id",
		"x-amz-version-id",
	}
)

// S3BucketChecker implements cors.BucketChecker interface
type S3BucketChecker struct {
	server *S3ApiServer
}

func (c *S3BucketChecker) CheckBucket(r *http.Request, bucket string) s3err.ErrorCode {
	return c.server.checkBucket(r, bucket)
}

// S3CORSConfigGetter implements cors.CORSConfigGetter interface
type S3CORSConfigGetter struct {
	server *S3ApiServer
}

func (g *S3CORSConfigGetter) GetCORSConfiguration(bucket string) (*cors.CORSConfiguration, s3err.ErrorCode) {
	return g.server.getCORSConfiguration(bucket)
}

// getCORSMiddleware returns a CORS middleware instance with global fallback config
func (s3a *S3ApiServer) getCORSMiddleware() *cors.Middleware {
	bucketChecker := &S3BucketChecker{server: s3a}
	corsConfigGetter := &S3CORSConfigGetter{server: s3a}

	// Create fallback CORS configuration from global AllowedOrigins setting
	fallbackConfig := s3a.createFallbackCORSConfig()

	return cors.NewMiddleware(bucketChecker, corsConfigGetter, fallbackConfig)
}

// createFallbackCORSConfig creates a CORS configuration from global AllowedOrigins
func (s3a *S3ApiServer) createFallbackCORSConfig() *cors.CORSConfiguration {
	if len(s3a.option.AllowedOrigins) == 0 {
		return nil
	}

	// Create a permissive CORS rule based on global allowed origins
	// This matches the behavior of handleCORSOriginValidation
	rule := cors.CORSRule{
		AllowedOrigins: s3a.option.AllowedOrigins,
		AllowedMethods: defaultFallbackAllowedMethods,
		AllowedHeaders: []string{"*"},
		ExposeHeaders:  defaultFallbackExposeHeaders,
		MaxAgeSeconds:  nil, // No max age by default
	}

	return &cors.CORSConfiguration{
		CORSRules: []cors.CORSRule{rule},
	}
}

// GetBucketCorsHandler handles Get bucket CORS configuration
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketCors.html
func (s3a *S3ApiServer) GetBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
	bucket, _ := s3_constants.GetBucketAndObject(r)
	glog.V(3).Infof("GetBucketCorsHandler %s", bucket)

	if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
		s3err.WriteErrorResponse(w, r, err)
		return
	}

	// Load CORS configuration from cache
	config, errCode := s3a.getCORSConfiguration(bucket)
	if errCode != s3err.ErrNone {
		if errCode == s3err.ErrNoSuchBucket {
			s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
		} else {
			s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
		}
		return
	}

	if config == nil {
		s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchCORSConfiguration)
		return
	}

	// Return CORS configuration as XML
	writeSuccessResponseXML(w, r, config)
}

// PutBucketCorsHandler handles Put bucket CORS configuration
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketCors.html
func (s3a *S3ApiServer) PutBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
	bucket, _ := s3_constants.GetBucketAndObject(r)
	glog.V(3).Infof("PutBucketCorsHandler %s", bucket)

	if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
		s3err.WriteErrorResponse(w, r, err)
		return
	}

	// Parse CORS configuration from request body
	var config cors.CORSConfiguration
	if err := xml.NewDecoder(r.Body).Decode(&config); err != nil {
		glog.V(1).Infof("Failed to parse CORS configuration: %v", err)
		s3err.WriteErrorResponse(w, r, s3err.ErrMalformedXML)
		return
	}

	// Validate CORS configuration
	if err := cors.ValidateConfiguration(&config); err != nil {
		glog.V(1).Infof("Invalid CORS configuration: %v", err)
		s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
		return
	}

	// Store CORS configuration and update cache
	// This handles both cache update and persistent storage through the unified bucket config system
	if err := s3a.updateCORSConfiguration(bucket, &config); err != s3err.ErrNone {
		glog.Errorf("Failed to update CORS configuration: %v", err)
		s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
		return
	}

	// Return success
	writeSuccessResponseEmpty(w, r)
}

// DeleteBucketCorsHandler handles Delete bucket CORS configuration
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketCors.html
func (s3a *S3ApiServer) DeleteBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
	bucket, _ := s3_constants.GetBucketAndObject(r)
	glog.V(3).Infof("DeleteBucketCorsHandler %s", bucket)

	if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
		s3err.WriteErrorResponse(w, r, err)
		return
	}

	// Remove CORS configuration from cache and persistent storage
	// This handles both cache invalidation and persistent storage cleanup through the unified bucket config system
	if err := s3a.removeCORSConfiguration(bucket); err != s3err.ErrNone {
		glog.Errorf("Failed to remove CORS configuration: %v", err)
		s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
		return
	}

	// Return success (204 No Content)
	w.WriteHeader(http.StatusNoContent)
}