1
0
mirror of https://github.com/nitezs/sub2clash.git synced 2024-12-23 14:54:42 -05:00

♻️ Refactor parsers

This commit is contained in:
Nite07 2024-04-23 14:39:16 +08:00
parent 48dece2a51
commit ebc91d8aad
15 changed files with 549 additions and 371 deletions

View File

@ -76,7 +76,7 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
err = yaml.Unmarshal(data, &sub)
var newProxies []model.Proxy
if err != nil {
reg, _ := regexp.Compile("(ssr|ss|vmess|trojan|vless|hysteria)://")
reg, _ := regexp.Compile("(ssr|ss|vmess|trojan|vless|hysteria|hy2|hysteria2)://")
if reg.Match(data) {
p := utils.ParseProxy(strings.Split(string(data), "\n")...)
newProxies = p

View File

@ -1,11 +1,12 @@
package constant
const (
HysteriaPrefix string = "hysteria://"
Hysteria2Prefix1 string = "hysteria2://"
Hysteria2Prefix2 string = "hy2://"
ShadowsocksPrefix string = "ss://"
TrojanPrefix string = "trojan://"
VLESSPrefix string = "vless://"
VMessPrefix string = "vmess://"
HysteriaPrefix string = "hysteria://"
Hysteria2Prefix1 string = "hysteria2://"
Hysteria2Prefix2 string = "hy2://"
ShadowsocksPrefix string = "ss://"
ShadowsocksRPrefix string = "ssr://"
TrojanPrefix string = "trojan://"
VLESSPrefix string = "vless://"
VMessPrefix string = "vmess://"
)

View File

@ -7,6 +7,11 @@ import (
func DecodeBase64(s string) (string, error) {
s = strings.TrimSpace(s)
// url safe
if strings.Contains(s, "-") || strings.Contains(s, "_") {
s = strings.Replace(s, "-", "+", -1)
s = strings.Replace(s, "_", "/", -1)
}
if len(s)%4 != 0 {
s += strings.Repeat("=", 4-len(s)%4)
}

View File

@ -1,79 +1,86 @@
package parser
import (
"errors"
"net/url"
"strconv"
"strings"
"sub2clash/constant"
"sub2clash/model"
)
//hysteria://host:port?protocol=udp&auth=123456&peer=sni.domain&insecure=1&upmbps=100&downmbps=100&alpn=hysteria&obfs=xplus&obfsParam=123456#remarks
//
//- host: hostname or IP address of the server to connect to (required)
//- port: port of the server to connect to (required)
//- protocol: protocol to use ("udp", "wechat-video", "faketcp") (optional, default: "udp")
//- auth: authentication payload (string) (optional)
//- peer: SNI for TLS (optional)
//- insecure: ignore certificate errors (optional)
//- upmbps: upstream bandwidth in Mbps (required)
//- downmbps: downstream bandwidth in Mbps (required)
//- alpn: QUIC ALPN (optional)
//- obfs: Obfuscation mode (optional, empty or "xplus")
//- obfsParam: Obfuscation password (optional)
//- remarks: remarks (optional)
func ParseHysteria(proxy string) (model.Proxy, error) {
// 判断是否以 hysteria:// 开头
if !strings.HasPrefix(proxy, "hysteria://") {
return model.Proxy{}, errors.New("invalid hysteria Url")
if !strings.HasPrefix(proxy, constant.HysteriaPrefix) {
return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy}
}
// 分割
parts := strings.SplitN(strings.TrimPrefix(proxy, "hysteria://"), "?", 2)
serverInfo := strings.SplitN(parts[0], ":", 2)
proxy = strings.TrimPrefix(proxy, constant.HysteriaPrefix)
urlParts := strings.SplitN(proxy, "?", 2)
if len(urlParts) != 2 {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "missing character '?' in url",
Raw: proxy,
}
}
serverInfo := strings.SplitN(urlParts[0], ":", 2)
if len(serverInfo) != 2 {
return model.Proxy{}, errors.New("invalid hysteria Url")
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "missing server host or port",
Raw: proxy,
}
}
params, err := url.ParseQuery(parts[1])
server, portStr := serverInfo[0], serverInfo[1]
port, err := ParsePort(portStr)
if err != nil {
return model.Proxy{}, errors.New("invalid hysteria Url")
return model.Proxy{}, &ParseError{
Type: ErrInvalidPort,
Message: err.Error(),
Raw: proxy,
}
}
host := serverInfo[0]
port, err := strconv.Atoi(serverInfo[1])
params, err := url.ParseQuery(urlParts[1])
if err != nil {
return model.Proxy{}, errors.New("invalid hysteria Url")
return model.Proxy{}, &ParseError{
Type: ErrCannotParseParams,
Raw: proxy,
Message: err.Error(),
}
}
protocol := params.Get("protocol")
auth := params.Get("auth")
peer := params.Get("peer")
insecure := params.Get("insecure")
upmbps := params.Get("upmbps")
downmbps := params.Get("downmbps")
alpn := params.Get("alpn")
obfs := params.Get("obfs")
obfsParam := params.Get("obfsParam")
remarks := ""
if strings.Contains(parts[1], "#") {
r := strings.Split(parts[1], "#")
remarks = r[len(r)-1]
} else {
remarks = serverInfo[0] + ":" + serverInfo[1]
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")
insecureBool, err := strconv.ParseBool(insecure)
if err != nil {
insecureBool = false
}
// 返回结果
var alpn []string
alpnStr = strings.TrimSpace(alpnStr)
if alpnStr != "" {
alpn = strings.Split(alpnStr, ",")
}
remarks := server + ":" + portStr
if params.Get("remarks") != "" {
remarks = params.Get("remarks")
}
result := model.Proxy{
Type: "hysteria",
Name: remarks,
Server: host,
Server: server,
Port: port,
Up: upmbps,
Down: downmbps,
Auth: auth,
Obfs: obfs,
Sni: peer,
SkipCertVerify: insecure == "1",
Alpn: strings.Split(alpn, ","),
ObfsParam: obfsParam,
Alpn: alpn,
Protocol: protocol,
AllowInsecure: insecureBool,
}
return result, nil
}

View File

@ -1,57 +1,89 @@
package parser
import (
"errors"
"net/url"
"strconv"
"strings"
"sub2clash/constant"
"sub2clash/model"
)
// hysteria2://letmein@example.com/?insecure=1&obfs=salamander&obfs-password=gawrgura&pinSHA256=deadbeef&sni=real.example.com#name
func ParseHysteria2(proxy string) (model.Proxy, error) {
// 判断是否以 hysteria2:// 开头
if !strings.HasPrefix(proxy, "hysteria2://") && !strings.HasPrefix(proxy, "hy2://") {
return model.Proxy{}, errors.New("invalid hysteria2 Url")
if !strings.HasPrefix(proxy, constant.Hysteria2Prefix1) &&
!strings.HasPrefix(proxy, constant.Hysteria2Prefix2) {
return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy}
}
// 分割
parts := strings.SplitN(strings.TrimPrefix(proxy, "hysteria2://"), "@", 2)
// 分割
serverInfo := strings.SplitN(parts[1], "/?", 2)
proxy = strings.TrimPrefix(proxy, constant.Hysteria2Prefix1)
proxy = strings.TrimPrefix(proxy, constant.Hysteria2Prefix2)
urlParts := strings.SplitN(proxy, "@", 2)
if len(urlParts) != 2 {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "missing character '@' in url",
Raw: proxy,
}
}
password := urlParts[0]
serverInfo := strings.SplitN(urlParts[1], "/?", 2)
if len(serverInfo) != 2 {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "missing params in url",
Raw: proxy,
}
}
paramStr := serverInfo[1]
serverAndPort := strings.SplitN(serverInfo[0], ":", 2)
var server string
var portStr string
if len(serverAndPort) == 1 {
serverAndPort = append(serverAndPort, "443")
} else if len(serverAndPort) != 2 {
return model.Proxy{}, errors.New("invalid hysteria2 Url")
}
params, err := url.ParseQuery(serverInfo[1])
if err != nil {
return model.Proxy{}, errors.New("invalid hysteria2 Url")
}
// 获取端口
port, err := strconv.Atoi(serverAndPort[1])
if err != nil {
return model.Proxy{}, errors.New("invalid hysteria2 Url")
}
name := ""
if strings.Contains(proxy, "#") {
splitResult := strings.Split(proxy, "#")
name, _ = url.QueryUnescape(splitResult[len(splitResult)-1])
portStr = "443"
} else if len(serverAndPort) == 2 {
server, portStr = serverAndPort[0], serverAndPort[1]
} else {
name = strings.Join(serverAndPort, ":")
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "missing server host or port",
Raw: proxy,
}
}
// 返回结果
port, err := ParsePort(portStr)
if err != nil {
return model.Proxy{}, &ParseError{
Type: ErrInvalidPort,
Message: err.Error(),
Raw: proxy,
}
}
params, err := url.ParseQuery(paramStr)
if err != nil {
return model.Proxy{}, &ParseError{
Type: ErrCannotParseParams,
Raw: proxy,
Message: err.Error(),
}
}
remarks, network, obfs, obfsPassword, pinSHA256, insecure, sni := params.Get("name"), params.Get("network"), params.Get("obfs"), params.Get("obfs-password"), params.Get("pinSHA256"), params.Get("insecure"), params.Get("sni")
enableTLS := pinSHA256 != ""
insecureBool := insecure == "1"
result := model.Proxy{
Type: "hysteria2",
Name: name,
Server: serverAndPort[0],
Name: remarks,
Server: server,
Port: port,
Password: parts[0],
Obfs: params.Get("obfs"),
ObfsParam: params.Get("obfs-password"),
Sni: params.Get("sni"),
SkipCertVerify: params.Get("insecure") == "1",
Password: password,
Obfs: obfs,
ObfsParam: obfsPassword,
Sni: sni,
SkipCertVerify: insecureBool,
TLS: enableTLS,
Network: network,
}
return result, nil
}

