Jelajahi Sumber

feat(sub): serve the HTML info page for browser requests on JSON and Clash URLs

Opening the /json or /clash subscription URL in a browser dumped raw JSON/YAML while the base64 URL rendered the info page. Extract the browser-detection and page-rendering branch from subs into maybeServeSubPage and run it first in all three handlers, so every subscription URL shows the same info page in a browser while client apps keep receiving the raw body.

Closes #5348
MHSanaei 9 jam lalu
induk
melakukan
ff3bd63656
1 mengubah file dengan 50 tambahan dan 27 penghapusan
  1. 50 27
      internal/sub/controller.go

+ 50 - 27
internal/sub/controller.go

@@ -146,18 +146,54 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) {
 	}
 }
 
+// maybeServeSubPage renders the HTML info page when the request comes from a
+// browser (Accept: text/html) or explicitly asks for it (?html=1 or ?view=html).
+// It reports whether the request was handled. The remark template's per-client
+// info is for the content a client app imports — the raw subscription body. A
+// browser viewing the HTML info page gets clean, name-only remarks (usage is
+// shown in the page summary).
+func (a *SUBController) maybeServeSubPage(c *gin.Context) bool {
+	accept := c.GetHeader("Accept")
+	wantsHTML := strings.Contains(strings.ToLower(accept), "text/html") || c.Query("html") == "1" || strings.EqualFold(c.Query("view"), "html")
+	if !wantsHTML {
+		return false
+	}
+	subId := c.Param("subid")
+	_, host, _, hostHeader := a.subService.ResolveRequest(c)
+	subReq := a.subService.ForRequest(host)
+	subReq.subscriptionBody = false
+	subs, emails, lastOnline, traffic, err := subReq.getSubs(subId)
+	if err != nil || len(subs) == 0 {
+		writeSubError(c, err)
+		return true
+	}
+	subURL, subJsonURL, subClashURL := subReq.BuildURLs(a.subPath, a.subJsonPath, a.subClashPath, subId)
+	if !a.jsonEnabled {
+		subJsonURL = ""
+	}
+	if !a.clashEnabled {
+		subClashURL = ""
+	}
+	basePath, exists := c.Get("base_path")
+	if !exists {
+		basePath = "/"
+	}
+	basePathStr := basePath.(string)
+	page := subReq.BuildPageData(subId, hostHeader, traffic, lastOnline, subs, emails, subURL, subJsonURL, subClashURL, basePathStr, a.subTitle, a.subSupportUrl)
+	a.serveSubPage(c, basePathStr, page)
+	return true
+}
+
 // subs handles HTTP requests for subscription links, returning either HTML page or base64-encoded subscription data.
 func (a *SUBController) subs(c *gin.Context) {
+	if a.maybeServeSubPage(c) {
+		return
+	}
 	subId := c.Param("subid")
-	scheme, host, hostWithPort, hostHeader := a.subService.ResolveRequest(c)
+	scheme, host, hostWithPort, _ := a.subService.ResolveRequest(c)
 	subReq := a.subService.ForRequest(host)
-	// The remark template's per-client info is for the content a client app
-	// imports — the raw subscription body. A browser viewing the HTML info page
-	// gets clean, name-only remarks (usage is shown in the page summary).
-	accept := c.GetHeader("Accept")
-	wantsHTML := strings.Contains(strings.ToLower(accept), "text/html") || c.Query("html") == "1" || strings.EqualFold(c.Query("view"), "html")
-	subReq.subscriptionBody = !wantsHTML
-	subs, emails, lastOnline, traffic, err := subReq.getSubs(subId)
+	subReq.subscriptionBody = true
+	subs, _, _, traffic, err := subReq.getSubs(subId)
 	if err != nil || len(subs) == 0 {
 		writeSubError(c, err)
 	} else {
@@ -167,25 +203,6 @@ func (a *SUBController) subs(c *gin.Context) {
 			result.WriteString("\n")
 		}
 
-		// If the request expects HTML (e.g., browser) or explicitly asked (?html=1 or ?view=html), render the info page here
-		if wantsHTML {
-			subURL, subJsonURL, subClashURL := subReq.BuildURLs(a.subPath, a.subJsonPath, a.subClashPath, subId)
-			if !a.jsonEnabled {
-				subJsonURL = ""
-			}
-			if !a.clashEnabled {
-				subClashURL = ""
-			}
-			basePath, exists := c.Get("base_path")
-			if !exists {
-				basePath = "/"
-			}
-			basePathStr := basePath.(string)
-			page := subReq.BuildPageData(subId, hostHeader, traffic, lastOnline, subs, emails, subURL, subJsonURL, subClashURL, basePathStr, a.subTitle, a.subSupportUrl)
-			a.serveSubPage(c, basePathStr, page)
-			return
-		}
-
 		// Add headers
 		header := fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
 		profileUrl := a.subProfileUrl
@@ -366,6 +383,9 @@ func (a *SUBController) loadSubTemplate(themeDir string) (*template.Template, er
 
 // subJsons handles HTTP requests for JSON subscription configurations.
 func (a *SUBController) subJsons(c *gin.Context) {
+	if a.maybeServeSubPage(c) {
+		return
+	}
 	subId := c.Param("subid")
 	scheme, host, hostWithPort, _ := a.subService.ResolveRequest(c)
 	jsonSub, header, err := a.subJsonService.GetJson(subId, host)
@@ -383,6 +403,9 @@ func (a *SUBController) subJsons(c *gin.Context) {
 }
 
 func (a *SUBController) subClashs(c *gin.Context) {
+	if a.maybeServeSubPage(c) {
+		return
+	}
 	subId := c.Param("subid")
 	scheme, host, hostWithPort, _ := a.subService.ResolveRequest(c)
 	clashSub, header, err := a.subClashService.GetClash(subId, host)