2 Commits

Author SHA1 Message Date
putyy
12ac74614d feat: add domain rule configuration 2025-12-26 17:19:36 +08:00
putyy
e0d1da3338 perf: optimization type support 2025-12-26 15:33:02 +08:00
13 changed files with 185 additions and 10 deletions

View File

@@ -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
}

View File

@@ -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)
@@ -161,6 +163,7 @@ func getDefaultMimeMap() map[string]MimeInfo {
"application/vnd.apple.mpegurl": {Type: "m3u8", Suffix: ".m3u8"},
"application/x-mpegurl": {Type: "m3u8", Suffix: ".m3u8"},
"application/x-mpeg": {Type: "m3u8", Suffix: ".m3u8"},
"audio/x-mpegurl": {Type: "m3u8", Suffix: ".m3u8"},
"application/pdf": {Type: "pdf", Suffix: ".pdf"},
"application/vnd.ms-powerpoint": {Type: "ppt", Suffix: ".ppt"},
"application/vnd.openxmlformats-officedocument.presentationml.presentation": {Type: "ppt", Suffix: ".pptx"},
@@ -172,7 +175,8 @@ func getDefaultMimeMap() map[string]MimeInfo {
"text/rtf": {Type: "doc", Suffix: ".rtf"},
"application/vnd.oasis.opendocument.text": {Type: "doc", Suffix: ".odt"},
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": {Type: "doc", Suffix: ".docx"},
"font/woff": {Type: "font", Suffix: ".woff"},
"font/woff": {Type: "font", Suffix: ".woff"},
"application/octet-stream": {Type: "stream", Suffix: "default"},
}
}
@@ -205,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
@@ -223,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()
@@ -279,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
}

View File

@@ -5,8 +5,10 @@ import (
"github.com/elazarl/goproxy"
gonanoid "github.com/matoous/go-nanoid/v2"
"net/http"
"path/filepath"
"res-downloader/core/shared"
"strconv"
"strings"
)
type DefaultPlugin struct {
@@ -26,7 +28,7 @@ func (p *DefaultPlugin) OnRequest(r *http.Request, ctx *goproxy.ProxyCtx) (*http
}
func (p *DefaultPlugin) OnResponse(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
if resp == nil || resp.Request == nil || (resp.StatusCode != 200 && resp.StatusCode != 206) {
if resp == nil || resp.Request == nil || (resp.StatusCode != 200 && resp.StatusCode != 206 && resp.StatusCode != 304) {
return resp
}
@@ -39,6 +41,13 @@ func (p *DefaultPlugin) OnResponse(resp *http.Response, ctx *goproxy.ProxyCtx) *
isAll, _ := p.bridge.GetResType("all")
isClassify, _ := p.bridge.GetResType(classify)
if suffix == "default" {
ext := filepath.Ext(filepath.Base(strings.Split(strings.Split(rawUrl, "?")[0], "#")[0]))
if ext != "" {
suffix = ext
}
}
urlSign := shared.Md5(rawUrl)
if ok := p.bridge.MediaIsMarked(urlSign); !ok && (isAll || isClassify) {
value, _ := strconv.ParseFloat(resp.Header.Get("content-length"), 64)

View File

@@ -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)
}

118
core/rule.go Normal file
View File

@@ -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
}

View File

@@ -47,6 +47,7 @@
"video": "Video",
"m3u8": "M3U8",
"live": "Live Stream",
"stream": "Data Stream",
"xls": "Spreadsheet",
"doc": "Document",
"pdf": "PDF",
@@ -55,7 +56,7 @@
"choice": "choice",
"type": "Type",
"preview": "Preview",
"preview_tip": "Preview not supported",
"preview_tip": "Cannot preview",
"status": "Status",
"description": "Description",
"resource_size": "Resource Size",
@@ -122,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 rulesupports 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",

View File

@@ -47,6 +47,7 @@
"video": "视频",
"m3u8": "m3u8",
"live": "直播流",
"stream": "流数据",
"xls": "表格",
"doc": "文档",
"pdf": "pdf",
@@ -55,7 +56,7 @@
"choice": "已选",
"type": "类型",
"preview": "预览",
"preview_tip": "暂不支持预览",
"preview_tip": "无法预览",
"status": "状态",
"description": "描述",
"resource_size": "资源大小",
@@ -122,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": "基础设置",

View File

@@ -33,7 +33,8 @@ export const useIndexStore = defineStore("index-store", () => {
UserAgent: "",
UseHeaders: "",
InsertTail: true,
MimeMap: {}
MimeMap: {},
Rule: "*"
})
const envInfo = ref({

View File

@@ -31,6 +31,7 @@ export namespace appType {
UseHeaders: string
InsertTail: boolean
MimeMap: { [key: string]: MimeMap }
Rule: string
}
interface MediaInfo {

View File

@@ -205,6 +205,7 @@ const classifyAlias: { [key: string]: any } = {
xls: computed(() => t("index.xls")),
doc: computed(() => t("index.doc")),
pdf: computed(() => t("index.pdf")),
stream: computed(() => t("index.stream")),
font: computed(() => t("index.font"))
}

View File

@@ -197,6 +197,22 @@
</NTooltip>
</NFormItem>
<NFormItem :label="t('setting.domain_rule')" path="DomainRule">
<NInput
v-model:value="formValue.Rule"
type="textarea"
rows="5"
:placeholder="t('setting.domain_rule_tip')"
/>
<NTooltip trigger="hover">
<template #trigger>
<NIcon size="18" class="ml-1 text-gray-500">
<HelpCircleOutline/>
</NIcon>
</template>
{{ t("setting.domain_rule_tip") }}
</NTooltip>
</NFormItem>
<NFormItem :label="t('setting.mime_map')" path="MimeMap">
<NInput
v-model:value="MimeMap"

2
go.mod
View File

@@ -5,7 +5,7 @@ go 1.22.0
toolchain go1.23.2
require (
github.com/elazarl/goproxy v0.0.0-20241223171911-d5978cb8c956
github.com/elazarl/goproxy v1.7.2
github.com/matoous/go-nanoid/v2 v2.1.0
github.com/rs/zerolog v1.33.0
github.com/vrischmann/userdir v0.0.0-20151206171402-20f291cebd68

5
go.sum
View File

@@ -3,8 +3,8 @@ github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3IS
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/elazarl/goproxy v0.0.0-20241223171911-d5978cb8c956 h1:HyPt0ZkHkpke+HFl/4dDMz55A/AjFn7ZnLSm8GfdnwU=
github.com/elazarl/goproxy v0.0.0-20241223171911-d5978cb8c956/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
@@ -90,4 +90,3 @@ golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=