package utils import ( "fmt" "os" "path/filepath" "strings" ) func ExistsInPath(path string, s string) bool { _, err := os.Stat(filepath.Join(path, s)) return err == nil } func InTrustedRoot(path string, trustedRoot string) error { for { parent := filepath.Dir(path) // Dir stops changing at "/" for an absolute path and at "." for a // relative one; waiting for "/" alone spins forever on the latter. if parent == path { return fmt.Errorf("path is outside of trusted root") } path = parent if path == trustedRoot { return nil } } } // VerifyPath verifies that path, taken relative to basePath, is based in // basePath. It joins path onto basePath first, so an absolute path is read as // relative to the base as well: give it the untrusted relative name, never a // path that has already been joined. For a full path use VerifyResolvedPath. func VerifyPath(path, basePath string) error { c := filepath.Clean(filepath.Join(basePath, path)) return InTrustedRoot(c, filepath.Clean(basePath)) } // VerifyResolvedPath verifies that path, a full path rather than one relative // to basePath, is based in basePath. func VerifyResolvedPath(path, basePath string) error { return InTrustedRoot(filepath.Clean(path), filepath.Clean(basePath)) } // SanitizeFileName sanitizes the given filename func SanitizeFileName(fileName string) string { // filepath.Clean to clean the path cleanName := filepath.Clean(fileName) // filepath.Base to ensure we only get the final element, not any directory path baseName := filepath.Base(cleanName) // Replace any remaining tricky characters that might have survived cleaning safeName := strings.ReplaceAll(baseName, "..", "") return safeName } func GenerateUniqueFileName(dir, baseName, ext string) string { counter := 1 fileName := baseName + ext for { filePath := filepath.Join(dir, fileName) _, err := os.Stat(filePath) if os.IsNotExist(err) { return fileName } counter++ fileName = fmt.Sprintf("%s_%d%s", baseName, counter, ext) } }