View File

@ -1,6 +1,7 @@
package parser
import (
"errors"
"strconv"
)
@ -8,16 +9,10 @@ func ParsePort(portStr string) (int, error) {
port, err := strconv.Atoi(portStr)
if err != nil {
return 0, &ParseError{
Type: ErrInvalidPort,
Message: portStr,
}
return 0, err
}
if port < 1 || port > 65535 {
return 0, &ParseError{
Type: ErrInvalidPort,
Message: portStr,
}
return 0, errors.New("invaild port range")
}
return port, nil
}

91
parser/shadowsocks.go Normal file
View File

@ -0,0 +1,91 @@
package parser
import (
"net/url"
"strings"
"sub2clash/constant"
"sub2clash/model"
)
func ParseShadowsocks(proxy string) (model.Proxy, error) {
if !strings.HasPrefix(proxy, constant.ShadowsocksPrefix) {
return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy}
}
proxy = strings.TrimPrefix(proxy, constant.ShadowsocksPrefix)
urlParts := strings.SplitN(proxy, "@", 2)
if len(urlParts) != 2 {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "missing character '@' in url",
Raw: proxy,
}
}
var serverAndPort []string
if !strings.Contains(urlParts[0], ":") {
decoded, err := DecodeBase64(urlParts[0])
if err != nil {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "invalid base64 encoded",
Raw: proxy,
}
}
urlParts[0] = decoded
}
credentials := strings.SplitN(urlParts[0], ":", 2)
if len(credentials) != 2 {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "missing server host or port",
Raw: proxy,
}
}
method, password := credentials[0], credentials[1]
serverInfo := strings.SplitN(urlParts[1], "#", 2)
serverAndPort = strings.SplitN(serverInfo[0], ":", 2)
server, portStr := serverAndPort[0], serverAndPort[1]
if len(serverInfo) != 2 {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "missing server host or port",
Raw: proxy,
}
}
port, err := ParsePort(portStr)
if err != nil {
return model.Proxy{}, &ParseError{
Type: ErrInvalidPort,
Message: err.Error(),
Raw: proxy,
}
}
var remarks string
if len(serverInfo) == 2 {
unescape, err := url.QueryUnescape(serverInfo[1])
if err != nil {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "cannot unescape remarks",
Raw: proxy,
}
}
remarks = strings.TrimSpace(unescape)
} else {
remarks = strings.TrimSpace(server + ":" + portStr)
}
result := model.Proxy{
Type: "ss",
Cipher: method,
Password: password,
Server: server,
Port: port,
Name: remarks,
}
return result, nil
}

