service_sharelink_test.go 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  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{remarkModel: "-ieo"}
  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. // TestGenVlessLink_RealityParamsMapped locks the reality field mapping
  50. // (applyShareRealityParams, service.go:1147). serverNames/shortIds are single-element
  51. // so random.Num is deterministic (index 0); spx is random so it is asserted by prefix.
  52. // Distinct pbk/sid values catch a pbk<->sid swap mutant.
  53. func TestGenVlessLink_RealityParamsMapped(t *testing.T) {
  54. stream := `{
  55. "network":"tcp","security":"reality",
  56. "tcpSettings":{"header":{"type":"none"}},
  57. "realitySettings":{
  58. "serverNames":["reality.example.com"],
  59. "shortIds":["ab12cd"],
  60. "settings":{"publicKey":"PBKvalue","fingerprint":"firefox"}
  61. }
  62. }`
  63. s := &SubService{remarkModel: "-ieo"}
  64. link := s.genVlessLink(shareLinkInbound(stream), "user")
  65. wants := []string{
  66. "security=reality",
  67. "sni=reality.example.com",
  68. "pbk=PBKvalue",
  69. "sid=ab12cd",
  70. "fp=firefox",
  71. "spx=%2F", // "/" + random.Seq(15), percent-encoded leading slash
  72. }
  73. for _, w := range wants {
  74. if !strings.Contains(link, w) {
  75. t.Fatalf("reality link missing %q\n got: %s", w, link)
  76. }
  77. }
  78. // A pbk<->sid swap must not silently pass: pbk must not carry the shortId.
  79. if strings.Contains(link, "pbk=ab12cd") || strings.Contains(link, "sid=PBKvalue") {
  80. t.Fatalf("reality pbk/sid mapping crossed: %s", link)
  81. }
  82. }