inactive_external_sub_test.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. package sub
  2. import (
  3. "fmt"
  4. "net/http"
  5. "net/http/httptest"
  6. "strings"
  7. "testing"
  8. "time"
  9. "github.com/gin-gonic/gin"
  10. "github.com/mhsanaei/3x-ui/v3/internal/database"
  11. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  12. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  13. )
  14. func seedInactiveExternalOnlySub(t *testing.T, subID, email string, enabled bool, expiry int64) {
  15. t.Helper()
  16. db := database.GetDB()
  17. rec := &model.ClientRecord{Email: email, SubID: subID, UUID: subID + "-uuid", Enable: true, ExpiryTime: expiry}
  18. if err := db.Create(rec).Error; err != nil {
  19. t.Fatalf("seed client: %v", err)
  20. }
  21. if !enabled {
  22. if err := db.Model(rec).Update("enable", false).Error; err != nil {
  23. t.Fatalf("disable client: %v", err)
  24. }
  25. }
  26. if err := db.Create(&xray.ClientTraffic{Email: email, Up: 11, Down: 22, Total: 1024, ExpiryTime: expiry}).Error; err != nil {
  27. t.Fatalf("seed traffic: %v", err)
  28. }
  29. link := "vless://[email protected]:443?type=tcp&security=reality&pbk=abc&sid=12&fp=chrome#external"
  30. if err := db.Create(&model.ClientExternalLink{ClientId: rec.Id, Kind: model.ExternalLinkKindLink, Value: link, SortIndex: 1}).Error; err != nil {
  31. t.Fatalf("seed external link: %v", err)
  32. }
  33. }
  34. func TestInactiveExternalOnlySubRemainsKnownWithoutExposingLinks(t *testing.T) {
  35. gin.SetMode(gin.TestMode)
  36. states := []struct {
  37. name string
  38. enabled bool
  39. expiry int64
  40. }{
  41. {name: "disabled", enabled: false, expiry: time.Now().Add(time.Hour).UnixMilli()},
  42. {name: "expired", enabled: true, expiry: time.Now().Add(-time.Hour).UnixMilli()},
  43. }
  44. for _, state := range states {
  45. t.Run(state.name, func(t *testing.T) {
  46. initSubDB(t)
  47. subID := "external-" + state.name
  48. email := state.name + "@example.com"
  49. seedInactiveExternalOnlySub(t, subID, email, state.enabled, state.expiry)
  50. oldDistFS := distFS
  51. distFS = testDistFS
  52. t.Cleanup(func() { distFS = oldDistFS })
  53. router := gin.New()
  54. NewSUBController(
  55. router.Group("/"),
  56. WithSUBJsonEnabled(true),
  57. WithSUBClashEnabled(true),
  58. WithSUBEncryption(false),
  59. )
  60. wantHeader := fmt.Sprintf("upload=11; download=22; total=1024; expire=%d", state.expiry/1000)
  61. for _, path := range []string{"/sub/" + subID, "/json/" + subID + "?view=raw", "/clash/" + subID + "?view=raw"} {
  62. t.Run(path, func(t *testing.T) {
  63. if err := database.GetDB().Model(&xray.ClientTraffic{}).Where("email = ?", email).Update("last_sub_fetch", 0).Error; err != nil {
  64. t.Fatalf("reset last_sub_fetch: %v", err)
  65. }
  66. req := httptest.NewRequest(http.MethodGet, path, nil)
  67. req.Host = "sub.example.com"
  68. w := httptest.NewRecorder()
  69. router.ServeHTTP(w, req)
  70. if w.Code != http.StatusOK {
  71. t.Fatalf("status = %d, want 200; body=%s", w.Code, w.Body.String())
  72. }
  73. if w.Body.Len() != 0 {
  74. t.Fatalf("inactive external link leaked in body: %s", w.Body.String())
  75. }
  76. if got := w.Header().Get("Subscription-Userinfo"); got != wantHeader {
  77. t.Fatalf("Subscription-Userinfo = %q, want %q", got, wantHeader)
  78. }
  79. var traffic xray.ClientTraffic
  80. if err := database.GetDB().Where("email = ?", email).First(&traffic).Error; err != nil {
  81. t.Fatalf("load traffic: %v", err)
  82. }
  83. if traffic.LastSubFetch == 0 {
  84. t.Fatal("successful empty response did not update last_sub_fetch")
  85. }
  86. })
  87. }
  88. req := httptest.NewRequest(http.MethodGet, "/sub/"+subID, nil)
  89. req.Host = "sub.example.com"
  90. req.Header.Set("Accept", "text/html")
  91. w := httptest.NewRecorder()
  92. router.ServeHTTP(w, req)
  93. if w.Code != http.StatusOK {
  94. t.Fatalf("HTML status = %d, want 200; body=%s", w.Code, w.Body.String())
  95. }
  96. if strings.Contains(w.Body.String(), "11111111-1111-1111-1111-111111111111") {
  97. t.Fatalf("HTML page exposed inactive external link: %s", w.Body.String())
  98. }
  99. if !strings.Contains(w.Body.String(), `"links":[]`) {
  100. t.Fatalf("HTML page did not render an empty links list: %s", w.Body.String())
  101. }
  102. })
  103. }
  104. }