utils.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. package backup
  2. import (
  3. "io"
  4. "os"
  5. "path/filepath"
  6. "strings"
  7. "github.com/0xJacky/Nginx-UI/settings"
  8. "github.com/uozi-tech/cosy"
  9. )
  10. // ValidatePathAccess validates if a given path is within the granted access paths.
  11. // This function ensures that all backup read/write operations are restricted to
  12. // authorized directories only, preventing unauthorized file system access.
  13. //
  14. // Parameters:
  15. // - path: The file system path to validate
  16. //
  17. // Returns:
  18. // - error: CosyError if path is not allowed, nil if path is valid
  19. func ValidatePathAccess(path string) error {
  20. if path == "" {
  21. return cosy.WrapErrorWithParams(ErrInvalidPath, "path cannot be empty")
  22. }
  23. // Clean the path to resolve any relative components like ".." or "."
  24. cleanPath := filepath.Clean(path)
  25. // Check if the path is within any of the granted access paths
  26. for _, allowedPath := range settings.BackupSettings.GrantedAccessPath {
  27. if allowedPath == "" {
  28. continue
  29. }
  30. // Clean the allowed path as well for consistent comparison
  31. cleanAllowedPath := filepath.Clean(allowedPath)
  32. // Check if the path is within the allowed path
  33. if strings.HasPrefix(cleanPath, cleanAllowedPath) {
  34. // Ensure it's actually a subdirectory or the same directory
  35. // This prevents "/tmp" from matching "/tmpfoo"
  36. if cleanPath == cleanAllowedPath || strings.HasPrefix(cleanPath, cleanAllowedPath+string(filepath.Separator)) {
  37. return nil
  38. }
  39. }
  40. }
  41. return cosy.WrapErrorWithParams(ErrPathNotInGrantedAccess, cleanPath)
  42. }
  43. // ValidateBackupPath validates the backup source path for custom directory backups.
  44. // This function checks if the source directory exists and is accessible.
  45. //
  46. // Parameters:
  47. // - path: The backup source path to validate
  48. //
  49. // Returns:
  50. // - error: CosyError if validation fails, nil if path is valid
  51. func ValidateBackupPath(path string) error {
  52. // First check if path is in granted access paths
  53. if err := ValidatePathAccess(path); err != nil {
  54. return err
  55. }
  56. // Check if the path exists and is a directory
  57. info, err := os.Stat(path)
  58. if err != nil {
  59. if os.IsNotExist(err) {
  60. return cosy.WrapErrorWithParams(ErrBackupPathNotExist, path)
  61. }
  62. return cosy.WrapErrorWithParams(ErrBackupPathAccess, path, err.Error())
  63. }
  64. if !info.IsDir() {
  65. return cosy.WrapErrorWithParams(ErrBackupPathNotDirectory, path)
  66. }
  67. return nil
  68. }
  69. // ValidateStoragePath validates the storage destination path for backup files.
  70. // This function ensures the storage directory exists or can be created.
  71. //
  72. // Parameters:
  73. // - path: The storage destination path to validate
  74. //
  75. // Returns:
  76. // - error: CosyError if validation fails, nil if path is valid
  77. func ValidateStoragePath(path string) error {
  78. // First check if path is in granted access paths
  79. if err := ValidatePathAccess(path); err != nil {
  80. return err
  81. }
  82. // Check if the directory exists, if not try to create it
  83. if _, err := os.Stat(path); os.IsNotExist(err) {
  84. if err := os.MkdirAll(path, 0755); err != nil {
  85. return cosy.WrapErrorWithParams(ErrCreateStorageDir, path, err.Error())
  86. }
  87. } else if err != nil {
  88. return cosy.WrapErrorWithParams(ErrStoragePathAccess, path, err.Error())
  89. }
  90. return nil
  91. }
  92. // copyFile copies a file from source to destination with proper error handling.
  93. // This function handles file copying operations used in backup processes.
  94. //
  95. // Parameters:
  96. // - src: Source file path
  97. // - dst: Destination file path
  98. //
  99. // Returns:
  100. // - error: Standard error if copy operation fails
  101. func copyFile(src, dst string) error {
  102. // Open source file for reading
  103. source, err := os.Open(src)
  104. if err != nil {
  105. return err
  106. }
  107. defer source.Close()
  108. // Create destination file for writing
  109. destination, err := os.Create(dst)
  110. if err != nil {
  111. return err
  112. }
  113. defer destination.Close()
  114. // Copy file content from source to destination
  115. _, err = io.Copy(destination, source)
  116. return err
  117. }
  118. // copyDirectory recursively copies a directory from source to destination.
  119. // This function preserves file permissions and handles symbolic links properly.
  120. //
  121. // Parameters:
  122. // - src: Source directory path
  123. // - dst: Destination directory path
  124. //
  125. // Returns:
  126. // - error: CosyError if copy operation fails
  127. func copyDirectory(src, dst string) error {
  128. // Verify source is a directory
  129. srcInfo, err := os.Stat(src)
  130. if err != nil {
  131. return err
  132. }
  133. if !srcInfo.IsDir() {
  134. return cosy.WrapErrorWithParams(ErrCopyNginxConfigDir, "%s is not a directory", src)
  135. }
  136. // Create destination directory with same permissions as source
  137. if err := os.MkdirAll(dst, srcInfo.Mode()); err != nil {
  138. return err
  139. }
  140. // Walk through source directory and copy all contents
  141. return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
  142. if err != nil {
  143. return err
  144. }
  145. // Calculate relative path from source root
  146. relPath, err := filepath.Rel(src, path)
  147. if err != nil {
  148. return err
  149. }
  150. if relPath == "." {
  151. return nil
  152. }
  153. // Construct target path
  154. targetPath := filepath.Join(dst, relPath)
  155. // Handle symbolic links by recreating them
  156. if info.Mode()&os.ModeSymlink != 0 {
  157. linkTarget, err := os.Readlink(path)
  158. if err != nil {
  159. return err
  160. }
  161. return os.Symlink(linkTarget, targetPath)
  162. }
  163. // Create directories with original permissions
  164. if info.IsDir() {
  165. return os.MkdirAll(targetPath, info.Mode())
  166. }
  167. // Copy regular files
  168. return copyFile(path, targetPath)
  169. })
  170. }