|
|
@@ -6,6 +6,7 @@ import (
|
|
|
"encoding/json"
|
|
|
"fmt"
|
|
|
"html/template"
|
|
|
+ "io/fs"
|
|
|
"net/http"
|
|
|
"net/url"
|
|
|
"os"
|
|
|
@@ -340,11 +341,11 @@ func (a *SUBController) subs(c *gin.Context) {
|
|
|
logSubscriptionRoute(userAgent, "html")
|
|
|
return
|
|
|
}
|
|
|
- if shouldAutoServeClash(a.subClashAutoDetect, a.clashEnabled, false, userAgent, a.clashUserAgent) && a.serveClashBody(c) {
|
|
|
+ if shouldAutoServeClash(a.subClashAutoDetect, a.clashEnabled, false, userAgent, a.clashUserAgent) && a.serveClashBody(c, false) {
|
|
|
logSubscriptionRoute(userAgent, "clash")
|
|
|
return
|
|
|
}
|
|
|
- if shouldAutoServeJson(a.jsonAutoDetect, a.jsonEnabled, false, userAgent, a.jsonUserAgent) && a.serveJsonBody(c, true, "application/json; charset=utf-8") {
|
|
|
+ if shouldAutoServeJson(a.jsonAutoDetect, a.jsonEnabled, false, userAgent, a.jsonUserAgent) && a.serveJsonBody(c, true, "application/json; charset=utf-8", false) {
|
|
|
logSubscriptionRoute(userAgent, "json")
|
|
|
return
|
|
|
}
|
|
|
@@ -446,7 +447,7 @@ func (a *SUBController) serveSubPage(c *gin.Context, basePath string, page PageD
|
|
|
if diskBody, diskErr := os.ReadFile("internal/web/dist/subpage.html"); diskErr == nil {
|
|
|
body = diskBody
|
|
|
} else {
|
|
|
- readBody, err := distFS.ReadFile("dist/subpage.html")
|
|
|
+ readBody, err := fs.ReadFile(distFS, "dist/subpage.html")
|
|
|
if err != nil {
|
|
|
c.String(http.StatusInternalServerError, "missing embedded subpage")
|
|
|
return
|
|
|
@@ -596,6 +597,12 @@ 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 strings.EqualFold(c.Query("view"), "raw") {
|
|
|
+ if !a.serveJsonBody(c, a.jsonAlwaysArray, "application/json; charset=utf-8", true) {
|
|
|
+ writeSubError(c, nil)
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
if a.maybeServeSubPage(c) {
|
|
|
return
|
|
|
}
|
|
|
@@ -603,12 +610,12 @@ func (a *SUBController) subJsons(c *gin.Context) {
|
|
|
}
|
|
|
|
|
|
func (a *SUBController) serveJson(c *gin.Context, alwaysReturnArray bool, contentType string) {
|
|
|
- if !a.serveJsonBody(c, alwaysReturnArray, contentType) {
|
|
|
+ if !a.serveJsonBody(c, alwaysReturnArray, contentType, false) {
|
|
|
writeSubError(c, nil)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func (a *SUBController) serveJsonBody(c *gin.Context, alwaysReturnArray bool, contentType string) bool {
|
|
|
+func (a *SUBController) serveJsonBody(c *gin.Context, alwaysReturnArray bool, contentType string, rawDownload bool) bool {
|
|
|
subId := c.Param("subid")
|
|
|
scheme, host, hostWithPort, _ := a.subService.ResolveRequest(c)
|
|
|
jsonSub, header, err := a.subJsonService.GetJson(subId, host, alwaysReturnArray)
|
|
|
@@ -624,21 +631,30 @@ func (a *SUBController) serveJsonBody(c *gin.Context, alwaysReturnArray bool, co
|
|
|
profileUrl = fmt.Sprintf("%s://%s%s", scheme, hostWithPort, c.Request.RequestURI)
|
|
|
}
|
|
|
a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle, a.subSupportUrl, profileUrl, a.subAnnounce, a.subEnableRouting, a.subRoutingRules, a.subHideSettings)
|
|
|
+ if rawDownload {
|
|
|
+ c.Writer.Header().Set("Content-Disposition", `attachment; filename="subscription.json"`)
|
|
|
+ }
|
|
|
|
|
|
c.Data(200, contentType, []byte(jsonSub))
|
|
|
return true
|
|
|
}
|
|
|
|
|
|
func (a *SUBController) subClashs(c *gin.Context) {
|
|
|
+ if strings.EqualFold(c.Query("view"), "raw") {
|
|
|
+ if !a.serveClashBody(c, true) {
|
|
|
+ writeSubError(c, nil)
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
if a.maybeServeSubPage(c) {
|
|
|
return
|
|
|
}
|
|
|
- if !a.serveClashBody(c) {
|
|
|
+ if !a.serveClashBody(c, false) {
|
|
|
writeSubError(c, nil)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func (a *SUBController) serveClashBody(c *gin.Context) bool {
|
|
|
+func (a *SUBController) serveClashBody(c *gin.Context, rawDownload bool) bool {
|
|
|
subId := c.Param("subid")
|
|
|
scheme, host, hostWithPort, _ := a.subService.ResolveRequest(c)
|
|
|
clashSub, header, err := a.subClashService.GetClash(subId, host)
|
|
|
@@ -654,7 +670,9 @@ func (a *SUBController) serveClashBody(c *gin.Context) bool {
|
|
|
profileUrl = fmt.Sprintf("%s://%s%s", scheme, hostWithPort, c.Request.RequestURI)
|
|
|
}
|
|
|
a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle, a.subSupportUrl, profileUrl, a.subAnnounce, a.subEnableRouting, a.subRoutingRules, a.subHideSettings)
|
|
|
- if a.subTitle != "" {
|
|
|
+ if rawDownload {
|
|
|
+ c.Writer.Header().Set("Content-Disposition", `attachment; filename="subscription.yaml"`)
|
|
|
+ } else if a.subTitle != "" {
|
|
|
// Clash clients commonly use Content-Disposition to choose the imported profile name.
|
|
|
c.Writer.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename*=UTF-8''%s`, url.PathEscape(a.subTitle)))
|
|
|
}
|