utils.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  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. // Special case: if allowed path is root directory, allow all paths
  33. if cleanAllowedPath == string(filepath.Separator) {
  34. return nil
  35. }
  36. // Check if the path is within the allowed path
  37. if strings.HasPrefix(cleanPath, cleanAllowedPath) {
  38. // Ensure it's actually a subdirectory or the same directory
  39. // This prevents "/tmp" from matching "/tmpfoo"
  40. if cleanPath == cleanAllowedPath || strings.HasPrefix(cleanPath, cleanAllowedPath+string(filepath.Separator)) {
  41. return nil
  42. }
  43. }
  44. }
  45. return cosy.WrapErrorWithParams(ErrPathNotInGrantedAccess, cleanPath)
  46. }
  47. // ValidateBackupPath validates the backup source path for custom directory backups.
  48. // This function checks if the source directory exists and is accessible.
  49. //
  50. // Parameters:
  51. // - path: The backup source path to validate
  52. //
  53. // Returns:
  54. // - error: CosyError if validation fails, nil if path is valid
  55. func ValidateBackupPath(path string) error {
  56. // First check if path is in granted access paths
  57. if err := ValidatePathAccess(path); err != nil {
  58. return err
  59. }
  60. // Check if the path exists and is a directory
  61. info, err := os.Stat(path)
  62. if err != nil {
  63. if os.IsNotExist(err) {
  64. return cosy.WrapErrorWithParams(ErrBackupPathNotExist, path)
  65. }
  66. return cosy.WrapErrorWithParams(ErrBackupPathAccess, path, err.Error())
  67. }
  68. if !info.IsDir() {
  69. return cosy.WrapErrorWithParams(ErrBackupPathNotDirectory, path)
  70. }
  71. return nil
  72. }
  73. // ValidateStoragePath validates the storage destination path for backup files.
  74. // This function ensures the storage directory exists or can be created.
  75. //
  76. // Parameters:
  77. // - path: The storage destination path to validate
  78. //
  79. // Returns:
  80. // - error: CosyError if validation fails, nil if path is valid
  81. func ValidateStoragePath(path string) error {
  82. // First check if path is in granted access paths
  83. if err := ValidatePathAccess(path); err != nil {
  84. return err
  85. }
  86. // Check if the directory exists, if not try to create it
  87. if _, err := os.Stat(path); os.IsNotExist(err) {
  88. if err := os.MkdirAll(path, 0755); err != nil {
  89. return cosy.WrapErrorWithParams(ErrCreateStorageDir, path, err.Error())
  90. }
  91. } else if err != nil {
  92. return cosy.WrapErrorWithParams(ErrStoragePathAccess, path, err.Error())
  93. }
  94. return nil
  95. }
  96. // copyFile copies a file from source to destination with proper error handling.
  97. // This function handles file copying operations used in backup processes.
  98. //
  99. // Parameters:
  100. // - src: Source file path
  101. // - dst: Destination file path
  102. //
  103. // Returns:
  104. // - error: Standard error if copy operation fails
  105. func copyFile(src, dst string) error {
  106. // Open source file for reading
  107. source, err := os.Open(src)
  108. if err != nil {
  109. return err
  110. }
  111. defer source.Close()
  112. // Create destination file for writing
  113. destination, err := os.Create(dst)
  114. if err != nil {
  115. return err
  116. }
  117. defer destination.Close()
  118. // Copy file content from source to destination
  119. _, err = io.Copy(destination, source)
  120. return err
  121. }
  122. // copyDirectory recursively copies a directory from source to destination.
  123. // This function preserves file permissions and handles symbolic links properly.
  124. //
  125. // Parameters:
  126. // - src: Source directory path
  127. // - dst: Destination directory path
  128. //
  129. // Returns:
  130. // - error: CosyError if copy operation fails
  131. func copyDirectory(src, dst string) error {
  132. // Verify source is a directory
  133. srcInfo, err := os.Stat(src)
  134. if err != nil {
  135. return err
  136. }
  137. if !srcInfo.IsDir() {
  138. return cosy.WrapErrorWithParams(ErrCopyNginxConfigDir, "%s is not a directory", src)
  139. }
  140. // Create destination directory with same permissions as source
  141. if err := os.MkdirAll(dst, srcInfo.Mode()); err != nil {
  142. return err
  143. }
  144. // Walk through source directory and copy all contents
  145. return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
  146. if err != nil {
  147. return err
  148. }
  149. // Calculate relative path from source root
  150. relPath, err := filepath.Rel(src, path)
  151. if err != nil {
  152. return err
  153. }
  154. if relPath == "." {
  155. return nil
  156. }
  157. // Construct target path
  158. targetPath := filepath.Join(dst, relPath)
  159. // Handle symbolic links by recreating them
  160. if info.Mode()&os.ModeSymlink != 0 {
  161. linkTarget, err := os.Readlink(path)
  162. if err != nil {
  163. return err
  164. }
  165. return os.Symlink(linkTarget, targetPath)
  166. }
  167. // Create directories with original permissions
  168. if info.IsDir() {
  169. return os.MkdirAll(targetPath, info.Mode())
  170. }
  171. // Copy regular files
  172. return copyFile(path, targetPath)
  173. })
  174. }