A pure Rust library for path canonicalization that works with non-existing paths.
Unlike std::fs::canonicalize()
, this library resolves and normalizes paths even when components don't exist on the filesystem. Useful for security validation, path preprocessing, and working with paths before file creation.
Comprehensive test suite with 51 tests ensuring 100% behavioral compatibility with std::fs::canonicalize for existing paths.
Inspired by Python's pathlib.Path.resolve()
behavior.
- 🚀 Works with non-existing paths: Canonicalizes paths that don't exist yet
- 🌍 Cross-platform: Windows, macOS, and Linux support
- ⚡ Zero dependencies: Only uses std library
- 🔒 Security focused: Proper
..
and symlink handling - 🧮 Pure algorithm: No filesystem modification
Add this to your Cargo.toml
:
[dependencies]
soft-canonicalize = "0.1.2"
use soft_canonicalize::soft_canonicalize;
// Works even if file doesn't exist!
let user_path = soft_canonicalize("../../../etc/passwd")?;
let jail_path = std::fs::canonicalize("/safe/jail/dir").expect("Jail directory must exist");
let is_safe = user_path.starts_with(&jail_path); // false - attack blocked!
- Web servers: Path validation before file creation
- Build tools: Resolving non-existing output paths
- Security applications: Safe path handling with symlink resolution
- Lexical Resolution: Process
..
and.
components without filesystem access - Incremental Symlink Resolution: Resolve symlinks as encountered using
std::fs::canonicalize
- Hybrid Approach: Uses
std::fs::canonicalize
for existing path portions, lexical resolution for non-existing parts - Optimized Access: Only check filesystem when components exist
Implementation: Finds the longest existing path prefix, canonicalizes it with std::fs::canonicalize
, then appends the remaining non-existing components. This ensures you get the same results as the standard library for existing paths, with extended support for non-existing paths.
- Time: O(n) path components
- Space: O(n) component storage
- Cross-platform: Windows (drive letters, UNC), Unix (symlinks)
- Comprehensive Testing: 51 tests including Python-inspired edge cases and cross-platform validation
- 100% Behavioral Compatibility: Passes all std::fs::canonicalize tests for existing paths
- Directory Traversal Prevention:
..
components resolved before filesystem access - Symlink Resolution: Existing symlinks properly resolved
- No Side Effects: No temporary files created
- Path Injection Protection: Handles various path formats safely
Security Advantage: Resolves symlinks, preventing jail break attacks where malicious symlinks point outside intended boundaries (unlike path_absolutize
).
Use Case | soft_canonicalize |
std::fs::canonicalize |
dunce::canonicalize |
normpath::normalize |
path_absolutize::absolutize |
jailed-path * |
---|---|---|---|---|---|---|
Works with non-existing paths | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ |
Resolves symlinks | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
Zero dependencies | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ (uses this crate) |
Handles .. components |
✅ (resolves) | ✅ (resolves) | ✅ (resolves) | ✅ (resolves) | ✅ (safe with virtual root) | ❌ (rejects) |
Prevents symlink jail breaks | ✅ | ✅ | ✅ | N/A | ❌ (vulnerable) | ✅ |
Built-in path jailing | ❌ | ❌ | ❌ | ❌ | ✅ (virtual root) | ✅ |
*jailed-path
uses soft_canonicalize
as a dependency.
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
See CHANGELOG.md for a detailed history of changes.