util.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. package controller
  2. import (
  3. "fmt"
  4. "net"
  5. "net/http"
  6. "net/netip"
  7. "strings"
  8. "github.com/mhsanaei/3x-ui/v2/config"
  9. "github.com/mhsanaei/3x-ui/v2/logger"
  10. "github.com/mhsanaei/3x-ui/v2/web/entity"
  11. "github.com/gin-gonic/gin"
  12. )
  13. // getRemoteIp extracts the real IP address from the request headers or remote address.
  14. func getRemoteIp(c *gin.Context) string {
  15. if ip, ok := extractTrustedIP(c.GetHeader("X-Real-IP")); ok {
  16. return ip
  17. }
  18. if xff := c.GetHeader("X-Forwarded-For"); xff != "" {
  19. for _, part := range strings.Split(xff, ",") {
  20. if ip, ok := extractTrustedIP(part); ok {
  21. return ip
  22. }
  23. }
  24. }
  25. if ip, ok := extractTrustedIP(c.Request.RemoteAddr); ok {
  26. return ip
  27. }
  28. return "unknown"
  29. }
  30. func extractTrustedIP(value string) (string, bool) {
  31. candidate := strings.TrimSpace(value)
  32. if candidate == "" {
  33. return "", false
  34. }
  35. if ip, ok := parseIPCandidate(candidate); ok {
  36. return ip.String(), true
  37. }
  38. if host, _, err := net.SplitHostPort(candidate); err == nil {
  39. if ip, ok := parseIPCandidate(host); ok {
  40. return ip.String(), true
  41. }
  42. }
  43. if strings.Count(candidate, ":") == 1 {
  44. if host, _, err := net.SplitHostPort(fmt.Sprintf("[%s]", candidate)); err == nil {
  45. if ip, ok := parseIPCandidate(host); ok {
  46. return ip.String(), true
  47. }
  48. }
  49. }
  50. return "", false
  51. }
  52. func parseIPCandidate(value string) (netip.Addr, bool) {
  53. ip, err := netip.ParseAddr(strings.TrimSpace(value))
  54. if err != nil {
  55. return netip.Addr{}, false
  56. }
  57. return ip.Unmap(), true
  58. }
  59. // jsonMsg sends a JSON response with a message and error status.
  60. func jsonMsg(c *gin.Context, msg string, err error) {
  61. jsonMsgObj(c, msg, nil, err)
  62. }
  63. // jsonObj sends a JSON response with an object and error status.
  64. func jsonObj(c *gin.Context, obj any, err error) {
  65. jsonMsgObj(c, "", obj, err)
  66. }
  67. // jsonMsgObj sends a JSON response with a message, object, and error status.
  68. func jsonMsgObj(c *gin.Context, msg string, obj any, err error) {
  69. m := entity.Msg{
  70. Obj: obj,
  71. }
  72. if err == nil {
  73. m.Success = true
  74. if msg != "" {
  75. m.Msg = msg
  76. }
  77. } else {
  78. m.Success = false
  79. errStr := err.Error()
  80. if errStr != "" {
  81. m.Msg = msg + " (" + errStr + ")"
  82. logger.Warning(msg+" "+I18nWeb(c, "fail")+": ", err)
  83. } else if msg != "" {
  84. m.Msg = msg
  85. logger.Warning(msg + " " + I18nWeb(c, "fail"))
  86. } else {
  87. m.Msg = I18nWeb(c, "somethingWentWrong")
  88. logger.Warning(I18nWeb(c, "somethingWentWrong") + " " + I18nWeb(c, "fail"))
  89. }
  90. }
  91. c.JSON(http.StatusOK, m)
  92. }
  93. // pureJsonMsg sends a pure JSON message response with custom status code.
  94. func pureJsonMsg(c *gin.Context, statusCode int, success bool, msg string) {
  95. c.JSON(statusCode, entity.Msg{
  96. Success: success,
  97. Msg: msg,
  98. })
  99. }
  100. // html renders an HTML template with the provided data and title.
  101. func html(c *gin.Context, name string, title string, data gin.H) {
  102. if data == nil {
  103. data = gin.H{}
  104. }
  105. data["title"] = title
  106. host := c.GetHeader("X-Forwarded-Host")
  107. if host == "" {
  108. host = c.GetHeader("X-Real-IP")
  109. }
  110. if host == "" {
  111. var err error
  112. host, _, err = net.SplitHostPort(c.Request.Host)
  113. if err != nil {
  114. host = c.Request.Host
  115. }
  116. }
  117. data["host"] = host
  118. data["request_uri"] = c.Request.RequestURI
  119. data["base_path"] = c.GetString("base_path")
  120. c.HTML(http.StatusOK, name, getContext(data))
  121. }
  122. // getContext adds version and other context data to the provided gin.H.
  123. func getContext(h gin.H) gin.H {
  124. a := gin.H{
  125. "cur_ver": config.GetVersion(),
  126. }
  127. for key, value := range h {
  128. a[key] = value
  129. }
  130. return a
  131. }
  132. // isAjax checks if the request is an AJAX request.
  133. func isAjax(c *gin.Context) bool {
  134. return c.GetHeader("X-Requested-With") == "XMLHttpRequest"
  135. }