podman

Форк
0
352 строки · 9.4 Кб
1
package handlers
2

3
import (
4
	"net/http"
5
	"strconv"
6
	"strings"
7
)
8

9
// CORSOption represents a functional option for configuring the CORS middleware.
10
type CORSOption func(*cors) error
11

12
type cors struct {
13
	h                      http.Handler
14
	allowedHeaders         []string
15
	allowedMethods         []string
16
	allowedOrigins         []string
17
	allowedOriginValidator OriginValidator
18
	exposedHeaders         []string
19
	maxAge                 int
20
	ignoreOptions          bool
21
	allowCredentials       bool
22
	optionStatusCode       int
23
}
24

25
// OriginValidator takes an origin string and returns whether or not that origin is allowed.
26
type OriginValidator func(string) bool
27

28
var (
29
	defaultCorsOptionStatusCode = http.StatusOK
30
	defaultCorsMethods          = []string{http.MethodGet, http.MethodHead, http.MethodPost}
31
	defaultCorsHeaders          = []string{"Accept", "Accept-Language", "Content-Language", "Origin"}
32
	// (WebKit/Safari v9 sends the Origin header by default in AJAX requests).
33
)
34

35
const (
36
	corsOptionMethod           string = http.MethodOptions
37
	corsAllowOriginHeader      string = "Access-Control-Allow-Origin"
38
	corsExposeHeadersHeader    string = "Access-Control-Expose-Headers"
39
	corsMaxAgeHeader           string = "Access-Control-Max-Age"
40
	corsAllowMethodsHeader     string = "Access-Control-Allow-Methods"
41
	corsAllowHeadersHeader     string = "Access-Control-Allow-Headers"
42
	corsAllowCredentialsHeader string = "Access-Control-Allow-Credentials"
43
	corsRequestMethodHeader    string = "Access-Control-Request-Method"
44
	corsRequestHeadersHeader   string = "Access-Control-Request-Headers"
45
	corsOriginHeader           string = "Origin"
46
	corsVaryHeader             string = "Vary"
47
	corsOriginMatchAll         string = "*"
48
)
49

50
func (ch *cors) ServeHTTP(w http.ResponseWriter, r *http.Request) {
51
	origin := r.Header.Get(corsOriginHeader)
52
	if !ch.isOriginAllowed(origin) {
53
		if r.Method != corsOptionMethod || ch.ignoreOptions {
54
			ch.h.ServeHTTP(w, r)
55
		}
56

57
		return
58
	}
59

60
	if r.Method == corsOptionMethod {
61
		if ch.ignoreOptions {
62
			ch.h.ServeHTTP(w, r)
63
			return
64
		}
65

66
		if _, ok := r.Header[corsRequestMethodHeader]; !ok {
67
			w.WriteHeader(http.StatusBadRequest)
68
			return
69
		}
70

71
		method := r.Header.Get(corsRequestMethodHeader)
72
		if !ch.isMatch(method, ch.allowedMethods) {
73
			w.WriteHeader(http.StatusMethodNotAllowed)
74
			return
75
		}
76

77
		requestHeaders := strings.Split(r.Header.Get(corsRequestHeadersHeader), ",")
78
		allowedHeaders := []string{}
79
		for _, v := range requestHeaders {
80
			canonicalHeader := http.CanonicalHeaderKey(strings.TrimSpace(v))
81
			if canonicalHeader == "" || ch.isMatch(canonicalHeader, defaultCorsHeaders) {
82
				continue
83
			}
84

85
			if !ch.isMatch(canonicalHeader, ch.allowedHeaders) {
86
				w.WriteHeader(http.StatusForbidden)
87
				return
88
			}
89

90
			allowedHeaders = append(allowedHeaders, canonicalHeader)
91
		}
92

93
		if len(allowedHeaders) > 0 {
94
			w.Header().Set(corsAllowHeadersHeader, strings.Join(allowedHeaders, ","))
95
		}
96

97
		if ch.maxAge > 0 {
98
			w.Header().Set(corsMaxAgeHeader, strconv.Itoa(ch.maxAge))
99
		}
100

101
		if !ch.isMatch(method, defaultCorsMethods) {
102
			w.Header().Set(corsAllowMethodsHeader, method)
103
		}
104
	} else if len(ch.exposedHeaders) > 0 {
105
		w.Header().Set(corsExposeHeadersHeader, strings.Join(ch.exposedHeaders, ","))
106
	}
107

108
	if ch.allowCredentials {
109
		w.Header().Set(corsAllowCredentialsHeader, "true")
110
	}
111

112
	if len(ch.allowedOrigins) > 1 {
113
		w.Header().Set(corsVaryHeader, corsOriginHeader)
114
	}
115

116
	returnOrigin := origin
117
	if ch.allowedOriginValidator == nil && len(ch.allowedOrigins) == 0 {
118
		returnOrigin = "*"
119
	} else {
120
		for _, o := range ch.allowedOrigins {
121
			// A configuration of * is different than explicitly setting an allowed
122
			// origin. Returning arbitrary origin headers in an access control allow
123
			// origin header is unsafe and is not required by any use case.
124
			if o == corsOriginMatchAll {
125
				returnOrigin = "*"
126
				break
127
			}
128
		}
129
	}
130
	w.Header().Set(corsAllowOriginHeader, returnOrigin)
131

132
	if r.Method == corsOptionMethod {
133
		w.WriteHeader(ch.optionStatusCode)
134
		return
135
	}
136
	ch.h.ServeHTTP(w, r)
137
}
138

