proxy_parser.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. package upstream
  2. import (
  3. "net/url"
  4. "regexp"
  5. "strings"
  6. "github.com/0xJacky/Nginx-UI/internal/nginx"
  7. "github.com/0xJacky/Nginx-UI/settings"
  8. )
  9. // ProxyTarget represents a proxy destination
  10. type ProxyTarget struct {
  11. Host string `json:"host"`
  12. Port string `json:"port"`
  13. Type string `json:"type"` // "proxy_pass" or "upstream"
  14. }
  15. // ParseProxyTargetsFromRawContent parses proxy targets from raw nginx configuration content
  16. func ParseProxyTargetsFromRawContent(content string) []ProxyTarget {
  17. var targets []ProxyTarget
  18. // First, collect all upstream names
  19. upstreamNames := make(map[string]bool)
  20. upstreamRegex := regexp.MustCompile(`(?s)upstream\s+([^\s]+)\s*\{([^}]+)\}`)
  21. upstreamMatches := upstreamRegex.FindAllStringSubmatch(content, -1)
  22. // Parse upstream blocks and collect upstream names
  23. for _, match := range upstreamMatches {
  24. if len(match) >= 3 {
  25. upstreamName := match[1]
  26. upstreamNames[upstreamName] = true
  27. upstreamContent := match[2]
  28. serverRegex := regexp.MustCompile(`(?m)^\s*server\s+([^;]+);`)
  29. serverMatches := serverRegex.FindAllStringSubmatch(upstreamContent, -1)
  30. for _, serverMatch := range serverMatches {
  31. if len(serverMatch) >= 2 {
  32. target := parseServerAddress(strings.TrimSpace(serverMatch[1]), "upstream")
  33. if target.Host != "" {
  34. targets = append(targets, target)
  35. }
  36. }
  37. }
  38. }
  39. }
  40. // Parse proxy_pass directives, but skip upstream references
  41. proxyPassRegex := regexp.MustCompile(`(?m)^\s*proxy_pass\s+([^;]+);`)
  42. proxyMatches := proxyPassRegex.FindAllStringSubmatch(content, -1)
  43. for _, match := range proxyMatches {
  44. if len(match) >= 2 {
  45. proxyPassURL := strings.TrimSpace(match[1])
  46. // Skip if this proxy_pass references an upstream
  47. if !isUpstreamReference(proxyPassURL, upstreamNames) {
  48. target := parseProxyPassURL(proxyPassURL)
  49. if target.Host != "" {
  50. targets = append(targets, target)
  51. }
  52. }
  53. }
  54. }
  55. return deduplicateTargets(targets)
  56. }
  57. // parseUpstreamServers extracts server addresses from upstream blocks
  58. func parseUpstreamServers(upstream *nginx.NgxUpstream) []ProxyTarget {
  59. var targets []ProxyTarget
  60. for _, directive := range upstream.Directives {
  61. if directive.Directive == "server" {
  62. target := parseServerAddress(directive.Params, "upstream")
  63. if target.Host != "" {
  64. targets = append(targets, target)
  65. }
  66. }
  67. }
  68. return targets
  69. }
  70. // parseLocationProxyPass extracts proxy_pass from location content
  71. func parseLocationProxyPass(content string) []ProxyTarget {
  72. var targets []ProxyTarget
  73. // Use regex to find proxy_pass directives
  74. proxyPassRegex := regexp.MustCompile(`(?m)^\s*proxy_pass\s+([^;]+);`)
  75. matches := proxyPassRegex.FindAllStringSubmatch(content, -1)
  76. for _, match := range matches {
  77. if len(match) >= 2 {
  78. target := parseProxyPassURL(strings.TrimSpace(match[1]))
  79. if target.Host != "" {
  80. targets = append(targets, target)
  81. }
  82. }
  83. }
  84. return targets
  85. }
  86. // parseProxyPassURL parses a proxy_pass URL and extracts host and port
  87. func parseProxyPassURL(proxyPass string) ProxyTarget {
  88. proxyPass = strings.TrimSpace(proxyPass)
  89. // Skip URLs that contain Nginx variables
  90. if strings.Contains(proxyPass, "$") {
  91. return ProxyTarget{}
  92. }
  93. // Handle HTTP/HTTPS URLs (e.g., "http://backend")
  94. if strings.HasPrefix(proxyPass, "http://") || strings.HasPrefix(proxyPass, "https://") {
  95. if parsedURL, err := url.Parse(proxyPass); err == nil {
  96. host := parsedURL.Hostname()
  97. port := parsedURL.Port()
  98. // Set default ports if not specified
  99. if port == "" {
  100. if parsedURL.Scheme == "https" {
  101. port = "443"
  102. } else {
  103. port = "80"
  104. }
  105. }
  106. // Skip if this is the HTTP challenge port used by Let's Encrypt
  107. if host == "127.0.0.1" && port == settings.CertSettings.HTTPChallengePort {
  108. return ProxyTarget{}
  109. }
  110. return ProxyTarget{
  111. Host: host,
  112. Port: port,
  113. Type: "proxy_pass",
  114. }
  115. }
  116. }
  117. // Handle direct address format for stream module (e.g., "127.0.0.1:8080", "backend.example.com:12345")
  118. // This is used in stream configurations where proxy_pass doesn't require a protocol
  119. if !strings.Contains(proxyPass, "://") {
  120. target := parseServerAddress(proxyPass, "proxy_pass")
  121. // Skip if this is the HTTP challenge port used by Let's Encrypt
  122. if target.Host == "127.0.0.1" && target.Port == settings.CertSettings.HTTPChallengePort {
  123. return ProxyTarget{}
  124. }
  125. return target
  126. }
  127. return ProxyTarget{}
  128. }
  129. // parseServerAddress parses upstream server address
  130. func parseServerAddress(serverAddr string, targetType string) ProxyTarget {
  131. serverAddr = strings.TrimSpace(serverAddr)
  132. // Remove additional parameters (weight, max_fails, etc.)
  133. parts := strings.Fields(serverAddr)
  134. if len(parts) == 0 {
  135. return ProxyTarget{}
  136. }
  137. addr := parts[0]
  138. // Check if the address contains Nginx variables - skip if it does
  139. if strings.Contains(addr, "$") {
  140. return ProxyTarget{}
  141. }
  142. // Handle IPv6 addresses
  143. if strings.HasPrefix(addr, "[") {
  144. // IPv6 format: [::1]:8080
  145. if idx := strings.LastIndex(addr, "]:"); idx != -1 {
  146. host := addr[1:idx]
  147. port := addr[idx+2:]
  148. // Skip if this is the HTTP challenge port used by Let's Encrypt
  149. if host == "::1" && port == settings.CertSettings.HTTPChallengePort {
  150. return ProxyTarget{}
  151. }
  152. return ProxyTarget{
  153. Host: host,
  154. Port: port,
  155. Type: targetType,
  156. }
  157. }
  158. // IPv6 without port: [::1]
  159. host := strings.Trim(addr, "[]")
  160. return ProxyTarget{
  161. Host: host,
  162. Port: "80",
  163. Type: targetType,
  164. }
  165. }
  166. // Handle IPv4 addresses and hostnames
  167. if strings.Contains(addr, ":") {
  168. parts := strings.Split(addr, ":")
  169. if len(parts) == 2 {
  170. // Skip if this is the HTTP challenge port used by Let's Encrypt
  171. if parts[0] == "127.0.0.1" && parts[1] == settings.CertSettings.HTTPChallengePort {
  172. return ProxyTarget{}
  173. }
  174. return ProxyTarget{
  175. Host: parts[0],
  176. Port: parts[1],
  177. Type: targetType,
  178. }
  179. }
  180. }
  181. // No port specified, use default
  182. return ProxyTarget{
  183. Host: addr,
  184. Port: "80",
  185. Type: targetType,
  186. }
  187. }
  188. // deduplicateTargets removes duplicate proxy targets
  189. func deduplicateTargets(targets []ProxyTarget) []ProxyTarget {
  190. seen := make(map[string]bool)
  191. var result []ProxyTarget
  192. for _, target := range targets {
  193. key := target.Host + ":" + target.Port + ":" + target.Type
  194. if !seen[key] {
  195. seen[key] = true
  196. result = append(result, target)
  197. }
  198. }
  199. return result
  200. }
  201. // isUpstreamReference checks if a proxy_pass URL references an upstream block
  202. func isUpstreamReference(proxyPass string, upstreamNames map[string]bool) bool {
  203. proxyPass = strings.TrimSpace(proxyPass)
  204. // For HTTP/HTTPS URLs, parse the URL to extract the hostname
  205. if strings.HasPrefix(proxyPass, "http://") || strings.HasPrefix(proxyPass, "https://") {
  206. // Handle URLs with nginx variables (e.g., "https://myUpStr$request_uri")
  207. // Extract the scheme and hostname part before any nginx variables
  208. schemeAndHost := proxyPass
  209. if dollarIndex := strings.Index(proxyPass, "$"); dollarIndex != -1 {
  210. schemeAndHost = proxyPass[:dollarIndex]
  211. }
  212. // Try to parse the URL, if it fails, try manual extraction
  213. if parsedURL, err := url.Parse(schemeAndHost); err == nil {
  214. hostname := parsedURL.Hostname()
  215. // Check if the hostname matches any upstream name
  216. return upstreamNames[hostname]
  217. } else {
  218. // Fallback: manually extract hostname for URLs with variables
  219. // Remove scheme prefix
  220. withoutScheme := proxyPass
  221. if strings.HasPrefix(proxyPass, "https://") {
  222. withoutScheme = strings.TrimPrefix(proxyPass, "https://")
  223. } else if strings.HasPrefix(proxyPass, "http://") {
  224. withoutScheme = strings.TrimPrefix(proxyPass, "http://")
  225. }
  226. // Extract hostname before any path, port, or variable
  227. hostname := withoutScheme
  228. if slashIndex := strings.Index(hostname, "/"); slashIndex != -1 {
  229. hostname = hostname[:slashIndex]
  230. }
  231. if colonIndex := strings.Index(hostname, ":"); colonIndex != -1 {
  232. hostname = hostname[:colonIndex]
  233. }
  234. if dollarIndex := strings.Index(hostname, "$"); dollarIndex != -1 {
  235. hostname = hostname[:dollarIndex]
  236. }
  237. return upstreamNames[hostname]
  238. }
  239. }
  240. // For stream module, proxy_pass can directly reference upstream name without protocol
  241. // Check if the proxy_pass value directly matches an upstream name
  242. if !strings.Contains(proxyPass, "://") && !strings.Contains(proxyPass, ":") {
  243. return upstreamNames[proxyPass]
  244. }
  245. return false
  246. }