diff --git a/api/handler/clash.go b/api/handler/clash.go index e9b2acb..ea35537 100644 --- a/api/handler/clash.go +++ b/api/handler/clash.go @@ -11,7 +11,7 @@ import ( ) func SubmodHandler(c *gin.Context) { - // 从请求中获取参数 + query, err := validator.ParseQuery(c) if err != nil { c.String(http.StatusBadRequest, err.Error()) @@ -22,7 +22,7 @@ func SubmodHandler(c *gin.Context) { c.String(http.StatusInternalServerError, err.Error()) return } - // 输出 + if query.NodeListMode { nodelist := model.NodeList{} nodelist.Proxies = sub.Proxies diff --git a/api/handler/default.go b/api/handler/default.go index ba36a64..25ee8c9 100644 --- a/api/handler/default.go +++ b/api/handler/default.go @@ -22,12 +22,12 @@ import ( func BuildSub(clashType model.ClashType, query validator.SubValidator, template string) ( *model.Subscription, error, ) { - // 定义变量 + var temp = &model.Subscription{} var sub = &model.Subscription{} var err error var templateBytes []byte - // 加载模板 + if query.Template != "" { template = query.Template } @@ -52,14 +52,14 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template return nil, errors.New("加载模板失败: " + err.Error()) } } - // 解析模板 + err = yaml.Unmarshal(templateBytes, &temp) if err != nil { logger.Logger.Debug("parse template failed", zap.Error(err)) return nil, errors.New("解析模板失败: " + err.Error()) } var proxyList []model.Proxy - // 加载订阅 + for i := range query.Subs { data, err := common.LoadSubscription(query.Subs[i], query.Refresh, query.UserAgent) subName := "" @@ -72,7 +72,7 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template ) return nil, errors.New("加载订阅失败: " + err.Error()) } - // 解析订阅 + err = yaml.Unmarshal(data, &sub) var newProxies []model.Proxy if err != nil { @@ -81,7 +81,7 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template p := common.ParseProxy(strings.Split(string(data), "\n")...) newProxies = p } else { - // 如果无法直接解析,尝试Base64解码 + base64, err := parser.DecodeBase64(string(data)) if err != nil { logger.Logger.Debug( @@ -104,17 +104,17 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template } proxyList = append(proxyList, newProxies...) } - // 添加自定义节点 + if len(query.Proxies) != 0 { proxyList = append(proxyList, common.ParseProxy(query.Proxies...)...) } - // 给节点添加订阅名称 + for i := range proxyList { if proxyList[i].SubName != "" { proxyList[i].Name = strings.TrimSpace(proxyList[i].SubName) + " " + strings.TrimSpace(proxyList[i].Name) } } - // 去掉配置相同的节点 + proxies := make(map[string]*model.Proxy) newProxies := make([]model.Proxy, 0, len(proxyList)) for i := range proxyList { @@ -125,7 +125,7 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template } } proxyList = newProxies - // 删除节点 + if strings.TrimSpace(query.Remove) != "" { newProxyList := make([]model.Proxy, 0, len(proxyList)) for i := range proxyList { @@ -134,17 +134,17 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template logger.Logger.Debug("remove regexp compile failed", zap.Error(err)) return nil, errors.New("remove 参数非法: " + err.Error()) } - // 删除匹配到的节点 + if removeReg.MatchString(proxyList[i].Name) { - continue // 如果匹配到要删除的元素,跳过该元素,不添加到新切片中 + continue } - newProxyList = append(newProxyList, proxyList[i]) // 将要保留的元素添加到新切片中 + newProxyList = append(newProxyList, proxyList[i]) } proxyList = newProxyList } - // 重命名 + if len(query.ReplaceKeys) != 0 { - // 创建重命名正则表达式 + replaceRegs := make([]*regexp.Regexp, 0, len(query.ReplaceKeys)) for _, v := range query.ReplaceKeys { replaceReg, err := regexp.Compile(v) @@ -155,7 +155,7 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template replaceRegs = append(replaceRegs, replaceReg) } for i := range proxyList { - // 重命名匹配到的节点 + for j, v := range replaceRegs { if v.MatchString(proxyList[i].Name) { proxyList[i].Name = v.ReplaceAllString( @@ -165,7 +165,7 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template } } } - // 重名检测 + names := make(map[string]int) for i := range proxyList { if _, exist := names[proxyList[i].Name]; exist { @@ -175,14 +175,14 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template names[proxyList[i].Name] = 0 } } - // trim + for i := range proxyList { proxyList[i].Name = strings.TrimSpace(proxyList[i].Name) } - // 将新增节点都添加到临时变量 t 中,防止策略组排序错乱 + var t = &model.Subscription{} common.AddProxy(t, query.AutoTest, query.Lazy, clashType, proxyList...) - // 排序策略组 + switch query.Sort { case "sizeasc": sort.Sort(model.ProxyGroupsSortBySize(t.ProxyGroups)) @@ -195,9 +195,9 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template default: sort.Sort(model.ProxyGroupsSortByName(t.ProxyGroups)) } - // 合并新节点和模板 + MergeSubAndTemplate(temp, t, query.IgnoreCountryGrooup) - // 处理自定义规则 + for _, v := range query.Rules { if v.Prepend { common.PrependRules(temp, v.Rule) @@ -205,7 +205,7 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template common.AppendRules(temp, v.Rule) } } - // 处理自定义 ruleProvider + for _, v := range query.RuleProviders { hash := sha256.Sum224([]byte(v.Url)) name := hex.EncodeToString(hash[:]) @@ -230,8 +230,7 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template } func MergeSubAndTemplate(temp *model.Subscription, sub *model.Subscription, igcg bool) { - // 只合并节点、策略组 - // 统计所有国家策略组名称 + var countryGroupNames []string for _, proxyGroup := range sub.ProxyGroups { if proxyGroup.IsCountryGrop { @@ -244,9 +243,9 @@ func MergeSubAndTemplate(temp *model.Subscription, sub *model.Subscription, igcg for _, proxy := range sub.Proxies { proxyNames = append(proxyNames, proxy.Name) } - // 将订阅中的节点添加到模板中 + temp.Proxies = append(temp.Proxies, sub.Proxies...) - // 将订阅中的策略组添加到模板中 + for i := range temp.ProxyGroups { if temp.ProxyGroups[i].IsCountryGrop { continue diff --git a/api/handler/meta.go b/api/handler/meta.go index 38ee020..4249acf 100644 --- a/api/handler/meta.go +++ b/api/handler/meta.go @@ -12,7 +12,7 @@ import ( ) func SubHandler(c *gin.Context) { - // 从请求中获取参数 + query, err := validator.ParseQuery(c) if err != nil { c.String(http.StatusBadRequest, err.Error()) @@ -23,7 +23,7 @@ func SubHandler(c *gin.Context) { c.String(http.StatusInternalServerError, err.Error()) return } - // 输出 + if query.NodeListMode { nodelist := model.NodeList{} nodelist.Proxies = sub.Proxies diff --git a/api/handler/short_link.go b/api/handler/short_link.go index 1eb865a..f410f33 100644 --- a/api/handler/short_link.go +++ b/api/handler/short_link.go @@ -88,7 +88,7 @@ func UpdateLinkHandler(c *gin.Context) { } func GetRawConfHandler(c *gin.Context) { - // 获取动态路由参数 + hash := c.Param("hash") password := c.Query("password") @@ -97,27 +97,24 @@ func GetRawConfHandler(c *gin.Context) { return } - // 查询数据库中的短链接 shortLink, err := database.FindShortLinkByHash(hash) if err != nil { c.String(http.StatusNotFound, "未找到短链接或密码错误") return } - // 校验密码 if shortLink.Password != "" && shortLink.Password != password { c.String(http.StatusNotFound, "未找到短链接或密码错误") return } - // 更新最后访问时间 shortLink.LastRequestTime = time.Now().Unix() err = database.SaveShortLink(shortLink) if err != nil { respondWithError(c, http.StatusInternalServerError, "数据库错误") return } - // 请求短链接指向的URL + response, err := http.Get("http://localhost:" + strconv.Itoa(config.Default.Port) + "/" + shortLink.Url) if err != nil { respondWithError(c, http.StatusInternalServerError, "请求错误: "+err.Error()) @@ -125,19 +122,17 @@ func GetRawConfHandler(c *gin.Context) { } defer response.Body.Close() - // 读取响应内容 all, err := io.ReadAll(response.Body) if err != nil { respondWithError(c, http.StatusInternalServerError, "读取错误: "+err.Error()) return } - // 返回响应内容 c.String(http.StatusOK, string(all)) } func GetRawConfUriHandler(c *gin.Context) { - // 获取动态路由参数 + hash := c.Query("hash") password := c.Query("password") @@ -146,14 +141,12 @@ func GetRawConfUriHandler(c *gin.Context) { return } - // 查询数据库中的短链接 shortLink, err := database.FindShortLinkByHash(hash) if err != nil { c.String(http.StatusNotFound, "未找到短链接或密码错误") return } - // 校验密码 if shortLink.Password != "" && shortLink.Password != password { c.String(http.StatusNotFound, "未找到短链接或密码错误") return diff --git a/common/database/database.go b/common/database/database.go index c714e67..fe79c1c 100644 --- a/common/database/database.go +++ b/common/database/database.go @@ -13,16 +13,14 @@ import ( var DB *bbolt.DB func ConnectDB() error { - // 确保数据目录存在 path := filepath.Join("data", "sub2clash.db") - // 打开或创建数据库文件 + db, err := bbolt.Open(path, 0600, nil) if err != nil { return err } DB = db - // 确保存储桶存在 return db.Update(func(tx *bbolt.Tx) error { _, err := tx.CreateBucketIfNotExists([]byte("ShortLinks")) return err diff --git a/common/get.go b/common/get.go index 4560bca..7b46e1d 100644 --- a/common/get.go +++ b/common/get.go @@ -22,7 +22,7 @@ func WithUserAgent(userAgent string) GetOption { func Get(url string, options ...GetOption) (resp *http.Response, err error) { retryTimes := config.Default.RequestRetryTimes haveTried := 0 - retryDelay := time.Second // 延迟1秒再重试 + retryDelay := time.Second getConfig := GetConfig{} for _, option := range options { option(&getConfig) @@ -45,7 +45,7 @@ func Get(url string, options ...GetOption) (resp *http.Response, err error) { time.Sleep(retryDelay) continue } else { - // 如果文件大小大于设定,直接返回错误 + if get != nil && get.ContentLength > config.Default.RequestMaxFileSize { return nil, errors.New("文件过大") } diff --git a/common/proxy.go b/common/proxy.go index 26dfba2..b8ffed2 100644 --- a/common/proxy.go +++ b/common/proxy.go @@ -11,7 +11,7 @@ import ( ) func GetContryName(countryKey string) string { - // 创建一个切片包含所有的国家映射 + countryMaps := []map[string]string{ model.CountryFlag, model.CountryChineseName, @@ -19,11 +19,9 @@ func GetContryName(countryKey string) string { model.CountryEnglishName, } - // 对每一个映射进行检查 for i, countryMap := range countryMaps { if i == 2 { - // 对ISO匹配做特殊处理 - // 根据常用分割字符分割字符串 + splitChars := []string{"-", "_", " "} key := make([]string, 0) for _, splitChar := range splitChars { @@ -34,9 +32,9 @@ func GetContryName(countryKey string) string { } } } - // 对每一个分割后的字符串进行检查 + for _, v := range key { - // 如果匹配到了国家 + if country, ok := countryMap[strings.ToUpper(v)]; ok { return country } @@ -56,7 +54,7 @@ func AddProxy( lazy bool, clashType model.ClashType, proxies ...model.Proxy, ) { proxyTypes := model.GetSupportProxyTypes(clashType) - // 添加节点 + for _, proxy := range proxies { if !proxyTypes[proxy.Type] { continue @@ -106,7 +104,7 @@ func ParseProxy(proxies ...string) []model.Proxy { if proxy != "" { var proxyItem model.Proxy var err error - // 解析节点 + if strings.HasPrefix(proxy, constant.ShadowsocksPrefix) { proxyItem, err = parser.ParseShadowsocks(proxy) } diff --git a/common/random_string.go b/common/random_string.go index e6e2e8b..8c7036f 100644 --- a/common/random_string.go +++ b/common/random_string.go @@ -3,7 +3,7 @@ package common import "math/rand" func RandomString(length int) string { - // 生成随机字符串 + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" var result []byte for i := 0; i < length; i++ { diff --git a/common/sub.go b/common/sub.go index 551dd37..d22bd38 100644 --- a/common/sub.go +++ b/common/sub.go @@ -29,7 +29,7 @@ func LoadSubscription(url string, refresh bool, userAgent string) ([]byte, error } return FetchSubscriptionFromAPI(url, userAgent) } - lastGetTime := stat.ModTime().Unix() // 单位是秒 + lastGetTime := stat.ModTime().Unix() if lastGetTime+config.Default.CacheExpire > time.Now().Unix() { file, err := os.Open(fileName) if err != nil { diff --git a/common/template.go b/common/template.go index 489c0eb..733290e 100644 --- a/common/template.go +++ b/common/template.go @@ -7,8 +7,6 @@ import ( "path/filepath" ) -// LoadTemplate 加载模板 -// templates 模板文件名 func LoadTemplate(template string) ([]byte, error) { tPath := filepath.Join("templates", template) if _, err := os.Stat(tPath); err == nil { diff --git a/main.go b/main.go index e9a009f..33dd1ab 100644 --- a/main.go +++ b/main.go @@ -22,24 +22,24 @@ var templateClash string func init() { var err error - // 创建文件夹 + err = common.MkEssentialDir() if err != nil { logger.Logger.Panic("create essential dir failed", zap.Error(err)) } - // 加载配置 + err = config.LoadConfig() - // 初始化日志 + logger.InitLogger(config.Default.LogLevel) if err != nil { logger.Logger.Panic("load config failed", zap.Error(err)) } - // 写入默认模板 + err = common.WriteDefalutTemplate(templateMeta, templateClash) if err != nil { logger.Logger.Panic("write default template failed", zap.Error(err)) } - // 连接数据库 + err = database.ConnectDB() if err != nil { logger.Logger.Panic("database connect failed", zap.Error(err)) @@ -48,13 +48,13 @@ func init() { } func main() { - // 设置运行模式 + gin.SetMode(gin.ReleaseMode) - // 关闭 Gin 的日志输出 + gin.DefaultWriter = io.Discard - // 创建路由 + r := gin.Default() - // 设置路由 + api.SetRoute(r) logger.Logger.Info("server is running at http://localhost:" + strconv.Itoa(config.Default.Port)) err := r.Run(":" + strconv.Itoa(config.Default.Port)) diff --git a/model/country_code_map.go b/model/country_code_map.go index 76673fe..efad25f 100644 --- a/model/country_code_map.go +++ b/model/country_code_map.go @@ -1,8 +1,5 @@ package model -// https://zh.wikipedia.org/wiki/%E5%8C%BA%E5%9F%9F%E6%8C%87%E7%A4%BA%E7%AC%A6 -// https://zh.wikipedia.org/zh-sg/ISO_3166-1%E4%BA%8C%E4%BD%8D%E5%AD%97%E6%AF%8D%E4%BB%A3%E7%A0%81 - var CountryEnglishName = map[string]string{ "Andorra": "安道尔(AD)", "United Arab Emirates": "阿联酋(AE)", diff --git a/model/proxy_group.go b/model/proxy_group.go index 9a85c80..70be38f 100644 --- a/model/proxy_group.go +++ b/model/proxy_group.go @@ -9,12 +9,12 @@ type ProxyGroup struct { Type string `yaml:"type,omitempty"` Name string `yaml:"name,omitempty"` Proxies []string `yaml:"proxies,omitempty"` - IsCountryGrop bool `yaml:"-"` // 是否是国家分组 + IsCountryGrop bool `yaml:"-"` Url string `yaml:"url,omitempty"` Interval int `yaml:"interval,omitempty"` Tolerance int `yaml:"tolerance,omitempty"` Lazy bool `yaml:"lazy"` - Size int `yaml:"-"` // 代理数量 + Size int `yaml:"-"` DisableUDP bool `yaml:"disable-udp,omitempty"` Strategy string `yaml:"strategy,omitempty"` Icon string `yaml:"icon,omitempty"` @@ -42,16 +42,15 @@ func (p ProxyGroupsSortBySize) Len() int { } func (p ProxyGroupsSortByName) Less(i, j int) bool { - // 定义一组备选语言:首选英语,其次中文 + tags := []language.Tag{ language.English, language.Chinese, } matcher := language.NewMatcher(tags) - // 假设我们的请求语言是 "zh"(中文),则使用匹配器找到最佳匹配的语言 bestMatch, _, _ := matcher.Match(language.Make("zh")) - // 使用最佳匹配的语言进行排序 + c := collate.New(bestMatch) return c.CompareString(p[i].Name, p[j].Name) < 0 } diff --git a/model/proxy_hysteria.go b/model/proxy_hysteria.go index 06c4a75..d87a3a2 100644 --- a/model/proxy_hysteria.go +++ b/model/proxy_hysteria.go @@ -7,11 +7,11 @@ type Hysteria struct { Port int `yaml:"port,omitempty"` Ports string `yaml:"ports,omitempty"` Protocol string `yaml:"protocol,omitempty"` - ObfsProtocol string `yaml:"obfs-protocol,omitempty"` // compatible with Stash + ObfsProtocol string `yaml:"obfs-protocol,omitempty"` Up string `yaml:"up"` - UpSpeed int `yaml:"up-speed,omitempty"` // compatible with Stash + UpSpeed int `yaml:"up-speed,omitempty"` Down string `yaml:"down"` - DownSpeed int `yaml:"down-speed,omitempty"` // compatible with Stash + DownSpeed int `yaml:"down-speed,omitempty"` Auth string `yaml:"auth,omitempty"` AuthStringOLD string `yaml:"auth_str,omitempty"` AuthString string `yaml:"auth-str,omitempty"` diff --git a/model/sub.go b/model/sub.go index 7cbab37..50adb5d 100644 --- a/model/sub.go +++ b/model/sub.go @@ -103,7 +103,6 @@ type IPTables struct { DnsRedirect bool `yaml:"dns-redirect,omitempty" json:"dns-redirect"` } -// EBpf config type EBpf struct { RedirectToTun []string `yaml:"redirect-to-tun,omitempty" json:"redirect-to-tun"` AutoRedir []string `yaml:"auto-redir,omitempty" json:"auto-redir"` diff --git a/parser/base64.go b/parser/base64.go index 1a775fa..e3677bb 100644 --- a/parser/base64.go +++ b/parser/base64.go @@ -7,7 +7,7 @@ import ( func DecodeBase64(s string) (string, error) { s = strings.TrimSpace(s) - // url safe + if strings.Contains(s, "-") || strings.Contains(s, "_") { s = strings.Replace(s, "-", "+", -1) s = strings.Replace(s, "_", "/", -1) diff --git a/parser/hysteria.go b/parser/hysteria.go index 0a19fb7..f0c6523 100644 --- a/parser/hysteria.go +++ b/parser/hysteria.go @@ -1,6 +1,7 @@ package parser import ( + "fmt" "net/url" "strconv" "strings" @@ -13,25 +14,31 @@ func ParseHysteria(proxy string) (model.Proxy, error) { return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy} } - proxy = strings.TrimPrefix(proxy, constant.HysteriaPrefix) - urlParts := strings.SplitN(proxy, "?", 2) - if len(urlParts) != 2 { + link, err := url.Parse(proxy) + if err != nil { return model.Proxy{}, &ParseError{ Type: ErrInvalidStruct, - Message: "missing character '?' in url", + Message: "url parse error", + Raw: proxy, + } + } + server := link.Hostname() + if server == "" { + return model.Proxy{}, &ParseError{ + Type: ErrInvalidStruct, + Message: "missing server host", Raw: proxy, } } - serverInfo := strings.SplitN(urlParts[0], ":", 2) - if len(serverInfo) != 2 { + portStr := link.Port() + if portStr == "" { return model.Proxy{}, &ParseError{ Type: ErrInvalidStruct, - Message: "missing server host or port", + Message: "missing server port", Raw: proxy, } } - server, portStr := serverInfo[0], serverInfo[1] port, err := ParsePort(portStr) if err != nil { @@ -42,16 +49,9 @@ func ParseHysteria(proxy string) (model.Proxy, error) { } } - params, err := url.ParseQuery(urlParts[1]) - if err != nil { - return model.Proxy{}, &ParseError{ - Type: ErrCannotParseParams, - Raw: proxy, - Message: err.Error(), - } - } + query := link.Query() - protocol, auth, insecure, upmbps, downmbps, obfs, alpnStr := params.Get("protocol"), params.Get("auth"), params.Get("insecure"), params.Get("upmbps"), params.Get("downmbps"), params.Get("obfs"), params.Get("alpn") + protocol, auth, insecure, upmbps, downmbps, obfs, alpnStr := query.Get("protocol"), query.Get("auth"), query.Get("insecure"), query.Get("upmbps"), query.Get("downmbps"), query.Get("obfs"), query.Get("alpn") insecureBool, err := strconv.ParseBool(insecure) if err != nil { insecureBool = false @@ -63,10 +63,11 @@ func ParseHysteria(proxy string) (model.Proxy, error) { alpn = strings.Split(alpnStr, ",") } - remarks := server + ":" + portStr - if params.Get("remarks") != "" { - remarks = params.Get("remarks") + remarks := link.Fragment + if remarks == "" { + remarks = fmt.Sprintf("%s:%s", server, portStr) } + remarks = strings.TrimSpace(remarks) result := model.Proxy{ Type: "hysteria", @@ -77,7 +78,7 @@ func ParseHysteria(proxy string) (model.Proxy, error) { Down: downmbps, Auth: auth, Obfs: obfs, - SkipCertVerify: insecure == "1", + SkipCertVerify: insecureBool, Alpn: alpn, Protocol: protocol, AllowInsecure: insecureBool, diff --git a/parser/hysteria2.go b/parser/hysteria2.go index 33a7d9e..62d0d2c 100644 --- a/parser/hysteria2.go +++ b/parser/hysteria2.go @@ -1,6 +1,7 @@ package parser import ( + "fmt" "net/url" "strings" "sub2clash/constant" @@ -13,72 +14,53 @@ func ParseHysteria2(proxy string) (model.Proxy, error) { return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy} } - proxy = strings.TrimPrefix(proxy, constant.Hysteria2Prefix1) - proxy = strings.TrimPrefix(proxy, constant.Hysteria2Prefix2) - urlParts := strings.SplitN(proxy, "@", 2) - if len(urlParts) != 2 { + link, err := url.Parse(proxy) + if err != nil { return model.Proxy{}, &ParseError{ Type: ErrInvalidStruct, - Message: "missing character '@' in url", - Raw: proxy, - } - } - password := urlParts[0] - - serverInfo := strings.SplitN(urlParts[1], "/?", 2) - if len(serverInfo) != 2 { - return model.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing params in url", - Raw: proxy, - } - } - paramStr := serverInfo[1] - - serverAndPort := strings.SplitN(serverInfo[0], ":", 2) - var server string - var portStr string - if len(serverAndPort) == 1 { - portStr = "443" - } else if len(serverAndPort) == 2 { - server, portStr = serverAndPort[0], serverAndPort[1] - } else { - return model.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing server host or port", + Message: "url parse error", Raw: proxy, } } + username := link.User.Username() + password, exist := link.User.Password() + if !exist { + password = username + } + + query := link.Query() + server := link.Hostname() + if server == "" { + return model.Proxy{}, &ParseError{ + Type: ErrInvalidStruct, + Message: "missing server host", + Raw: proxy, + } + } + portStr := link.Port() + if portStr == "" { + return model.Proxy{}, &ParseError{ + Type: ErrInvalidStruct, + Message: "missing server port", + Raw: proxy, + } + } port, err := ParsePort(portStr) if err != nil { return model.Proxy{}, &ParseError{ - Type: ErrInvalidPort, - Message: err.Error(), - Raw: proxy, + Type: ErrInvalidPort, + Raw: portStr, } } - - params, err := url.ParseQuery(paramStr) - if err != nil { - return model.Proxy{}, &ParseError{ - Type: ErrCannotParseParams, - Raw: proxy, - Message: err.Error(), - } - } - - remarks, network, obfs, obfsPassword, pinSHA256, insecure, sni := params.Get("name"), params.Get("network"), params.Get("obfs"), params.Get("obfs-password"), params.Get("pinSHA256"), params.Get("insecure"), params.Get("sni") - enableTLS := pinSHA256 != "" + network, obfs, obfsPassword, pinSHA256, insecure, sni := query.Get("network"), query.Get("obfs"), query.Get("obfs-password"), query.Get("pinSHA256"), query.Get("insecure"), query.Get("sni") + enableTLS := pinSHA256 != "" || sni != "" insecureBool := insecure == "1" - + remarks := link.Fragment if remarks == "" { - remarksIndex := strings.LastIndex(proxy, "#") - if remarksIndex != -1 { - remarks = proxy[remarksIndex:] - remarks, _ = url.QueryUnescape(remarks) - } + remarks = fmt.Sprintf("%s:%s", server, portStr) } + remarks = strings.TrimSpace(remarks) result := model.Proxy{ Type: "hysteria2", diff --git a/parser/shadowsocks.go b/parser/shadowsocks.go index 6143f30..aca013d 100644 --- a/parser/shadowsocks.go +++ b/parser/shadowsocks.go @@ -1,6 +1,7 @@ package parser import ( + "fmt" "net/url" "strings" "sub2clash/constant" @@ -12,71 +13,72 @@ func ParseShadowsocks(proxy string) (model.Proxy, error) { return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy} } - proxy = strings.TrimPrefix(proxy, constant.ShadowsocksPrefix) - urlParts := strings.SplitN(proxy, "@", 2) - if len(urlParts) != 2 { + link, err := url.Parse(proxy) + if err != nil { return model.Proxy{}, &ParseError{ Type: ErrInvalidStruct, - Message: "missing character '@' in url", + Message: "url parse error", Raw: proxy, } } - var serverAndPort []string - if !strings.Contains(urlParts[0], ":") { - decoded, err := DecodeBase64(urlParts[0]) - if err != nil { - return model.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "invalid base64 encoded", - Raw: proxy, - } - } - urlParts[0] = decoded - } - credentials := strings.SplitN(urlParts[0], ":", 2) - if len(credentials) != 2 { + server := link.Hostname() + if server == "" { return model.Proxy{}, &ParseError{ Type: ErrInvalidStruct, - Message: "missing server host or port", + Message: "missing server host", Raw: proxy, } } - method, password := credentials[0], credentials[1] - serverInfo := strings.SplitN(urlParts[1], "#", 2) - serverAndPort = strings.SplitN(serverInfo[0], ":", 2) - server, portStr := serverAndPort[0], serverAndPort[1] - if len(serverInfo) != 2 { + portStr := link.Port() + if portStr == "" { return model.Proxy{}, &ParseError{ Type: ErrInvalidStruct, - Message: "missing server host or port", + Message: "missing server port", Raw: proxy, } } port, err := ParsePort(portStr) if err != nil { return model.Proxy{}, &ParseError{ - Type: ErrInvalidPort, - Message: err.Error(), + Type: ErrInvalidStruct, + Raw: proxy, + } + } + + user, err := DecodeBase64(link.User.Username()) + if err != nil { + return model.Proxy{}, &ParseError{ + Type: ErrInvalidStruct, + Message: "missing method and password", Raw: proxy, } } - var remarks string - if len(serverInfo) == 2 { - unescape, err := url.QueryUnescape(serverInfo[1]) - if err != nil { - return model.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "cannot unescape remarks", - Raw: proxy, - } + if user == "" { + return model.Proxy{}, &ParseError{ + Type: ErrInvalidStruct, + Message: "missing method and password", + Raw: proxy, } - remarks = strings.TrimSpace(unescape) - } else { - remarks = strings.TrimSpace(server + ":" + portStr) } + methodAndPass := strings.SplitN(user, ":", 2) + if len(methodAndPass) != 2 { + return model.Proxy{}, &ParseError{ + Type: ErrInvalidStruct, + Message: "missing method and password", + Raw: proxy, + } + } + method := methodAndPass[0] + password := methodAndPass[1] + + remarks := link.Fragment + if remarks == "" { + remarks = fmt.Sprintf("%s:%s", server, portStr) + } + remarks = strings.TrimSpace(remarks) result := model.Proxy{ Type: "ss", diff --git a/parser/trojan.go b/parser/trojan.go index ee43bc4..09aac17 100644 --- a/parser/trojan.go +++ b/parser/trojan.go @@ -1,55 +1,42 @@ package parser import ( + "fmt" "net/url" "strings" "sub2clash/constant" "sub2clash/model" ) -// ParseTrojan 解析给定的Trojan代理URL并返回Proxy结构。 func ParseTrojan(proxy string) (model.Proxy, error) { if !strings.HasPrefix(proxy, constant.TrojanPrefix) { return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy} } - proxy = strings.TrimPrefix(proxy, constant.TrojanPrefix) - urlParts := strings.SplitN(proxy, "@", 2) - if len(urlParts) != 2 { - return model.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing character '@' in url", - Raw: proxy, - } - } - password := strings.TrimSpace(urlParts[0]) - - serverInfo := strings.SplitN(urlParts[1], "#", 2) - serverAndPortAndParams := strings.SplitN(serverInfo[0], "?", 2) - if len(serverAndPortAndParams) != 2 { - return model.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing character '?' in url", - Raw: proxy, - } - } - - serverAndPort := strings.SplitN(serverAndPortAndParams[0], ":", 2) - if len(serverAndPort) != 2 { - return model.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing server host or port", - Raw: proxy, - } - } - server, portStr := serverAndPort[0], serverAndPort[1] - - params, err := url.ParseQuery(serverAndPortAndParams[1]) + link, err := url.Parse(proxy) if err != nil { return model.Proxy{}, &ParseError{ - Type: ErrCannotParseParams, + Type: ErrInvalidStruct, + Message: "url parse error", + Raw: proxy, + } + } + + password := link.User.Username() + server := link.Hostname() + if server == "" { + return model.Proxy{}, &ParseError{ + Type: ErrInvalidStruct, + Message: "missing server host", + Raw: proxy, + } + } + portStr := link.Port() + if portStr == "" { + return model.Proxy{}, &ParseError{ + Type: ErrInvalidStruct, + Message: "missing server port", Raw: proxy, - Message: err.Error(), } } @@ -62,14 +49,14 @@ func ParseTrojan(proxy string) (model.Proxy, error) { } } - remarks := "" - if len(serverInfo) == 2 { - remarks, _ = url.QueryUnescape(strings.TrimSpace(serverInfo[1])) - } else { - remarks = serverAndPort[0] + remarks := link.Fragment + if remarks == "" { + remarks = fmt.Sprintf("%s:%s", server, portStr) } + remarks = strings.TrimSpace(remarks) - network, security, alpnStr, sni, pbk, sid, fp, path, host, serviceName := params.Get("type"), params.Get("security"), params.Get("alpn"), params.Get("sni"), params.Get("pbk"), params.Get("sid"), params.Get("fp"), params.Get("path"), params.Get("host"), params.Get("serviceName") + query := link.Query() + network, security, alpnStr, sni, pbk, sid, fp, path, host, serviceName := query.Get("type"), query.Get("security"), query.Get("alpn"), query.Get("sni"), query.Get("pbk"), query.Get("sid"), query.Get("fp"), query.Get("path"), query.Get("host"), query.Get("serviceName") var alpn []string if strings.Contains(alpnStr, ",") { @@ -78,9 +65,6 @@ func ParseTrojan(proxy string) (model.Proxy, error) { alpn = nil } - // enableUTLS := fp != "" - - // 构建Proxy结构体 result := model.Proxy{ Type: "trojan", Server: server, @@ -116,14 +100,6 @@ func ParseTrojan(proxy string) (model.Proxy, error) { } } - // if network == "http" { - // // 未查到相关支持文档 - // } - - // if network == "quic" { - // // 未查到相关支持文档 - // } - if network == "grpc" { result.GrpcOpts = model.GrpcOptions{ GrpcServiceName: serviceName, diff --git a/parser/vless.go b/parser/vless.go index 0bb07fc..bb418cf 100644 --- a/parser/vless.go +++ b/parser/vless.go @@ -1,6 +1,7 @@ package parser import ( + "fmt" "net/url" "strings" "sub2clash/constant" @@ -12,34 +13,24 @@ func ParseVless(proxy string) (model.Proxy, error) { return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy} } - urlParts := strings.SplitN(strings.TrimPrefix(proxy, constant.VLESSPrefix), "@", 2) - if len(urlParts) != 2 { + link, err := url.Parse(proxy) + if err != nil { return model.Proxy{}, &ParseError{ Type: ErrInvalidStruct, - Message: "missing character '@' in url", + Message: "url parse error", Raw: proxy, } } - serverInfo := strings.SplitN(urlParts[1], "#", 2) - serverAndPortAndParams := strings.SplitN(serverInfo[0], "?", 2) - if len(serverAndPortAndParams) != 2 { + server := link.Hostname() + if server == "" { return model.Proxy{}, &ParseError{ Type: ErrInvalidStruct, - Message: "missing character '?' in url", + Message: "missing server host", Raw: proxy, } } - - serverAndPort := strings.SplitN(serverAndPortAndParams[0], ":", 2) - if len(serverAndPort) != 2 { - return model.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing server host or port", - Raw: proxy, - } - } - server, portStr := serverAndPort[0], serverAndPort[1] + portStr := link.Port() port, err := ParsePort(portStr) if err != nil { return model.Proxy{}, &ParseError{ @@ -49,40 +40,10 @@ func ParseVless(proxy string) (model.Proxy, error) { } } - params, err := url.ParseQuery(serverAndPortAndParams[1]) - if err != nil { - return model.Proxy{}, &ParseError{ - Type: ErrCannotParseParams, - Raw: proxy, - Message: err.Error(), - } - } + query := link.Query() + uuid := link.User.Username() + flow, security, alpnStr, sni, insecure, fp, pbk, sid, path, host, serviceName, _type := query.Get("flow"), query.Get("security"), query.Get("alpn"), query.Get("sni"), query.Get("allowInsecure"), query.Get("fp"), query.Get("pbk"), query.Get("sid"), query.Get("path"), query.Get("host"), query.Get("serviceName"), query.Get("type") - remarks := "" - if len(serverInfo) == 2 { - if strings.Contains(serverInfo[1], "|") { - remarks = strings.SplitN(serverInfo[1], "|", 2)[1] - } else { - remarks, err = url.QueryUnescape(serverInfo[1]) - if err != nil { - return model.Proxy{}, &ParseError{ - Type: ErrCannotParseParams, - Raw: proxy, - Message: err.Error(), - } - } - } - } else { - remarks, err = url.QueryUnescape(server) - if err != nil { - return model.Proxy{}, err - } - } - - uuid := strings.TrimSpace(urlParts[0]) - flow, security, alpnStr, sni, insecure, fp, pbk, sid, path, host, serviceName, _type := params.Get("flow"), params.Get("security"), params.Get("alpn"), params.Get("sni"), params.Get("allowInsecure"), params.Get("fp"), params.Get("pbk"), params.Get("sid"), params.Get("path"), params.Get("host"), params.Get("serviceName"), params.Get("type") - - // enableUTLS := fp != "" insecureBool := insecure == "1" var alpn []string if strings.Contains(alpnStr, ",") { @@ -90,6 +51,11 @@ func ParseVless(proxy string) (model.Proxy, error) { } else { alpn = nil } + remarks := link.Fragment + if remarks == "" { + remarks = fmt.Sprintf("%s:%s", server, portStr) + } + remarks = strings.TrimSpace(remarks) result := model.Proxy{ Type: "vless", @@ -129,10 +95,6 @@ func ParseVless(proxy string) (model.Proxy, error) { } } - // if _type == "quic" { - // // 未查到相关支持文档 - // } - if _type == "grpc" { result.Network = "grpc" result.GrpcOpts = model.GrpcOptions{ diff --git a/parser/vmess.go b/parser/vmess.go index 5ab3186..287e779 100644 --- a/parser/vmess.go +++ b/parser/vmess.go @@ -100,10 +100,6 @@ func ParseVmess(proxy string) (model.Proxy, error) { } } - // if vmess.Net == "quic" { - // // 未查到相关支持文档 - // } - if vmess.Net == "grpc" { result.GrpcOpts = model.GrpcOptions{ GrpcServiceName: vmess.Path, diff --git a/validator/short_link.go b/validator/short_link.go index 88d3969..4d7f436 100644 --- a/validator/short_link.go +++ b/validator/short_link.go @@ -6,7 +6,7 @@ type ShortLinkGenValidator struct { } type GetUrlValidator struct { - Hash string `form:"hash" binding:"required"` // Hash: 短链接 + Hash string `form:"hash" binding:"required"` Password string `form:"password"` } diff --git a/validator/sub.go b/validator/sub.go index 5553337..ca0c95f 100644 --- a/validator/sub.go +++ b/validator/sub.go @@ -111,7 +111,7 @@ func ParseQuery(c *gin.Context) (SubValidator, error) { }, ) } - // 校验 Rule-Provider 是否有重名 + names := make(map[string]bool) for _, ruleProvider := range query.RuleProviders { if _, ok := names[ruleProvider.Name]; ok {