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  }