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