139
// CORS provides Cross-Origin Resource Sharing middleware.
140
// Example:
141
//
142
//	import (
143
//	    "net/http"
144
//
145
//	    "github.com/gorilla/handlers"
146
//	    "github.com/gorilla/mux"
147
//	)
148
//
149
//	func main() {
150
//	    r := mux.NewRouter()
151
//	    r.HandleFunc("/users", UserEndpoint)
152
//	    r.HandleFunc("/projects", ProjectEndpoint)
153
//
154
//	    // Apply the CORS middleware to our top-level router, with the defaults.
155
//	    http.ListenAndServe(":8000", handlers.CORS()(r))
156
//	}
157
func CORS(opts ...CORSOption) func(http.Handler) http.Handler {
158
	return func(h http.Handler) http.Handler {
159
		ch := parseCORSOptions(opts...)
160
		ch.h = h
161
		return ch
162
	}
163
}
164

165
func parseCORSOptions(opts ...CORSOption) *cors {
166
	ch := &cors{
167
		allowedMethods:   defaultCorsMethods,
168
		allowedHeaders:   defaultCorsHeaders,
169
		allowedOrigins:   []string{},
170
		optionStatusCode: defaultCorsOptionStatusCode,
171
	}
172

173
	for _, option := range opts {
174
		_ = option(ch) //TODO: @bharat-rajani, return error to caller if not nil?
175
	}
176

177
	return ch
178
}
179

180
//
181
// Functional options for configuring CORS.
182
//
183

184
// AllowedHeaders adds the provided headers to the list of allowed headers in a
185
// CORS request.
186
// This is an append operation so the headers Accept, Accept-Language,
187
// and Content-Language are always allowed.
188
// Content-Type must be explicitly declared if accepting Content-Types other than
189
// application/x-www-form-urlencoded, multipart/form-data, or text/plain.
190
func AllowedHeaders(headers []string) CORSOption {
191
	return func(ch *cors) error {
192
		for _, v := range headers {
193
			normalizedHeader := http.CanonicalHeaderKey(strings.TrimSpace(v))
194
			if normalizedHeader == "" {
195
				continue
196
			}
197

198
			if !ch.isMatch(normalizedHeader, ch.allowedHeaders) {
199
				ch.allowedHeaders = append(ch.allowedHeaders, normalizedHeader)
200
			}
201
		}
202

203
		return nil
204
	}
205
}
206

207
// AllowedMethods can be used to explicitly allow methods in the
208
// Access-Control-Allow-Methods header.
209
// This is a replacement operation so you must also
210
// pass GET, HEAD, and POST if you wish to support those methods.
211
func AllowedMethods(methods []string) CORSOption {
212
	return func(ch *cors) error {
213
		ch.allowedMethods = []string{}
214
		for _, v := range methods {
215
			normalizedMethod := strings.ToUpper(strings.TrimSpace(v))
216
			if normalizedMethod == "" {
217
				continue
218
			}
219

220
			if !ch.isMatch(normalizedMethod, ch.allowedMethods) {
221
				ch.allowedMethods = append(ch.allowedMethods, normalizedMethod)
222
			}
223
		}
224

225
		return nil
226
	}
227
}
228

