2fa2f7f816
Changelog: Share link import and mrs rule sets
287 lines
10 KiB
Go
287 lines
10 KiB
Go
package core
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/metacubex/mihomo/common/convert"
|
|
)
|
|
|
|
func lookupField(proxy map[string]any, path string) any {
|
|
var value any = proxy
|
|
for _, key := range strings.Split(path, ".") {
|
|
mapping, ok := value.(map[string]any)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
value = mapping[key]
|
|
}
|
|
return value
|
|
}
|
|
|
|
func TestShareLinksReadBackThroughTheConverter(t *testing.T) {
|
|
cases := []struct {
|
|
proxy map[string]any
|
|
prefix string
|
|
want map[string]string
|
|
}{
|
|
{
|
|
proxy: map[string]any{
|
|
"name": "ss node", "type": "ss", "server": "a.example", "port": 8388,
|
|
"cipher": "aes-256-gcm", "password": "p@ss:word", "udp-over-tcp": true,
|
|
"plugin": "obfs", "plugin-opts": map[string]any{"mode": "tls", "host": "cdn.example"},
|
|
},
|
|
prefix: "ss://aes-256-gcm:p%40ss%3Aword@a.example:8388",
|
|
want: map[string]string{
|
|
"name": "ss node", "server": "a.example", "port": "8388",
|
|
"cipher": "aes-256-gcm", "password": "p@ss:word", "udp-over-tcp": "true",
|
|
"plugin": "obfs", "plugin-opts.mode": "tls", "plugin-opts.host": "cdn.example",
|
|
},
|
|
},
|
|
{
|
|
proxy: map[string]any{
|
|
"name": "ss 2022", "type": "ss", "server": "2001:db8::1", "port": 443,
|
|
"cipher": "2022-blake3-aes-128-gcm", "password": "AAAAAAAAAAAAAAAAAAAAAA==",
|
|
},
|
|
want: map[string]string{
|
|
"server": "2001:db8::1", "cipher": "2022-blake3-aes-128-gcm",
|
|
"password": "AAAAAAAAAAAAAAAAAAAAAA==",
|
|
},
|
|
},
|
|
{
|
|
proxy: map[string]any{
|
|
"name": "ssr", "type": "ssr", "server": "b.example", "port": 443,
|
|
"cipher": "chacha20-ietf", "password": "secret", "obfs": "tls1.2_ticket_auth",
|
|
"protocol": "auth_aes128_md5", "obfs-param": "obfs.example", "protocol-param": "1:p",
|
|
},
|
|
want: map[string]string{
|
|
"name": "ssr", "server": "b.example", "port": "443", "cipher": "chacha20-ietf",
|
|
"password": "secret", "obfs": "tls1.2_ticket_auth", "protocol": "auth_aes128_md5",
|
|
"obfs-param": "obfs.example", "protocol-param": "1:p",
|
|
},
|
|
},
|
|
{
|
|
proxy: map[string]any{
|
|
"name": "vmess ws", "type": "vmess", "server": "c.example", "port": 443,
|
|
"uuid": "b831381d-6324-4d53-ad4f-8cda48b30811", "alterId": 64, "cipher": "auto",
|
|
"tls": true, "servername": "sni.example", "network": "ws",
|
|
"ws-opts": map[string]any{
|
|
"path": "/ray", "headers": map[string]any{"Host": "host.example"},
|
|
"max-early-data": 2048,
|
|
},
|
|
},
|
|
prefix: "vmess://eyJ",
|
|
want: map[string]string{
|
|
"name": "vmess ws", "server": "c.example", "port": "443",
|
|
"uuid": "b831381d-6324-4d53-ad4f-8cda48b30811", "alterId": "64", "cipher": "auto",
|
|
"tls": "true", "servername": "sni.example", "network": "ws",
|
|
"ws-opts.path": "/ray", "ws-opts.headers.Host": "host.example",
|
|
"ws-opts.max-early-data": "2048",
|
|
},
|
|
},
|
|
{
|
|
proxy: map[string]any{
|
|
"name": "vmess grpc", "type": "vmess", "server": "c.example", "port": "443",
|
|
"uuid": "u", "cipher": "aes-128-gcm", "network": "grpc",
|
|
"grpc-opts": map[string]any{"grpc-service-name": "svc"},
|
|
},
|
|
prefix: "vmess://u@c.example:443?",
|
|
want: map[string]string{
|
|
"network": "grpc", "grpc-opts.grpc-service-name": "svc", "tls": "<nil>",
|
|
"alterId": "0", "cipher": "aes-128-gcm",
|
|
},
|
|
},
|
|
{
|
|
proxy: map[string]any{
|
|
"name": "vmess aead ws", "type": "vmess", "server": "c.example", "port": 443,
|
|
"uuid": "u", "alterId": 0, "cipher": "auto", "tls": true,
|
|
"servername": "sni.example", "client-fingerprint": "chrome", "network": "ws",
|
|
"ws-opts": map[string]any{
|
|
"path": "/ray", "headers": map[string]any{"Host": "host.example"},
|
|
},
|
|
},
|
|
prefix: "vmess://u@c.example:443?",
|
|
want: map[string]string{
|
|
"name": "vmess aead ws", "cipher": "auto", "tls": "true",
|
|
"servername": "sni.example", "client-fingerprint": "chrome", "network": "ws",
|
|
"ws-opts.path": "/ray", "ws-opts.headers.Host": "host.example",
|
|
},
|
|
},
|
|
{
|
|
proxy: map[string]any{
|
|
"name": "vless reality", "type": "vless", "server": "d.example", "port": 443,
|
|
"uuid": "u-1", "flow": "xtls-rprx-vision", "tls": true, "servername": "www.example",
|
|
"client-fingerprint": "firefox", "network": "tcp",
|
|
"reality-opts": map[string]any{"public-key": "pk", "short-id": "ab"},
|
|
},
|
|
want: map[string]string{
|
|
"name": "vless reality", "uuid": "u-1", "flow": "xtls-rprx-vision", "tls": "true",
|
|
"servername": "www.example", "client-fingerprint": "firefox", "network": "tcp",
|
|
"reality-opts.public-key": "pk", "reality-opts.short-id": "ab", "xudp": "<nil>",
|
|
},
|
|
},
|
|
{
|
|
proxy: map[string]any{
|
|
"name": "vless h2", "type": "vless", "server": "d.example", "port": 443,
|
|
"uuid": "u-2", "xudp": true, "network": "h2",
|
|
"h2-opts": map[string]any{"path": "/h2", "host": []any{"h2.example"}},
|
|
},
|
|
want: map[string]string{
|
|
"network": "h2", "h2-opts.path": "/h2", "h2-opts.host": "[h2.example]", "xudp": "true",
|
|
},
|
|
},
|
|
{
|
|
proxy: map[string]any{
|
|
"name": "trojan", "type": "trojan", "server": "e.example", "port": 443,
|
|
"password": "pass word", "sni": "sni.example", "skip-cert-verify": true,
|
|
"alpn": []any{"h2", "http/1.1"}, "network": "grpc",
|
|
"grpc-opts": map[string]any{"grpc-service-name": "g"},
|
|
},
|
|
want: map[string]string{
|
|
"name": "trojan", "password": "pass word", "sni": "sni.example",
|
|
"skip-cert-verify": "true", "alpn": "[h2 http/1.1]", "network": "grpc",
|
|
"grpc-opts.grpc-service-name": "g",
|
|
},
|
|
},
|
|
{
|
|
proxy: map[string]any{
|
|
"name": "trojan ws", "type": "trojan", "server": "e.example", "port": 443,
|
|
"password": "p", "network": "ws", "ws-opts": map[string]any{"path": "/ws"},
|
|
},
|
|
want: map[string]string{"network": "ws", "ws-opts.path": "/ws"},
|
|
},
|
|
{
|
|
proxy: map[string]any{
|
|
"name": "hy", "type": "hysteria", "server": "f.example", "port": 443,
|
|
"auth-str": "token", "up": "30 Mbps", "down": "200 Mbps", "sni": "hy.example",
|
|
"protocol": "udp", "obfs": "o",
|
|
},
|
|
want: map[string]string{
|
|
"auth_str": "token", "up": "30 Mbps", "down": "200 Mbps", "sni": "hy.example",
|
|
"protocol": "udp", "obfs": "o",
|
|
},
|
|
},
|
|
{
|
|
proxy: map[string]any{
|
|
"name": "hy2", "type": "hysteria2", "server": "g.example", "port": 443,
|
|
"ports": "443,20000-30000", "password": "pw", "obfs": "salamander",
|
|
"obfs-password": "op", "sni": "hy2.example", "skip-cert-verify": true,
|
|
},
|
|
want: map[string]string{
|
|
"name": "hy2", "port": "443", "ports": "443,20000-30000", "password": "pw",
|
|
"obfs": "salamander", "obfs-password": "op", "sni": "hy2.example",
|
|
"skip-cert-verify": "true",
|
|
},
|
|
},
|
|
{
|
|
proxy: map[string]any{
|
|
"name": "tuic", "type": "tuic", "server": "h.example", "port": 443,
|
|
"uuid": "u-3", "password": "pw", "congestion-controller": "bbr",
|
|
"udp-relay-mode": "quic", "alpn": []string{"h3"},
|
|
},
|
|
want: map[string]string{
|
|
"uuid": "u-3", "password": "pw", "congestion-controller": "bbr",
|
|
"udp-relay-mode": "quic", "alpn": "[h3]",
|
|
},
|
|
},
|
|
{
|
|
proxy: map[string]any{
|
|
"name": "socks", "type": "socks5", "server": "i.example", "port": 1080,
|
|
"username": "me", "password": "secret",
|
|
},
|
|
want: map[string]string{
|
|
"name": "socks", "type": "socks5", "username": "me", "password": "secret",
|
|
},
|
|
},
|
|
{
|
|
proxy: map[string]any{
|
|
"name": "https", "type": "http", "server": "j.example", "port": 443,
|
|
"username": "test", "tls": true,
|
|
},
|
|
want: map[string]string{
|
|
"type": "http", "username": "test", "password": "", "tls": "true",
|
|
},
|
|
},
|
|
{
|
|
proxy: map[string]any{
|
|
"name": "anytls", "type": "anytls", "server": "k.example", "port": 443,
|
|
"password": "pw", "sni": "a.example", "skip-cert-verify": true,
|
|
},
|
|
want: map[string]string{
|
|
"name": "anytls", "password": "pw", "sni": "a.example", "skip-cert-verify": "true",
|
|
},
|
|
},
|
|
{
|
|
proxy: map[string]any{
|
|
"name": "mieru", "type": "mieru", "server": "l.example", "port": 2999,
|
|
"transport": "TCP", "username": "u", "password": "p", "multiplexing": "MULTIPLEXING_LOW",
|
|
},
|
|
want: map[string]string{
|
|
"name": "mieru:2999/TCP", "port": "2999", "transport": "TCP", "username": "u",
|
|
"password": "p", "multiplexing": "MULTIPLEXING_LOW",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
name, _ := tc.proxy["name"].(string)
|
|
t.Run(name, func(t *testing.T) {
|
|
links := handleEncodeShareLinks([]map[string]any{tc.proxy})
|
|
if !strings.HasPrefix(links[0], tc.prefix) {
|
|
t.Errorf("link %q does not start with %q", links[0], tc.prefix)
|
|
}
|
|
proxies, err := convert.ConvertsV2Ray([]byte(links[0]))
|
|
if err != nil || len(proxies) != 1 {
|
|
t.Fatalf("link %q read back as %v, %v", links[0], proxies, err)
|
|
}
|
|
for path, want := range tc.want {
|
|
if got := fmt.Sprint(lookupField(proxies[0], path)); got != want {
|
|
t.Errorf("%s from %q = %q, want %q", path, links[0], got, want)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestShareLinksLeaveOutWhatNoLinkCarries(t *testing.T) {
|
|
links := handleEncodeShareLinks([]map[string]any{
|
|
{"name": "wg", "type": "wireguard", "server": "a.example", "port": 51820},
|
|
{"name": "no server", "type": "ss", "cipher": "aes-128-gcm", "password": "p"},
|
|
{"name": "ssr v6", "type": "ssr", "server": "2001:db8::1", "port": 443},
|
|
{
|
|
"name": "realm", "type": "hysteria2", "server": "a.example", "port": 443,
|
|
"realm-opts": map[string]any{"enable": true},
|
|
},
|
|
{
|
|
"name": "shadow-tls", "type": "ss", "server": "a.example", "port": 443,
|
|
"cipher": "aes-128-gcm", "password": "p", "plugin": "shadow-tls",
|
|
"plugin-opts": map[string]any{"host": "cdn.example", "password": "x"},
|
|
},
|
|
{
|
|
"name": "trojan ws host", "type": "trojan", "server": "a.example", "port": 443,
|
|
"password": "p", "network": "ws",
|
|
"ws-opts": map[string]any{"path": "/ws", "headers": map[string]any{"Host": "cdn.example"}},
|
|
},
|
|
})
|
|
for i, link := range links {
|
|
if link != "" {
|
|
t.Errorf("mapping %d encoded to %q", i, link)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecodeShareLinksAnswersEveryLine(t *testing.T) {
|
|
results := handleDecodeShareLinks([]string{
|
|
"trojan://pw@a.example:443#one",
|
|
"not a link",
|
|
"socks5://b.example:1080#two",
|
|
})
|
|
if len(results) != 3 || len(results[0]) != 1 || len(results[1]) != 0 || len(results[2]) != 1 {
|
|
t.Fatalf("results = %v", results)
|
|
}
|
|
if results[0][0]["name"] != "one" || results[2][0]["name"] != "two" {
|
|
t.Errorf("names = %v, %v", results[0][0]["name"], results[2][0]["name"])
|
|
}
|
|
}
|