|
|
@@ -1,6 +1,7 @@
|
|
|
package link
|
|
|
|
|
|
import (
|
|
|
+ "encoding/base64"
|
|
|
"net/url"
|
|
|
"strings"
|
|
|
"testing"
|
|
|
@@ -113,6 +114,115 @@ func TestSanitizeFinalMaskQuicParams_ClampsAndRejects(t *testing.T) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+func TestParseShadowsocks(t *testing.T) {
|
|
|
+ modernUser := base64.StdEncoding.EncodeToString([]byte("aes-256-gcm:secretpass"))
|
|
|
+ legacyBody := base64.StdEncoding.EncodeToString([]byte("aes-256-gcm:[email protected]:8388"))
|
|
|
+ cases := []struct {
|
|
|
+ name string
|
|
|
+ link string
|
|
|
+ host string
|
|
|
+ port int
|
|
|
+ method string
|
|
|
+ pass string
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ name: "modern",
|
|
|
+ link: "ss://" + modernUser + "@1.2.3.4:8388#node",
|
|
|
+ host: "1.2.3.4",
|
|
|
+ port: 8388,
|
|
|
+ method: "aes-256-gcm",
|
|
|
+ pass: "secretpass",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "modern with plugin query",
|
|
|
+ link: "ss://" + modernUser + "@1.2.3.4:8388?plugin=v2ray-plugin#node",
|
|
|
+ host: "1.2.3.4",
|
|
|
+ port: 8388,
|
|
|
+ method: "aes-256-gcm",
|
|
|
+ pass: "secretpass",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "modern sip002 slash query",
|
|
|
+ link: "ss://" + modernUser + "@1.2.3.4:8388/?plugin=obfs-local%3Bobfs%3Dhttp#node",
|
|
|
+ host: "1.2.3.4",
|
|
|
+ port: 8388,
|
|
|
+ method: "aes-256-gcm",
|
|
|
+ pass: "secretpass",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "legacy",
|
|
|
+ link: "ss://" + legacyBody + "#node",
|
|
|
+ host: "1.2.3.4",
|
|
|
+ port: 8388,
|
|
|
+ method: "aes-256-gcm",
|
|
|
+ pass: "secretpass",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "base64url userinfo with plugin and trailing slash",
|
|
|
+ link: "ss://" + base64.RawURLEncoding.EncodeToString([]byte("aes-128-gcm:pa+ss/word")) + "@1.2.3.4:8388/?plugin=obfs-local%3Bobfs%3Dhttp#node",
|
|
|
+ host: "1.2.3.4",
|
|
|
+ port: 8388,
|
|
|
+ method: "aes-128-gcm",
|
|
|
+ pass: "pa+ss/word",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "sip022 percent-encoded userinfo",
|
|
|
+ link: "ss://2022-blake3-aes-256-gcm:YctPZ6U7xPPcU%2Bgp3u%2B0tx%2FtRizJN9K8y%2BuKlW2qjlI%[email protected]:8888#Example3",
|
|
|
+ host: "example.com",
|
|
|
+ port: 8888,
|
|
|
+ method: "2022-blake3-aes-256-gcm",
|
|
|
+ pass: "YctPZ6U7xPPcU+gp3u+0tx/tRizJN9K8y+uKlW2qjlI=",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "sip022 dual-key password with type query preserves inner colon",
|
|
|
+ link: "ss://2022-blake3-aes-256-gcm:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%3D:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB%[email protected]:9999?type=tcp#node",
|
|
|
+ host: "1.2.3.4",
|
|
|
+ port: 9999,
|
|
|
+ method: "2022-blake3-aes-256-gcm",
|
|
|
+ pass: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=",
|
|
|
+ },
|
|
|
+ }
|
|
|
+ for _, c := range cases {
|
|
|
+ t.Run(c.name, func(t *testing.T) {
|
|
|
+ res, err := ParseLink(c.link)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("parse ss: %v", err)
|
|
|
+ }
|
|
|
+ if res.Outbound["protocol"] != "shadowsocks" {
|
|
|
+ t.Fatalf("protocol = %v, want shadowsocks", res.Outbound["protocol"])
|
|
|
+ }
|
|
|
+ srv := res.Outbound["settings"].(map[string]any)["servers"].([]any)[0].(map[string]any)
|
|
|
+ if srv["address"] != c.host {
|
|
|
+ t.Errorf("address = %v, want %v", srv["address"], c.host)
|
|
|
+ }
|
|
|
+ if srv["port"] != c.port {
|
|
|
+ t.Errorf("port = %v, want %v", srv["port"], c.port)
|
|
|
+ }
|
|
|
+ if srv["method"] != c.method {
|
|
|
+ t.Errorf("method = %v, want %v", srv["method"], c.method)
|
|
|
+ }
|
|
|
+ if srv["password"] != c.pass {
|
|
|
+ t.Errorf("password = %v, want %v", srv["password"], c.pass)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestParseShadowsocksBadPort(t *testing.T) {
|
|
|
+ user := base64.StdEncoding.EncodeToString([]byte("aes-256-gcm:secretpass"))
|
|
|
+ cases := map[string]string{
|
|
|
+ "modern": "ss://" + user + "@1.2.3.4:notaport#node",
|
|
|
+ "legacy": "ss://" + base64.StdEncoding.EncodeToString([]byte("aes-256-gcm:[email protected]:notaport")) + "#node",
|
|
|
+ }
|
|
|
+ for name, link := range cases {
|
|
|
+ t.Run(name, func(t *testing.T) {
|
|
|
+ if _, err := ParseLink(link); err == nil {
|
|
|
+ t.Errorf("expected parse error for non-numeric port, got nil")
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
func TestParseSubscriptionBody_Base64(t *testing.T) {
|
|
|
// base64 of the two joined links:
|
|
|
// vless://u@h:443?type=tcp#A\nvless://u2@h2:443?type=tcp#B
|