123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- package backup
- import (
- "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 valid
- func 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 valid
- func 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 valid
- func 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 fails
- func 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 fails
- func 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)
- })
- }
|