sub_panic_test.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. package sub
  2. import (
  3. "fmt"
  4. "strings"
  5. "testing"
  6. "github.com/gin-gonic/gin"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database"
  8. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  9. )
  10. // A subscription is built by iterating every client's share link with no
  11. // recover(), so any panic in the link generators 500s the whole subscription
  12. // for every client. Valid-but-unusual stream settings (an empty Reality
  13. // shortIds/serverNames array, a tcp-http header with no request, a grpc block
  14. // missing its keys) must therefore produce a link, not a panic.
  15. func TestGetSubsToleratesUnusualStreamSettings(t *testing.T) {
  16. gin.SetMode(gin.TestMode)
  17. cases := []struct {
  18. name string
  19. stream string
  20. }{
  21. {"reality empty arrays", `{"network":"tcp","security":"reality","realitySettings":{"serverNames":[],"shortIds":[],"settings":{"publicKey":"pk"}}}`},
  22. {"tcp http missing request", `{"network":"tcp","security":"none","tcpSettings":{"header":{"type":"http","response":{"headers":{}}}}}`},
  23. {"tcp http empty path", `{"network":"tcp","security":"none","tcpSettings":{"header":{"type":"http","request":{"path":[]}}}}`},
  24. {"grpc missing keys", `{"network":"grpc","security":"none","grpcSettings":{}}`},
  25. {"empty stream settings", `{}`},
  26. }
  27. for i, tc := range cases {
  28. t.Run(tc.name, func(t *testing.T) {
  29. seedSubDB(t)
  30. subId := fmt.Sprintf("s%d", i)
  31. seedSubInbound(t, subId, fmt.Sprintf("t%d", i), 46000+i, 1, tc.stream)
  32. links, _, _, _, err := NewSubService("").GetSubs(subId, "req.example.com")
  33. if err != nil {
  34. t.Fatalf("GetSubs errored: %v", err)
  35. }
  36. if len(links) != 1 {
  37. t.Fatalf("expected 1 share link, got %d", len(links))
  38. }
  39. })
  40. }
  41. }
  42. // The JSON subscription generator for a Hysteria inbound whose StreamSettings
  43. // omit the hysteriaSettings key must not panic (which would 500 the whole JSON
  44. // subscription); the raw generator already tolerates this shape.
  45. func TestGetJsonToleratesHysteriaWithoutHysteriaSettings(t *testing.T) {
  46. seedSubDB(t)
  47. db := database.GetDB()
  48. const subId = "hy1"
  49. const email = "hy@e"
  50. ib := &model.Inbound{
  51. UserId: 1, Tag: "hy", Enable: true, Listen: "203.0.113.5", Port: 46200,
  52. Protocol: model.Hysteria,
  53. Remark: "hy",
  54. Settings: fmt.Sprintf(`{"version":2,"clients":[{"auth":"hyauth","email":%q,"subId":%q,"enable":true}]}`, email, subId),
  55. StreamSettings: `{"security":"tls","tlsSettings":{"serverName":"hy.sni"}}`,
  56. }
  57. if err := db.Create(ib).Error; err != nil {
  58. t.Fatalf("seed inbound: %v", err)
  59. }
  60. client := &model.ClientRecord{Email: email, SubID: subId, Enable: true}
  61. if err := db.Create(client).Error; err != nil {
  62. t.Fatalf("seed client: %v", err)
  63. }
  64. if err := db.Create(&model.ClientInbound{ClientId: client.Id, InboundId: ib.Id}).Error; err != nil {
  65. t.Fatalf("seed client_inbound: %v", err)
  66. }
  67. jsonService := NewSubJsonService("", "", "", NewSubService(""))
  68. out, _, err := jsonService.GetJson(subId, "sub.example.com", true)
  69. if err != nil {
  70. t.Fatalf("GetJson: %v", err)
  71. }
  72. if out == "" {
  73. t.Fatal("GetJson returned empty for a hysteria inbound without hysteriaSettings")
  74. }
  75. }
  76. // A Clash subscription must carry the pinned peer certificate SHA-256 when the
  77. // inbound configures one, matching the JSON subscription; dropping it silently
  78. // downgrades certificate pinning for Clash subscribers.
  79. func TestGetClashEmitsPinnedCertSha256(t *testing.T) {
  80. seedSubDB(t)
  81. const pin = "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
  82. stream := `{"network":"tcp","security":"tls","tlsSettings":{"serverName":"pin.sni","settings":{"pinnedPeerCertSha256":["` + pin + `"]}}}`
  83. seedSubInbound(t, "pin1", "pin", 46300, 1, stream)
  84. out, _, err := NewSubClashService(false, "", NewSubService("")).GetClash("pin1", "sub.example.com")
  85. if err != nil {
  86. t.Fatalf("GetClash: %v", err)
  87. }
  88. if !strings.Contains(out, "pin-sha256") {
  89. t.Fatalf("Clash proxy dropped the pinned cert sha256:\n%s", out)
  90. }
  91. }