86
parser/shadowsocksr.go Normal file
View File

@ -0,0 +1,86 @@
package parser
import (
"net/url"
"strconv"
"strings"
"sub2clash/constant"
"sub2clash/model"
)
func ParseShadowsocksR(proxy string) (model.Proxy, error) {
if !strings.HasPrefix(proxy, constant.ShadowsocksRPrefix) {
return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy}
}
proxy = strings.TrimPrefix(proxy, constant.ShadowsocksRPrefix)
proxy, err := DecodeBase64(proxy)
if err != nil {
return model.Proxy{}, &ParseError{
Type: ErrInvalidBase64,
Raw: proxy,
}
}
serverInfoAndParams := strings.SplitN(proxy, "/?", 2)
parts := strings.Split(serverInfoAndParams[0], ":")
server := parts[0]
protocol := parts[2]
method := parts[3]
obfs := parts[4]
password := parts[5]
port, err := ParsePort(parts[1])
if err != nil {
return model.Proxy{}, &ParseError{
Type: ErrInvalidPort,
Message: err.Error(),
Raw: proxy,
}
}
var obfsParam string
var protoParam string
var remarks string
if len(serverInfoAndParams) == 2 {
params, err := url.ParseQuery(serverInfoAndParams[1])
if err != nil {
return model.Proxy{}, &ParseError{
Type: ErrCannotParseParams,
Raw: proxy,
Message: err.Error(),
}
}
if params.Get("obfsparam") != "" {
obfsParam, err = DecodeBase64(params.Get("obfsparam"))
}
if params.Get("protoparam") != "" {
protoParam, err = DecodeBase64(params.Get("protoparam"))
}
if params.Get("remarks") != "" {
remarks, err = DecodeBase64(params.Get("remarks"))
} else {
remarks = server + ":" + strconv.Itoa(port)
}
if err != nil {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Raw: proxy,
Message: err.Error(),
}
}
}
result := model.Proxy{
Name: remarks,
Type: "ssr",
Server: server,
Port: port,
Protocol: protocol,
Cipher: method,
Obfs: obfs,
Password: password,
ObfsParam: obfsParam,
ProtocolParam: protoParam,
}
return result, nil
}

