| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- package sub
- import (
- "encoding/base64"
- "net/http"
- "net/http/httptest"
- "net/url"
- "testing"
- "github.com/goccy/go-yaml"
- "github.com/mhsanaei/3x-ui/v3/internal/database"
- "github.com/mhsanaei/3x-ui/v3/internal/database/model"
- )
- const clashExternalVlessLink = "vless://[email protected]:443?type=tcp&security=reality&sni=example.com&pbk=test-public-key&sid=ab12&fp=chrome&flow=xtls-rprx-vision"
- func TestClashExternalVlessEncryption(t *testing.T) {
- svc := NewSubClashService(false, "", &SubService{})
- for _, tc := range []struct {
- name string
- query string
- want string
- }{
- {"encrypted", "&encryption=" + url.QueryEscape(testMlkemEncryption), testMlkemEncryption},
- {"trimmed", "&encryption=" + url.QueryEscape(" \t"+testMlkemEncryption+" \n"), testMlkemEncryption},
- {"none", "&encryption=none", ""},
- {"trimmed none", "&encryption=%20none%20", ""},
- {"empty", "&encryption=", ""},
- {"whitespace", "&encryption=%20%09", ""},
- {"missing", "", ""},
- } {
- t.Run(tc.name, func(t *testing.T) {
- proxy := svc.clashProxyFromExternal(clashExternalVlessLink+tc.query+"#external", "external")
- if proxy == nil {
- t.Fatal("expected a VLESS proxy")
- }
- if got, exists := proxy["encryption"]; tc.want == "" {
- if exists {
- t.Errorf("plain VLESS encryption should be omitted, got %v", got)
- }
- } else if got != tc.want {
- t.Errorf("encryption = %v, want %q", got, tc.want)
- }
- for key, want := range map[string]any{
- "type": "vless", "uuid": "22222222-2222-4222-8222-222222222222",
- "server": "198.51.100.9", "port": 443, "network": "tcp",
- "flow": "xtls-rprx-vision", "tls": true, "servername": "example.com",
- } {
- if got := proxy[key]; got != want {
- t.Errorf("%s = %v, want %v", key, got, want)
- }
- }
- if _, exists := proxy["packet-encoding"]; exists {
- t.Error("VLESS encryption must not be exported as packet-encoding")
- }
- })
- }
- }
- // Regression for #6572: both pasted links and fetched subscriptions must keep
- // encryption when their nodes are merged with the client's local inbound.
- func TestClashMergedExternalVlessEncryption(t *testing.T) {
- link := clashExternalVlessLink + "&encryption=" + url.QueryEscape(testMlkemEncryption) + "#external"
- for _, source := range []string{"link", "subscription-plain", "subscription-base64"} {
- t.Run(source, func(t *testing.T) {
- seedSubDB(t)
- resetSubscriptionCache(t)
- seedSubInbound(t, "merged-vless", "local", 10001, 1, wsTLSStream)
- db := database.GetDB()
- var client model.ClientRecord
- if err := db.Where("email = ?", "local@e").First(&client).Error; err != nil {
- t.Fatal(err)
- }
- entry := model.ClientExternalLink{ClientId: client.Id, Kind: model.ExternalLinkKindLink, Value: link}
- if source != "link" {
- body := link + "\n"
- if source == "subscription-base64" {
- body = base64.StdEncoding.EncodeToString([]byte(body))
- }
- srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- _, _ = w.Write([]byte(body))
- }))
- defer srv.Close()
- previousClient := subscriptionHTTPClient
- subscriptionHTTPClient = srv.Client()
- t.Cleanup(func() { subscriptionHTTPClient = previousClient })
- entry.Kind = model.ExternalLinkKindSubscription
- entry.Value = srv.URL
- }
- if err := db.Create(&entry).Error; err != nil {
- t.Fatal(err)
- }
- w := fetchClashSub(t, clashSubRouter(t), "/clash/merged-vless?view=raw")
- if w.Code != http.StatusOK {
- t.Fatalf("status = %d, want 200: %s", w.Code, w.Body.String())
- }
- var config struct {
- Proxies []struct {
- UUID string `yaml:"uuid"`
- Encryption string `yaml:"encryption"`
- } `yaml:"proxies"`
- }
- if err := yaml.Unmarshal(w.Body.Bytes(), &config); err != nil {
- t.Fatalf("decode Clash YAML: %v", err)
- }
- if len(config.Proxies) != 2 {
- t.Fatalf("expected local and external proxies, got %#v", config.Proxies)
- }
- want := map[string]string{client.UUID: "", "22222222-2222-4222-8222-222222222222": testMlkemEncryption}
- for _, proxy := range config.Proxies {
- encryption, ok := want[proxy.UUID]
- if !ok || proxy.Encryption != encryption {
- t.Errorf("unexpected proxy: %#v", proxy)
- }
- delete(want, proxy.UUID)
- }
- if len(want) != 0 {
- t.Errorf("missing proxies: %v", want)
- }
- })
- }
- }
|