diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 1eec8448af..7b173614d1 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -6,6 +6,7 @@ import ( "errors" "net" "net/http" + "net/url" "os" "strings" "time" @@ -95,6 +96,7 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ AllowedHeaders: []string{"Content-Type", "Authorization"}, MaxAge: 300, }) + chiRouter.Use(setPrivateNetworkAccess(options.TrustedDomain)) chiRouter.Use(cors.Handler) chiRouter.Group(func(r chi.Router) { r.Use(authentication(options.Secret)) @@ -270,6 +272,46 @@ func castMetadata(metadata adapter.InboundContext) trafficontrol.Metadata { } } +func setPrivateNetworkAccess(domainList []string) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" && checkDomain(r, domainList) { + w.Header().Add("Access-Control-Allow-Private-Network", "true") + } + next.ServeHTTP(w, r) + }) + } +} + +func parseOriginHost(r *http.Request) string { + if referer := r.Header.Get("Referer"); referer != "" { + URL, err := url.Parse(referer) + if err == nil { + return URL.Host + } + } + if origin := r.Header.Get("Origin"); origin != "" { + URL, err := url.Parse(origin) + if err == nil { + return URL.Host + } + } + return "" +} + +func checkDomain(r *http.Request, list []string) bool { + host := parseOriginHost(r) + if host == "" { + return false + } + for _, domain := range list { + if host == domain { + return true + } + } + return false +} + func authentication(serverSecret string) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { diff --git a/option/experimental.go b/option/experimental.go index c685f51f54..a8e7e7fe49 100644 --- a/option/experimental.go +++ b/option/experimental.go @@ -15,13 +15,14 @@ type CacheFileOptions struct { } type ClashAPIOptions struct { - ExternalController string `json:"external_controller,omitempty"` - ExternalUI string `json:"external_ui,omitempty"` - ExternalUIDownloadURL string `json:"external_ui_download_url,omitempty"` - ExternalUIDownloadDetour string `json:"external_ui_download_detour,omitempty"` - Secret string `json:"secret,omitempty"` - DefaultMode string `json:"default_mode,omitempty"` - ModeList []string `json:"-"` + ExternalController string `json:"external_controller,omitempty"` + ExternalUI string `json:"external_ui,omitempty"` + ExternalUIDownloadURL string `json:"external_ui_download_url,omitempty"` + ExternalUIDownloadDetour string `json:"external_ui_download_detour,omitempty"` + TrustedDomain Listable[string] `json:"trusted_domain,omitempty"` + Secret string `json:"secret,omitempty"` + DefaultMode string `json:"default_mode,omitempty"` + ModeList []string `json:"-"` // Deprecated: migrated to global cache file CacheFile string `json:"cache_file,omitempty"`