File System Operations
Path Manipulation
Path and PathBuf provide platform-aware methods for decomposing paths into components (parent, file_name, file_stem, extension) and constructing new paths (join, push, with_file_name, with_extension) using the target platform's path separator (/ on Unix, \ on Windows).
Decomposing a Path into Its Parts
Path: `/Users/prime/Developer/rust/archive.tar.gz`
|<------------ parent() ----------->|
|<-- file_name() -->|
|<- file_stem() ->| |<- extension() ->|
`/Users/prime/Developer/rust` `archive.tar.gz` `archive.tar` `gz`
| Decomposition Method | Result on "/dir/archive.tar.gz" |
Result on "/dir/.gitignore" |
|---|---|---|
.parent() |
Some("/dir") |
Some("/dir") |
.file_name() |
Some("archive.tar.gz") |
Some(".gitignore") |
.file_stem() |
Some("archive.tar") |
Some(".gitignore") |
.extension() |
Some("gz") |
None (leading dot alone is not an extension!) |
.components() |
[RootDir, Normal("dir"), Normal("archive.tar.gz")] |
[RootDir, Normal("dir"), Normal(".gitignore")] |
Constructing and Modifying Paths (join, push, with_extension)
Borrowed &Path Method (Returns New PathBuf) |
Mutating PathBuf Method (Modifies in Place) |
Behavior |
|---|---|---|
path.join(child) |
path_buf.push(child) |
Appends child with a platform separator (Important: if child is an absolute path like "/etc", it replaces the entire existing path!) |
path.parent() |
path_buf.pop() |
Truncates path_buf to its parent directory |
path.with_file_name(name) |
path_buf.set_file_name(name) |
Replaces the final filename component |
path.with_extension(ext) |
path_buf.set_extension(ext) |
Replaces (or adds) the file extension (pass "" to remove extension) |
use std::path::{Component, Path, PathBuf};
fn main() {
let mut path = PathBuf::from("/workspace/project/src/main.rs");
println!("parent: {:?}", path.parent());
println!("file_name: {:?}", path.file_name());
println!("file_stem: {:?}", path.file_stem());
println!("extension: {:?}", path.extension());
// Create sibling paths non-destructively via `.with_extension` and `.with_file_name`:
let object_file = path.with_extension("o");
let lib_file = path.with_file_name("lib.rs");
println!("object_file: {}", object_file.display());
println!("lib_file: {}", lib_file.display());
// Inspect structured `Component` variants:
let comps: Vec<Component<'_>> = Path::new("../src/./lib.rs").components().collect();
println!("components of '../src/./lib.rs': {:?}", comps);
// Watch out for the absolute-path replacement rule on `.push()` / `.join()`:
path.push("/etc/hosts");
println!("after pushing absolute '/etc/hosts': {}", path.display());
}
parent: Some("/workspace/project/src")
file_name: Some("main.rs")
file_stem: Some("main")
extension: Some("rs")
object_file: /workspace/project/src/main.o
lib_file: /workspace/project/src/lib.rs
components of '../src/./lib.rs': [ParentDir, Normal("src"), CurDir, Normal("lib.rs")]
after pushing absolute '/etc/hosts': /etc/hosts
Security Trap: .join(untrusted_input) and Absolute / .. Path Traversal
Because base_dir.join("/etc/passwd") returns "/etc/passwd" (completely discarding base_dir!) and base_dir.join("../../secret") traverses above base_dir, never pass untrusted user input directly to .join() or .push() without first verifying that every component in untrusted_path.components() is Component::Normal(_):
use std::path::{Component, Path, PathBuf};
fn safe_subpath_join(base: &Path, untrusted_rel: &Path) -> Option<PathBuf> {
// Reject absolute paths, `..` (`ParentDir`), and drive prefixes:
if untrusted_rel
.components()
.all(|c| matches!(c, Component::Normal(_)))
{
Some(base.join(untrusted_rel))
} else {
None
}
}
fn main() {
let base = Path::new("/srv/www/public");
println!(
"safe 'assets/logo.png': {:?}",
safe_subpath_join(base, Path::new("assets/logo.png"))
);
println!(
"blocked '/etc/passwd': {:?}",
safe_subpath_join(base, Path::new("/etc/passwd"))
);
println!(
"blocked '../secret': {:?}",
safe_subpath_join(base, Path::new("../secret"))
);
}
safe 'assets/logo.png': Some("/srv/www/public/assets/logo.png")
blocked '/etc/passwd': None
blocked '../secret': None