index.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. package site
  2. import (
  3. "net"
  4. "path/filepath"
  5. "regexp"
  6. "strconv"
  7. "strings"
  8. "github.com/0xJacky/Nginx-UI/internal/cache"
  9. "github.com/0xJacky/Nginx-UI/internal/upstream"
  10. )
  11. type SiteIndex struct {
  12. Path string
  13. Content string
  14. Urls []string
  15. ProxyTargets []ProxyTarget
  16. }
  17. var (
  18. IndexedSites = make(map[string]*SiteIndex)
  19. )
  20. func GetIndexedSite(path string) *SiteIndex {
  21. if site, ok := IndexedSites[path]; ok {
  22. return site
  23. }
  24. return &SiteIndex{}
  25. }
  26. func init() {
  27. cache.RegisterCallback(scanForSite)
  28. }
  29. func scanForSite(configPath string, content []byte) error {
  30. // Regular expressions for server_name and listen directives
  31. serverNameRegex := regexp.MustCompile(`(?m)server_name\s+([^;]+);`)
  32. listenRegex := regexp.MustCompile(`(?m)listen\s+([^;]+);`)
  33. // Find server blocks
  34. serverBlockRegex := regexp.MustCompile(`(?ms)server\s*\{[^\{]*((.*?\{.*?\})*?[^\}]*)\}`)
  35. serverBlocks := serverBlockRegex.FindAllSubmatch(content, -1)
  36. siteIndex := SiteIndex{
  37. Path: configPath,
  38. Content: string(content),
  39. Urls: []string{},
  40. ProxyTargets: []ProxyTarget{},
  41. }
  42. // Map to track hosts, their SSL status and port
  43. type hostInfo struct {
  44. hasSSL bool
  45. port int
  46. }
  47. hostMap := make(map[string]hostInfo)
  48. for _, block := range serverBlocks {
  49. serverBlockContent := block[0]
  50. // Extract server_name values
  51. serverNameMatches := serverNameRegex.FindSubmatch(serverBlockContent)
  52. if len(serverNameMatches) < 2 {
  53. continue
  54. }
  55. // Get all server names
  56. serverNames := strings.Fields(string(serverNameMatches[1]))
  57. var validServerNames []string
  58. // Filter valid domain names and IPs
  59. for _, name := range serverNames {
  60. // Skip placeholder names
  61. if name == "_" || name == "localhost" {
  62. continue
  63. }
  64. // Check if it's a valid IP
  65. if net.ParseIP(name) != nil {
  66. validServerNames = append(validServerNames, name)
  67. continue
  68. }
  69. // Basic domain validation
  70. if isValidDomain(name) {
  71. validServerNames = append(validServerNames, name)
  72. }
  73. }
  74. if len(validServerNames) == 0 {
  75. continue
  76. }
  77. // Check if SSL is enabled and extract port
  78. listenMatches := listenRegex.FindAllSubmatch(serverBlockContent, -1)
  79. hasSSL := false
  80. port := 80 // Default HTTP port
  81. for _, match := range listenMatches {
  82. if len(match) >= 2 {
  83. listenValue := string(match[1])
  84. if strings.Contains(listenValue, "ssl") {
  85. hasSSL = true
  86. port = 443 // Default HTTPS port
  87. }
  88. // Extract port number if present
  89. portRegex := regexp.MustCompile(`^(?:(\d+)|.*:(\d+))`)
  90. portMatches := portRegex.FindStringSubmatch(listenValue)
  91. if len(portMatches) > 0 {
  92. // Check which capture group has the port
  93. portStr := ""
  94. if portMatches[1] != "" {
  95. portStr = portMatches[1]
  96. } else if portMatches[2] != "" {
  97. portStr = portMatches[2]
  98. }
  99. if portStr != "" {
  100. if extractedPort, err := strconv.Atoi(portStr); err == nil {
  101. port = extractedPort
  102. }
  103. }
  104. }
  105. }
  106. }
  107. // Update host map with SSL status and port
  108. for _, name := range validServerNames {
  109. // Only update if this host doesn't have SSL yet or we're adding SSL now
  110. info, exists := hostMap[name]
  111. if !exists || (!info.hasSSL && hasSSL) {
  112. hostMap[name] = hostInfo{hasSSL: hasSSL, port: port}
  113. }
  114. }
  115. }
  116. // Generate URLs from the host map
  117. for host, info := range hostMap {
  118. protocol := "http"
  119. defaultPort := 80
  120. if info.hasSSL {
  121. protocol = "https"
  122. defaultPort = 443
  123. }
  124. url := protocol + "://" + host
  125. // Add port to URL if non-standard
  126. if info.port != defaultPort {
  127. url += ":" + strconv.Itoa(info.port)
  128. }
  129. siteIndex.Urls = append(siteIndex.Urls, url)
  130. }
  131. // Parse proxy targets from the configuration content
  132. siteIndex.ProxyTargets = upstream.ParseProxyTargetsFromRawContent(string(content))
  133. // Only store if we found valid URLs or proxy targets
  134. if len(siteIndex.Urls) > 0 || len(siteIndex.ProxyTargets) > 0 {
  135. IndexedSites[filepath.Base(configPath)] = &siteIndex
  136. }
  137. return nil
  138. }
  139. // isValidDomain performs a basic validation of domain names
  140. func isValidDomain(domain string) bool {
  141. // Basic validation: contains at least one dot and no spaces
  142. return strings.Contains(domain, ".") && !strings.Contains(domain, " ")
  143. }