Clash.Meta/component/resource/vehicle.go
2024-10-20 06:01:02 +08:00

178 lines
3.6 KiB
Go

package resource
import (
"context"
"errors"
"io"
"net/http"
"os"
"path/filepath"
"time"
"github.com/metacubex/mihomo/common/utils"
mihomoHttp "github.com/metacubex/mihomo/component/http"
"github.com/metacubex/mihomo/component/profile/cachefile"
types "github.com/metacubex/mihomo/constant/provider"
)
const (
DefaultHttpTimeout = time.Second * 20
fileMode os.FileMode = 0o666
dirMode os.FileMode = 0o755
)
var (
etag = false
)
func ETag() bool {
return etag
}
func SetETag(b bool) {
etag = b
}
func safeWrite(path string, buf []byte) error {
dir := filepath.Dir(path)
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.MkdirAll(dir, dirMode); err != nil {
return err
}
}
return os.WriteFile(path, buf, fileMode)
}
type FileVehicle struct {
path string
}
func (f *FileVehicle) Type() types.VehicleType {
return types.File
}
func (f *FileVehicle) Path() string {
return f.path
}
func (f *FileVehicle) Url() string {
return "file://" + f.path
}
func (f *FileVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []byte, hash utils.HashType, err error) {
buf, err = os.ReadFile(f.path)
if err != nil {
return
}
hash = utils.MakeHash(buf)
return
}
func (f *FileVehicle) Proxy() string {
return ""
}
func (f *FileVehicle) Write(buf []byte) error {
return safeWrite(f.path, buf)
}
func NewFileVehicle(path string) *FileVehicle {
return &FileVehicle{path: path}
}
type HTTPVehicle struct {
url string
path string
proxy string
header http.Header
timeout time.Duration
provider types.ProxyProvider
}
func (h *HTTPVehicle) Url() string {
return h.url
}
func (h *HTTPVehicle) Type() types.VehicleType {
return types.HTTP
}
func (h *HTTPVehicle) Path() string {
return h.path
}
func (h *HTTPVehicle) Proxy() string {
return h.proxy
}
func (h *HTTPVehicle) Write(buf []byte) error {
return safeWrite(h.path, buf)
}
func (h *HTTPVehicle) SetProvider(provider types.ProxyProvider) {
h.provider = provider
}
func (h *HTTPVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []byte, hash utils.HashType, err error) {
ctx, cancel := context.WithTimeout(ctx, h.timeout)
defer cancel()
header := h.header
setIfNoneMatch := false
if etag && oldHash.IsValid() {
etagWithHash := cachefile.Cache().GetETagWithHash(h.url)
if oldHash.Equal(etagWithHash.Hash) && etagWithHash.ETag != "" {
if header == nil {
header = http.Header{}
} else {
header = header.Clone()
}
header.Set("If-None-Match", etagWithHash.ETag)
setIfNoneMatch = true
}
}
resp, err := mihomoHttp.HttpRequestWithProxy(ctx, h.url, http.MethodGet, header, nil, h.proxy)
if err != nil {
return
}
defer resp.Body.Close()
if subscriptionInfo := resp.Header.Get("subscription-userinfo"); h.provider != nil && subscriptionInfo != "" {
cachefile.Cache().SetSubscriptionInfo(h.provider.Name(), subscriptionInfo)
h.provider.SetSubscriptionInfo(subscriptionInfo)
}
if resp.StatusCode < 200 || resp.StatusCode > 299 {
if setIfNoneMatch && resp.StatusCode == http.StatusNotModified {
return nil, oldHash, nil
}
err = errors.New(resp.Status)
return
}
buf, err = io.ReadAll(resp.Body)
if err != nil {
return
}
hash = utils.MakeHash(buf)
if etag {
cachefile.Cache().SetETagWithHash(h.url, cachefile.EtagWithHash{
Hash: hash,
ETag: resp.Header.Get("ETag"),
Time: time.Now(),
})
}
return
}
func NewHTTPVehicle(url string, path string, proxy string, header http.Header, timeout time.Duration) *HTTPVehicle {
return &HTTPVehicle{
url: url,
path: path,
proxy: proxy,
header: header,
timeout: timeout,
}
}