mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-16 16:20:59 +00:00
43bc915397
* fix(sub): show copy-only page for browser subscription visits Browser navigation to /sub previously rendered the normal subscription page, which exposed subscription material in page data or raw base64 depending on request headers. Keep VPN clients on the raw subscription body, but classify browser document requests and return a neutral static copy-only HTML page with no embedded share links or page data. This preserves the C1 LimitIP parser fix in the same master candidate while avoiding a DE rollback of the browser subscription UX. * fix(sub): keep the themed page for an explicit html request Only implicit browser navigation is downgraded to the copy-only page. An operator who appends html=1 or view=html already holds the URL, so the themed subscription page keeps rendering for them and serveSubPage stays in use. * fix(sub): keep browser pages copy-only
151 lines
4.7 KiB
Go
151 lines
4.7 KiB
Go
package sub
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
|
)
|
|
|
|
func seedInfoEndpointSub(t *testing.T, subId, email string) {
|
|
t.Helper()
|
|
db := database.GetDB()
|
|
rec := &model.ClientRecord{Email: email, SubID: subId, UUID: "info-uuid", Enable: true}
|
|
if err := db.Create(rec).Error; err != nil {
|
|
t.Fatalf("seed client: %v", err)
|
|
}
|
|
link := "vless://11111111-1111-1111-1111-111111111111@example.com:443?type=tcp&security=reality&pbk=abc&sid=12&fp=chrome#orig"
|
|
if err := db.Create(&model.ClientExternalLink{ClientId: rec.Id, Kind: model.ExternalLinkKindLink, Value: link, Remark: "DE-Provider", SortIndex: 1}).Error; err != nil {
|
|
t.Fatalf("seed external link: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSubInfoEndpoint_ServesStatusJSONEvenForBrowsers(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
initSubDB(t)
|
|
seedInfoEndpointSub(t, "info-sub", "info@x")
|
|
|
|
router := gin.New()
|
|
NewSUBController(router.Group("/"))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/sub/info-sub?format=info", nil)
|
|
req.Host = "sub.example.com"
|
|
req.Header.Set("Accept", "text/html")
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200; body=%s", w.Code, w.Body.String())
|
|
}
|
|
if ct := w.Header().Get("Content-Type"); !strings.Contains(ct, "application/json") {
|
|
t.Fatalf("Content-Type = %q, want application/json", ct)
|
|
}
|
|
if cc := w.Header().Get("Cache-Control"); !strings.Contains(cc, "no-store") {
|
|
t.Fatalf("Cache-Control = %q, want a no-store directive", cc)
|
|
}
|
|
|
|
var info map[string]any
|
|
if err := json.Unmarshal(w.Body.Bytes(), &info); err != nil {
|
|
t.Fatalf("body is not valid JSON: %v; body=%s", err, w.Body.String())
|
|
}
|
|
if info["sId"] != "info-sub" {
|
|
t.Fatalf("sId = %v, want %q", info["sId"], "info-sub")
|
|
}
|
|
if _, hasLinks := info["links"]; hasLinks {
|
|
t.Fatal("info payload must not include the links list")
|
|
}
|
|
if isOnline, ok := info["isOnline"].(bool); !ok || isOnline {
|
|
t.Fatalf("isOnline = %v, want false with no live xray", info["isOnline"])
|
|
}
|
|
emails, ok := info["emails"].([]any)
|
|
if !ok || len(emails) != 1 || emails[0] != "info@x" {
|
|
t.Fatalf("emails = %v, want [info@x]", info["emails"])
|
|
}
|
|
subUrl, _ := info["subUrl"].(string)
|
|
if !strings.HasSuffix(subUrl, "/sub/info-sub") {
|
|
t.Fatalf("subUrl = %q, want a /sub/info-sub suffix", subUrl)
|
|
}
|
|
for _, key := range []string{"enabled", "used", "remained", "expire", "lastOnline", "datepicker", "announce"} {
|
|
if _, present := info[key]; !present {
|
|
t.Fatalf("info payload missing %q; body=%s", key, w.Body.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSubInfoEndpoint_UnknownSubIs404(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
initSubDB(t)
|
|
|
|
router := gin.New()
|
|
NewSUBController(router.Group("/"))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/sub/does-not-exist?format=info", nil)
|
|
req.Host = "sub.example.com"
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusNotFound {
|
|
t.Fatalf("status = %d, want 404", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestSubInfoEndpoint_HTMLPageStillWinsWithoutFormatParam(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
initSubDB(t)
|
|
seedInfoEndpointSub(t, "html-sub", "html@x")
|
|
oldDistFS := distFS
|
|
distFS = testDistFS
|
|
t.Cleanup(func() { distFS = oldDistFS })
|
|
|
|
router := gin.New()
|
|
NewSUBController(router.Group("/"))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/sub/html-sub", nil)
|
|
req.Host = "sub.example.com"
|
|
req.Header.Set("Accept", "text/html")
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200; body=%s", w.Code, w.Body.String())
|
|
}
|
|
if ct := w.Header().Get("Content-Type"); !strings.Contains(ct, "text/html") {
|
|
t.Fatalf("Content-Type = %q, want text/html for a browser request", ct)
|
|
}
|
|
if strings.Contains(w.Body.String(), "__SUB_PAGE_DATA__") {
|
|
t.Fatal("copy-only browser page must not embed subscription page data")
|
|
}
|
|
if !strings.Contains(w.Body.String(), "This is a subscription link") {
|
|
t.Fatalf("browser request did not get the copy-only page; body=%s", w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestExplicitHTMLRequestUsesCopyOnlyPage(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
initSubDB(t)
|
|
seedInfoEndpointSub(t, "explicit-html", "explicit@x")
|
|
oldDistFS := distFS
|
|
distFS = testDistFS
|
|
t.Cleanup(func() { distFS = oldDistFS })
|
|
|
|
router := gin.New()
|
|
NewSUBController(router.Group("/"))
|
|
req := httptest.NewRequest(http.MethodGet, "/sub/explicit-html?html=1", nil)
|
|
req.Host = "sub.example.com"
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200", w.Code)
|
|
}
|
|
if strings.Contains(w.Body.String(), "__SUB_PAGE_DATA__") {
|
|
t.Fatal("explicit HTML request exposed subscription page data")
|
|
}
|
|
}
|