| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 | package backupimport (	"io"	"os"	"path/filepath"	"strings"	"github.com/0xJacky/Nginx-UI/settings"	"github.com/uozi-tech/cosy")// ValidatePathAccess validates if a given path is within the granted access paths.// This function ensures that all backup read/write operations are restricted to// authorized directories only, preventing unauthorized file system access.//// Parameters://   - path: The file system path to validate//// Returns://   - error: CosyError if path is not allowed, nil if path is validfunc ValidatePathAccess(path string) error {	if path == "" {		return cosy.WrapErrorWithParams(ErrInvalidPath, "path cannot be empty")	}	// Clean the path to resolve any relative components like ".." or "."	cleanPath := filepath.Clean(path)	// Check if the path is within any of the granted access paths	for _, allowedPath := range settings.BackupSettings.GrantedAccessPath {		if allowedPath == "" {			continue		}		// Clean the allowed path as well for consistent comparison		cleanAllowedPath := filepath.Clean(allowedPath)		// Check if the path is within the allowed path		if strings.HasPrefix(cleanPath, cleanAllowedPath) {			// Ensure it's actually a subdirectory or the same directory			// This prevents "/tmp" from matching "/tmpfoo"			if cleanPath == cleanAllowedPath || strings.HasPrefix(cleanPath, cleanAllowedPath+string(filepath.Separator)) {				return nil			}		}	}	return cosy.WrapErrorWithParams(ErrPathNotInGrantedAccess, cleanPath)}// ValidateBackupPath validates the backup source path for custom directory backups.// This function checks if the source directory exists and is accessible.//// Parameters://   - path: The backup source path to validate//// Returns://   - error: CosyError if validation fails, nil if path is validfunc ValidateBackupPath(path string) error {	// First check if path is in granted access paths	if err := ValidatePathAccess(path); err != nil {		return err	}	// Check if the path exists and is a directory	info, err := os.Stat(path)	if err != nil {		if os.IsNotExist(err) {			return cosy.WrapErrorWithParams(ErrBackupPathNotExist, path)		}		return cosy.WrapErrorWithParams(ErrBackupPathAccess, path, err.Error())	}	if !info.IsDir() {		return cosy.WrapErrorWithParams(ErrBackupPathNotDirectory, path)	}	return nil}// ValidateStoragePath validates the storage destination path for backup files.// This function ensures the storage directory exists or can be created.//// Parameters://   - path: The storage destination path to validate//// Returns://   - error: CosyError if validation fails, nil if path is validfunc ValidateStoragePath(path string) error {	// First check if path is in granted access paths	if err := ValidatePathAccess(path); err != nil {		return err	}	// Check if the directory exists, if not try to create it	if _, err := os.Stat(path); os.IsNotExist(err) {		if err := os.MkdirAll(path, 0755); err != nil {			return cosy.WrapErrorWithParams(ErrCreateStorageDir, path, err.Error())		}	} else if err != nil {		return cosy.WrapErrorWithParams(ErrStoragePathAccess, path, err.Error())	}	return nil}// copyFile copies a file from source to destination with proper error handling.// This function handles file copying operations used in backup processes.//// Parameters://   - src: Source file path//   - dst: Destination file path//// Returns://   - error: Standard error if copy operation failsfunc copyFile(src, dst string) error {	// Open source file for reading	source, err := os.Open(src)	if err != nil {		return err	}	defer source.Close()	// Create destination file for writing	destination, err := os.Create(dst)	if err != nil {		return err	}	defer destination.Close()	// Copy file content from source to destination	_, err = io.Copy(destination, source)	return err}// copyDirectory recursively copies a directory from source to destination.// This function preserves file permissions and handles symbolic links properly.//// Parameters://   - src: Source directory path//   - dst: Destination directory path//// Returns://   - error: CosyError if copy operation failsfunc copyDirectory(src, dst string) error {	// Verify source is a directory	srcInfo, err := os.Stat(src)	if err != nil {		return err	}	if !srcInfo.IsDir() {		return cosy.WrapErrorWithParams(ErrCopyNginxConfigDir, "%s is not a directory", src)	}	// Create destination directory with same permissions as source	if err := os.MkdirAll(dst, srcInfo.Mode()); err != nil {		return err	}	// Walk through source directory and copy all contents	return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {		if err != nil {			return err		}		// Calculate relative path from source root		relPath, err := filepath.Rel(src, path)		if err != nil {			return err		}		if relPath == "." {			return nil		}		// Construct target path		targetPath := filepath.Join(dst, relPath)		// Handle symbolic links by recreating them		if info.Mode()&os.ModeSymlink != 0 {			linkTarget, err := os.Readlink(path)			if err != nil {				return err			}			return os.Symlink(linkTarget, targetPath)		}		// Create directories with original permissions		if info.IsDir() {			return os.MkdirAll(targetPath, info.Mode())		}		// Copy regular files		return copyFile(path, targetPath)	})}
 |