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  }