229
// AllowedOrigins sets the allowed origins for CORS requests, as used in the
230
// 'Allow-Access-Control-Origin' HTTP header.
231
// Note: Passing in a []string{"*"} will allow any domain.
232
func AllowedOrigins(origins []string) CORSOption {
233
	return func(ch *cors) error {
234
		for _, v := range origins {
235
			if v == corsOriginMatchAll {
236
				ch.allowedOrigins = []string{corsOriginMatchAll}
237
				return nil
238
			}
239
		}
240

241
		ch.allowedOrigins = origins
242
		return nil
243
	}
244
}
245

246
// AllowedOriginValidator sets a function for evaluating allowed origins in CORS requests, represented by the
247
// 'Allow-Access-Control-Origin' HTTP header.
248
func AllowedOriginValidator(fn OriginValidator) CORSOption {
249
	return func(ch *cors) error {
250
		ch.allowedOriginValidator = fn
251
		return nil
252
	}
253
}
254

255
// OptionStatusCode sets a custom status code on the OPTIONS requests.
256
// Default behaviour sets it to 200 to reflect best practices. This is option is not mandatory
257
// and can be used if you need a custom status code (i.e 204).
258
//
259
// More informations on the spec:
260
// https://fetch.spec.whatwg.org/#cors-preflight-fetch
261
func OptionStatusCode(code int) CORSOption {
262
	return func(ch *cors) error {
263
		ch.optionStatusCode = code
264
		return nil
265
	}
266
}
267

268
// ExposedHeaders can be used to specify headers that are available
269
// and will not be stripped out by the user-agent.
270
func ExposedHeaders(headers []string) CORSOption {
271
	return func(ch *cors) error {
272
		ch.exposedHeaders = []string{}
273
		for _, v := range headers {
274
			normalizedHeader := http.CanonicalHeaderKey(strings.TrimSpace(v))
275
			if normalizedHeader == "" {
276
				continue
277
			}
278

279
			if !ch.isMatch(normalizedHeader, ch.exposedHeaders) {
280
				ch.exposedHeaders = append(ch.exposedHeaders, normalizedHeader)
281
			}
282
		}
283

284
		return nil
285
	}
286
}
287

288
// MaxAge determines the maximum age (in seconds) between preflight requests. A
289
// maximum of 10 minutes is allowed. An age above this value will default to 10
290
// minutes.
291
func MaxAge(age int) CORSOption {
292
	return func(ch *cors) error {
293
		// Maximum of 10 minutes.
294
		if age > 600 {
295
			age = 600
296
		}
297

298
		ch.maxAge = age
299
		return nil
300
	}
301
}
302

303
// IgnoreOptions causes the CORS middleware to ignore OPTIONS requests, instead
304
// passing them through to the next handler. This is useful when your application
305
// or framework has a pre-existing mechanism for responding to OPTIONS requests.
306
func IgnoreOptions() CORSOption {
307
	return func(ch *cors) error {
308
		ch.ignoreOptions = true
309
		return nil
310
	}
311
}
312

313
// AllowCredentials can be used to specify that the user agent may pass
314
// authentication details along with the request.
315
func AllowCredentials() CORSOption {
316
	return func(ch *cors) error {
317
		ch.allowCredentials = true
318
		return nil
319
	}
320
}
321

322
func (ch *cors) isOriginAllowed(origin string) bool {
323
	if origin == "" {
324
		return false
325
	}
326

327
	if ch.allowedOriginValidator != nil {
328
		return ch.allowedOriginValidator(origin)
329
	}
330

331
	if len(ch.allowedOrigins) == 0 {
332
		return true
333
	}
334

335
	for _, allowedOrigin := range ch.allowedOrigins {
336
		if allowedOrigin == origin || allowedOrigin == corsOriginMatchAll {
337
			return true
338
		}
339
	}
340

341
	return false
342
}
343

344
func (ch *cors) isMatch(needle string, haystack []string) bool {
345
	for _, v := range haystack {
346
		if v == needle {
347
			return true
348
		}
349
	}
350

351
	return false
352
}
353

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.