View File

@ -1,67 +0,0 @@
package parser
import (
"errors"
"net/url"
"strconv"
"strings"
"sub2clash/model"
)
// ParseSS 解析 SSShadowsocksUrl
func ParseSS(proxy string) (model.Proxy, error) {
// 判断是否以 ss:// 开头
if !strings.HasPrefix(proxy, "ss://") {
return model.Proxy{}, errors.New("invalid ss Url")
}
// 分割
parts := strings.SplitN(strings.TrimPrefix(proxy, "ss://"), "@", 2)
if len(parts) != 2 {
return model.Proxy{}, errors.New("invalid ss Url")
}
if !strings.Contains(parts[0], ":") {
// 解码
decoded, err := DecodeBase64(parts[0])
if err != nil {
return model.Proxy{}, errors.New("invalid ss Url" + err.Error())
}
parts[0] = decoded
}
credentials := strings.SplitN(parts[0], ":", 2)
if len(credentials) != 2 {
return model.Proxy{}, errors.New("invalid ss Url")
}
// 分割
serverInfo := strings.SplitN(parts[1], "#", 2)
serverAndPort := strings.SplitN(serverInfo[0], ":", 2)
if len(serverAndPort) != 2 {
return model.Proxy{}, errors.New("invalid ss Url")
}
// 转换端口字符串为数字
port, err := strconv.Atoi(strings.TrimSpace(serverAndPort[1]))
if err != nil {
return model.Proxy{}, errors.New("invalid ss Url" + err.Error())
}
// 返回结果
result := model.Proxy{
Type: "ss",
Cipher: strings.TrimSpace(credentials[0]),
Password: strings.TrimSpace(credentials[1]),
Server: strings.TrimSpace(serverAndPort[0]),
Port: port,
UDP: true,
Name: serverAndPort[0],
}
// 如果有节点名称
if len(serverInfo) == 2 {
unescape, err := url.QueryUnescape(serverInfo[1])
if err != nil {
return model.Proxy{}, errors.New("invalid ss Url" + err.Error())
}
result.Name = strings.TrimSpace(unescape)
} else {
result.Name = strings.TrimSpace(serverAndPort[0])
}
return result, nil
}

