util.go 3.8 KB

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