diff --git a/core/app.go b/core/app.go
index 11e299a..e9d1ee7 100644
--- a/core/app.go
+++ b/core/app.go
@@ -37,6 +37,7 @@ var (
systemOnce *SystemSetup
proxyOnce *Proxy
httpServerOnce *HttpServer
+ ruleOnce *RuleSet
)
func GetApp(assets embed.FS, wjs string) *App {
@@ -120,6 +121,7 @@ ILKEQKmPPzKs7kp/7Nz+2cT3
initResource()
initHttpServer()
initSystem()
+ initRule()
}
return appOnce
}
diff --git a/core/config.go b/core/config.go
index 0f752d5..bc5c553 100644
--- a/core/config.go
+++ b/core/config.go
@@ -37,6 +37,7 @@ type Config struct {
UseHeaders string `json:"UseHeaders"`
InsertTail bool `json:"InsertTail"`
MimeMap map[string]MimeInfo `json:"MimeMap"`
+ Rule string `json:"Rule"`
}
var (
@@ -68,6 +69,7 @@ func initConfig() *Config {
UseHeaders: "User-Agent,Referer,Authorization,Cookie",
InsertTail: true,
MimeMap: getDefaultMimeMap(),
+ Rule: "*",
}
rawDefaults, err := json.Marshal(defaultConfig)
@@ -207,6 +209,7 @@ func getDefaultDownloadDir() string {
func (c *Config) setConfig(config Config) {
oldProxy := c.UpstreamProxy
openProxy := c.OpenProxy
+ oldRule := c.Rule
c.Host = config.Host
c.Port = config.Port
c.Theme = config.Theme
@@ -225,10 +228,18 @@ func (c *Config) setConfig(config Config) {
c.WxAction = config.WxAction
c.UseHeaders = config.UseHeaders
c.InsertTail = config.InsertTail
+ c.Rule = config.Rule
if oldProxy != c.UpstreamProxy || openProxy != c.OpenProxy {
proxyOnce.setTransport()
}
+ if oldRule != c.Rule {
+ err := ruleOnce.Load(c.Rule)
+ if err != nil {
+ globalLogger.Esg(err, "set rule failed")
+ }
+ }
+
mimeMux.Lock()
c.MimeMap = config.MimeMap
mimeMux.Unlock()
@@ -281,6 +292,8 @@ func (c *Config) getConfig(key string) interface{} {
mimeMux.RLock()
defer mimeMux.RUnlock()
return c.MimeMap
+ case "Rule":
+ return c.Rule
default:
return nil
}
diff --git a/core/proxy.go b/core/proxy.go
index a329851..6fe953a 100644
--- a/core/proxy.go
+++ b/core/proxy.go
@@ -80,7 +80,14 @@ func (p *Proxy) Startup() {
//p.Proxy.KeepDestinationHeaders = true
//p.Proxy.Verbose = false
p.setTransport()
- p.Proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)
+ //p.Proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)
+ p.Proxy.OnRequest().HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
+ if ruleOnce.shouldMitm(host) {
+ return goproxy.MitmConnect, host
+ }
+ return goproxy.OkConnect, host
+ })
+
p.Proxy.OnRequest().DoFunc(p.httpRequestEvent)
p.Proxy.OnResponse().DoFunc(p.httpResponseEvent)
}
diff --git a/core/rule.go b/core/rule.go
new file mode 100644
index 0000000..9f44ba0
--- /dev/null
+++ b/core/rule.go
@@ -0,0 +1,118 @@
+package core
+
+import (
+ "bufio"
+ "net"
+ "strings"
+ "sync"
+)
+
+type Rule struct {
+ raw string
+ isNeg bool // 是否否定规则(以 ! 开头)
+ isWildcard bool // 是否为 *.domain 形式
+ domain string // 域名部分,不含 "*."
+}
+
+type RuleSet struct {
+ mu sync.RWMutex
+ rules []Rule
+}
+
+func initRule() *RuleSet {
+ if ruleOnce == nil {
+ ruleOnce = &RuleSet{}
+ err := ruleOnce.Load(globalConfig.Rule)
+ if err != nil {
+ globalLogger.Esg(err, "init rule failed")
+ return nil
+ }
+ }
+ return ruleOnce
+}
+
+func (r *RuleSet) Load(rs string) error {
+ reader := strings.NewReader(rs)
+ scanner := bufio.NewScanner(reader)
+
+ var rules []Rule
+ for scanner.Scan() {
+ line := strings.TrimSpace(scanner.Text())
+ if line == "" || strings.HasPrefix(line, "#") {
+ continue
+ }
+
+ isNeg := false
+ if strings.HasPrefix(line, "!") {
+ isNeg = true
+ line = strings.TrimSpace(line[1:])
+ if line == "" {
+ continue
+ }
+ }
+
+ isWildcard := false
+ domain := line
+ if strings.HasPrefix(line, "*.") {
+ isWildcard = true
+ domain = line[2:]
+ }
+
+ rules = append(rules, Rule{
+ raw: line,
+ isNeg: isNeg,
+ isWildcard: isWildcard,
+ domain: strings.ToLower(domain),
+ })
+ }
+
+ if err := scanner.Err(); err != nil {
+ return err
+ }
+
+ r.mu.Lock()
+ r.rules = rules
+ r.mu.Unlock()
+ return nil
+}
+
+// shouldMitm: 根据当前规则集判断是否对 host 做 MITM
+// host 可能带端口(example.com:443),函数会只匹配 hostname 部分
+// 返回 true => MITM(解密),false => 透传
+func (r *RuleSet) shouldMitm(host string) bool {
+ h := host
+ if strings.HasPrefix(h, "[") {
+ if hostSplitIdx := strings.LastIndex(h, "]"); hostSplitIdx != -1 {
+ h = h[:hostSplitIdx+1]
+ }
+ }
+ if hp, _, err := net.SplitHostPort(host); err == nil {
+ h = hp
+ }
+ h = strings.ToLower(strings.Trim(h, "[]"))
+
+ r.mu.RLock()
+ defer r.mu.RUnlock()
+
+ action := false
+ for _, rule := range r.rules {
+ if rule.isWildcard {
+ if h == rule.domain || strings.HasSuffix(h, "."+rule.domain) {
+ if rule.isNeg {
+ action = false
+ } else {
+ action = true
+ }
+ }
+ } else {
+ if h == rule.domain {
+ if rule.isNeg {
+ action = false
+ } else {
+ action = true
+ }
+ }
+ }
+ }
+ return action
+}
diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json
index a64887a..6eda6f7 100644
--- a/frontend/src/locales/en.json
+++ b/frontend/src/locales/en.json
@@ -123,6 +123,8 @@
"use_headers_tip": "Define headers for downloads, comma separated",
"mime_map": "Intercept Rules",
"mime_map_tip": "JSON format, keep default if unsure, please restart software after modification",
+ "domain_rule": "Domain Rule",
+ "domain_rule_tip": "Default * matches all domains, One line for each rule,supports the following: \n*.qq.com\nvideo.qq.com\nexample.com\n\n# Exclude\n!static.qq.com",
"port_format_error": "port format error",
"host_format_error": "host format error",
"basic_setting": "Basic Setting",
diff --git a/frontend/src/locales/zh.json b/frontend/src/locales/zh.json
index cc601d8..ddf4b41 100644
--- a/frontend/src/locales/zh.json
+++ b/frontend/src/locales/zh.json
@@ -123,6 +123,8 @@
"use_headers_tip": "定义下载时可使用的header参数,逗号分割",
"mime_map": "拦截规则",
"mime_map_tip": "json格式,如果不清楚保持默认就行,修改后请重启软件",
+ "domain_rule": "域名规则",
+ "domain_rule_tip": "默认*匹配所有域,每个规则一行,支持如下: \n*.qq.com\nvideo.qq.com\nexample.com\n\n# 排除\n!static.qq.com",
"port_format_error": "port 格式错误",
"host_format_error": "host 格式错误",
"basic_setting": "基础设置",
diff --git a/frontend/src/stores/index.ts b/frontend/src/stores/index.ts
index 215a33d..ed2a435 100644
--- a/frontend/src/stores/index.ts
+++ b/frontend/src/stores/index.ts
@@ -33,7 +33,8 @@ export const useIndexStore = defineStore("index-store", () => {
UserAgent: "",
UseHeaders: "",
InsertTail: true,
- MimeMap: {}
+ MimeMap: {},
+ Rule: "*"
})
const envInfo = ref({
diff --git a/frontend/src/types/app.d.ts b/frontend/src/types/app.d.ts
index 6686766..6cce76e 100644
--- a/frontend/src/types/app.d.ts
+++ b/frontend/src/types/app.d.ts
@@ -31,6 +31,7 @@ export namespace appType {
UseHeaders: string
InsertTail: boolean
MimeMap: { [key: string]: MimeMap }
+ Rule: string
}
interface MediaInfo {
diff --git a/frontend/src/views/setting.vue b/frontend/src/views/setting.vue
index 6c673b1..8d34278 100644
--- a/frontend/src/views/setting.vue
+++ b/frontend/src/views/setting.vue
@@ -197,6 +197,22 @@
+
+
+
+
+
+
+
+
+ {{ t("setting.domain_rule_tip") }}
+
+