github.com/gogf/gf@v1.16.9/net/ghttp/ghttp_response_cors.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/gogf/gf.
     6  //
     7  
     8  package ghttp
     9  
    10  import (
    11  	"github.com/gogf/gf/text/gstr"
    12  	"github.com/gogf/gf/util/gconv"
    13  	"net/http"
    14  	"net/url"
    15  )
    16  
    17  // CORSOptions is the options for CORS feature.
    18  // See https://www.w3.org/TR/cors/ .
    19  type CORSOptions struct {
    20  	AllowDomain      []string // Used for allowing requests from custom domains
    21  	AllowOrigin      string   // Access-Control-Allow-Origin
    22  	AllowCredentials string   // Access-Control-Allow-Credentials
    23  	ExposeHeaders    string   // Access-Control-Expose-Headers
    24  	MaxAge           int      // Access-Control-Max-Age
    25  	AllowMethods     string   // Access-Control-Allow-Methods
    26  	AllowHeaders     string   // Access-Control-Allow-Headers
    27  }
    28  
    29  var (
    30  	// defaultAllowHeaders is the default allowed headers for CORS.
    31  	// It's defined another map for better header key searching performance.
    32  	defaultAllowHeaders    = "Origin,Content-Type,Accept,User-Agent,Cookie,Authorization,X-Auth-Token,X-Requested-With"
    33  	defaultAllowHeadersMap = make(map[string]struct{})
    34  )
    35  
    36  func init() {
    37  	array := gstr.SplitAndTrim(defaultAllowHeaders, ",")
    38  	for _, header := range array {
    39  		defaultAllowHeadersMap[header] = struct{}{}
    40  	}
    41  }
    42  
    43  // DefaultCORSOptions returns the default CORS options,
    44  // which allows any cross-domain request.
    45  func (r *Response) DefaultCORSOptions() CORSOptions {
    46  	options := CORSOptions{
    47  		AllowOrigin:      "*",
    48  		AllowMethods:     supportedHttpMethods,
    49  		AllowCredentials: "true",
    50  		AllowHeaders:     defaultAllowHeaders,
    51  		MaxAge:           3628800,
    52  	}
    53  	// Allow all client's custom headers in default.
    54  	if headers := r.Request.Header.Get("Access-Control-Request-Headers"); headers != "" {
    55  		array := gstr.SplitAndTrim(headers, ",")
    56  		for _, header := range array {
    57  			if _, ok := defaultAllowHeadersMap[header]; !ok {
    58  				options.AllowHeaders += "," + header
    59  			}
    60  		}
    61  	}
    62  	// Allow all anywhere origin in default.
    63  	if origin := r.Request.Header.Get("Origin"); origin != "" {
    64  		options.AllowOrigin = origin
    65  	} else if referer := r.Request.Referer(); referer != "" {
    66  		if p := gstr.PosR(referer, "/", 6); p != -1 {
    67  			options.AllowOrigin = referer[:p]
    68  		} else {
    69  			options.AllowOrigin = referer
    70  		}
    71  	}
    72  	return options
    73  }
    74  
    75  // CORS sets custom CORS options.
    76  // See https://www.w3.org/TR/cors/ .
    77  func (r *Response) CORS(options CORSOptions) {
    78  	if r.CORSAllowedOrigin(options) {
    79  		r.Header().Set("Access-Control-Allow-Origin", options.AllowOrigin)
    80  	}
    81  	if options.AllowCredentials != "" {
    82  		r.Header().Set("Access-Control-Allow-Credentials", options.AllowCredentials)
    83  	}
    84  	if options.ExposeHeaders != "" {
    85  		r.Header().Set("Access-Control-Expose-Headers", options.ExposeHeaders)
    86  	}
    87  	if options.MaxAge != 0 {
    88  		r.Header().Set("Access-Control-Max-Age", gconv.String(options.MaxAge))
    89  	}
    90  	if options.AllowMethods != "" {
    91  		r.Header().Set("Access-Control-Allow-Methods", options.AllowMethods)
    92  	}
    93  	if options.AllowHeaders != "" {
    94  		r.Header().Set("Access-Control-Allow-Headers", options.AllowHeaders)
    95  	}
    96  	// No continue service handling if it's OPTIONS request.
    97  	// Note that there's special checks in previous router searching,
    98  	// so if it goes to here it means there's already serving handler exist.
    99  	if gstr.Equal(r.Request.Method, "OPTIONS") {
   100  		if r.Status == 0 {
   101  			r.Status = http.StatusOK
   102  		}
   103  		// No continue serving.
   104  		r.Request.ExitAll()
   105  	}
   106  }
   107  
   108  // CORSAllowed checks whether the current request origin is allowed cross-domain.
   109  func (r *Response) CORSAllowedOrigin(options CORSOptions) bool {
   110  	if options.AllowDomain == nil {
   111  		return true
   112  	}
   113  	origin := r.Request.Header.Get("Origin")
   114  	if origin == "" {
   115  		return true
   116  	}
   117  	parsed, err := url.Parse(origin)
   118  	if err != nil {
   119  		return false
   120  	}
   121  	for _, v := range options.AllowDomain {
   122  		if gstr.IsSubDomain(parsed.Host, v) {
   123  			return true
   124  		}
   125  	}
   126  	return false
   127  }
   128  
   129  // CORSDefault sets CORS with default CORS options,
   130  // which allows any cross-domain request.
   131  func (r *Response) CORSDefault() {
   132  	r.CORS(r.DefaultCORSOptions())
   133  }