github.com/argoproj/argo-cd/v3@v3.2.1/util/io/files/util.go (about) 1 package files 2 3 import ( 4 "errors" 5 "fmt" 6 "io/fs" 7 "os" 8 "path" 9 "path/filepath" 10 "strings" 11 12 "github.com/google/uuid" 13 ) 14 15 var ErrRelativeOutOfBound = errors.New("full path does not contain base path") 16 17 // RelativePath will remove the basePath string from the fullPath 18 // including the path separator. Differently from filepath.Rel, this 19 // function will return error (ErrRelativeOutOfBound) if basePath 20 // does not match (example 2). 21 // 22 // Example 1: 23 // 24 // fullPath: /home/test/app/readme.md 25 // basePath: /home/test 26 // return: app/readme.md 27 // 28 // Example 2: 29 // 30 // fullPath: /home/test/app/readme.md 31 // basePath: /somewhere/else 32 // return: "", RelativeOutOfBoundErr 33 // 34 // Example 3: 35 // 36 // fullPath: /home/test/app/readme.md 37 // basePath: /home/test/app/readme.md 38 // return: . 39 func RelativePath(fullPath, basePath string) (string, error) { 40 fp := filepath.Clean(fullPath) 41 if !strings.HasPrefix(fp, filepath.Clean(basePath)) { 42 return "", ErrRelativeOutOfBound 43 } 44 return filepath.Rel(basePath, fp) 45 } 46 47 // CreateTempDir will create a temporary directory in baseDir 48 // with CSPRNG entropy in the name to avoid clashes and mitigate 49 // directory traversal. If baseDir is empty string, os.TempDir() 50 // will be used. It is the caller's responsibility to remove the 51 // directory after use. Will return the full path of the generated 52 // directory. 53 func CreateTempDir(baseDir string) (string, error) { 54 base := baseDir 55 if base == "" { 56 base = os.TempDir() 57 } 58 newUUID, err := uuid.NewRandom() 59 if err != nil { 60 return "", fmt.Errorf("error creating directory name: %w", err) 61 } 62 tempDir := path.Join(base, newUUID.String()) 63 if err := os.MkdirAll(tempDir, 0o755); err != nil { 64 return "", fmt.Errorf("error creating tempDir: %w", err) 65 } 66 return tempDir, nil 67 } 68 69 // IsSymlink return true if the given FileInfo relates to a 70 // symlink file. Returns false otherwise. 71 func IsSymlink(fi os.FileInfo) bool { 72 return fi.Mode()&fs.ModeSymlink == fs.ModeSymlink 73 } 74 75 // Inbound will validate if the given candidate path is inside the 76 // baseDir. This is useful to make sure that malicious candidates 77 // are not targeting a file outside of baseDir boundaries. 78 // Considerations: 79 // - baseDir must be absolute path. Will return false otherwise 80 // - candidate can be absolute or relative path 81 // - candidate should not be symlink as only syntatic validation is 82 // applied by this function 83 func Inbound(candidate, baseDir string) bool { 84 if !filepath.IsAbs(baseDir) { 85 return false 86 } 87 var target string 88 if filepath.IsAbs(candidate) { 89 target = filepath.Clean(candidate) 90 } else { 91 target = filepath.Join(baseDir, candidate) 92 } 93 return strings.HasPrefix(target, filepath.Clean(baseDir)+string(os.PathSeparator)) 94 }