From cfebdf04a243ddc6af293d4b7a05ca09d5df7d5a Mon Sep 17 00:00:00 2001 From: gVisor bot Date: Fri, 19 Aug 2022 19:46:50 +0800 Subject: [PATCH 1/3] chore: Clean converter code and add doc --- common/convert/base64.go | 45 +++++++++++++++++++++++++++++++++++++ common/convert/converter.go | 30 ------------------------- 2 files changed, 45 insertions(+), 30 deletions(-) create mode 100644 common/convert/base64.go diff --git a/common/convert/base64.go b/common/convert/base64.go new file mode 100644 index 00000000..21d818f3 --- /dev/null +++ b/common/convert/base64.go @@ -0,0 +1,45 @@ +package convert + +import ( + "encoding/base64" + "strings" +) + +var ( + encRaw = base64.RawStdEncoding + enc = base64.StdEncoding +) + +// DecodeBase64 try to decode content from the given bytes, +// which can be in base64.RawStdEncoding, base64.StdEncoding or just plaintext. +func DecodeBase64(buf []byte) []byte { + result, err := tryDecodeBase64(buf) + if err != nil { + return buf + } + return result +} + +func tryDecodeBase64(buf []byte) ([]byte, error) { + dBuf := make([]byte, encRaw.DecodedLen(len(buf))) + n, err := encRaw.Decode(dBuf, buf) + if err != nil { + n, err = enc.Decode(dBuf, buf) + if err != nil { + return nil, err + } + } + return dBuf[:n], nil +} + +func urlSafe(data string) string { + return strings.NewReplacer("+", "-", "/", "_").Replace(data) +} + +func decodeUrlSafe(data string) string { + dcBuf, err := base64.RawURLEncoding.DecodeString(data) + if err != nil { + return "" + } + return string(dcBuf) +} diff --git a/common/convert/converter.go b/common/convert/converter.go index 811acecb..9264200f 100644 --- a/common/convert/converter.go +++ b/common/convert/converter.go @@ -2,7 +2,6 @@ package convert import ( "bytes" - "encoding/base64" "encoding/json" "fmt" "net/url" @@ -10,23 +9,6 @@ import ( "strings" ) -var ( - encRaw = base64.RawStdEncoding - enc = base64.StdEncoding -) - -func DecodeBase64(buf []byte) []byte { - dBuf := make([]byte, encRaw.DecodedLen(len(buf))) - n, err := encRaw.Decode(dBuf, buf) - if err != nil { - n, err = enc.Decode(dBuf, buf) - if err != nil { - return buf - } - } - return dBuf[:n] -} - // ConvertsV2Ray convert V2Ray subscribe proxies data to clash proxies config func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { data := DecodeBase64(buf) @@ -450,18 +432,6 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { return proxies, nil } -func urlSafe(data string) string { - return strings.ReplaceAll(strings.ReplaceAll(data, "+", "-"), "/", "_") -} - -func decodeUrlSafe(data string) string { - dcBuf, err := base64.RawURLEncoding.DecodeString(data) - if err != nil { - return "" - } - return string(dcBuf) -} - func uniqueName(names map[string]int, name string) string { if index, ok := names[name]; ok { index++ From 078c37f1a98dd57c7f17f34596bb876c1f6772ef Mon Sep 17 00:00:00 2001 From: gVisor bot Date: Fri, 19 Aug 2022 21:17:44 +0800 Subject: [PATCH 2/3] fix: Converter VMess security field typo --- common/convert/converter.go | 63 +++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/common/convert/converter.go b/common/convert/converter.go index 9264200f..db870c41 100644 --- a/common/convert/converter.go +++ b/common/convert/converter.go @@ -136,13 +136,11 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { if strings.HasSuffix(tls, "tls") { vless["tls"] = true } - sni := query.Get("sni") - if sni != "" { + if sni := query.Get("sni"); sni != "" { vless["servername"] = sni } - flow := strings.ToLower(query.Get("flow")) - if flow != "" { + if flow := strings.ToLower(query.Get("flow")); flow != "" { vless["flow"] = flow } @@ -161,16 +159,16 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { httpOpts := make(map[string]any) httpOpts["path"] = []string{"/"} - if query.Get("host") != "" { - headers["Host"] = []string{query.Get("host")} + if host := query.Get("host"); host != "" { + headers["Host"] = []string{host} } - if query.Get("method") != "" { - httpOpts["method"] = query.Get("method") + if method := query.Get("method"); method != "" { + httpOpts["method"] = method } - if query.Get("path") != "" { - httpOpts["path"] = []string{query.Get("path")} + if path := query.Get("path"); path != "" { + httpOpts["path"] = []string{path} } httpOpts["headers"] = headers vless["http-opts"] = httpOpts @@ -180,11 +178,11 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { headers := make(map[string]any) h2Opts := make(map[string]any) h2Opts["path"] = []string{"/"} - if query.Get("path") != "" { - h2Opts["path"] = []string{query.Get("path")} + if path := query.Get("path"); path != "" { + h2Opts["path"] = []string{path} } - if query.Get("host") != "" { - h2Opts["host"] = []string{query.Get("host")} + if host := query.Get("host"); host != "" { + h2Opts["host"] = []string{host} } h2Opts["headers"] = headers vless["h2-opts"] = h2Opts @@ -208,8 +206,12 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { proxies = append(proxies, vless) case "vmess": - dcBuf, err := encRaw.DecodeString(body) + // V2RayN-styled share link + // https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2) + dcBuf, err := tryDecodeBase64([]byte(body)) if err != nil { + // TODO: Xray VMessAEAD / VLESS share link standard + // https://github.com/XTLS/Xray-core/discussions/716 continue } @@ -233,17 +235,16 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { } else { vmess["alterId"] = 0 } - vmess["cipher"] = "auto" vmess["udp"] = true vmess["tls"] = false vmess["skip-cert-verify"] = false - if values["cipher"] != nil && values["cipher"] != "" { - vmess["cipher"] = values["cipher"] + vmess["cipher"] = "auto" + if cipher, ok := values["scy"]; ok && cipher != "" { + vmess["cipher"] = cipher } - sni := values["sni"] - if sni != "" { + if sni, ok := values["sni"]; ok && sni != "" { vmess["servername"] = sni } @@ -256,7 +257,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { vmess["network"] = network tls := strings.ToLower(values["tls"].(string)) - if strings.Contains(tls, "tls") { + if strings.HasSuffix(tls, "tls") { vmess["tls"] = true } @@ -264,12 +265,12 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { case "http": headers := make(map[string]any) httpOpts := make(map[string]any) - if values["host"] != "" && values["host"] != nil { - headers["Host"] = []string{values["host"].(string)} + if host, ok := values["host"]; ok && host != "" { + headers["Host"] = []string{host.(string)} } httpOpts["path"] = []string{"/"} - if values["path"] != "" && values["path"] != nil { - httpOpts["path"] = []string{values["path"].(string)} + if path, ok := values["path"]; ok && path != "" { + httpOpts["path"] = []string{path.(string)} } httpOpts["headers"] = headers @@ -278,8 +279,8 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { case "h2": headers := make(map[string]any) h2Opts := make(map[string]any) - if values["host"] != "" && values["host"] != nil { - headers["Host"] = []string{values["host"].(string)} + if host, ok := values["host"]; ok && host != "" { + headers["Host"] = []string{host.(string)} } h2Opts["path"] = values["path"] @@ -291,11 +292,11 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { headers := make(map[string]any) wsOpts := make(map[string]any) wsOpts["path"] = []string{"/"} - if values["host"] != "" && values["host"] != nil { - headers["Host"] = values["host"].(string) + if host, ok := values["host"]; ok && host != "" { + headers["Host"] = host.(string) } - if values["path"] != "" && values["path"] != nil { - wsOpts["path"] = values["path"].(string) + if path, ok := values["path"]; ok && path != "" { + wsOpts["path"] = path.(string) } wsOpts["headers"] = headers vmess["ws-opts"] = wsOpts From b51fb5bb72c986670ac750b2e1a3b6fccf453042 Mon Sep 17 00:00:00 2001 From: gVisor bot Date: Fri, 19 Aug 2022 22:00:22 +0800 Subject: [PATCH 3/3] feat: Converter VMessAEAD share link standard support --- common/convert/converter.go | 105 +++++++----------------------------- common/convert/v.go | 89 ++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 86 deletions(-) create mode 100644 common/convert/v.go diff --git a/common/convert/converter.go b/common/convert/converter.go index db870c41..9876e51f 100644 --- a/common/convert/converter.go +++ b/common/convert/converter.go @@ -114,95 +114,16 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { proxies = append(proxies, trojan) case "vless": - urlVless, err := url.Parse(line) + urlVLess, err := url.Parse(line) if err != nil { continue } - - query := urlVless.Query() - - name := uniqueName(names, urlVless.Fragment) + query := urlVLess.Query() vless := make(map[string]any, 20) - - vless["name"] = name - vless["type"] = scheme - vless["server"] = urlVless.Hostname() - vless["port"] = urlVless.Port() - vless["uuid"] = urlVless.User.Username() - vless["udp"] = true - vless["skip-cert-verify"] = false - vless["tls"] = false - tls := strings.ToLower(query.Get("security")) - if strings.HasSuffix(tls, "tls") { - vless["tls"] = true + handleVShareLink(names, urlVLess, scheme, vless) + if flow := query.Get("flow"); flow != "" { + vless["flow"] = strings.ToLower(flow) } - if sni := query.Get("sni"); sni != "" { - vless["servername"] = sni - } - - if flow := strings.ToLower(query.Get("flow")); flow != "" { - vless["flow"] = flow - } - - network := strings.ToLower(query.Get("type")) - fakeType := strings.ToLower(query.Get("headerType")) - if fakeType == "http" { - network = "http" - } else if network == "http" { - network = "h2" - } - vless["network"] = network - switch network { - case "tcp": - if fakeType != "none" { - headers := make(map[string]any) - httpOpts := make(map[string]any) - httpOpts["path"] = []string{"/"} - - if host := query.Get("host"); host != "" { - headers["Host"] = []string{host} - } - - if method := query.Get("method"); method != "" { - httpOpts["method"] = method - } - - if path := query.Get("path"); path != "" { - httpOpts["path"] = []string{path} - } - httpOpts["headers"] = headers - vless["http-opts"] = httpOpts - } - - case "http": - headers := make(map[string]any) - h2Opts := make(map[string]any) - h2Opts["path"] = []string{"/"} - if path := query.Get("path"); path != "" { - h2Opts["path"] = []string{path} - } - if host := query.Get("host"); host != "" { - h2Opts["host"] = []string{host} - } - h2Opts["headers"] = headers - vless["h2-opts"] = h2Opts - - case "ws": - headers := make(map[string]any) - wsOpts := make(map[string]any) - headers["User-Agent"] = RandUserAgent() - headers["Host"] = query.Get("host") - wsOpts["path"] = query.Get("path") - wsOpts["headers"] = headers - - vless["ws-opts"] = wsOpts - - case "grpc": - grpcOpts := make(map[string]any) - grpcOpts["grpc-service-name"] = query.Get("serviceName") - vless["grpc-opts"] = grpcOpts - } - proxies = append(proxies, vless) case "vmess": @@ -210,8 +131,20 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { // https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2) dcBuf, err := tryDecodeBase64([]byte(body)) if err != nil { - // TODO: Xray VMessAEAD / VLESS share link standard - // https://github.com/XTLS/Xray-core/discussions/716 + // Xray VMessAEAD share link + urlVMess, err := url.Parse(line) + if err != nil { + continue + } + query := urlVMess.Query() + vmess := make(map[string]any, 20) + handleVShareLink(names, urlVMess, scheme, vmess) + vmess["alterId"] = 0 + vmess["cipher"] = "auto" + if encryption := query.Get("encryption"); encryption != "" { + vmess["cipher"] = encryption + } + proxies = append(proxies, vmess) continue } diff --git a/common/convert/v.go b/common/convert/v.go new file mode 100644 index 00000000..0e3960f0 --- /dev/null +++ b/common/convert/v.go @@ -0,0 +1,89 @@ +package convert + +import ( + "net/url" + "strings" +) + +func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy map[string]any) { + // Xray VMessAEAD / VLESS share link standard + // https://github.com/XTLS/Xray-core/discussions/716 + query := url.Query() + proxy["name"] = uniqueName(names, url.Fragment) + proxy["type"] = scheme + proxy["server"] = url.Hostname() + proxy["port"] = url.Port() + proxy["uuid"] = url.User.Username() + proxy["udp"] = true + proxy["skip-cert-verify"] = false + proxy["tls"] = false + tls := strings.ToLower(query.Get("security")) + if strings.HasSuffix(tls, "tls") { + proxy["tls"] = true + } + if sni := query.Get("sni"); sni != "" { + proxy["servername"] = sni + } + + network := strings.ToLower(query.Get("type")) + if network == "" { + network = "tcp" + } + fakeType := strings.ToLower(query.Get("headerType")) + if fakeType == "http" { + network = "http" + } else if network == "http" { + network = "h2" + } + proxy["network"] = network + switch network { + case "tcp": + if fakeType != "none" { + headers := make(map[string]any) + httpOpts := make(map[string]any) + httpOpts["path"] = []string{"/"} + + if host := query.Get("host"); host != "" { + headers["Host"] = []string{host} + } + + if method := query.Get("method"); method != "" { + httpOpts["method"] = method + } + + if path := query.Get("path"); path != "" { + httpOpts["path"] = []string{path} + } + httpOpts["headers"] = headers + proxy["http-opts"] = httpOpts + } + + case "http": + headers := make(map[string]any) + h2Opts := make(map[string]any) + h2Opts["path"] = []string{"/"} + if path := query.Get("path"); path != "" { + h2Opts["path"] = []string{path} + } + if host := query.Get("host"); host != "" { + h2Opts["host"] = []string{host} + } + h2Opts["headers"] = headers + proxy["h2-opts"] = h2Opts + + case "ws": + headers := make(map[string]any) + wsOpts := make(map[string]any) + headers["User-Agent"] = RandUserAgent() + headers["Host"] = query.Get("host") + wsOpts["path"] = query.Get("path") + wsOpts["headers"] = headers + + proxy["ws-opts"] = wsOpts + + case "grpc": + grpcOpts := make(map[string]any) + grpcOpts["grpc-service-name"] = query.Get("serviceName") + proxy["grpc-opts"] = grpcOpts + } +}