1
0

subController.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. package sub
  2. import (
  3. "encoding/base64"
  4. "net"
  5. "strconv"
  6. "strings"
  7. "x-ui/util/common"
  8. "github.com/gin-gonic/gin"
  9. )
  10. type SUBController struct {
  11. subTitle string
  12. subPath string
  13. subJsonPath string
  14. subEncrypt bool
  15. updateInterval string
  16. subService *SubService
  17. subJsonService *SubJsonService
  18. }
  19. func NewSUBController(
  20. g *gin.RouterGroup,
  21. subPath string,
  22. jsonPath string,
  23. encrypt bool,
  24. showInfo bool,
  25. rModel string,
  26. update string,
  27. jsonFragment string,
  28. jsonNoise string,
  29. jsonMux string,
  30. jsonRules string,
  31. subTitle string,
  32. ) *SUBController {
  33. sub := NewSubService(showInfo, rModel)
  34. a := &SUBController{
  35. subTitle: subTitle,
  36. subPath: subPath,
  37. subJsonPath: jsonPath,
  38. subEncrypt: encrypt,
  39. updateInterval: update,
  40. subService: sub,
  41. subJsonService: NewSubJsonService(jsonFragment, jsonNoise, jsonMux, jsonRules, sub),
  42. }
  43. a.initRouter(g)
  44. return a
  45. }
  46. func (a *SUBController) initRouter(g *gin.RouterGroup) {
  47. gLink := g.Group(a.subPath)
  48. gJson := g.Group(a.subJsonPath)
  49. gLink.GET(":subid", a.subs)
  50. gJson.GET(":subid", a.subJsons)
  51. }
  52. func (a *SUBController) subs(c *gin.Context) {
  53. subId := c.Param("subid")
  54. var host string
  55. if h, err := getHostFromXFH(c.GetHeader("X-Forwarded-Host")); err == nil {
  56. host = h
  57. }
  58. if host == "" {
  59. host = c.GetHeader("X-Real-IP")
  60. }
  61. if host == "" {
  62. var err error
  63. host, _, err = net.SplitHostPort(c.Request.Host)
  64. if err != nil {
  65. host = c.Request.Host
  66. }
  67. }
  68. subs, header, lastOnline, err := a.subService.GetSubs(subId, host)
  69. if err != nil || len(subs) == 0 {
  70. c.String(400, "Error!")
  71. } else {
  72. result := ""
  73. for _, sub := range subs {
  74. result += sub + "\n"
  75. }
  76. // Add headers
  77. c.Writer.Header().Set("Subscription-Userinfo", header)
  78. c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
  79. c.Writer.Header().Set("Profile-Title", "base64:"+base64.StdEncoding.EncodeToString([]byte(a.subTitle)))
  80. // Also include whole subscription content in base64 as requested
  81. c.Writer.Header().Set("Subscription-Content-Base64", base64.StdEncoding.EncodeToString([]byte(result)))
  82. // If the request expects HTML (e.g., browser) or explicitly asked (?html=1 or ?view=html), render the info page here
  83. accept := c.GetHeader("Accept")
  84. if strings.Contains(strings.ToLower(accept), "text/html") || c.Query("html") == "1" || strings.EqualFold(c.Query("view"), "html") {
  85. // Determine scheme
  86. scheme := "http"
  87. if c.Request.TLS != nil || strings.EqualFold(c.GetHeader("X-Forwarded-Proto"), "https") {
  88. scheme = "https"
  89. }
  90. // Parse header values
  91. var uploadByte, downloadByte, totalByte, expire int64
  92. parts := strings.Split(header, ";")
  93. for _, p := range parts {
  94. kv := strings.Split(strings.TrimSpace(p), "=")
  95. if len(kv) != 2 {
  96. continue
  97. }
  98. key := strings.ToLower(strings.TrimSpace(kv[0]))
  99. val := strings.TrimSpace(kv[1])
  100. switch key {
  101. case "upload":
  102. if v, err := parseInt64(val); err == nil {
  103. uploadByte = v
  104. }
  105. case "download":
  106. if v, err := parseInt64(val); err == nil {
  107. downloadByte = v
  108. }
  109. case "total":
  110. if v, err := parseInt64(val); err == nil {
  111. totalByte = v
  112. }
  113. case "expire":
  114. if v, err := parseInt64(val); err == nil {
  115. expire = v
  116. }
  117. }
  118. }
  119. download := common.FormatTraffic(downloadByte)
  120. upload := common.FormatTraffic(uploadByte)
  121. total := "∞"
  122. used := common.FormatTraffic(uploadByte + downloadByte)
  123. remained := ""
  124. if totalByte > 0 {
  125. total = common.FormatTraffic(totalByte)
  126. left := max(totalByte-(uploadByte+downloadByte), 0)
  127. remained = common.FormatTraffic(left)
  128. }
  129. // Build sub URL
  130. subURL := scheme + "://" + host + strings.TrimRight(a.subPath, "/") + "/" + subId
  131. if strings.HasSuffix(a.subPath, "/") {
  132. subURL = scheme + "://" + host + a.subPath + subId
  133. }
  134. basePath := "/"
  135. hostHeader := c.GetHeader("X-Forwarded-Host")
  136. if hostHeader == "" {
  137. hostHeader = c.GetHeader("X-Real-IP")
  138. }
  139. if hostHeader == "" {
  140. hostHeader = host
  141. }
  142. c.HTML(200, "subscription.html", gin.H{
  143. "title": "subscription.title",
  144. "host": hostHeader,
  145. "base_path": basePath,
  146. "sId": subId,
  147. "download": download,
  148. "upload": upload,
  149. "total": total,
  150. "used": used,
  151. "remained": remained,
  152. "expire": expire,
  153. "lastOnline": lastOnline,
  154. "datepicker": a.subService.datepicker,
  155. "downloadByte": downloadByte,
  156. "uploadByte": uploadByte,
  157. "totalByte": totalByte,
  158. "subUrl": subURL,
  159. "result": subs,
  160. })
  161. return
  162. }
  163. if a.subEncrypt {
  164. c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
  165. } else {
  166. c.String(200, result)
  167. }
  168. }
  169. }
  170. func (a *SUBController) subJsons(c *gin.Context) {
  171. subId := c.Param("subid")
  172. var host string
  173. if h, err := getHostFromXFH(c.GetHeader("X-Forwarded-Host")); err == nil {
  174. host = h
  175. }
  176. if host == "" {
  177. host = c.GetHeader("X-Real-IP")
  178. }
  179. if host == "" {
  180. var err error
  181. host, _, err = net.SplitHostPort(c.Request.Host)
  182. if err != nil {
  183. host = c.Request.Host
  184. }
  185. }
  186. jsonSub, header, err := a.subJsonService.GetJson(subId, host)
  187. if err != nil || len(jsonSub) == 0 {
  188. c.String(400, "Error!")
  189. } else {
  190. // Add headers
  191. c.Writer.Header().Set("Subscription-Userinfo", header)
  192. c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
  193. c.Writer.Header().Set("Profile-Title", "base64:"+base64.StdEncoding.EncodeToString([]byte(a.subTitle)))
  194. c.String(200, jsonSub)
  195. }
  196. }
  197. func getHostFromXFH(s string) (string, error) {
  198. if strings.Contains(s, ":") {
  199. realHost, _, err := net.SplitHostPort(s)
  200. if err != nil {
  201. return "", err
  202. }
  203. return realHost, nil
  204. }
  205. return s, nil
  206. }
  207. func parseInt64(s string) (int64, error) {
  208. var n int64
  209. var err error
  210. // handle potential quotes
  211. s = strings.Trim(s, "\"'")
  212. n, err = strconv.ParseInt(s, 10, 64)
  213. return n, err
  214. }