Skip to content

Commit bc53510

Browse files
committed
feat(site): enhance site scanning to support include directives and improve listen directive handling
Close #1378, #1463
1 parent de97830 commit bc53510

File tree

1 file changed

+81
-1
lines changed

1 file changed

+81
-1
lines changed

internal/site/index.go

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@ package site
22

33
import (
44
"net"
5+
"os"
56
"path/filepath"
67
"regexp"
78
"strconv"
89
"strings"
910
"sync"
1011

1112
"github.com/0xJacky/Nginx-UI/internal/cache"
13+
"github.com/0xJacky/Nginx-UI/internal/nginx"
1214
"github.com/0xJacky/Nginx-UI/internal/upstream"
15+
"github.com/uozi-tech/cosy/logger"
1316
)
1417

1518
type Index struct {
@@ -50,8 +53,26 @@ func scanForSite(configPath string, content []byte) error {
5053
// Regular expressions for server_name and listen directives
5154
serverNameRegex := regexp.MustCompile(`(?m)^[ \t]*server_name\s+([^;#]+);`)
5255
listenRegex := regexp.MustCompile(`(?m)^[ \t]*listen\s+([^;#]+);`)
56+
includeRegex := regexp.MustCompile(`(?m)^[ \t]*include\s+([^;#]+);`)
5357
returnRegex := regexp.MustCompile(`(?m)^[ \t]*return\s+30[1-8]\s+https://[^\s;#]+`)
5458

59+
baseDir := filepath.Dir(configPath)
60+
baseAbs, err := filepath.Abs(baseDir)
61+
if err != nil {
62+
logger.Debugf("Failed to resolve base path for config %s: %v", configPath, err)
63+
return err
64+
}
65+
66+
allowedRoot := baseAbs
67+
if confRoot := nginx.GetConfPath(); confRoot != "" {
68+
confRootAbs, err := filepath.Abs(confRoot)
69+
if err != nil {
70+
logger.Debugf("Failed to resolve nginx conf root %s: %v", confRoot, err)
71+
} else {
72+
allowedRoot = confRootAbs
73+
}
74+
}
75+
5576
// Find server blocks
5677
serverBlockRegex := regexp.MustCompile(`(?ms)server\s*\{[^\{]*((.*?\{.*?\})*?[^\}]*)\}`)
5778
serverBlocks := serverBlockRegex.FindAllSubmatch(content, -1)
@@ -111,9 +132,68 @@ func scanForSite(configPath string, content []byte) error {
111132
// Check if this server block has HTTPS redirect
112133
hasRedirect := returnRegex.Match(serverBlockContent)
113134

114-
// Check if SSL is enabled and extract port
135+
// Collect listen directives from this server block and its includes
115136
listenMatches := listenRegex.FindAllSubmatch(serverBlockContent, -1)
116137

138+
// Parse includes inside the server block (common pattern: listen is placed in snippet)
139+
includeMatches := includeRegex.FindAllSubmatch(serverBlockContent, -1)
140+
for _, match := range includeMatches {
141+
if len(match) < 2 {
142+
continue
143+
}
144+
includePath := strings.TrimSpace(string(match[1]))
145+
includePath = strings.Trim(includePath, `"'`)
146+
// Skip wildcard includes for now; they can represent multiple files
147+
if strings.Contains(includePath, "*") {
148+
logger.Debugf("Skipping wildcard include in site scan: %s", includePath)
149+
continue
150+
}
151+
152+
// Resolve relative paths: prefer same directory; fallback to nginx root (allowedRoot)
153+
if !filepath.IsAbs(includePath) {
154+
candidate := filepath.Join(baseDir, includePath)
155+
if _, err := os.Stat(candidate); err != nil {
156+
altCandidate := filepath.Join(allowedRoot, includePath)
157+
if _, altErr := os.Stat(altCandidate); altErr == nil {
158+
candidate = altCandidate
159+
}
160+
}
161+
includePath = candidate
162+
}
163+
164+
includePath = filepath.Clean(includePath)
165+
includeAbs, err := filepath.Abs(includePath)
166+
if err != nil {
167+
logger.Debugf("Failed to resolve absolute path for include during site scan: %s, error: %v", includePath, err)
168+
continue
169+
}
170+
171+
rel, err := filepath.Rel(allowedRoot, includeAbs)
172+
if err != nil || strings.HasPrefix(rel, ".."+string(filepath.Separator)) || rel == ".." {
173+
logger.Debugf("Blocked include outside base config directory during site scan: %s", includeAbs)
174+
continue
175+
}
176+
177+
includeContent, err := os.ReadFile(includeAbs)
178+
if err != nil {
179+
logger.Debugf("Failed to read include file during site scan: %s, error: %v", includeAbs, err)
180+
continue
181+
}
182+
183+
includeListens := listenRegex.FindAllSubmatch(includeContent, -1)
184+
if len(includeListens) == 0 {
185+
logger.Debugf("No listen directives found in include file during site scan: %s", includePath)
186+
continue
187+
}
188+
listenMatches = append(listenMatches, includeListens...)
189+
}
190+
191+
// If no listen directives were found, assume default HTTP :80 to keep site visible
192+
if len(listenMatches) == 0 {
193+
logger.Debugf("No listen directive found for server block in %s, defaulting to :80 http", configPath)
194+
listenMatches = append(listenMatches, [][]byte{{}, []byte("80")})
195+
}
196+
117197
for _, match := range listenMatches {
118198
if len(match) >= 2 {
119199
listenValue := strings.TrimSpace(string(match[1]))

0 commit comments

Comments
 (0)