service_sharelink_test.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. package sub
  2. import (
  3. "strings"
  4. "testing"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  6. )
  7. // shareLinkInbound builds a VLESS inbound with one client and the given stream
  8. // settings, mirroring flowTestInbound but without forcing a flow.
  9. func shareLinkInbound(streamSettings string) *model.Inbound {
  10. return &model.Inbound{
  11. Listen: "203.0.113.1",
  12. Port: 443,
  13. Protocol: model.VLESS,
  14. Remark: "sharelink",
  15. Settings: `{"clients":[{"id":"11111111-2222-4333-8444-555555555555","email":"user"}],"decryption":"none","encryption":"none"}`,
  16. StreamSettings: streamSettings,
  17. }
  18. }
  19. // TestGenVlessLink_TLSParamsMapped locks every field that applyShareTLSParams
  20. // (service.go:1029) writes into a TLS share link. Without these assertions a mutant
  21. // that drops `sni`, swaps a key, or skips `pcs`/`alpn`/`fp` survives the whole suite —
  22. // the existing flow tests only check `flow=`.
  23. func TestGenVlessLink_TLSParamsMapped(t *testing.T) {
  24. stream := `{
  25. "network":"tcp","security":"tls",
  26. "tcpSettings":{"header":{"type":"none"}},
  27. "tlsSettings":{
  28. "serverName":"sni.example.com",
  29. "alpn":["h2","http/1.1"],
  30. "settings":{"fingerprint":"chrome","pinnedPeerCertSha256":["YWJj"]}
  31. }
  32. }`
  33. s := &SubService{}
  34. link := s.genVlessLink(shareLinkInbound(stream), "user")
  35. // url.Values.Encode() percent-encodes values: "," -> %2C, "/" -> %2F.
  36. wants := []string{
  37. "security=tls",
  38. "sni=sni.example.com",
  39. "fp=chrome",
  40. "alpn=h2%2Chttp%2F1.1",
  41. "pcs=YWJj",
  42. }
  43. for _, w := range wants {
  44. if !strings.Contains(link, w) {
  45. t.Fatalf("TLS link missing %q\n got: %s", w, link)
  46. }
  47. }
  48. }
  49. // Locks the reality field mapping of applyShareRealityParams; a configured
  50. // spiderX must round-trip verbatim (#5718), distinct pbk/sid catch a swap mutant.
  51. func TestGenVlessLink_RealityParamsMapped(t *testing.T) {
  52. stream := `{
  53. "network":"tcp","security":"reality",
  54. "tcpSettings":{"header":{"type":"none"}},
  55. "realitySettings":{
  56. "serverNames":["reality.example.com"],
  57. "shortIds":["ab12cd"],
  58. "settings":{"publicKey":"PBKvalue","fingerprint":"firefox","spiderX":"/mypath"}
  59. }
  60. }`
  61. s := &SubService{}
  62. link := s.genVlessLink(shareLinkInbound(stream), "user")
  63. wants := []string{
  64. "security=reality",
  65. "sni=reality.example.com",
  66. "pbk=PBKvalue",
  67. "sid=ab12cd",
  68. "fp=firefox",
  69. "spx=%2Fmypath",
  70. }
  71. for _, w := range wants {
  72. if !strings.Contains(link, w) {
  73. t.Fatalf("reality link missing %q\n got: %s", w, link)
  74. }
  75. }
  76. // A pbk<->sid swap must not silently pass: pbk must not carry the shortId.
  77. if strings.Contains(link, "pbk=ab12cd") || strings.Contains(link, "sid=PBKvalue") {
  78. t.Fatalf("reality pbk/sid mapping crossed: %s", link)
  79. }
  80. }
  81. // Without a configured spiderX, spx must still fall back to a random
  82. // "/"-prefixed value so clients always receive a plausible path.
  83. func TestGenVlessLink_RealitySpiderXFallsBackToRandom(t *testing.T) {
  84. stream := `{
  85. "network":"tcp","security":"reality",
  86. "tcpSettings":{"header":{"type":"none"}},
  87. "realitySettings":{
  88. "serverNames":["reality.example.com"],
  89. "shortIds":["ab12cd"],
  90. "settings":{"publicKey":"PBKvalue","fingerprint":"firefox"}
  91. }
  92. }`
  93. s := &SubService{}
  94. link := s.genVlessLink(shareLinkInbound(stream), "user")
  95. if !strings.Contains(link, "spx=%2F") {
  96. t.Fatalf("reality link missing random spx fallback\n got: %s", link)
  97. }
  98. }