package config import ( "encoding/json" "fmt" "io" "net/http" U "net/url" "os" P "path" "runtime" "time" "cfa/native/app" "github.com/Dreamacro/clash/component/dialer" ) type Status struct { Action string `json:"action"` Args []string `json:"args"` Progress int `json:"progress"` MaxProgress int `json:"max"` } var client = &http.Client{ Transport: &http.Transport{ DisableKeepAlives: true, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, DialContext: dialer.DialTunnelContext, }, Timeout: 60 * time.Second, } func openUrl(url string) (io.ReadCloser, error) { request, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err } request.Header.Set("User-Agent", "ClashForAndroid/"+app.VersionName()) response, err := client.Do(request) if err != nil { return nil, err } return response.Body, nil } func openContent(url string) (io.ReadCloser, error) { return app.OpenContent(url) } func fetch(url *U.URL, file string) error { var reader io.ReadCloser var err error switch url.Scheme { case "http", "https": reader, err = openUrl(url.String()) case "content": reader, err = openContent(url.String()) default: err = fmt.Errorf("unsupported scheme %s of %s", url.Scheme, url) } if err != nil { return err } defer reader.Close() _ = os.MkdirAll(P.Dir(file), 0700) f, err := os.OpenFile(file, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600) if err != nil { return err } defer f.Close() _, err = io.Copy(f, reader) if err != nil { _ = os.Remove(file) } return err } func FetchAndValid( path string, url string, force bool, reportStatus func(string), ) error { configPath := P.Join(path, "config.yaml") if _, err := os.Stat(configPath); os.IsNotExist(err) || force { url, err := U.Parse(url) if err != nil { return err } bytes, _ := json.Marshal(&Status{ Action: "FetchConfiguration", Args: []string{url.Host}, Progress: -1, MaxProgress: -1, }) reportStatus(string(bytes)) if err := fetch(url, configPath); err != nil { return err } } defer runtime.GC() rawCfg, err := UnmarshalAndPatch(path) if err != nil { return err } forEachProviders(rawCfg, func(index int, total int, name string, provider map[string]any) { bytes, _ := json.Marshal(&Status{ Action: "FetchProviders", Args: []string{name}, Progress: index, MaxProgress: total, }) reportStatus(string(bytes)) u, uok := provider["url"] p, pok := provider["path"] if !uok || !pok { return } us, uok := u.(string) ps, pok := p.(string) if !uok || !pok { return } if _, err := os.Stat(ps); err == nil { return } url, err := U.Parse(us) if err != nil { return } _ = fetch(url, ps) }) bytes, _ := json.Marshal(&Status{ Action: "Verifying", Args: []string{}, Progress: 0xffff, MaxProgress: 0xffff, }) reportStatus(string(bytes)) cfg, err := Parse(rawCfg) if err != nil { return err } destroyProviders(cfg) return nil }