3 // Copyright 2013 Ernest Micklei. All rights reserved.
4 // Use of this source code is governed by a license
5 // that can be found in the LICENSE file.
13 // CrossOriginResourceSharing is used to create a Container Filter that implements CORS.
14 // Cross-origin resource sharing (CORS) is a mechanism that allows JavaScript on a web page
15 // to make XMLHttpRequests to another domain, not the domain the JavaScript originated from.
17 // http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
18 // http://enable-cors.org/server.html
19 // http://www.html5rocks.com/en/tutorials/cors/#toc-handling-a-not-so-simple-request
20 type CrossOriginResourceSharing struct {
21 ExposeHeaders []string // list of Header names
22 AllowedHeaders []string // list of Header names
23 AllowedDomains []string // list of allowed values for Http Origin. An allowed value can be a regular expression to support subdomain matching. If empty all are allowed.
24 AllowedMethods []string
25 MaxAge int // number of seconds before requiring new Options request
29 allowedOriginPatterns []*regexp.Regexp // internal field for origin regexp check.
32 // Filter is a filter function that implements the CORS flow as documented on http://enable-cors.org/server.html
33 // and http://www.html5rocks.com/static/images/cors_server_flowchart.png
34 func (c CrossOriginResourceSharing) Filter(req *Request, resp *Response, chain *FilterChain) {
35 origin := req.Request.Header.Get(HEADER_Origin)
38 traceLogger.Print("no Http header Origin set")
40 chain.ProcessFilter(req, resp)
43 if !c.isOriginAllowed(origin) { // check whether this origin is allowed
45 traceLogger.Printf("HTTP Origin:%s is not part of %v, neither matches any part of %v", origin, c.AllowedDomains, c.allowedOriginPatterns)
47 chain.ProcessFilter(req, resp)
50 if req.Request.Method != "OPTIONS" {
51 c.doActualRequest(req, resp)
52 chain.ProcessFilter(req, resp)
55 if acrm := req.Request.Header.Get(HEADER_AccessControlRequestMethod); acrm != "" {
56 c.doPreflightRequest(req, resp)
58 c.doActualRequest(req, resp)
59 chain.ProcessFilter(req, resp)
64 func (c CrossOriginResourceSharing) doActualRequest(req *Request, resp *Response) {
65 c.setOptionsHeaders(req, resp)
66 // continue processing the response
69 func (c *CrossOriginResourceSharing) doPreflightRequest(req *Request, resp *Response) {
70 if len(c.AllowedMethods) == 0 {
71 if c.Container == nil {
72 c.AllowedMethods = DefaultContainer.computeAllowedMethods(req)
74 c.AllowedMethods = c.Container.computeAllowedMethods(req)
78 acrm := req.Request.Header.Get(HEADER_AccessControlRequestMethod)
79 if !c.isValidAccessControlRequestMethod(acrm, c.AllowedMethods) {
81 traceLogger.Printf("Http header %s:%s is not in %v",
82 HEADER_AccessControlRequestMethod,
88 acrhs := req.Request.Header.Get(HEADER_AccessControlRequestHeaders)
90 for _, each := range strings.Split(acrhs, ",") {
91 if !c.isValidAccessControlRequestHeader(strings.Trim(each, " ")) {
93 traceLogger.Printf("Http header %s:%s is not in %v",
94 HEADER_AccessControlRequestHeaders,
102 resp.AddHeader(HEADER_AccessControlAllowMethods, strings.Join(c.AllowedMethods, ","))
103 resp.AddHeader(HEADER_AccessControlAllowHeaders, acrhs)
104 c.setOptionsHeaders(req, resp)
106 // return http 200 response, no body
109 func (c CrossOriginResourceSharing) setOptionsHeaders(req *Request, resp *Response) {
110 c.checkAndSetExposeHeaders(resp)
111 c.setAllowOriginHeader(req, resp)
112 c.checkAndSetAllowCredentials(resp)
114 resp.AddHeader(HEADER_AccessControlMaxAge, strconv.Itoa(c.MaxAge))
118 func (c CrossOriginResourceSharing) isOriginAllowed(origin string) bool {
119 if len(origin) == 0 {
122 if len(c.AllowedDomains) == 0 {
127 for _, domain := range c.AllowedDomains {
128 if domain == origin {
135 if len(c.allowedOriginPatterns) == 0 {
136 // compile allowed domains to allowed origin patterns
137 allowedOriginRegexps, err := compileRegexps(c.AllowedDomains)
141 c.allowedOriginPatterns = allowedOriginRegexps
144 for _, pattern := range c.allowedOriginPatterns {
145 if allowed = pattern.MatchString(origin); allowed {
154 func (c CrossOriginResourceSharing) setAllowOriginHeader(req *Request, resp *Response) {
155 origin := req.Request.Header.Get(HEADER_Origin)
156 if c.isOriginAllowed(origin) {
157 resp.AddHeader(HEADER_AccessControlAllowOrigin, origin)
161 func (c CrossOriginResourceSharing) checkAndSetExposeHeaders(resp *Response) {
162 if len(c.ExposeHeaders) > 0 {
163 resp.AddHeader(HEADER_AccessControlExposeHeaders, strings.Join(c.ExposeHeaders, ","))
167 func (c CrossOriginResourceSharing) checkAndSetAllowCredentials(resp *Response) {
168 if c.CookiesAllowed {
169 resp.AddHeader(HEADER_AccessControlAllowCredentials, "true")
173 func (c CrossOriginResourceSharing) isValidAccessControlRequestMethod(method string, allowedMethods []string) bool {
174 for _, each := range allowedMethods {
182 func (c CrossOriginResourceSharing) isValidAccessControlRequestHeader(header string) bool {
183 for _, each := range c.AllowedHeaders {
184 if strings.ToLower(each) == strings.ToLower(header) {
191 // Take a list of strings and compile them into a list of regular expressions.
192 func compileRegexps(regexpStrings []string) ([]*regexp.Regexp, error) {
193 regexps := []*regexp.Regexp{}
194 for _, regexpStr := range regexpStrings {
195 r, err := regexp.Compile(regexpStr)
199 regexps = append(regexps, r)