View File

@ -1,71 +0,0 @@
package parser
import (
"fmt"
"net/url"
"strconv"
"strings"
"sub2clash/model"
)
func ParseShadowsocksR(proxy string) (model.Proxy, error) {
// 判断是否以 ssr:// 开头
if !strings.HasPrefix(proxy, "ssr://") {
return model.Proxy{}, fmt.Errorf("invalid ssr Url")
}
var err error
proxy = strings.TrimPrefix(proxy, "ssr://")
if !strings.Contains(proxy, ":") {
proxy, err = DecodeBase64(strings.TrimPrefix(proxy, "ssr://"))
if err != nil {
return model.Proxy{}, err
}
}
// 分割
detailsAndParams := strings.SplitN(proxy, "/?", 2)
parts := strings.Split(detailsAndParams[0], ":")
params, err := url.ParseQuery(detailsAndParams[1])
if err != nil {
return model.Proxy{}, err
}
// 处理端口
port, err := strconv.Atoi(parts[1])
if err != nil {
return model.Proxy{}, err
}
var obfsParam string
var protoParam string
var remarks string
if params.Get("obfsparam") != "" {
obfsParam, err = DecodeBase64(params.Get("obfsparam"))
}
if params.Get("protoparam") != "" {
protoParam, err = DecodeBase64(params.Get("protoparam"))
}
if params.Get("remarks") != "" {
remarks, err = DecodeBase64(params.Get("remarks"))
}
if err != nil {
return model.Proxy{}, err
}
result := model.Proxy{
Name: remarks,
Type: "ssr",
Server: parts[0],
Port: port,
Protocol: parts[2],
Cipher: parts[3],
Obfs: parts[4],
Password: parts[5],
ObfsParam: obfsParam,
ProtocolParam: protoParam,
}
if result.Name == "" {
result.Name = result.Server
}
return result, nil
}

View File

