123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- package sub
- import (
- "encoding/base64"
- "net"
- "strconv"
- "strings"
- "x-ui/util/common"
- "github.com/gin-gonic/gin"
- )
- type SUBController struct {
- subTitle string
- subPath string
- subJsonPath string
- subEncrypt bool
- updateInterval string
- subService *SubService
- subJsonService *SubJsonService
- }
- func NewSUBController(
- g *gin.RouterGroup,
- subPath string,
- jsonPath string,
- encrypt bool,
- showInfo bool,
- rModel string,
- update string,
- jsonFragment string,
- jsonNoise string,
- jsonMux string,
- jsonRules string,
- subTitle string,
- ) *SUBController {
- sub := NewSubService(showInfo, rModel)
- a := &SUBController{
- subTitle: subTitle,
- subPath: subPath,
- subJsonPath: jsonPath,
- subEncrypt: encrypt,
- updateInterval: update,
- subService: sub,
- subJsonService: NewSubJsonService(jsonFragment, jsonNoise, jsonMux, jsonRules, sub),
- }
- a.initRouter(g)
- return a
- }
- func (a *SUBController) initRouter(g *gin.RouterGroup) {
- gLink := g.Group(a.subPath)
- gJson := g.Group(a.subJsonPath)
- gLink.GET(":subid", a.subs)
- gJson.GET(":subid", a.subJsons)
- }
- func (a *SUBController) subs(c *gin.Context) {
- subId := c.Param("subid")
- var host string
- if h, err := getHostFromXFH(c.GetHeader("X-Forwarded-Host")); err == nil {
- host = h
- }
- if host == "" {
- host = c.GetHeader("X-Real-IP")
- }
- if host == "" {
- var err error
- host, _, err = net.SplitHostPort(c.Request.Host)
- if err != nil {
- host = c.Request.Host
- }
- }
- subs, header, lastOnline, err := a.subService.GetSubs(subId, host)
- if err != nil || len(subs) == 0 {
- c.String(400, "Error!")
- } else {
- result := ""
- for _, sub := range subs {
- result += sub + "\n"
- }
- // Add headers
- c.Writer.Header().Set("Subscription-Userinfo", header)
- c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
- c.Writer.Header().Set("Profile-Title", "base64:"+base64.StdEncoding.EncodeToString([]byte(a.subTitle)))
- // Also include whole subscription content in base64 as requested
- c.Writer.Header().Set("Subscription-Content-Base64", base64.StdEncoding.EncodeToString([]byte(result)))
- // 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") {
- // Determine scheme
- scheme := "http"
- if c.Request.TLS != nil || strings.EqualFold(c.GetHeader("X-Forwarded-Proto"), "https") {
- scheme = "https"
- }
- // Parse header values
- var uploadByte, downloadByte, totalByte, expire int64
- parts := strings.Split(header, ";")
- for _, p := range parts {
- kv := strings.Split(strings.TrimSpace(p), "=")
- if len(kv) != 2 {
- continue
- }
- key := strings.ToLower(strings.TrimSpace(kv[0]))
- val := strings.TrimSpace(kv[1])
- switch key {
- case "upload":
- if v, err := parseInt64(val); err == nil {
- uploadByte = v
- }
- case "download":
- if v, err := parseInt64(val); err == nil {
- downloadByte = v
- }
- case "total":
- if v, err := parseInt64(val); err == nil {
- totalByte = v
- }
- case "expire":
- if v, err := parseInt64(val); err == nil {
- expire = v
- }
- }
- }
- download := common.FormatTraffic(downloadByte)
- upload := common.FormatTraffic(uploadByte)
- total := "∞"
- used := common.FormatTraffic(uploadByte + downloadByte)
- remained := ""
- if totalByte > 0 {
- total = common.FormatTraffic(totalByte)
- left := max(totalByte-(uploadByte+downloadByte), 0)
- remained = common.FormatTraffic(left)
- }
- // Build sub URL
- subURL := scheme + "://" + host + strings.TrimRight(a.subPath, "/") + "/" + subId
- if strings.HasSuffix(a.subPath, "/") {
- subURL = scheme + "://" + host + a.subPath + subId
- }
- basePath := "/"
- hostHeader := c.GetHeader("X-Forwarded-Host")
- if hostHeader == "" {
- hostHeader = c.GetHeader("X-Real-IP")
- }
- if hostHeader == "" {
- hostHeader = host
- }
- c.HTML(200, "subscription.html", gin.H{
- "title": "subscription.title",
- "host": hostHeader,
- "base_path": basePath,
- "sId": subId,
- "download": download,
- "upload": upload,
- "total": total,
- "used": used,
- "remained": remained,
- "expire": expire,
- "lastOnline": lastOnline,
- "datepicker": a.subService.datepicker,
- "downloadByte": downloadByte,
- "uploadByte": uploadByte,
- "totalByte": totalByte,
- "subUrl": subURL,
- "result": subs,
- })
- return
- }
- if a.subEncrypt {
- c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
- } else {
- c.String(200, result)
- }
- }
- }
- func (a *SUBController) subJsons(c *gin.Context) {
- subId := c.Param("subid")
- var host string
- if h, err := getHostFromXFH(c.GetHeader("X-Forwarded-Host")); err == nil {
- host = h
- }
- if host == "" {
- host = c.GetHeader("X-Real-IP")
- }
- if host == "" {
- var err error
- host, _, err = net.SplitHostPort(c.Request.Host)
- if err != nil {
- host = c.Request.Host
- }
- }
- jsonSub, header, err := a.subJsonService.GetJson(subId, host)
- if err != nil || len(jsonSub) == 0 {
- c.String(400, "Error!")
- } else {
- // Add headers
- c.Writer.Header().Set("Subscription-Userinfo", header)
- c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
- c.Writer.Header().Set("Profile-Title", "base64:"+base64.StdEncoding.EncodeToString([]byte(a.subTitle)))
- c.String(200, jsonSub)
- }
- }
- func getHostFromXFH(s string) (string, error) {
- if strings.Contains(s, ":") {
- realHost, _, err := net.SplitHostPort(s)
- if err != nil {
- return "", err
- }
- return realHost, nil
- }
- return s, nil
- }
- func parseInt64(s string) (int64, error) {
- var n int64
- var err error
- // handle potential quotes
- s = strings.Trim(s, "\"'")
- n, err = strconv.ParseInt(s, 10, 64)
- return n, err
- }
|