diff --git a/.gitignore b/.gitignore index c60f928..3bc0a97 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ template.json .idea test sub2sing-box.json -config.json \ No newline at end of file +config.json +*test.go \ No newline at end of file diff --git a/parser/hysteria.go b/parser/hysteria.go index 2b26188..bfc7e6f 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.Outbound, error) { return model.Outbound{}, &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.Outbound{}, &ParseError{ Type: ErrInvalidStruct, - Message: "missing character '?' in url", + Message: "url parse error", + Raw: proxy, + } + } + server := link.Hostname() + if server == "" { + return model.Outbound{}, &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.Outbound{}, &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.Outbound, error) { } } - params, err := url.ParseQuery(urlParts[1]) - if err != nil { - return model.Outbound{}, &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.Outbound, 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) return model.Outbound{ Type: "hysteria", diff --git a/parser/hysteria2.go b/parser/hysteria2.go index c3fbc72..1569f7d 100644 --- a/parser/hysteria2.go +++ b/parser/hysteria2.go @@ -1,6 +1,7 @@ package parser import ( + "fmt" "net/url" "strings" "sub2sing-box/constant" @@ -13,68 +14,57 @@ func ParseHysteria2(proxy string) (model.Outbound, error) { return model.Outbound{}, &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.Outbound{}, &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.Outbound{}, &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.Outbound{}, &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.Outbound{}, &ParseError{ + Type: ErrInvalidStruct, + Message: "missing server host", + Raw: proxy, + } + } + portStr := link.Port() + if portStr == "" { + return model.Outbound{}, &ParseError{ + Type: ErrInvalidStruct, + Message: "missing server port", + Raw: proxy, + } + } port, err := ParsePort(portStr) if err != nil { return model.Outbound{}, &ParseError{ - Type: ErrInvalidPort, - Message: err.Error(), - Raw: proxy, + Type: ErrInvalidPort, + Raw: portStr, } } - - params, err := url.ParseQuery(paramStr) - if err != nil { - return model.Outbound{}, &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") + 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 != "" insecureBool := insecure == "1" + remarks := link.Fragment + if remarks == "" { + remarks = fmt.Sprintf("%s:%s", server, portStr) + } + remarks = strings.TrimSpace(remarks) result := model.Outbound{ Type: "hysteria2", - Tag: remarks, + Tag: strings.TrimSpace(remarks), Hysteria2Options: model.Hysteria2OutboundOptions{ ServerOptions: model.ServerOptions{ Server: server, diff --git a/parser/shadowsocks.go b/parser/shadowsocks.go index 56c1d02..156f354 100644 --- a/parser/shadowsocks.go +++ b/parser/shadowsocks.go @@ -1,8 +1,8 @@ package parser import ( + "fmt" "net/url" - "strconv" "strings" "sub2sing-box/constant" "sub2sing-box/model" @@ -10,6 +10,10 @@ import ( ) func ParseShadowsocks(proxy string) (model.Outbound, error) { + if !strings.HasPrefix(proxy, constant.ShadowsocksPrefix) { + return model.Outbound{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy} + } + link, err := url.Parse(proxy) if err != nil { return model.Outbound{}, &ParseError{ @@ -28,20 +32,31 @@ func ParseShadowsocks(proxy string) (model.Outbound, error) { } } - if link.Scheme+"://" != constant.ShadowsocksPrefix { - return model.Outbound{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy} - } - - port, err := strconv.Atoi(link.Port()) - if err != nil { + portStr := link.Port() + if portStr == "" { return model.Outbound{}, &ParseError{ Type: ErrInvalidStruct, Message: "missing server port", Raw: proxy, } } + port, err := ParsePort(portStr) + if err != nil { + return model.Outbound{}, &ParseError{ + Type: ErrInvalidStruct, + Raw: proxy, + } + } user, err := util.DecodeBase64(link.User.Username()) + if err != nil { + return model.Outbound{}, &ParseError{ + Type: ErrInvalidStruct, + Message: "missing method and password", + Raw: proxy, + } + } + if user == "" { return model.Outbound{}, &ParseError{ Type: ErrInvalidStruct, @@ -70,13 +85,19 @@ func ParseShadowsocks(proxy string) (model.Outbound, error) { } } + remarks := link.Fragment + if remarks == "" { + remarks = fmt.Sprintf("%s:%s", server, portStr) + } + remarks = strings.TrimSpace(remarks) + result := model.Outbound{ Type: "shadowsocks", - Tag: link.Fragment, + Tag: remarks, ShadowsocksOptions: model.ShadowsocksOutboundOptions{ ServerOptions: model.ServerOptions{ Server: server, - ServerPort: uint16(port), + ServerPort: port, }, Method: methodAndPass[0], Password: methodAndPass[1], diff --git a/parser/trojan.go b/parser/trojan.go index 84e3100..20730ca 100644 --- a/parser/trojan.go +++ b/parser/trojan.go @@ -1,6 +1,7 @@ package parser import ( + "fmt" "net/url" "strings" "sub2sing-box/constant" @@ -12,43 +13,30 @@ func ParseTrojan(proxy string) (model.Outbound, error) { return model.Outbound{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy} } - proxy = strings.TrimPrefix(proxy, constant.TrojanPrefix) - urlParts := strings.SplitN(proxy, "@", 2) - if len(urlParts) != 2 { - return model.Outbound{}, &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.Outbound{}, &ParseError{ - Type: ErrInvalidStruct, - Message: "missing character '?' in url", - Raw: proxy, - } - } - - serverAndPort := strings.SplitN(serverAndPortAndParams[0], ":", 2) - if len(serverAndPort) != 2 { - return model.Outbound{}, &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.Outbound{}, &ParseError{ - Type: ErrCannotParseParams, + Type: ErrInvalidStruct, + Message: "url parse error", + Raw: proxy, + } + } + + password := link.User.Username() + server := link.Hostname() + if server == "" { + return model.Outbound{}, &ParseError{ + Type: ErrInvalidStruct, + Message: "missing server host", + Raw: proxy, + } + } + portStr := link.Port() + if portStr == "" { + return model.Outbound{}, &ParseError{ + Type: ErrInvalidStruct, + Message: "missing server port", Raw: proxy, - Message: err.Error(), } } @@ -61,14 +49,14 @@ func ParseTrojan(proxy string) (model.Outbound, 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, allowInsecure := 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("allowInsecure") var alpn []string if strings.Contains(alpnStr, ",") { @@ -92,12 +80,13 @@ func ParseTrojan(proxy string) (model.Outbound, error) { }, } - if security == "xtls" || security == "tls" { + if security == "xtls" || security == "tls" || sni != "" { result.TrojanOptions.OutboundTLSOptionsContainer = model.OutboundTLSOptionsContainer{ TLS: &model.OutboundTLSOptions{ Enabled: true, ALPN: alpn, ServerName: sni, + Insecure: allowInsecure == "1", }, } } @@ -116,6 +105,7 @@ func ParseTrojan(proxy string) (model.Outbound, error) { Enabled: enableUTLS, Fingerprint: fp, }, + Insecure: allowInsecure == "1", }, } } diff --git a/parser/vless.go b/parser/vless.go index ceea338..039e79a 100644 --- a/parser/vless.go +++ b/parser/vless.go @@ -1,6 +1,7 @@ package parser import ( + "fmt" "net/url" "strings" "sub2sing-box/constant" @@ -12,34 +13,24 @@ func ParseVless(proxy string) (model.Outbound, error) { return model.Outbound{}, &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.Outbound{}, &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.Outbound{}, &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.Outbound{}, &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.Outbound{}, &ParseError{ @@ -49,38 +40,9 @@ func ParseVless(proxy string) (model.Outbound, error) { } } - params, err := url.ParseQuery(serverAndPortAndParams[1]) - if err != nil { - return model.Outbound{}, &ParseError{ - Type: ErrCannotParseParams, - Raw: proxy, - Message: err.Error(), - } - } - - 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.Outbound{}, &ParseError{ - Type: ErrCannotParseParams, - Raw: proxy, - Message: err.Error(), - } - } - } - } else { - remarks, err = url.QueryUnescape(server) - if err != nil { - return model.Outbound{}, 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") + 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") enableUTLS := fp != "" insecureBool := insecure == "1" @@ -90,6 +52,11 @@ func ParseVless(proxy string) (model.Outbound, error) { } else { alpn = nil } + remarks := link.Fragment + if remarks == "" { + remarks = fmt.Sprintf("%s:%s", server, portStr) + } + remarks = strings.TrimSpace(remarks) result := model.Outbound{ Type: "vless",