github.com/yaling888/clash@v1.53.0/adapter/provider/vehicle.go (about) 1 package provider 2 3 import ( 4 "bytes" 5 "context" 6 "io" 7 "net" 8 "net/http" 9 "net/url" 10 "os" 11 "regexp" 12 "time" 13 14 "github.com/yaling888/clash/common/convert" 15 "github.com/yaling888/clash/component/dialer" 16 "github.com/yaling888/clash/component/profile/cachefile" 17 "github.com/yaling888/clash/constant" 18 types "github.com/yaling888/clash/constant/provider" 19 "github.com/yaling888/clash/listener/auth" 20 ) 21 22 var _ types.Vehicle = (*FileVehicle)(nil) 23 24 type FileVehicle struct { 25 path string 26 } 27 28 func (f *FileVehicle) Type() types.VehicleType { 29 return types.File 30 } 31 32 func (f *FileVehicle) Path() string { 33 return f.path 34 } 35 36 func (*FileVehicle) Proxy() bool { 37 return false 38 } 39 40 func (f *FileVehicle) Read() ([]byte, error) { 41 return os.ReadFile(f.path) 42 } 43 44 func NewFileVehicle(path string) *FileVehicle { 45 return &FileVehicle{path: path} 46 } 47 48 var _ types.Vehicle = (*HTTPVehicle)(nil) 49 50 type HTTPVehicle struct { 51 path string 52 url string 53 urlProxy bool 54 header http.Header 55 subscription *Subscription 56 } 57 58 func (h *HTTPVehicle) Type() types.VehicleType { 59 return types.HTTP 60 } 61 62 func (h *HTTPVehicle) Path() string { 63 return h.path 64 } 65 66 func (h *HTTPVehicle) Proxy() bool { 67 return h.urlProxy 68 } 69 70 func (h *HTTPVehicle) Read() ([]byte, error) { 71 ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) 72 defer cancel() 73 74 uri, err := url.Parse(h.url) 75 if err != nil { 76 return nil, err 77 } 78 79 q := uri.Query() 80 q.Del("list") 81 q.Del("sub") 82 q.Del("mu") 83 if !q.Has("clash") { 84 q.Set("clash", "1") 85 } 86 if !q.Has("flag") { 87 q.Set("flag", "clash") 88 } 89 uri.RawQuery = q.Encode() 90 91 req, err := http.NewRequest(http.MethodGet, uri.String(), nil) 92 if err != nil { 93 return nil, err 94 } 95 96 if h.header != nil { 97 req.Header = h.header 98 } 99 100 if user := uri.User; user != nil { 101 password, _ := user.Password() 102 req.SetBasicAuth(user.Username(), password) 103 } 104 105 convert.SetUserAgent(req.Header) 106 107 req = req.WithContext(ctx) 108 109 transport := &http.Transport{ 110 // from http.DefaultTransport 111 MaxIdleConns: 100, 112 IdleConnTimeout: 90 * time.Second, 113 TLSHandshakeTimeout: 10 * time.Second, 114 ExpectContinueTimeout: 1 * time.Second, 115 DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { 116 if h.urlProxy { 117 // forward to tun if tun enabled 118 // do not reject the Clash’s own traffic by rule `PROCESS-NAME` 119 return (&net.Dialer{}).DialContext(ctx, network, address) 120 } 121 return dialer.DialContext(ctx, network, address, dialer.WithDirect()) // with direct 122 }, 123 } 124 125 // fallback to proxy url if tun disabled, make sure enable at least one inbound port 126 // do not reject the Clash’s own traffic by rule `PROCESS-NAME` 127 if h.urlProxy && !constant.GetTunConf().Enable { 128 transport.Proxy = constant.ProxyURL(auth.Authenticator()) 129 } 130 131 client := http.Client{Transport: transport} 132 resp, err := client.Do(req) 133 if err != nil { 134 return nil, err 135 } 136 defer func() { 137 _ = resp.Body.Close() 138 }() 139 140 buf, err := io.ReadAll(resp.Body) 141 if err != nil { 142 return nil, err 143 } 144 145 userinfo := resp.Header.Get("Subscription-Userinfo") 146 if userinfo != "" { 147 if h.subscription == nil { 148 h.subscription = &Subscription{} 149 } 150 h.subscription.parse(userinfo) 151 if h.subscription.Total != 0 { 152 cachefile.Cache().SetSubscription(h.url, userinfo) 153 } 154 } 155 156 return removeComment(buf), nil 157 } 158 159 func (h *HTTPVehicle) Subscription() *Subscription { 160 return h.subscription 161 } 162 163 func NewHTTPVehicle(path string, url string, urlProxy bool, header http.Header) *HTTPVehicle { 164 var ( 165 userinfo = cachefile.Cache().GetSubscription(url) 166 subscription *Subscription 167 ) 168 if userinfo != "" { 169 subscription = &Subscription{} 170 subscription.parse(userinfo) 171 } 172 return &HTTPVehicle{ 173 path: path, 174 url: url, 175 urlProxy: urlProxy, 176 header: header, 177 subscription: subscription, 178 } 179 } 180 181 func removeComment(buf []byte) []byte { 182 arr := regexp.MustCompile(`(.*#.*\n)`).FindAllSubmatch(buf, -1) 183 for _, subs := range arr { 184 sub := subs[0] 185 if !bytes.HasPrefix(bytes.TrimLeft(sub, " "), []byte("#")) { 186 continue 187 } 188 buf = bytes.Replace(buf, sub, []byte(""), 1) 189 } 190 return buf 191 }