-
-
Notifications
You must be signed in to change notification settings - Fork 113
/
cors.go
189 lines (155 loc) · 5.84 KB
/
cors.go
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
188
189
package server
import (
"net/http"
"net/url"
"strconv"
"strings"
)
// Possible improvements:
// If AllowedMethods["*"] then Access-Control-Allow-Methods is set to the requested methods
// If AllowedHeaderss["*"] then Access-Control-Allow-Headers is set to the requested headers
// Put some presets in AllowedHeaders
// Put some presets in AccessControlExposeHeaders
// CorsMiddleware provides a configurable CORS implementation.
type CorsMiddleware struct {
allowedMethods map[string]bool
allowedMethodsCsv string
allowedHeaders map[string]bool
allowedHeadersCsv string
// Reject non CORS requests if true. See CorsInfo.IsCors.
RejectNonCorsRequests bool
// Function excecuted for every CORS requests to validate the Origin. (Required)
// Must return true if valid, false if invalid.
// For instance: simple equality, regexp, DB lookup, ...
OriginValidator func(origin string, request *http.Request) bool
// List of allowed HTTP methods. Note that the comparison will be made in
// uppercase to avoid common mistakes. And that the
// Access-Control-Allow-Methods response header also uses uppercase.
// (see CorsInfo.AccessControlRequestMethod)
AllowedMethods []string
// List of allowed HTTP Headers. Note that the comparison will be made with
// noarmalized names (http.CanonicalHeaderKey). And that the response header
// also uses normalized names.
// (see CorsInfo.AccessControlRequestHeaders)
AllowedHeaders []string
// List of headers used to set the Access-Control-Expose-Headers header.
AccessControlExposeHeaders []string
// User to se the Access-Control-Allow-Credentials response header.
AccessControlAllowCredentials bool
// Used to set the Access-Control-Max-Age response header, in seconds.
AccessControlMaxAge int
}
// MiddlewareFunc makes CorsMiddleware implement the Middleware interface.
func (mw *CorsMiddleware) MiddlewareFunc(handler http.HandlerFunc) http.HandlerFunc {
// precompute as much as possible at init time
mw.allowedMethods = map[string]bool{}
normedMethods := []string{}
for _, allowedMethod := range mw.AllowedMethods {
normed := strings.ToUpper(allowedMethod)
mw.allowedMethods[normed] = true
normedMethods = append(normedMethods, normed)
}
mw.allowedMethodsCsv = strings.Join(normedMethods, ",")
mw.allowedHeaders = map[string]bool{}
normedHeaders := []string{}
for _, allowedHeader := range mw.AllowedHeaders {
normed := http.CanonicalHeaderKey(allowedHeader)
mw.allowedHeaders[normed] = true
normedHeaders = append(normedHeaders, normed)
}
mw.allowedHeadersCsv = strings.Join(normedHeaders, ",")
return func(writer http.ResponseWriter, request *http.Request) {
corsInfo := GetCorsInfo(request)
// non CORS requests
if !corsInfo.IsCors {
if mw.RejectNonCorsRequests {
http.Error(writer, "Non CORS request", http.StatusForbidden)
return
}
// continue, execute the wrapped middleware
handler(writer, request)
return
}
// Validate the Origin
if mw.OriginValidator(corsInfo.Origin, request) == false {
http.Error(writer, "Invalid Origin", http.StatusForbidden)
return
}
if corsInfo.IsPreflight {
// check the request methods
if mw.allowedMethods[corsInfo.AccessControlRequestMethod] == false {
http.Error(writer, "Invalid Preflight Request", http.StatusForbidden)
return
}
// check the request headers
for _, requestedHeader := range corsInfo.AccessControlRequestHeaders {
if mw.allowedHeaders[requestedHeader] == false {
http.Error(writer, "Invalid Preflight Request", http.StatusForbidden)
return
}
}
writer.Header().Set("Access-Control-Allow-Methods", mw.allowedMethodsCsv)
writer.Header().Set("Access-Control-Allow-Headers", mw.allowedHeadersCsv)
writer.Header().Set("Access-Control-Allow-Origin", corsInfo.Origin)
if mw.AccessControlAllowCredentials == true {
writer.Header().Set("Access-Control-Allow-Credentials", "true")
}
writer.Header().Set("Access-Control-Max-Age", strconv.Itoa(mw.AccessControlMaxAge))
writer.WriteHeader(http.StatusOK)
return
}
// Non-preflight requests
for _, exposed := range mw.AccessControlExposeHeaders {
writer.Header().Add("Access-Control-Expose-Headers", exposed)
}
writer.Header().Set("Access-Control-Allow-Origin", corsInfo.Origin)
if mw.AccessControlAllowCredentials == true {
writer.Header().Set("Access-Control-Allow-Credentials", "true")
}
// continure, execute the wrapped middleware
handler(writer, request)
return
}
}
func GetCorsInfo(r *http.Request) *CorsInfo {
origin := r.Header.Get("Origin")
var originUrl *url.URL
var isCors bool
if origin == "" {
isCors = false
} else if origin == "null" {
isCors = true
} else {
var err error
originUrl, err = url.ParseRequestURI(origin)
isCors = err == nil && r.Host != originUrl.Host
}
reqMethod := r.Header.Get("Access-Control-Request-Method")
reqHeaders := []string{}
rawReqHeaders := r.Header[http.CanonicalHeaderKey("Access-Control-Request-Headers")]
for _, rawReqHeader := range rawReqHeaders {
// net/http does not handle comma delimited headers for us
for _, reqHeader := range strings.Split(rawReqHeader, ",") {
reqHeaders = append(reqHeaders, http.CanonicalHeaderKey(strings.TrimSpace(reqHeader)))
}
}
isPreflight := isCors && r.Method == "OPTIONS" && reqMethod != ""
return &CorsInfo{
IsCors: isCors,
IsPreflight: isPreflight,
Origin: origin,
OriginUrl: originUrl,
AccessControlRequestMethod: strings.ToUpper(reqMethod),
AccessControlRequestHeaders: reqHeaders,
}
}
type CorsInfo struct {
IsCors bool
IsPreflight bool
Origin string
OriginUrl *url.URL
// The header value is converted to uppercase to avoid common mistakes.
AccessControlRequestMethod string
// The header values are normalized with http.CanonicalHeaderKey.
AccessControlRequestHeaders []string
}