|
@@ -2,6 +2,7 @@ package link
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
"encoding/base64"
|
|
"encoding/base64"
|
|
|
|
|
+ "encoding/json"
|
|
|
"net/url"
|
|
"net/url"
|
|
|
"strings"
|
|
"strings"
|
|
|
"testing"
|
|
"testing"
|
|
@@ -503,3 +504,46 @@ func TestSlugAndSuggest(t *testing.T) {
|
|
|
t.Errorf("unicode suggest tag got %q", got)
|
|
t.Errorf("unicode suggest tag got %q", got)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+// The obfs-local plugin the panel exports carries the only description of
|
|
|
|
|
+// shadowsocks tcp/http obfuscation, so it has to become that header.
|
|
|
|
|
+func TestParseShadowsocksObfsLocalPlugin(t *testing.T) {
|
|
|
|
|
+ user := base64.RawURLEncoding.EncodeToString([]byte("aes-256-gcm:secretpass"))
|
|
|
|
|
+ const httpObfs = "obfs-local;obfs=http;obfs-host=obfs.example.com"
|
|
|
|
|
+ for _, tc := range []struct {
|
|
|
|
|
+ name, query, wantHeader, wantHost string
|
|
|
|
|
+ }{
|
|
|
|
|
+ {"http obfs becomes the tcp header", "plugin=" + url.QueryEscape(httpObfs), "http", "obfs.example.com"},
|
|
|
|
|
+ {"unencoded separators map the same way", "plugin=" + httpObfs, "http", "obfs.example.com"},
|
|
|
|
|
+ {"tls obfs has no xray header", "plugin=" + url.QueryEscape("obfs-local;obfs=tls"), "none", ""},
|
|
|
|
|
+ {"an unrelated plugin is left alone", "plugin=v2ray-plugin", "none", ""},
|
|
|
|
|
+ } {
|
|
|
|
|
+ t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
+ res, err := ParseLink("ss://" + user + "@1.2.3.4:8388/?" + tc.query + "#node")
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatalf("parse ss: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+ raw, err := json.Marshal(res.Outbound["streamSettings"])
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatalf("marshal stream: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+ var stream map[string]any
|
|
|
|
|
+ _ = json.Unmarshal(raw, &stream)
|
|
|
|
|
+ tcp, _ := stream["tcpSettings"].(map[string]any)
|
|
|
|
|
+ header, _ := tcp["header"].(map[string]any)
|
|
|
|
|
+ if header == nil || header["type"] != tc.wantHeader {
|
|
|
|
|
+ t.Fatalf("header = %v, want type %q", header, tc.wantHeader)
|
|
|
|
|
+ }
|
|
|
|
|
+ request, _ := header["request"].(map[string]any)
|
|
|
|
|
+ headers, _ := request["headers"].(map[string]any)
|
|
|
|
|
+ hosts, _ := headers["Host"].([]any)
|
|
|
|
|
+ got := ""
|
|
|
|
|
+ if len(hosts) > 0 {
|
|
|
|
|
+ got, _ = hosts[0].(string)
|
|
|
|
|
+ }
|
|
|
|
|
+ if got != tc.wantHost {
|
|
|
|
|
+ t.Errorf("host = %q, want %q", got, tc.wantHost)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+}
|