diff --git a/common/sub.go b/common/sub.go index b8e7596..80b1113 100644 --- a/common/sub.go +++ b/common/sub.go @@ -154,7 +154,10 @@ func BuildSub(clashType model.ClashType, query model.SubConfig, template string, return nil, NewRegexInvalidError("prefix", err) } if reg.Match(data) { - p := parser.ParseProxies(strings.Split(string(data), "\n")...) + p, err := parser.ParseProxies(strings.Split(string(data), "\n")...) + if err != nil { + return nil, err + } newProxies = p } else { base64, err := parser.DecodeBase64(string(data)) @@ -166,7 +169,10 @@ func BuildSub(clashType model.ClashType, query model.SubConfig, template string, ) return nil, NewSubscriptionParseError(err) } - p := parser.ParseProxies(strings.Split(base64, "\n")...) + p, err := parser.ParseProxies(strings.Split(base64, "\n")...) + if err != nil { + return nil, err + } newProxies = p } } else { @@ -181,7 +187,11 @@ func BuildSub(clashType model.ClashType, query model.SubConfig, template string, } if len(query.Proxy) != 0 { - proxyList = append(proxyList, parser.ParseProxies(query.Proxies...)...) + p, err := parser.ParseProxies(query.Proxies...) + if err != nil { + return nil, err + } + proxyList = append(proxyList, p...) } for i := range proxyList { diff --git a/parser/anytls.go b/parser/anytls.go index a371246..c6e909d 100644 --- a/parser/anytls.go +++ b/parser/anytls.go @@ -28,16 +28,12 @@ func (p *AnytlsParser) GetType() string { func (p *AnytlsParser) Parse(proxy string) (P.Proxy, error) { if !hasPrefix(proxy, p.GetPrefixes()) { - return P.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy} + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidPrefix, proxy) } link, err := url.Parse(proxy) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "url parse error", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, err.Error()) } username := link.User.Username() @@ -49,26 +45,15 @@ func (p *AnytlsParser) Parse(proxy string) (P.Proxy, error) { query := link.Query() server := link.Hostname() if server == "" { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing server host", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, "missing server host") } portStr := link.Port() if portStr == "" { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing server port", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, "missing server port") } port, err := ParsePort(portStr) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidPort, - Raw: portStr, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidPort, err.Error()) } insecure, sni := query.Get("insecure"), query.Get("sni") insecureBool := insecure == "1" diff --git a/parser/common.go b/parser/common.go index 96b46d5..c5ce72b 100644 --- a/parser/common.go +++ b/parser/common.go @@ -6,9 +6,7 @@ import ( "strconv" "strings" - "github.com/bestnite/sub2clash/logger" P "github.com/bestnite/sub2clash/model/proxy" - "go.uber.org/zap" ) func hasPrefix(proxy string, prefixes []string) bool { @@ -65,7 +63,7 @@ func DecodeBase64(s string) (string, error) { return string(decodeStr), nil } -func ParseProxies(proxies ...string) []P.Proxy { +func ParseProxies(proxies ...string) ([]P.Proxy, error) { var result []P.Proxy for _, proxy := range proxies { if proxy != "" { @@ -73,15 +71,11 @@ func ParseProxies(proxies ...string) []P.Proxy { var err error proxyItem, err = ParseProxyWithRegistry(proxy) - - if err == nil { - result = append(result, proxyItem) - } else { - logger.Logger.Debug( - "parse proxy failed", zap.String("proxy", proxy), zap.Error(err), - ) + if err != nil { + return nil, err } + result = append(result, proxyItem) } } - return result + return result, nil } diff --git a/parser/errors.go b/parser/errors.go index af95f0b..bbc83ea 100644 --- a/parser/errors.go +++ b/parser/errors.go @@ -1,11 +1,5 @@ package parser -type ParseError struct { - Type ParseErrorType - Message string - Raw string -} - type ParseErrorType string const ( @@ -16,9 +10,6 @@ const ( ErrInvalidBase64 ParseErrorType = "invalid base64" ) -func (e *ParseError) Error() string { - if e.Message != "" { - return string(e.Type) + ": " + e.Message + " \"" + e.Raw + "\"" - } - return string(e.Type) +func (e ParseErrorType) Error() string { + return string(e) } diff --git a/parser/hysteria.go b/parser/hysteria.go index 0831499..40a8632 100644 --- a/parser/hysteria.go +++ b/parser/hysteria.go @@ -29,42 +29,26 @@ func (p *HysteriaParser) GetType() string { func (p *HysteriaParser) Parse(proxy string) (P.Proxy, error) { if !hasPrefix(proxy, p.GetPrefixes()) { - return P.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy} + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidPrefix, proxy) } link, err := url.Parse(proxy) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "url parse error", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, err.Error()) } server := link.Hostname() if server == "" { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing server host", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, "missing server host") } portStr := link.Port() if portStr == "" { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing server port", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, "missing server port") } port, err := ParsePort(portStr) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidPort, - Message: err.Error(), - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidPort, err.Error()) } query := link.Query() diff --git a/parser/hysteria2.go b/parser/hysteria2.go index d56a72b..10df50f 100644 --- a/parser/hysteria2.go +++ b/parser/hysteria2.go @@ -28,16 +28,12 @@ func (p *Hysteria2Parser) GetType() string { func (p *Hysteria2Parser) Parse(proxy string) (P.Proxy, error) { if !hasPrefix(proxy, p.GetPrefixes()) { - return P.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy} + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidPrefix, proxy) } link, err := url.Parse(proxy) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "url parse error", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, err.Error()) } username := link.User.Username() @@ -49,26 +45,15 @@ func (p *Hysteria2Parser) Parse(proxy string) (P.Proxy, error) { query := link.Query() server := link.Hostname() if server == "" { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing server host", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, "missing server host") } portStr := link.Port() if portStr == "" { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing server port", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, "missing server port") } port, err := ParsePort(portStr) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidPort, - Raw: portStr, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidPort, err.Error()) } obfs, obfsPassword, insecure, sni := query.Get("obfs"), query.Get("obfs-password"), query.Get("insecure"), query.Get("sni") insecureBool := insecure == "1" diff --git a/parser/registry.go b/parser/registry.go index 49928eb..fb7e09e 100644 --- a/parser/registry.go +++ b/parser/registry.go @@ -1,6 +1,7 @@ package parser import ( + "fmt" "strings" "sync" @@ -66,7 +67,7 @@ func GetAllPrefixes() []string { func ParseProxyWithRegistry(proxy string) (P.Proxy, error) { proxy = strings.TrimSpace(proxy) if proxy == "" { - return P.Proxy{}, &ParseError{Type: ErrInvalidStruct, Raw: proxy, Message: "empty proxy string"} + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, "empty proxy string") } for prefix, parser := range registry.parsers { @@ -75,5 +76,5 @@ func ParseProxyWithRegistry(proxy string) (P.Proxy, error) { } } - return P.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy, Message: "unsupported protocol"} + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidPrefix, "unsupported protocol") } diff --git a/parser/shadowsocks.go b/parser/shadowsocks.go index ba0adae..e32823c 100644 --- a/parser/shadowsocks.go +++ b/parser/shadowsocks.go @@ -32,7 +32,7 @@ func (p *ShadowsocksParser) GetType() string { // Parse 解析Shadowsocks代理 func (p *ShadowsocksParser) Parse(proxy string) (P.Proxy, error) { if !hasPrefix(proxy, p.GetPrefixes()) { - return P.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy} + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidPrefix, proxy) } if !strings.Contains(proxy, "@") { @@ -45,11 +45,7 @@ func (p *ShadowsocksParser) Parse(proxy string) (P.Proxy, error) { } d, err := DecodeBase64(s[0]) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "url parse error", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, err.Error()) } if len(s) == 2 { proxy = "ss://" + d + "#" + s[1] @@ -59,36 +55,21 @@ func (p *ShadowsocksParser) Parse(proxy string) (P.Proxy, error) { } link, err := url.Parse(proxy) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "url parse error", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, err.Error()) } server := link.Hostname() if server == "" { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing server host", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, "missing server host") } portStr := link.Port() if portStr == "" { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing server port", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, "missing server port") } port, err := ParsePort(portStr) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, err.Error()) } method := link.User.Username() @@ -109,11 +90,7 @@ func (p *ShadowsocksParser) Parse(proxy string) (P.Proxy, error) { if password != "" && isLikelyBase64(password) { password, err = DecodeBase64(password) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "password decode error", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, err.Error()) } } diff --git a/parser/shadowsocksr.go b/parser/shadowsocksr.go index d6848f6..3bf52bc 100644 --- a/parser/shadowsocksr.go +++ b/parser/shadowsocksr.go @@ -1,6 +1,7 @@ package parser import ( + "fmt" "net/url" "strconv" "strings" @@ -28,7 +29,7 @@ func (p *ShadowsocksRParser) GetType() string { func (p *ShadowsocksRParser) Parse(proxy string) (P.Proxy, error) { if !hasPrefix(proxy, p.GetPrefixes()) { - return P.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy} + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidPrefix, proxy) } for _, prefix := range p.GetPrefixes() { @@ -40,32 +41,27 @@ func (p *ShadowsocksRParser) Parse(proxy string) (P.Proxy, error) { proxy, err := DecodeBase64(proxy) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidBase64, - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidBase64, err.Error()) } serverInfoAndParams := strings.SplitN(proxy, "/?", 2) - parts := strings.Split(serverInfoAndParams[0], ":") + if len(serverInfoAndParams) != 2 { + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, proxy) + } + parts := SplitNRight(serverInfoAndParams[0], ":", 6) + if len(parts) < 6 { + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, proxy) + } server := parts[0] protocol := parts[2] method := parts[3] obfs := parts[4] password, err := DecodeBase64(parts[5]) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Raw: proxy, - Message: err.Error(), - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, err.Error()) } port, err := ParsePort(parts[1]) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidPort, - Message: err.Error(), - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidPort, err.Error()) } var obfsParam string @@ -74,11 +70,7 @@ func (p *ShadowsocksRParser) Parse(proxy string) (P.Proxy, error) { if len(serverInfoAndParams) == 2 { params, err := url.ParseQuery(serverInfoAndParams[1]) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrCannotParseParams, - Raw: proxy, - Message: err.Error(), - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrCannotParseParams, err.Error()) } if params.Get("obfsparam") != "" { obfsParam, err = DecodeBase64(params.Get("obfsparam")) @@ -92,11 +84,7 @@ func (p *ShadowsocksRParser) Parse(proxy string) (P.Proxy, error) { remarks = server + ":" + strconv.Itoa(port) } if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Raw: proxy, - Message: err.Error(), - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, err.Error()) } } @@ -121,3 +109,26 @@ func (p *ShadowsocksRParser) Parse(proxy string) (P.Proxy, error) { func init() { RegisterParser(&ShadowsocksRParser{}) } + +func SplitNRight(s, sep string, n int) []string { + if n <= 0 { + return nil + } + if n == 1 { + return []string{s} + } + parts := strings.Split(s, sep) + if len(parts) <= n { + return parts + } + result := make([]string, n) + for i, j := len(parts)-1, 0; i >= 0; i, j = i-1, j+1 { + if j < n-1 { + result[n-j-1] = parts[len(parts)-j-1] + } else { + result[0] = strings.Join(parts[:i+1], sep) + break + } + } + return result +} diff --git a/parser/socks.go b/parser/socks.go index 23e5aea..081ef41 100644 --- a/parser/socks.go +++ b/parser/socks.go @@ -27,39 +27,24 @@ func (p *SocksParser) GetType() string { func (p *SocksParser) Parse(proxy string) (P.Proxy, error) { if !hasPrefix(proxy, p.GetPrefixes()) { - return P.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy} + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidPrefix, proxy) } link, err := url.Parse(proxy) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "url parse error", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, err.Error()) } server := link.Hostname() if server == "" { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing server host", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, "missing server host") } portStr := link.Port() if portStr == "" { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing server port", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, "missing server port") } port, err := ParsePort(portStr) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidPort, - Raw: portStr, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidPort, err.Error()) } remarks := link.Fragment @@ -86,6 +71,8 @@ func (p *SocksParser) Parse(proxy string) (P.Proxy, error) { } } + tls, udp := link.Query().Get("tls"), link.Query().Get("udp") + return P.Proxy{ Type: p.GetType(), Name: remarks, @@ -94,6 +81,8 @@ func (p *SocksParser) Parse(proxy string) (P.Proxy, error) { Port: port, UserName: username, Password: password, + TLS: tls == "true", + UDP: udp == "true", }, }, nil } diff --git a/parser/trojan.go b/parser/trojan.go index f267ec6..d280183 100644 --- a/parser/trojan.go +++ b/parser/trojan.go @@ -28,43 +28,27 @@ func (p *TrojanParser) GetType() string { func (p *TrojanParser) Parse(proxy string) (P.Proxy, error) { if !hasPrefix(proxy, p.GetPrefixes()) { - return P.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy} + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidPrefix, proxy) } link, err := url.Parse(proxy) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "url parse error", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, err.Error()) } password := link.User.Username() server := link.Hostname() if server == "" { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing server host", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, "missing server host") } portStr := link.Port() if portStr == "" { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing server port", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, "missing server port") } port, err := ParsePort(portStr) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidPort, - Message: err.Error(), - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidPort, err.Error()) } remarks := link.Fragment @@ -74,7 +58,7 @@ func (p *TrojanParser) Parse(proxy string) (P.Proxy, error) { remarks = strings.TrimSpace(remarks) 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") + network, security, alpnStr, sni, pbk, sid, fp, path, host, serviceName, udp := 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"), query.Get("udp") var alpn []string if strings.Contains(alpnStr, ",") { @@ -88,6 +72,7 @@ func (p *TrojanParser) Parse(proxy string) (P.Proxy, error) { Port: port, Password: password, Network: network, + UDP: udp == "true", } if security == "xtls" || security == "tls" { diff --git a/parser/vless.go b/parser/vless.go index bab9fbc..c8a3fc0 100644 --- a/parser/vless.go +++ b/parser/vless.go @@ -28,39 +28,27 @@ func (p *VlessParser) GetType() string { func (p *VlessParser) Parse(proxy string) (P.Proxy, error) { if !hasPrefix(proxy, p.GetPrefixes()) { - return P.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy} + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidPrefix, proxy) } link, err := url.Parse(proxy) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "url parse error", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, err.Error()) } server := link.Hostname() if server == "" { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing server host", - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, "missing server host") } portStr := link.Port() port, err := ParsePort(portStr) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidPort, - Message: err.Error(), - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidPort, 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") + flow, security, alpnStr, sni, insecure, fp, pbk, sid, path, host, serviceName, _type, udp := 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"), query.Get("udp") insecureBool := insecure == "1" var alpn []string @@ -80,13 +68,15 @@ func (p *VlessParser) Parse(proxy string) (P.Proxy, error) { Port: port, UUID: uuid, Flow: flow, + UDP: udp == "true", } if security == "tls" { result.TLS = true result.ALPN = alpn result.SkipCertVerify = insecureBool - result.ClientFingerprint = fp + result.Fingerprint = fp + result.ServerName = sni } if security == "reality" { @@ -96,7 +86,7 @@ func (p *VlessParser) Parse(proxy string) (P.Proxy, error) { PublicKey: pbk, ShortID: sid, } - result.ClientFingerprint = fp + result.Fingerprint = fp } if _type == "ws" { @@ -125,11 +115,7 @@ func (p *VlessParser) Parse(proxy string) (P.Proxy, error) { hosts, err := url.QueryUnescape(host) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrCannotParseParams, - Raw: proxy, - Message: err.Error(), - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrCannotParseParams, err.Error()) } result.Network = "http" if hosts != "" { diff --git a/parser/vmess.go b/parser/vmess.go index 908b86e..fca5765 100644 --- a/parser/vmess.go +++ b/parser/vmess.go @@ -2,6 +2,7 @@ package parser import ( "encoding/json" + "fmt" "net/url" "strconv" "strings" @@ -47,7 +48,7 @@ func (p *VmessParser) GetType() string { func (p *VmessParser) Parse(proxy string) (P.Proxy, error) { if !hasPrefix(proxy, p.GetPrefixes()) { - return P.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy} + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidPrefix, proxy) } for _, prefix := range p.GetPrefixes() { @@ -58,13 +59,13 @@ func (p *VmessParser) Parse(proxy string) (P.Proxy, error) { } base64, err := DecodeBase64(proxy) if err != nil { - return P.Proxy{}, &ParseError{Type: ErrInvalidBase64, Raw: proxy, Message: err.Error()} + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidBase64, err.Error()) } var vmess VmessJson err = json.Unmarshal([]byte(base64), &vmess) if err != nil { - return P.Proxy{}, &ParseError{Type: ErrInvalidStruct, Raw: proxy, Message: err.Error()} + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, err.Error()) } var port int @@ -72,11 +73,7 @@ func (p *VmessParser) Parse(proxy string) (P.Proxy, error) { case string: port, err = ParsePort(vmess.Port.(string)) if err != nil { - return P.Proxy{}, &ParseError{ - Type: ErrInvalidPort, - Message: err.Error(), - Raw: proxy, - } + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidPort, err.Error()) } case float64: port = int(vmess.Port.(float64)) @@ -87,7 +84,7 @@ func (p *VmessParser) Parse(proxy string) (P.Proxy, error) { case string: aid, err = strconv.Atoi(vmess.Aid.(string)) if err != nil { - return P.Proxy{}, &ParseError{Type: ErrInvalidStruct, Raw: proxy, Message: err.Error()} + return P.Proxy{}, fmt.Errorf("%w: %s", ErrInvalidStruct, err.Error()) } case float64: aid = int(vmess.Aid.(float64)) diff --git a/test/parser/anytls_test.go b/test/parser/anytls_test.go new file mode 100644 index 0000000..96a8d8b --- /dev/null +++ b/test/parser/anytls_test.go @@ -0,0 +1,219 @@ +package test + +import ( + "testing" + + "github.com/bestnite/sub2clash/model/proxy" + "github.com/bestnite/sub2clash/parser" +) + +func TestAnytls_Basic_SimpleLink(t *testing.T) { + p := &parser.AnytlsParser{} + input := "anytls://password123@127.0.0.1:8080#Anytls%20Proxy" + + expected := proxy.Proxy{ + Type: "anytls", + Name: "Anytls Proxy", + Anytls: proxy.Anytls{ + Server: "127.0.0.1", + Port: 8080, + Password: "password123", + SkipCertVerify: false, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestAnytls_Basic_WithSNI(t *testing.T) { + p := &parser.AnytlsParser{} + input := "anytls://password123@proxy.example.com:443?sni=proxy.example.com#Anytls%20SNI" + + expected := proxy.Proxy{ + Type: "anytls", + Name: "Anytls SNI", + Anytls: proxy.Anytls{ + Server: "proxy.example.com", + Port: 443, + Password: "password123", + SNI: "proxy.example.com", + SkipCertVerify: false, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestAnytls_Basic_WithInsecure(t *testing.T) { + p := &parser.AnytlsParser{} + input := "anytls://password123@proxy.example.com:443?insecure=1&sni=proxy.example.com#Anytls%20Insecure" + + expected := proxy.Proxy{ + Type: "anytls", + Name: "Anytls Insecure", + Anytls: proxy.Anytls{ + Server: "proxy.example.com", + Port: 443, + Password: "password123", + SNI: "proxy.example.com", + SkipCertVerify: true, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestAnytls_Basic_IPv6Address(t *testing.T) { + p := &parser.AnytlsParser{} + input := "anytls://password123@[2001:db8::1]:8080#Anytls%20IPv6" + + expected := proxy.Proxy{ + Type: "anytls", + Name: "Anytls IPv6", + Anytls: proxy.Anytls{ + Server: "2001:db8::1", + Port: 8080, + Password: "password123", + SkipCertVerify: false, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestAnytls_Basic_ComplexPassword(t *testing.T) { + p := &parser.AnytlsParser{} + input := "anytls://ComplexPassword!%40%23%24@proxy.example.com:8443?sni=example.com&insecure=1#Anytls%20Full" + + expected := proxy.Proxy{ + Type: "anytls", + Name: "Anytls Full", + Anytls: proxy.Anytls{ + Server: "proxy.example.com", + Port: 8443, + Password: "ComplexPassword!@#$", + SNI: "example.com", + SkipCertVerify: true, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestAnytls_Basic_NoPassword(t *testing.T) { + p := &parser.AnytlsParser{} + input := "anytls://@127.0.0.1:8080#No%20Password" + + expected := proxy.Proxy{ + Type: "anytls", + Name: "No Password", + Anytls: proxy.Anytls{ + Server: "127.0.0.1", + Port: 8080, + Password: "", + SkipCertVerify: false, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestAnytls_Basic_UsernameOnly(t *testing.T) { + p := &parser.AnytlsParser{} + input := "anytls://username@127.0.0.1:8080#Username%20Only" + + expected := proxy.Proxy{ + Type: "anytls", + Name: "Username Only", + Anytls: proxy.Anytls{ + Server: "127.0.0.1", + Port: 8080, + Password: "username", + SkipCertVerify: false, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestAnytls_Error_MissingServer(t *testing.T) { + p := &parser.AnytlsParser{} + input := "anytls://password123@:8080" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestAnytls_Error_MissingPort(t *testing.T) { + p := &parser.AnytlsParser{} + input := "anytls://password123@127.0.0.1" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestAnytls_Error_InvalidPort(t *testing.T) { + p := &parser.AnytlsParser{} + input := "anytls://password123@127.0.0.1:99999" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestAnytls_Error_InvalidProtocol(t *testing.T) { + p := &parser.AnytlsParser{} + input := "anyssl://example.com:8080" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} diff --git a/test/parser/hysteria2_test.go b/test/parser/hysteria2_test.go new file mode 100644 index 0000000..8de4c3e --- /dev/null +++ b/test/parser/hysteria2_test.go @@ -0,0 +1,198 @@ +package test + +import ( + "testing" + + "github.com/bestnite/sub2clash/model/proxy" + "github.com/bestnite/sub2clash/parser" +) + +func TestHysteria2_Basic_SimpleLink(t *testing.T) { + p := &parser.Hysteria2Parser{} + input := "hysteria2://password123@127.0.0.1:8080#Hysteria2%20Proxy" + + expected := proxy.Proxy{ + Type: "hysteria2", + Name: "Hysteria2 Proxy", + Hysteria2: proxy.Hysteria2{ + Server: "127.0.0.1", + Port: 8080, + Password: "password123", + SkipCertVerify: false, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestHysteria2_Basic_AltPrefix(t *testing.T) { + p := &parser.Hysteria2Parser{} + input := "hy2://password123@proxy.example.com:443?insecure=1&sni=proxy.example.com#Hysteria2%20Alt" + + expected := proxy.Proxy{ + Type: "hysteria2", + Name: "Hysteria2 Alt", + Hysteria2: proxy.Hysteria2{ + Server: "proxy.example.com", + Port: 443, + Password: "password123", + SNI: "proxy.example.com", + SkipCertVerify: true, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestHysteria2_Basic_WithObfs(t *testing.T) { + p := &parser.Hysteria2Parser{} + input := "hysteria2://password123@127.0.0.1:8080?obfs=salamander&obfs-password=obfs123#Hysteria2%20Obfs" + + expected := proxy.Proxy{ + Type: "hysteria2", + Name: "Hysteria2 Obfs", + Hysteria2: proxy.Hysteria2{ + Server: "127.0.0.1", + Port: 8080, + Password: "password123", + Obfs: "salamander", + ObfsPassword: "obfs123", + SkipCertVerify: false, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestHysteria2_Basic_IPv6Address(t *testing.T) { + p := &parser.Hysteria2Parser{} + input := "hysteria2://password123@[2001:db8::1]:8080#Hysteria2%20IPv6" + + expected := proxy.Proxy{ + Type: "hysteria2", + Name: "Hysteria2 IPv6", + Hysteria2: proxy.Hysteria2{ + Server: "2001:db8::1", + Port: 8080, + Password: "password123", + SkipCertVerify: false, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestHysteria2_Basic_FullConfig(t *testing.T) { + p := &parser.Hysteria2Parser{} + input := "hysteria2://password123@proxy.example.com:443?insecure=1&sni=proxy.example.com&obfs=salamander&obfs-password=obfs123#Hysteria2%20Full" + + expected := proxy.Proxy{ + Type: "hysteria2", + Name: "Hysteria2 Full", + Hysteria2: proxy.Hysteria2{ + Server: "proxy.example.com", + Port: 443, + Password: "password123", + SNI: "proxy.example.com", + Obfs: "salamander", + ObfsPassword: "obfs123", + SkipCertVerify: true, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestHysteria2_Basic_NoPassword(t *testing.T) { + p := &parser.Hysteria2Parser{} + input := "hysteria2://@127.0.0.1:8080#No%20Password" + + expected := proxy.Proxy{ + Type: "hysteria2", + Name: "No Password", + Hysteria2: proxy.Hysteria2{ + Server: "127.0.0.1", + Port: 8080, + Password: "", + SkipCertVerify: false, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestHysteria2_Error_MissingServer(t *testing.T) { + p := &parser.Hysteria2Parser{} + input := "hysteria2://password123@:8080" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestHysteria2_Error_MissingPort(t *testing.T) { + p := &parser.Hysteria2Parser{} + input := "hysteria2://password123@127.0.0.1" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestHysteria2_Error_InvalidPort(t *testing.T) { + p := &parser.Hysteria2Parser{} + input := "hysteria2://password123@127.0.0.1:99999" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestHysteria2_Error_InvalidProtocol(t *testing.T) { + p := &parser.Hysteria2Parser{} + input := "hysteria://example.com:8080" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} diff --git a/test/parser/hysteria_test.go b/test/parser/hysteria_test.go new file mode 100644 index 0000000..c56fc32 --- /dev/null +++ b/test/parser/hysteria_test.go @@ -0,0 +1,183 @@ +package test + +import ( + "testing" + + "github.com/bestnite/sub2clash/model/proxy" + "github.com/bestnite/sub2clash/parser" +) + +func TestHysteria_Basic_SimpleLink(t *testing.T) { + p := &parser.HysteriaParser{} + input := "hysteria://127.0.0.1:8080?protocol=udp&auth=password123&upmbps=100&downmbps=100#Hysteria%20Proxy" + + expected := proxy.Proxy{ + Type: "hysteria", + Name: "Hysteria Proxy", + Hysteria: proxy.Hysteria{ + Server: "127.0.0.1", + Port: 8080, + Protocol: "udp", + Auth: "password123", + Up: "100", + Down: "100", + SkipCertVerify: false, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestHysteria_Basic_WithAuthString(t *testing.T) { + p := &parser.HysteriaParser{} + input := "hysteria://proxy.example.com:443?protocol=wechat-video&auth-str=myauth&upmbps=50&downmbps=200&insecure=true#Hysteria%20Auth" + + expected := proxy.Proxy{ + Type: "hysteria", + Name: "Hysteria Auth", + Hysteria: proxy.Hysteria{ + Server: "proxy.example.com", + Port: 443, + Protocol: "wechat-video", + AuthString: "myauth", + Up: "50", + Down: "200", + SkipCertVerify: true, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestHysteria_Basic_WithObfs(t *testing.T) { + p := &parser.HysteriaParser{} + input := "hysteria://127.0.0.1:8080?auth=password123&upmbps=100&downmbps=100&obfs=xplus&alpn=h3#Hysteria%20Obfs" + + expected := proxy.Proxy{ + Type: "hysteria", + Name: "Hysteria Obfs", + Hysteria: proxy.Hysteria{ + Server: "127.0.0.1", + Port: 8080, + Auth: "password123", + Up: "100", + Down: "100", + Obfs: "xplus", + ALPN: []string{"h3"}, + SkipCertVerify: false, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestHysteria_Basic_IPv6Address(t *testing.T) { + p := &parser.HysteriaParser{} + input := "hysteria://[2001:db8::1]:8080?auth=password123&upmbps=100&downmbps=100#Hysteria%20IPv6" + + expected := proxy.Proxy{ + Type: "hysteria", + Name: "Hysteria IPv6", + Hysteria: proxy.Hysteria{ + Server: "2001:db8::1", + Port: 8080, + Auth: "password123", + Up: "100", + Down: "100", + SkipCertVerify: false, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestHysteria_Basic_MultiALPN(t *testing.T) { + p := &parser.HysteriaParser{} + input := "hysteria://proxy.example.com:443?auth=password123&upmbps=100&downmbps=100&alpn=h3,h2,http/1.1#Hysteria%20Multi%20ALPN" + + expected := proxy.Proxy{ + Type: "hysteria", + Name: "Hysteria Multi ALPN", + Hysteria: proxy.Hysteria{ + Server: "proxy.example.com", + Port: 443, + Auth: "password123", + Up: "100", + Down: "100", + ALPN: []string{"h3", "h2", "http/1.1"}, + SkipCertVerify: false, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestHysteria_Error_MissingServer(t *testing.T) { + p := &parser.HysteriaParser{} + input := "hysteria://:8080?auth=password123" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestHysteria_Error_MissingPort(t *testing.T) { + p := &parser.HysteriaParser{} + input := "hysteria://127.0.0.1?auth=password123" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestHysteria_Error_InvalidPort(t *testing.T) { + p := &parser.HysteriaParser{} + input := "hysteria://127.0.0.1:99999?auth=password123" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestHysteria_Error_InvalidProtocol(t *testing.T) { + p := &parser.HysteriaParser{} + input := "hysteria2://example.com:8080" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} diff --git a/test/parser/shadowsocks_test.go b/test/parser/shadowsocks_test.go new file mode 100644 index 0000000..c884a94 --- /dev/null +++ b/test/parser/shadowsocks_test.go @@ -0,0 +1,184 @@ +package test + +import ( + "errors" + "testing" + + "github.com/bestnite/sub2clash/model/proxy" + "github.com/bestnite/sub2clash/parser" +) + +func TestShadowsocks_Basic_SimpleLink(t *testing.T) { + p := &parser.ShadowsocksParser{} + input := "ss://YWVzLTI1Ni1nY206cGFzc3dvcmQ=@127.0.0.1:8080" + + expected := proxy.Proxy{ + Type: "ss", + Name: "127.0.0.1:8080", + ShadowSocks: proxy.ShadowSocks{ + Server: "127.0.0.1", + Port: 8080, + Cipher: "aes-256-gcm", + Password: "password", + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestShadowsocks_Basic_IPv6Address(t *testing.T) { + p := &parser.ShadowsocksParser{} + input := "ss://YWVzLTI1Ni1nY206cGFzc3dvcmQ=@[2001:db8::1]:8080" + + expected := proxy.Proxy{ + Type: "ss", + Name: "2001:db8::1:8080", + ShadowSocks: proxy.ShadowSocks{ + Server: "2001:db8::1", + Port: 8080, + Cipher: "aes-256-gcm", + Password: "password", + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestShadowsocks_Basic_WithRemark(t *testing.T) { + p := &parser.ShadowsocksParser{} + input := "ss://YWVzLTI1Ni1nY206cGFzc3dvcmQ=@proxy.example.com:8080#My%20SS%20Proxy" + + expected := proxy.Proxy{ + Type: "ss", + Name: "My SS Proxy", + ShadowSocks: proxy.ShadowSocks{ + Server: "proxy.example.com", + Port: 8080, + Cipher: "aes-256-gcm", + Password: "password", + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestShadowsocks_Advanced_Base64FullEncoded(t *testing.T) { + p := &parser.ShadowsocksParser{} + input := "ss://YWVzLTI1Ni1nY206cGFzc3dvcmRAbG9jYWxob3N0OjgwODA=#Local%20SS" + + expected := proxy.Proxy{ + Type: "ss", + Name: "Local SS", + ShadowSocks: proxy.ShadowSocks{ + Server: "localhost", + Port: 8080, + Cipher: "aes-256-gcm", + Password: "password", + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestShadowsocks_Advanced_PlainUserPassword(t *testing.T) { + p := &parser.ShadowsocksParser{} + input := "ss://aes-256-gcm:password@192.168.1.1:8080" + + expected := proxy.Proxy{ + Type: "ss", + Name: "192.168.1.1:8080", + ShadowSocks: proxy.ShadowSocks{ + Server: "192.168.1.1", + Port: 8080, + Cipher: "aes-256-gcm", + Password: "password", + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestShadowsocks_Advanced_ChaCha20Cipher(t *testing.T) { + p := &parser.ShadowsocksParser{} + input := "ss://chacha20-poly1305:mypassword@server.com:443#ChaCha20" + + expected := proxy.Proxy{ + Type: "ss", + Name: "ChaCha20", + ShadowSocks: proxy.ShadowSocks{ + Server: "server.com", + Port: 443, + Cipher: "chacha20-poly1305", + Password: "mypassword", + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +// 错误处理测试 +func TestShadowsocks_Error_MissingServer(t *testing.T) { + p := &parser.ShadowsocksParser{} + input := "ss://YWVzLTI1Ni1nY206cGFzc3dvcmQ=@:8080" + + _, err := p.Parse(input) + if !errors.Is(err, parser.ErrInvalidStruct) { + t.Errorf("Error is not expected: %v", err) + } +} + +func TestShadowsocks_Error_MissingPort(t *testing.T) { + p := &parser.ShadowsocksParser{} + input := "ss://YWVzLTI1Ni1nY206cGFzc3dvcmQ=@127.0.0.1" + + _, err := p.Parse(input) + if !errors.Is(err, parser.ErrInvalidStruct) { + t.Errorf("Error is not expected: %v", err) + } +} + +func TestShadowsocks_Error_InvalidProtocol(t *testing.T) { + p := &parser.ShadowsocksParser{} + input := "http://example.com:8080" + + _, err := p.Parse(input) + if !errors.Is(err, parser.ErrInvalidPrefix) { + t.Errorf("Error is not expected: %v", err) + } +} diff --git a/test/parser/shadowsocksr_test.go b/test/parser/shadowsocksr_test.go new file mode 100644 index 0000000..9d8e93f --- /dev/null +++ b/test/parser/shadowsocksr_test.go @@ -0,0 +1,112 @@ +package test + +import ( + "testing" + + "github.com/bestnite/sub2clash/model/proxy" + "github.com/bestnite/sub2clash/parser" +) + +func TestShadowsocksR_Basic_SimpleLink(t *testing.T) { + p := &parser.ShadowsocksRParser{} + input := "ssr://MTI3LjAuMC4xOjQ0MzpvcmlnaW46YWVzLTE5Mi1jZmI6cGxhaW46TVRJek1USXovP2dyb3VwPVpHVm1ZWFZzZEEmcmVtYXJrcz1TRUZJUVE" + + expected := proxy.Proxy{ + Type: "ssr", + Name: "HAHA", + ShadowSocksR: proxy.ShadowSocksR{ + Server: "127.0.0.1", + Port: 443, + Cipher: "aes-192-cfb", + Password: "123123", + ObfsParam: "", + Obfs: "plain", + Protocol: "origin", + ProtocolParam: "", + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestShadowsocksR_Basic_WithParams(t *testing.T) { + p := &parser.ShadowsocksRParser{} + input := "ssr://MTI3LjAuMC4xOjQ0MzpvcmlnaW46YWVzLTE5Mi1jZmI6dGxzMS4wX3Nlc3Npb25fYXV0aDpNVEl6TVRJei8/b2Jmc3BhcmFtPWIySm1jeTF3WVhKaGJXVjBaWEkmcHJvdG9wYXJhbT1jSEp2ZEc5allXd3RjR0Z5WVcxbGRHVnkmZ3JvdXA9WkdWbVlYVnNkQSZyZW1hcmtzPVNFRklRUQ" + + expected := proxy.Proxy{ + Type: "ssr", + Name: "HAHA", + ShadowSocksR: proxy.ShadowSocksR{ + Server: "127.0.0.1", + Port: 443, + Cipher: "aes-192-cfb", + Password: "123123", + ObfsParam: "obfs-parameter", + Obfs: "tls1.0_session_auth", + Protocol: "origin", + ProtocolParam: "protocal-parameter", + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestShadowsocksR_Basic_IPv6Address(t *testing.T) { + p := &parser.ShadowsocksRParser{} + input := "ssr://WzIwMDE6MGRiODo4NWEzOjAwMDA6MDAwMDo4YTJlOjAzNzA6NzMzNF06NDQzOm9yaWdpbjphZXMtMTkyLWNmYjpwbGFpbjpNVEl6TVRJei8/Z3JvdXA9WkdWbVlYVnNkQSZyZW1hcmtzPVNFRklRUQ" + + expected := proxy.Proxy{ + Type: "ssr", + Name: "HAHA", + ShadowSocksR: proxy.ShadowSocksR{ + Server: "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", + Port: 443, + Cipher: "aes-192-cfb", + Password: "123123", + ObfsParam: "", + Obfs: "plain", + Protocol: "origin", + ProtocolParam: "", + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestShadowsocksR_Error_InvalidBase64(t *testing.T) { + p := &parser.ShadowsocksRParser{} + input := "ssr://invalid_base64" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestShadowsocksR_Error_InvalidProtocol(t *testing.T) { + p := &parser.ShadowsocksRParser{} + input := "ss://example.com:8080" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} diff --git a/test/parser/socks_test.go b/test/parser/socks_test.go new file mode 100644 index 0000000..ab31995 --- /dev/null +++ b/test/parser/socks_test.go @@ -0,0 +1,168 @@ +package test + +import ( + "testing" + + "github.com/bestnite/sub2clash/model/proxy" + "github.com/bestnite/sub2clash/parser" +) + +func TestSocks_Basic_SimpleLink(t *testing.T) { + p := &parser.SocksParser{} + input := "socks://user:pass@127.0.0.1:1080#SOCKS%20Proxy" + + expected := proxy.Proxy{ + Type: "socks5", + Name: "SOCKS Proxy", + Socks: proxy.Socks{ + Server: "127.0.0.1", + Port: 1080, + UserName: "user", + Password: "pass", + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestSocks_Basic_NoAuth(t *testing.T) { + p := &parser.SocksParser{} + input := "socks://127.0.0.1:1080#SOCKS%20No%20Auth" + + expected := proxy.Proxy{ + Type: "socks5", + Name: "SOCKS No Auth", + Socks: proxy.Socks{ + Server: "127.0.0.1", + Port: 1080, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestSocks_Basic_IPv6Address(t *testing.T) { + p := &parser.SocksParser{} + input := "socks://user:pass@[2001:db8::1]:1080#SOCKS%20IPv6" + + expected := proxy.Proxy{ + Type: "socks5", + Name: "SOCKS IPv6", + Socks: proxy.Socks{ + Server: "2001:db8::1", + Port: 1080, + UserName: "user", + Password: "pass", + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestSocks_Basic_WithTLS(t *testing.T) { + p := &parser.SocksParser{} + input := "socks://user:pass@127.0.0.1:1080?tls=true&sni=example.com#SOCKS%20TLS" + + expected := proxy.Proxy{ + Type: "socks5", + Name: "SOCKS TLS", + Socks: proxy.Socks{ + Server: "127.0.0.1", + Port: 1080, + UserName: "user", + Password: "pass", + TLS: true, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestSocks_Basic_WithUDP(t *testing.T) { + p := &parser.SocksParser{} + input := "socks://user:pass@127.0.0.1:1080?udp=true#SOCKS%20UDP" + + expected := proxy.Proxy{ + Type: "socks5", + Name: "SOCKS UDP", + Socks: proxy.Socks{ + Server: "127.0.0.1", + Port: 1080, + UserName: "user", + Password: "pass", + UDP: true, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestSocks_Error_MissingServer(t *testing.T) { + p := &parser.SocksParser{} + input := "socks://user:pass@:1080" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestSocks_Error_MissingPort(t *testing.T) { + p := &parser.SocksParser{} + input := "socks://user:pass@127.0.0.1" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestSocks_Error_InvalidPort(t *testing.T) { + p := &parser.SocksParser{} + input := "socks://user:pass@127.0.0.1:99999" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestSocks_Error_InvalidProtocol(t *testing.T) { + p := &parser.SocksParser{} + input := "ss://example.com:8080" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} diff --git a/test/parser/trojan_test.go b/test/parser/trojan_test.go new file mode 100644 index 0000000..dfdf1e8 --- /dev/null +++ b/test/parser/trojan_test.go @@ -0,0 +1,182 @@ +package test + +import ( + "testing" + + "github.com/bestnite/sub2clash/model/proxy" + "github.com/bestnite/sub2clash/parser" +) + +func TestTrojan_Basic_SimpleLink(t *testing.T) { + p := &parser.TrojanParser{} + input := "trojan://password@127.0.0.1:443#Trojan%20Proxy" + + expected := proxy.Proxy{ + Type: "trojan", + Name: "Trojan Proxy", + Trojan: proxy.Trojan{ + Server: "127.0.0.1", + Port: 443, + Password: "password", + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestTrojan_Basic_WithTLS(t *testing.T) { + p := &parser.TrojanParser{} + input := "trojan://password@127.0.0.1:443?security=tls&sni=example.com&alpn=h2,http/1.1#Trojan%20TLS" + + expected := proxy.Proxy{ + Type: "trojan", + Name: "Trojan TLS", + Trojan: proxy.Trojan{ + Server: "127.0.0.1", + Port: 443, + Password: "password", + ALPN: []string{"h2", "http/1.1"}, + SNI: "example.com", + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestTrojan_Basic_WithReality(t *testing.T) { + p := &parser.TrojanParser{} + input := "trojan://password@127.0.0.1:443?security=reality&sni=example.com&pbk=publickey123&sid=shortid123&fp=chrome#Trojan%20Reality" + + expected := proxy.Proxy{ + Type: "trojan", + Name: "Trojan Reality", + Trojan: proxy.Trojan{ + Server: "127.0.0.1", + Port: 443, + Password: "password", + SNI: "example.com", + RealityOpts: proxy.RealityOptions{ + PublicKey: "publickey123", + ShortID: "shortid123", + }, + Fingerprint: "chrome", + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestTrojan_Basic_WithWebSocket(t *testing.T) { + p := &parser.TrojanParser{} + input := "trojan://password@127.0.0.1:443?type=ws&path=/ws&host=example.com#Trojan%20WS" + + expected := proxy.Proxy{ + Type: "trojan", + Name: "Trojan WS", + Trojan: proxy.Trojan{ + Server: "127.0.0.1", + Port: 443, + Password: "password", + Network: "ws", + WSOpts: proxy.WSOptions{ + Path: "/ws", + Headers: map[string]string{ + "Host": "example.com", + }, + }, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestTrojan_Basic_WithGrpc(t *testing.T) { + p := &parser.TrojanParser{} + input := "trojan://password@127.0.0.1:443?type=grpc&serviceName=grpc_service#Trojan%20gRPC" + + expected := proxy.Proxy{ + Type: "trojan", + Name: "Trojan gRPC", + Trojan: proxy.Trojan{ + Server: "127.0.0.1", + Port: 443, + Password: "password", + Network: "grpc", + GrpcOpts: proxy.GrpcOptions{ + GrpcServiceName: "grpc_service", + }, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestTrojan_Error_MissingServer(t *testing.T) { + p := &parser.TrojanParser{} + input := "trojan://password@:443" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestTrojan_Error_MissingPort(t *testing.T) { + p := &parser.TrojanParser{} + input := "trojan://password@127.0.0.1" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestTrojan_Error_InvalidPort(t *testing.T) { + p := &parser.TrojanParser{} + input := "trojan://password@127.0.0.1:99999" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestTrojan_Error_InvalidProtocol(t *testing.T) { + p := &parser.TrojanParser{} + input := "ss://example.com:8080" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} diff --git a/test/parser/utils.go b/test/parser/utils.go new file mode 100644 index 0000000..eaa9653 --- /dev/null +++ b/test/parser/utils.go @@ -0,0 +1,24 @@ +package test + +import ( + "reflect" + "testing" + + "github.com/bestnite/sub2clash/model/proxy" + "gopkg.in/yaml.v3" +) + +func validateResult(t *testing.T, expected proxy.Proxy, result proxy.Proxy) { + t.Helper() + + if result.Type != expected.Type { + t.Errorf("Type mismatch: expected %s, got %s", expected.Type, result.Type) + } + + if !reflect.DeepEqual(expected, result) { + expectedYaml, _ := yaml.Marshal(expected) + resultYaml, _ := yaml.Marshal(result) + + t.Errorf("Structure mismatch: \nexpected:\n %s\ngot:\n %s", string(expectedYaml), string(resultYaml)) + } +} diff --git a/test/parser/vless_test.go b/test/parser/vless_test.go new file mode 100644 index 0000000..98cde53 --- /dev/null +++ b/test/parser/vless_test.go @@ -0,0 +1,214 @@ +package test + +import ( + "testing" + + "github.com/bestnite/sub2clash/model/proxy" + "github.com/bestnite/sub2clash/parser" +) + +func TestVless_Basic_SimpleLink(t *testing.T) { + p := &parser.VlessParser{} + input := "vless://b831b0c4-33b7-4873-9834-28d66d87d4ce@127.0.0.1:8080#VLESS%20Proxy" + + expected := proxy.Proxy{ + Type: "vless", + Name: "VLESS Proxy", + Vless: proxy.Vless{ + Server: "127.0.0.1", + Port: 8080, + UUID: "b831b0c4-33b7-4873-9834-28d66d87d4ce", + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestVless_Basic_WithTLS(t *testing.T) { + p := &parser.VlessParser{} + input := "vless://b831b0c4-33b7-4873-9834-28d66d87d4ce@127.0.0.1:443?security=tls&sni=example.com&alpn=h2,http/1.1#VLESS%20TLS" + + expected := proxy.Proxy{ + Type: "vless", + Name: "VLESS TLS", + Vless: proxy.Vless{ + Server: "127.0.0.1", + Port: 443, + UUID: "b831b0c4-33b7-4873-9834-28d66d87d4ce", + TLS: true, + ALPN: []string{"h2", "http/1.1"}, + ServerName: "example.com", + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestVless_Basic_WithReality(t *testing.T) { + p := &parser.VlessParser{} + input := "vless://b831b0c4-33b7-4873-9834-28d66d87d4ce@127.0.0.1:443?security=reality&sni=example.com&pbk=publickey123&sid=shortid123&fp=chrome#VLESS%20Reality" + + expected := proxy.Proxy{ + Type: "vless", + Name: "VLESS Reality", + Vless: proxy.Vless{ + Server: "127.0.0.1", + Port: 443, + UUID: "b831b0c4-33b7-4873-9834-28d66d87d4ce", + TLS: true, + ServerName: "example.com", + RealityOpts: proxy.RealityOptions{ + PublicKey: "publickey123", + ShortID: "shortid123", + }, + Fingerprint: "chrome", + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestVless_Basic_WithWebSocket(t *testing.T) { + p := &parser.VlessParser{} + input := "vless://b831b0c4-33b7-4873-9834-28d66d87d4ce@127.0.0.1:443?type=ws&path=/ws&host=example.com#VLESS%20WS" + + expected := proxy.Proxy{ + Type: "vless", + Name: "VLESS WS", + Vless: proxy.Vless{ + Server: "127.0.0.1", + Port: 443, + UUID: "b831b0c4-33b7-4873-9834-28d66d87d4ce", + Network: "ws", + WSOpts: proxy.WSOptions{ + Path: "/ws", + Headers: map[string]string{ + "Host": "example.com", + }, + }, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestVless_Basic_WithGrpc(t *testing.T) { + p := &parser.VlessParser{} + input := "vless://b831b0c4-33b7-4873-9834-28d66d87d4ce@127.0.0.1:443?type=grpc&serviceName=grpc_service#VLESS%20gRPC" + + expected := proxy.Proxy{ + Type: "vless", + Name: "VLESS gRPC", + Vless: proxy.Vless{ + Server: "127.0.0.1", + Port: 443, + UUID: "b831b0c4-33b7-4873-9834-28d66d87d4ce", + Network: "grpc", + GrpcOpts: proxy.GrpcOptions{ + GrpcServiceName: "grpc_service", + }, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestVless_Basic_WithHTTP(t *testing.T) { + p := &parser.VlessParser{} + input := "vless://b831b0c4-33b7-4873-9834-28d66d87d4ce@127.0.0.1:443?type=http&path=/path1,/path2&host=host1.com,host2.com#VLESS%20HTTP" + + expected := proxy.Proxy{ + Type: "vless", + Name: "VLESS HTTP", + Vless: proxy.Vless{ + Server: "127.0.0.1", + Port: 443, + UUID: "b831b0c4-33b7-4873-9834-28d66d87d4ce", + Network: "http", + HTTPOpts: proxy.HTTPOptions{ + Path: []string{"/path1", "/path2"}, + Headers: map[string][]string{ + "host": {"host1.com", "host2.com"}, + }, + }, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestVless_Error_MissingServer(t *testing.T) { + p := &parser.VlessParser{} + input := "vless://b831b0c4-33b7-4873-9834-28d66d87d4ce@:8080" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestVless_Error_MissingPort(t *testing.T) { + p := &parser.VlessParser{} + input := "vless://b831b0c4-33b7-4873-9834-28d66d87d4ce@127.0.0.1" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestVless_Error_InvalidPort(t *testing.T) { + p := &parser.VlessParser{} + input := "vless://b831b0c4-33b7-4873-9834-28d66d87d4ce@127.0.0.1:99999" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestVless_Error_InvalidProtocol(t *testing.T) { + p := &parser.VlessParser{} + input := "ss://example.com:8080" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} diff --git a/test/parser/vmess_test.go b/test/parser/vmess_test.go new file mode 100644 index 0000000..66062a5 --- /dev/null +++ b/test/parser/vmess_test.go @@ -0,0 +1,232 @@ +package test + +import ( + "testing" + + "github.com/bestnite/sub2clash/model/proxy" + "github.com/bestnite/sub2clash/parser" +) + +func TestVmess_Basic_SimpleLink(t *testing.T) { + p := &parser.VmessParser{} + input := "vmess://eyJhZGQiOiIxMjcuMC4wLjEiLCJhaWQiOiIwIiwiaWQiOiIxMjM0NTY3OC05MDEyLTM0NTYtNzg5MC0xMjM0NTY3ODkwMTIiLCJuZXQiOiJ3cyIsInBvcnQiOiI0NDMiLCJwcyI6IkhBSEEiLCJ0bHMiOiJ0bHMiLCJ0eXBlIjoibm9uZSIsInYiOiIyIn0=" + + expected := proxy.Proxy{ + Type: "vmess", + Name: "HAHA", + Vmess: proxy.Vmess{ + UUID: "12345678-9012-3456-7890-123456789012", + AlterID: 0, + Cipher: "auto", + Server: "127.0.0.1", + Port: 443, + TLS: true, + Network: "ws", + WSOpts: proxy.WSOptions{ + Path: "/", + Headers: map[string]string{ + "Host": "127.0.0.1", + }, + }, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestVmess_Basic_WithPath(t *testing.T) { + p := &parser.VmessParser{} + input := "vmess://eyJhZGQiOiIxMjcuMC4wLjEiLCJhaWQiOiIwIiwiaWQiOiIxMjM0NTY3OC05MDEyLTM0NTYtNzg5MC0xMjM0NTY3ODkwMTIiLCJuZXQiOiJ3cyIsInBhdGgiOiIvd3MiLCJwb3J0IjoiNDQzIiwicHMiOiJIQUNLIiwidGxzIjoidGxzIiwidHlwZSI6Im5vbmUiLCJ2IjoiMiJ9" + + expected := proxy.Proxy{ + Type: "vmess", + Name: "HACK", + Vmess: proxy.Vmess{ + UUID: "12345678-9012-3456-7890-123456789012", + AlterID: 0, + Cipher: "auto", + Server: "127.0.0.1", + Port: 443, + TLS: true, + Network: "ws", + WSOpts: proxy.WSOptions{ + Path: "/ws", + Headers: map[string]string{ + "Host": "127.0.0.1", + }, + }, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestVmess_Basic_WithHost(t *testing.T) { + p := &parser.VmessParser{} + input := "vmess://eyJhZGQiOiIxMjcuMC4wLjEiLCJhaWQiOiIwIiwiaG9zdCI6ImV4YW1wbGUuY29tIiwiaWQiOiIxMjM0NTY3OC05MDEyLTM0NTYtNzg5MC0xMjM0NTY3ODkwMTIiLCJuZXQiOiJ3cyIsInBvcnQiOiI0NDMiLCJwcyI6IkhBSEEiLCJ0bHMiOiJ0bHMiLCJ0eXBlIjoibm9uZSIsInYiOiIyIn0=" + + expected := proxy.Proxy{ + Type: "vmess", + Name: "HAHA", + Vmess: proxy.Vmess{ + UUID: "12345678-9012-3456-7890-123456789012", + AlterID: 0, + Cipher: "auto", + Server: "127.0.0.1", + Port: 443, + TLS: true, + Network: "ws", + WSOpts: proxy.WSOptions{ + Path: "/", + Headers: map[string]string{ + "Host": "example.com", + }, + }, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestVmess_Basic_WithSNI(t *testing.T) { + p := &parser.VmessParser{} + input := "vmess://eyJhZGQiOiIxMjcuMC4wLjEiLCJhaWQiOiIwIiwiaWQiOiIxMjM0NTY3OC05MDEyLTM0NTYtNzg5MC0xMjM0NTY3ODkwMTIiLCJuZXQiOiJ3cyIsInBvcnQiOiI0NDMiLCJwcyI6IkhBSEEiLCJzbmkiOiJleGFtcGxlLmNvbSIsInRscyI6InRscyIsInR5cGUiOiJub25lIiwidiI6IjIifQ==" + + expected := proxy.Proxy{ + Type: "vmess", + Name: "HAHA", + Vmess: proxy.Vmess{ + UUID: "12345678-9012-3456-7890-123456789012", + AlterID: 0, + Cipher: "auto", + Server: "127.0.0.1", + Port: 443, + TLS: true, + Network: "ws", + ServerName: "example.com", + WSOpts: proxy.WSOptions{ + Path: "/", + Headers: map[string]string{ + "Host": "127.0.0.1", + }, + }, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestVmess_Basic_WithAlterID(t *testing.T) { + p := &parser.VmessParser{} + input := "vmess://eyJhZGQiOiIxMjcuMC4wLjEiLCJhaWQiOiIxIiwiaWQiOiIxMjM0NTY3OC05MDEyLTM0NTYtNzg5MC0xMjM0NTY3ODkwMTIiLCJuZXQiOiJ3cyIsInBvcnQiOiI0NDMiLCJwcyI6IkhBSEEiLCJ0bHMiOiJ0bHMiLCJ0eXBlIjoibm9uZSIsInYiOiIyIn0=" + + expected := proxy.Proxy{ + Type: "vmess", + Name: "HAHA", + Vmess: proxy.Vmess{ + UUID: "12345678-9012-3456-7890-123456789012", + AlterID: 1, + Cipher: "auto", + Server: "127.0.0.1", + Port: 443, + TLS: true, + Network: "ws", + WSOpts: proxy.WSOptions{ + Path: "/", + Headers: map[string]string{ + "Host": "127.0.0.1", + }, + }, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestVmess_Basic_GRPC(t *testing.T) { + p := &parser.VmessParser{} + input := "vmess://eyJhZGQiOiIxMjcuMC4wLjEiLCJhaWQiOiIwIiwiaWQiOiIxMjM0NTY3OC05MDEyLTM0NTYtNzg5MC0xMjM0NTY3ODkwMTIiLCJuZXQiOiJncnBjIiwicG9ydCI6IjQ0MyIsInBzIjoiSEFIQSIsInRscyI6InRscyIsInR5cGUiOiJub25lIiwidiI6IjIifQ==" + + expected := proxy.Proxy{ + Type: "vmess", + Name: "HAHA", + Vmess: proxy.Vmess{ + UUID: "12345678-9012-3456-7890-123456789012", + AlterID: 0, + Cipher: "auto", + Server: "127.0.0.1", + Port: 443, + TLS: true, + Network: "grpc", + GrpcOpts: proxy.GrpcOptions{}, + }, + } + + result, err := p.Parse(input) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + validateResult(t, expected, result) +} + +func TestVmess_Error_InvalidBase64(t *testing.T) { + p := &parser.VmessParser{} + input := "vmess://invalid_base64" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestVmess_Error_InvalidJSON(t *testing.T) { + p := &parser.VmessParser{} + input := "vmess://eyJpbnZhbGlkIjoianNvbn0=" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} + +func TestVmess_Error_InvalidProtocol(t *testing.T) { + p := &parser.VmessParser{} + input := "ss://example.com:8080" + + _, err := p.Parse(input) + if err == nil { + t.Errorf("Expected error but got none") + } +} diff --git a/test/socks_test.go b/test/socks_test.go deleted file mode 100644 index 3ac27cb..0000000 --- a/test/socks_test.go +++ /dev/null @@ -1,221 +0,0 @@ -package test - -import ( - "testing" - - "github.com/bestnite/sub2clash/model/proxy" - "github.com/bestnite/sub2clash/parser" -) - -func TestSocksParser_Parse(t *testing.T) { - p := &parser.SocksParser{} - - tests := []struct { - name string - input string - expected proxy.Proxy - expectError bool - errorType string - }{ - { - name: "无认证SOCKS5代理 - IPv4", - input: "socks://127.0.0.1:1080", - expected: proxy.Proxy{ - Type: "socks5", - Name: "127.0.0.1:1080", - Socks: proxy.Socks{ - Server: "127.0.0.1", - Port: 1080, - UserName: "", - Password: "", - }, - }, - expectError: false, - }, - { - name: "无认证SOCKS5代理 - IPv6", - input: "socks://[2001:db8::1]:1080", - expected: proxy.Proxy{ - Type: "socks5", - Name: "2001:db8::1:1080", - Socks: proxy.Socks{ - Server: "2001:db8::1", - Port: 1080, - UserName: "", - Password: "", - }, - }, - expectError: false, - }, - { - name: "明文用户名密码认证 - IPv4", - input: "socks://user:pass@127.0.0.1:1080", - expected: proxy.Proxy{ - Type: "socks5", - Name: "127.0.0.1:1080", - Socks: proxy.Socks{ - Server: "127.0.0.1", - Port: 1080, - UserName: "user", - Password: "pass", - }, - }, - expectError: false, - }, - { - name: "明文用户名密码认证 - IPv6", - input: "socks://user:pass@[2001:db8::1]:1080", - expected: proxy.Proxy{ - Type: "socks5", - Name: "2001:db8::1:1080", - Socks: proxy.Socks{ - Server: "2001:db8::1", - Port: 1080, - UserName: "user", - Password: "pass", - }, - }, - expectError: false, - }, - { - name: "Base64编码的用户名密码", - input: "socks://dXNlcm5hbWU6cGFzc3dvcmQ=@127.0.0.1:1080", - expected: proxy.Proxy{ - Type: "socks5", - Name: "127.0.0.1:1080", - Socks: proxy.Socks{ - Server: "127.0.0.1", - Port: 1080, - UserName: "username", - Password: "password", - }, - }, - expectError: false, - }, - { - name: "带Fragment的URL", - input: "socks://username:password@proxy.example.com:1080#MyProxy", - expected: proxy.Proxy{ - Type: "socks5", - Name: "MyProxy", - Socks: proxy.Socks{ - Server: "proxy.example.com", - Port: 1080, - UserName: "username", - Password: "password", - }, - }, - expectError: false, - }, - { - name: "只有用户名没有密码", - input: "socks://username@127.0.0.1:1080", - expected: proxy.Proxy{ - Type: "socks5", - Name: "127.0.0.1:1080", - Socks: proxy.Socks{ - Server: "127.0.0.1", - Port: 1080, - UserName: "username", - Password: "", - }, - }, - expectError: false, - }, - { - name: "Base64编码只有用户名", - input: "socks://dXNlcm5hbWU=@127.0.0.1:1080", - expected: proxy.Proxy{ - Type: "socks5", - Name: "127.0.0.1:1080", - Socks: proxy.Socks{ - Server: "127.0.0.1", - Port: 1080, - UserName: "username", - Password: "", - }, - }, - expectError: false, - }, - { - name: "域名代理", - input: "socks://proxy.example.com:8080", - expected: proxy.Proxy{ - Type: "socks5", - Name: "proxy.example.com:8080", - Socks: proxy.Socks{ - Server: "proxy.example.com", - Port: 8080, - UserName: "", - Password: "", - }, - }, - expectError: false, - }, - { - name: "特殊字符用户名密码", - input: "socks://user%40domain:p%40ss%21@127.0.0.1:1080", - expected: proxy.Proxy{ - Type: "socks5", - Name: "127.0.0.1:1080", - Socks: proxy.Socks{ - Server: "127.0.0.1", - Port: 1080, - UserName: "user@domain", - Password: "p@ss!", - }, - }, - expectError: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := p.Parse(tt.input) - - if tt.expectError { - if err == nil { - t.Errorf("期望出现错误,但没有错误") - return - } - - if parseErr, ok := err.(*parser.ParseError); ok { - if tt.errorType != "" && string(parseErr.Type) != tt.errorType { - t.Errorf("错误类型不匹配: 期望 %s, 实际 %s", tt.errorType, string(parseErr.Type)) - } - } - return - } - - if err != nil { - t.Errorf("意外的错误: %v", err) - return - } - - // 验证解析结果 - if result.Type != tt.expected.Type { - t.Errorf("Type不匹配: 期望 %s, 实际 %s", tt.expected.Type, result.Type) - } - - if result.Name != tt.expected.Name { - t.Errorf("Name不匹配: 期望 %s, 实际 %s", tt.expected.Name, result.Name) - } - - if result.Socks.Server != tt.expected.Socks.Server { - t.Errorf("Server不匹配: 期望 %s, 实际 %s", tt.expected.Socks.Server, result.Socks.Server) - } - - if result.Socks.Port != tt.expected.Socks.Port { - t.Errorf("Port不匹配: 期望 %d, 实际 %d", tt.expected.Socks.Port, result.Socks.Port) - } - - if result.Socks.UserName != tt.expected.Socks.UserName { - t.Errorf("UserName不匹配: 期望 %s, 实际 %s", tt.expected.Socks.UserName, result.Socks.UserName) - } - - if result.Socks.Password != tt.expected.Socks.Password { - t.Errorf("Password不匹配: 期望 %s, 实际 %s", tt.expected.Socks.Password, result.Socks.Password) - } - }) - } -}