2 Commits fa0d785aa0 ... dd8dfe0f8b

Author SHA1 Message Date
  Jacky dd8dfe0f8b enhance: file scanning with recursion protection and abs path resolution #1178 1 day ago
  Jacky 792e871ce3 perf: cache nginx prefix for log file scanning 1 day ago
2 changed files with 57 additions and 22 deletions
  1. 52 20
      internal/cache/index.go
  2. 5 2
      internal/nginx_log/nginx_log.go

+ 52 - 20
internal/cache/index.go

@@ -296,34 +296,66 @@ func (s *Scanner) watchForChanges() {
 
 // scanSingleFile scans a single file and executes all registered callbacks
 func (s *Scanner) scanSingleFile(filePath string) error {
-	// Set scanning state to true
-	s.scanMutex.Lock()
-	wasScanning := s.scanning
-	s.scanning = true
-	if !wasScanning {
-		// Only broadcast if status changed from not scanning to scanning
-		s.statusChan <- true
+	return s.scanSingleFileWithDepth(filePath, make(map[string]bool), 0)
+}
+
+// scanSingleFileWithDepth scans a single file with recursion protection
+func (s *Scanner) scanSingleFileWithDepth(filePath string, visited map[string]bool, depth int) error {
+	// Maximum recursion depth to prevent infinite recursion
+	const maxDepth = 10
+
+	if depth > maxDepth {
+		logger.Warn("Maximum recursion depth reached for file:", filePath)
+		return nil
 	}
-	s.scanMutex.Unlock()
 
-	// Ensure we reset scanning state when done
-	defer func() {
+	// Resolve the absolute path to handle symlinks properly
+	absPath, err := filepath.Abs(filePath)
+	if err != nil {
+		logger.Error("Failed to resolve absolute path for:", filePath, err)
+		return err
+	}
+
+	// Check for circular includes
+	if visited[absPath] {
+		// Circular include detected, skip this file
+		return nil
+	}
+
+	// Mark this file as visited
+	visited[absPath] = true
+
+	// Set scanning state to true only for the root call (depth 0)
+	var wasScanning bool
+	if depth == 0 {
 		s.scanMutex.Lock()
-		s.scanning = false
-		// Broadcast the completion
-		s.statusChan <- false
+		wasScanning = s.scanning
+		s.scanning = true
+		if !wasScanning {
+			// Only broadcast if status changed from not scanning to scanning
+			s.statusChan <- true
+		}
 		s.scanMutex.Unlock()
-	}()
+
+		// Ensure we reset scanning state when done (only for root call)
+		defer func() {
+			s.scanMutex.Lock()
+			s.scanning = false
+			// Broadcast the completion
+			s.statusChan <- false
+			s.scanMutex.Unlock()
+		}()
+	}
 
 	// Open the file
-	file, err := os.Open(filePath)
+	file, err := os.Open(absPath)
 	if err != nil {
 		return err
 	}
 	defer file.Close()
 
 	// Read the entire file content
-	content, err := os.ReadFile(filePath)
+	content, err := os.ReadFile(absPath)
 	if err != nil {
 		return err
 	}
@@ -331,9 +363,9 @@ func (s *Scanner) scanSingleFile(filePath string) error {
 	// Execute all registered callbacks
 	scanCallbacksMutex.RLock()
 	for _, callback := range scanCallbacks {
-		err := callback(filePath, content)
+		err := callback(absPath, content)
 		if err != nil {
-			logger.Error("Callback error for file", filePath, ":", err)
+			logger.Error("Callback error for file", absPath, ":", err)
 		}
 	}
 	scanCallbacksMutex.RUnlock()
@@ -363,7 +395,7 @@ func (s *Scanner) scanSingleFile(filePath string) error {
 				for _, matchedFile := range matchedFiles {
 					fileInfo, err := os.Stat(matchedFile)
 					if err == nil && !fileInfo.IsDir() {
-						err = s.scanSingleFile(matchedFile)
+						err = s.scanSingleFileWithDepth(matchedFile, visited, depth+1)
 						if err != nil {
 							logger.Error("Failed to scan included file:", matchedFile, err)
 						}
@@ -379,7 +411,7 @@ func (s *Scanner) scanSingleFile(filePath string) error {
 
 				fileInfo, err := os.Stat(includePath)
 				if err == nil && !fileInfo.IsDir() {
-					err = s.scanSingleFile(includePath)
+					err = s.scanSingleFileWithDepth(includePath, visited, depth+1)
 					if err != nil {
 						logger.Error("Failed to scan included file:", includePath, err)
 					}

+ 5 - 2
internal/nginx_log/nginx_log.go

@@ -15,10 +15,14 @@ import (
 )
 
 // Regular expression for log directives - matches access_log or error_log
-var logDirectiveRegex = regexp.MustCompile(`(?m)(access_log|error_log)\s+([^\s;]+)(?:\s+[^;]+)?;`)
+var (
+	logDirectiveRegex = regexp.MustCompile(`(?m)(access_log|error_log)\s+([^\s;]+)(?:\s+[^;]+)?;`)
+	prefix            = ""
+)
 
 // Use init function to automatically register callback
 func init() {
+	prefix = nginx.GetPrefix()
 	// Register the callback directly with the global registry
 	cache.RegisterCallback(scanForLogDirectives)
 }
@@ -32,7 +36,6 @@ func scanForLogDirectives(configPath string, content []byte) error {
 	// Find log directives using regex
 	matches := logDirectiveRegex.FindAllSubmatch(content, -1)
 
-	prefix := nginx.GetPrefix()
 	// Parse log paths
 	for _, match := range matches {
 		if len(match) >= 3 {