mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-16 23:27:14 +00:00
fix(sub): prevent default profile page URL disclosure (#6538)
* fix(sub): prevent default profile page URL disclosure Add explicit none, builtin, and custom profile page modes. Preserve existing custom URLs and warn before exposing the built-in page. Cover mode selection, legacy settings, and subscription response headers. * fix(subscription): add profile page link options and upgrade notes
This commit is contained in:
@@ -53,6 +53,7 @@ type cachedSubTemplate struct {
|
||||
type SUBController struct {
|
||||
subTitle string
|
||||
subSupportUrl string
|
||||
subProfileMode string
|
||||
subProfileUrl string
|
||||
subAnnounce string
|
||||
subEnableRouting bool
|
||||
@@ -115,6 +116,7 @@ type subControllerConfig struct {
|
||||
|
||||
subTitle string
|
||||
subSupportURL string
|
||||
subProfileMode string
|
||||
subProfileURL string
|
||||
subAnnounce string
|
||||
subEnableRouting bool
|
||||
@@ -224,6 +226,10 @@ func WithSUBProfileURL(value string) SUBControllerOption {
|
||||
return func(config *subControllerConfig) { config.subProfileURL = value }
|
||||
}
|
||||
|
||||
func WithSUBProfileMode(value string) SUBControllerOption {
|
||||
return func(config *subControllerConfig) { config.subProfileMode = value }
|
||||
}
|
||||
|
||||
func WithSUBAnnounce(value string) SUBControllerOption {
|
||||
return func(config *subControllerConfig) { config.subAnnounce = value }
|
||||
}
|
||||
@@ -260,6 +266,7 @@ func defaultSUBControllerConfig() subControllerConfig {
|
||||
subEncrypt: true,
|
||||
remarkTemplate: service.DefaultRemarkTemplate,
|
||||
updateInterval: "12",
|
||||
subProfileMode: service.SubProfileModeNone,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,6 +284,7 @@ func NewSUBController(g *gin.RouterGroup, options ...SUBControllerOption) *SUBCo
|
||||
a := &SUBController{
|
||||
subTitle: config.subTitle,
|
||||
subSupportUrl: config.subSupportURL,
|
||||
subProfileMode: config.subProfileMode,
|
||||
subProfileUrl: config.subProfileURL,
|
||||
subAnnounce: config.subAnnounce,
|
||||
subEnableRouting: config.subEnableRouting,
|
||||
@@ -485,8 +493,7 @@ func (a *SUBController) subs(c *gin.Context) {
|
||||
|
||||
// Add headers
|
||||
header := subReq.subscriptionUserinfo(traffic)
|
||||
profileURL := fmt.Sprintf("%s://%s%s", scheme, hostWithPort, c.Request.RequestURI)
|
||||
metadata := a.metadataForSubRequest(func() *SubService { return subReq }, subId, profileURL)
|
||||
metadata := a.metadataForSubRequest(func() *SubService { return subReq }, subId, builtinProfileURL(c, scheme, hostWithPort))
|
||||
a.ApplyCommonHeaders(c, header, a.updateInterval, metadata.Title, metadata.SupportURL, metadata.ProfileURL, metadata.Announce, a.subEnableRouting, a.subRoutingRules, a.subHideSettings)
|
||||
|
||||
if a.subIncyEnableRouting && a.subIncyRoutingRules != "" {
|
||||
@@ -818,14 +825,13 @@ func (a *SUBController) serveJsonBody(c *gin.Context, alwaysReturnArray bool, co
|
||||
if len(jsonSub) == 0 && header == "" {
|
||||
return false
|
||||
}
|
||||
profileURL := fmt.Sprintf("%s://%s%s", scheme, hostWithPort, c.Request.RequestURI)
|
||||
var subReq *SubService
|
||||
metadata := a.metadataForSubRequest(func() *SubService {
|
||||
if subReq == nil {
|
||||
subReq = a.subService.ForRequest(host)
|
||||
}
|
||||
return subReq
|
||||
}, subId, profileURL)
|
||||
}, subId, builtinProfileURL(c, scheme, hostWithPort))
|
||||
a.ApplyCommonHeaders(c, header, a.updateInterval, metadata.Title, metadata.SupportURL, metadata.ProfileURL, metadata.Announce, a.subEnableRouting, a.subRoutingRules, a.subHideSettings)
|
||||
if rawDownload {
|
||||
c.Writer.Header().Set("Content-Disposition", `attachment; filename="subscription.json"`)
|
||||
@@ -887,14 +893,13 @@ func (a *SUBController) serveClashBody(c *gin.Context, rawDownload bool, legacy
|
||||
if len(clashSub) == 0 && header == "" {
|
||||
return false
|
||||
}
|
||||
profileURL := fmt.Sprintf("%s://%s%s", scheme, hostWithPort, c.Request.RequestURI)
|
||||
var subReq *SubService
|
||||
metadata := a.metadataForSubRequest(func() *SubService {
|
||||
if subReq == nil {
|
||||
subReq = a.subService.ForRequest(host)
|
||||
}
|
||||
return subReq
|
||||
}, subId, profileURL)
|
||||
}, subId, builtinProfileURL(c, scheme, hostWithPort))
|
||||
a.ApplyCommonHeaders(c, header, a.updateInterval, metadata.Title, metadata.SupportURL, metadata.ProfileURL, metadata.Announce, a.subEnableRouting, a.subRoutingRules, a.subHideSettings)
|
||||
if rawDownload {
|
||||
c.Writer.Header().Set("Content-Disposition", `attachment; filename="subscription.yaml"`)
|
||||
@@ -906,6 +911,11 @@ func (a *SUBController) serveClashBody(c *gin.Context, rawDownload bool, legacy
|
||||
return true
|
||||
}
|
||||
|
||||
func builtinProfileURL(c *gin.Context, scheme, hostWithPort string) string {
|
||||
// Drop download/format selectors so the opt-in link always opens the HTML page.
|
||||
return fmt.Sprintf("%s://%s%s?html=1", scheme, hostWithPort, c.Request.URL.EscapedPath())
|
||||
}
|
||||
|
||||
// ApplyCommonHeaders sets common HTTP headers for subscription responses including user info, update interval, and profile title.
|
||||
func (a *SUBController) ApplyCommonHeaders(
|
||||
c *gin.Context,
|
||||
|
||||
Reference in New Issue
Block a user