nginx-ui/internal/site/index.go
2025-04-27 07:50:38 +00:00

168 lines
3.8 KiB
Go

package site
import (
"net"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/0xJacky/Nginx-UI/internal/cache"
)
type SiteIndex struct {
Path string
Content string
Urls []string
}
var (
IndexedSites = make(map[string]*SiteIndex)
)
func GetIndexedSite(path string) *SiteIndex {
if site, ok := IndexedSites[path]; ok {
return site
}
return &SiteIndex{}
}
func init() {
cache.RegisterCallback(scanForSite)
}
func scanForSite(configPath string, content []byte) error {
// Regular expressions for server_name and listen directives
serverNameRegex := regexp.MustCompile(`(?m)server_name\s+([^;]+);`)
listenRegex := regexp.MustCompile(`(?m)listen\s+([^;]+);`)
// Find server blocks
serverBlockRegex := regexp.MustCompile(`(?ms)server\s*\{[^\{]*((.*?\{.*?\})*?[^\}]*)\}`)
serverBlocks := serverBlockRegex.FindAllSubmatch(content, -1)
siteIndex := SiteIndex{
Path: configPath,
Content: string(content),
Urls: []string{},
}
// Map to track hosts, their SSL status and port
type hostInfo struct {
hasSSL bool
port int
}
hostMap := make(map[string]hostInfo)
for _, block := range serverBlocks {
serverBlockContent := block[0]
// Extract server_name values
serverNameMatches := serverNameRegex.FindSubmatch(serverBlockContent)
if len(serverNameMatches) < 2 {
continue
}
// Get all server names
serverNames := strings.Fields(string(serverNameMatches[1]))
var validServerNames []string
// Filter valid domain names and IPs
for _, name := range serverNames {
// Skip placeholder names
if name == "_" || name == "localhost" {
continue
}
// Check if it's a valid IP
if net.ParseIP(name) != nil {
validServerNames = append(validServerNames, name)
continue
}
// Basic domain validation
if isValidDomain(name) {
validServerNames = append(validServerNames, name)
}
}
if len(validServerNames) == 0 {
continue
}
// Check if SSL is enabled and extract port
listenMatches := listenRegex.FindAllSubmatch(serverBlockContent, -1)
hasSSL := false
port := 80 // Default HTTP port
for _, match := range listenMatches {
if len(match) >= 2 {
listenValue := string(match[1])
if strings.Contains(listenValue, "ssl") {
hasSSL = true
port = 443 // Default HTTPS port
}
// Extract port number if present
portRegex := regexp.MustCompile(`^(?:(\d+)|.*:(\d+))`)
portMatches := portRegex.FindStringSubmatch(listenValue)
if len(portMatches) > 0 {
// Check which capture group has the port
portStr := ""
if portMatches[1] != "" {
portStr = portMatches[1]
} else if portMatches[2] != "" {
portStr = portMatches[2]
}
if portStr != "" {
if extractedPort, err := strconv.Atoi(portStr); err == nil {
port = extractedPort
}
}
}
}
}
// Update host map with SSL status and port
for _, name := range validServerNames {
// Only update if this host doesn't have SSL yet or we're adding SSL now
info, exists := hostMap[name]
if !exists || (!info.hasSSL && hasSSL) {
hostMap[name] = hostInfo{hasSSL: hasSSL, port: port}
}
}
}
// Generate URLs from the host map
for host, info := range hostMap {
protocol := "http"
defaultPort := 80
if info.hasSSL {
protocol = "https"
defaultPort = 443
}
url := protocol + "://" + host
// Add port to URL if non-standard
if info.port != defaultPort {
url += ":" + strconv.Itoa(info.port)
}
siteIndex.Urls = append(siteIndex.Urls, url)
}
// Only store if we found valid URLs
if len(siteIndex.Urls) > 0 {
IndexedSites[filepath.Base(configPath)] = &siteIndex
}
return nil
}
// isValidDomain performs a basic validation of domain names
func isValidDomain(domain string) bool {
// Basic validation: contains at least one dot and no spaces
return strings.Contains(domain, ".") && !strings.Contains(domain, " ")
}