proxy_parser.go 9.4 KB

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