fix(subscriptions): avoid shared mutable state during generation (#5270)

* fix(subscriptions): avoid shared mutable state during generation

* fix(subscriptions): serve external-link-only subs in JSON/Clash; load remark settings per request

The ForRequest refactor added an early `len(inbounds) == 0` return to
GetJson/GetClash that fired before external links were fetched, so a
subscription whose only entries are external links (or whose inbounds are
all disabled) rendered empty in the JSON and Clash formats. Drop the
premature check — the existing inbounds+externalLinks empty guard already
covers the truly-empty case.

Also load datepicker/emailInRemark in PrepareForRequest rather than only in
getSubs, so JSON and Clash remarks honor these settings instead of seeing
the zero values (emailInRemark previously depended on the shared-state leak
this PR fixes).

Add a regression test covering an external-link-only sub across both formats.

---------

Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
n0ctal
2026-06-15 19:23:47 +05:00
committed by GitHub
parent 71616b7cf2
commit ac8cb505d1
7 changed files with 139 additions and 52 deletions
+4 -3
View File
@@ -137,7 +137,8 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) {
func (a *SUBController) subs(c *gin.Context) {
subId := c.Param("subid")
scheme, host, hostWithPort, hostHeader := a.subService.ResolveRequest(c)
subs, emails, lastOnline, traffic, err := a.subService.GetSubs(subId, host)
subReq := a.subService.ForRequest(host)
subs, emails, lastOnline, traffic, err := subReq.getSubs(subId)
if err != nil || len(subs) == 0 {
writeSubError(c, err)
} else {
@@ -149,7 +150,7 @@ func (a *SUBController) subs(c *gin.Context) {
// If the request expects HTML (e.g., browser) or explicitly asked (?html=1 or ?view=html), render the info page here
accept := c.GetHeader("Accept")
if strings.Contains(strings.ToLower(accept), "text/html") || c.Query("html") == "1" || strings.EqualFold(c.Query("view"), "html") {
subURL, subJsonURL, subClashURL := a.subService.BuildURLs(a.subPath, a.subJsonPath, a.subClashPath, subId)
subURL, subJsonURL, subClashURL := subReq.BuildURLs(a.subPath, a.subJsonPath, a.subClashPath, subId)
if !a.jsonEnabled {
subJsonURL = ""
}
@@ -161,7 +162,7 @@ func (a *SUBController) subs(c *gin.Context) {
basePath = "/"
}
basePathStr := basePath.(string)
page := a.subService.BuildPageData(subId, hostHeader, traffic, lastOnline, subs, emails, subURL, subJsonURL, subClashURL, basePathStr, a.subTitle, a.subSupportUrl)
page := subReq.BuildPageData(subId, hostHeader, traffic, lastOnline, subs, emails, subURL, subJsonURL, subClashURL, basePathStr, a.subTitle, a.subSupportUrl)
a.serveSubPage(c, basePathStr, page)
return
}