@ -55,7 +55,11 @@ func ParseTrojan(proxy string) (model.Proxy, error) {
port, err := ParsePort(portStr)
if err != nil {
return model.Proxy{}, err
return model.Proxy{}, &ParseError{
Type: ErrInvalidPort,
Message: err.Error(),
Raw: proxy,
}
}
remarks := ""
@ -112,16 +116,13 @@ func ParseTrojan(proxy string) (model.Proxy, error) {
}
}
if network == "http" {
result.HTTP2Opts = model.HTTP2Options{
Host: []string{host},
Path: path,
}
}
// if network == "http" {
// // 未查到相关支持文档
// }
if network == "quic" {
// 未查到相关支持文档
}
// if network == "quic" {
// // 未查到相关支持文档
// }
if network == "grpc" {
result.GrpcOpts = model.GrpcOptions{

View File

@ -1,92 +1,156 @@
package parser
import (
"fmt"
"net/url"
"strconv"
"strings"
"sub2clash/constant"
"sub2clash/model"
)
func ParseVless(proxy string) (model.Proxy, error) {
// 判断是否以 vless:// 开头
if !strings.HasPrefix(proxy, "vless://") {
return model.Proxy{}, fmt.Errorf("invalid vless Url")
if !strings.HasPrefix(proxy, constant.VLESSPrefix) {
return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy}
}
// 分割
parts := strings.SplitN(strings.TrimPrefix(proxy, "vless://"), "@", 2)
if len(parts) != 2 {
return model.Proxy{}, fmt.Errorf("invalid vless Url")
urlParts := strings.SplitN(strings.TrimPrefix(proxy, constant.VLESSPrefix), "@", 2)
if len(urlParts) != 2 {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "missing character '@' in url",
Raw: proxy,
}
}
// 分割
serverInfo := strings.SplitN(parts[1], "#", 2)
serverInfo := strings.SplitN(urlParts[1], "#", 2)
serverAndPortAndParams := strings.SplitN(serverInfo[0], "?", 2)
if len(serverAndPortAndParams) != 2 {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "missing character '?' in url",
Raw: proxy,
}
}
serverAndPort := strings.SplitN(serverAndPortAndParams[0], ":", 2)
if len(serverAndPort) != 2 {
return model.Proxy{}, &ParseError{
Type: ErrInvalidStruct,
Message: "missing server host or port",
Raw: proxy,
}
}
server, portStr := serverAndPort[0], serverAndPort[1]
port, err := ParsePort(portStr)
if err != nil {
return model.Proxy{}, &ParseError{
Type: ErrInvalidPort,
Message: err.Error(),
Raw: proxy,
}
}
params, err := url.ParseQuery(serverAndPortAndParams[1])
if err != nil {
return model.Proxy{}, err
}
if len(serverAndPort) != 2 {
return model.Proxy{}, fmt.Errorf("invalid vless")
}
// 处理端口
port, err := strconv.Atoi(strings.TrimSpace(serverAndPort[1]))
if err != nil {
return model.Proxy{}, err
}
// 返回结果
result := model.Proxy{
Type: "vless",
Server: strings.TrimSpace(serverAndPort[0]),
Port: port,
UUID: strings.TrimSpace(parts[0]),
UDP: true,
Sni: params.Get("sni"),
Network: params.Get("type"),
Flow: params.Get("flow"),
ClientFingerprint: params.Get("fp"),
Servername: params.Get("sni"),
}
if params.Get("alpn") != "" {
result.Alpn = strings.Split(params.Get("alpn"), ",")
}
if params.Get("security") == "reality" {
result.TLS = true
result.RealityOpts = model.RealityOptions{
PublicKey: params.Get("pbk"),
ShortID: params.Get("sid"),
return model.Proxy{}, &ParseError{
Type: ErrCannotParseParams,
Raw: proxy,
Message: err.Error(),
}
}
if params.Get("type") == "ws" {
result.TLS = true
result.WSOpts = model.WSOptions{
Path: params.Get("path"),
Headers: map[string]string{
"Host": params.Get("host"),
},
}
}
if params.Get("type") == "grpc" {
result.TLS = true
result.GrpcOpts = model.GrpcOptions{
GrpcServiceName: params.Get("serviceName"),
}
}
// 如果有节点名称
remarks := ""
if len(serverInfo) == 2 {
if strings.Contains(serverInfo[1], "|") {
result.Name = strings.SplitN(serverInfo[1], "|", 2)[1]
remarks = strings.SplitN(serverInfo[1], "|", 2)[1]
} else {
result.Name, err = url.QueryUnescape(serverInfo[1])
remarks, err = url.QueryUnescape(serverInfo[1])
if err != nil {
return model.Proxy{}, err
return model.Proxy{}, &ParseError{
Type: ErrCannotParseParams,
Raw: proxy,
Message: err.Error(),
}
}
}
} else {
result.Name, err = url.QueryUnescape(serverAndPort[0])
remarks, err = url.QueryUnescape(server)
if err != nil {
return model.Proxy{}, err
}
}
uuid := strings.TrimSpace(urlParts[0])
flow, security, alpnStr, sni, insecure, fp, pbk, sid, path, host, serviceName, _type := params.Get("flow"), params.Get("security"), params.Get("alpn"), params.Get("sni"), params.Get("allowInsecure"), params.Get("fp"), params.Get("pbk"), params.Get("sid"), params.Get("path"), params.Get("host"), params.Get("serviceName"), params.Get("type")
// enableUTLS := fp != ""
insecureBool := insecure == "1"
var alpn []string
if strings.Contains(alpnStr, ",") {
alpn = strings.Split(alpnStr, ",")
} else {
alpn = nil
}
result := model.Proxy{
Type: "vless",
Server: server,
Name: remarks,
Port: port,
UUID: uuid,
Flow: flow,
}
if security == "tls" {
result.TLS = true
result.Alpn = alpn
result.Sni = sni
result.AllowInsecure = insecureBool
result.Fingerprint = fp
}
if security == "reality" {
result.TLS = true
result.RealityOpts = model.RealityOptions{
PublicKey: pbk,
ShortID: sid,
}
}
if _type == "ws" {
result.Network = "ws"
result.WSOpts = model.WSOptions{
Path: path,
}
if host != "" {
result.WSOpts.Headers = make(map[string]string)
result.WSOpts.Headers["Host"] = host
}
}
// if _type == "quic" {
// // 未查到相关支持文档
// }
if _type == "grpc" {
result.Network = "grpc"
result.Servername = serviceName
}
if _type == "http" {
hosts, err := url.QueryUnescape(host)
if err != nil {
return model.Proxy{}, &ParseError{
Type: ErrCannotParseParams,
Raw: proxy,
Message: err.Error(),
}
}
result.Network = "http"
result.HTTPOpts = model.HTTPOptions{
Headers: map[string][]string{"Host": strings.Split(hosts, ",")},
}
}
return result, nil
}

View File

@ -2,83 +2,96 @@ package parser
import (
"encoding/json"
"errors"
"net/url"
"strconv"
"strings"
"sub2clash/constant"
"sub2clash/model"
)
func ParseVmess(proxy string) (model.Proxy, error) {
// 判断是否以 vmess:// 开头
if !strings.HasPrefix(proxy, "vmess://") {
return model.Proxy{}, errors.New("invalid vmess url")
if !strings.HasPrefix(proxy, constant.VMessPrefix) {
return model.Proxy{}, &ParseError{Type: ErrInvalidPrefix, Raw: proxy}
}
// 解码
base64, err := DecodeBase64(strings.TrimPrefix(proxy, "vmess://"))
proxy = strings.TrimPrefix(proxy, constant.VMessPrefix)
base64, err := DecodeBase64(proxy)
if err != nil {
return model.Proxy{}, errors.New("invalid vmess url" + err.Error())
return model.Proxy{}, &ParseError{Type: ErrInvalidBase64, Raw: proxy, Message: err.Error()}
}
// 解析
var vmess model.VmessJson
err = json.Unmarshal([]byte(base64), &vmess)
if err != nil {
return model.Proxy{}, errors.New("invalid vmess url" + err.Error())
return model.Proxy{}, &ParseError{Type: ErrInvalidStruct, Raw: proxy, Message: err.Error()}
}
// 解析端口
port := 0
var port int
switch vmess.Port.(type) {
case string:
port, err = strconv.Atoi(vmess.Port.(string))
port, err = ParsePort(vmess.Port.(string))
if err != nil {
return model.Proxy{}, errors.New("invalid vmess url" + err.Error())
return model.Proxy{}, &ParseError{
Type: ErrInvalidPort,
Message: err.Error(),
Raw: proxy,
}
}
case float64:
port = int(vmess.Port.(float64))
}
// 解析Aid
aid := 0
switch vmess.Aid.(type) {
case string:
aid, err = strconv.Atoi(vmess.Aid.(string))
if err != nil {
return model.Proxy{}, errors.New("invalid vmess url" + err.Error())
return model.Proxy{}, &ParseError{Type: ErrInvalidStruct, Raw: proxy, Message: err.Error()}
}
case float64:
aid = int(vmess.Aid.(float64))
}
// 设置默认值
if vmess.Scy == "" {
vmess.Scy = "auto"
}
if vmess.Net == "ws" && vmess.Path == "" {
vmess.Path = "/"
}
if vmess.Net == "ws" && vmess.Host == "" {
vmess.Host = vmess.Add
}
name, err := url.QueryUnescape(vmess.Ps)
if err != nil {
name = vmess.Ps
}
// 返回结果
result := model.Proxy{
Name: name,
Type: "vmess",
Server: vmess.Add,
Port: port,
UUID: vmess.Id,
AlterID: aid,
Cipher: vmess.Scy,
UDP: true,
TLS: vmess.Tls == "tls",
Fingerprint: vmess.Fp,
ClientFingerprint: "chrome",
SkipCertVerify: true,
Servername: vmess.Add,
Network: vmess.Net,
Name: name,
Type: "vmess",
Server: vmess.Add,
Port: port,
UUID: vmess.Id,
AlterID: aid,
Cipher: vmess.Scy,
}
if vmess.Tls == "tls" {
var alpn []string
if strings.Contains(vmess.Alpn, ",") {
alpn = strings.Split(vmess.Alpn, ",")
} else {
alpn = nil
}
result.TLS = true
result.Fingerprint = vmess.Fp
result.Alpn = alpn
result.Servername = vmess.Sni
}
if vmess.Net == "ws" {
if vmess.Path == "" {
vmess.Path = "/"
}
if vmess.Host == "" {
vmess.Host = vmess.Add
}
result.Network = "ws"
result.WSOpts = model.WSOptions{
Path: vmess.Path,
Headers: map[string]string{
@ -86,5 +99,25 @@ func ParseVmess(proxy string) (model.Proxy, error) {
},
}
}
// if vmess.Net == "quic" {
// // 未查到相关支持文档
// }
if vmess.Net == "grpc" {
result.GrpcOpts = model.GrpcOptions{
GrpcServiceName: vmess.Path,
}
result.Network = "grpc"
}
if vmess.Net == "h2" {
result.HTTP2Opts = model.HTTP2Options{
Host: strings.Split(vmess.Host, ","),
Path: vmess.Path,
}
result.Network = "h2"
}
return result, nil
}

View File

@ -1,22 +1,22 @@
package test
import (
"sub2clash/parser"
"strings"
"testing"
"gopkg.in/yaml.v3"
)
func TestHy2Parser(t *testing.T) {
res, err := parser.ParseTrojan("trojan://Abse64hhjewrs@test.com:8443?type=ws&path=%2Fx&host=test.com&security=tls&fp=&alpn=http%2F1.1&sni=test.com#test")
if err != nil {
t.Log(err.Error())
t.Fail()
}
bytes, err := yaml.Marshal(res)
if err != nil {
t.Log(err.Error())
t.Fail()
}
t.Log(string(bytes))
func TestParser(t *testing.T) {
// res, err := parser.ParseTrojan("trojan://Abse64hhjewrs@test.com:8443?type=ws&path=%2Fx&host=test.com&security=tls&fp=&alpn=http%2F1.1&sni=test.com#test")
// if err != nil {
// t.Log(err.Error())
// t.Fail()
// }
// bytes, err := yaml.Marshal(res)
// if err != nil {
// t.Log(err.Error())
// t.Fail()
// }
// t.Log(string(bytes))
t.Log(strings.SplitN("123456", "/?", 2))
}

View File

@ -2,6 +2,7 @@ package utils
import (
"strings"
"sub2clash/constant"
"sub2clash/logger"
"sub2clash/model"
"sub2clash/parser"
@ -106,25 +107,25 @@ func ParseProxy(proxies ...string) []model.Proxy {
var proxyItem model.Proxy
var err error
// 解析节点
if strings.HasPrefix(proxy, "ss://") {
proxyItem, err = parser.ParseSS(proxy)
if strings.HasPrefix(proxy, constant.ShadowsocksPrefix) {
proxyItem, err = parser.ParseShadowsocks(proxy)
}
if strings.HasPrefix(proxy, "trojan://") {
if strings.HasPrefix(proxy, constant.TrojanPrefix) {
proxyItem, err = parser.ParseTrojan(proxy)
}
if strings.HasPrefix(proxy, "vmess://") {
if strings.HasPrefix(proxy, constant.VMessPrefix) {
proxyItem, err = parser.ParseVmess(proxy)
}
if strings.HasPrefix(proxy, "vless://") {
if strings.HasPrefix(proxy, constant.VLESSPrefix) {
proxyItem, err = parser.ParseVless(proxy)
}
if strings.HasPrefix(proxy, "ssr://") {
if strings.HasPrefix(proxy, constant.ShadowsocksRPrefix) {
proxyItem, err = parser.ParseShadowsocksR(proxy)
}
if strings.HasPrefix(proxy, "hysteria2://") || strings.HasPrefix(proxy, "hy2://") {
if strings.HasPrefix(proxy, constant.Hysteria2Prefix1) || strings.HasPrefix(proxy, constant.Hysteria2Prefix2) {
proxyItem, err = parser.ParseHysteria2(proxy)
}
if strings.HasPrefix(proxy, "hysteria://") {
if strings.HasPrefix(proxy, constant.HysteriaPrefix) {
proxyItem, err = parser.ParseHysteria(proxy)
}
if err == nil {