| Crates.io | posix-portable-filename |
| lib.rs | posix-portable-filename |
| version | 0.2.3 |
| created_at | 2026-01-09 21:00:12.367955+00 |
| updated_at | 2026-01-11 04:44:19.436433+00 |
| description | Constructs POSIX portable filenames (A-Za-z0-9._-) |
| homepage | |
| repository | https://github.com/xpe/posix-portable-filename |
| max_upload_size | |
| id | 2032839 |
| size | 31,294 |
A validated type for POSIX portable filenames. It uses the newtype idiom.
Unix filesystems technically allow almost any byte sequence as a filename. David Wheeler explains the situation well:
This lack of limitations is flexible, but it also creates a legion of unnecessary problems. In particular, this lack of limitations makes it unnecessarily difficult to write correct programs (enabling many security flaws). It also makes it impossible to consistently and accurately display filenames, causes portability problems, and confuses users.
Some examples of what can go wrong:
-rf gets interpreted as command-line flags*, ?, [, ] double as shell glob metacharactersMost other Rust libraries sanitize filenames by transforming bad characters into valid ones. This crate takes a different approach; it only allows construction of its core type (PortableFilename) for a POSIX portable filename.
use posix_portable_filename::{PortableFilename, InvalidFilename};
fn main() -> Result<(), InvalidFilename> {
let name = PortableFilename::new("my_file.txt")?;
// `new()` only succeeds if the filename conforms.
println!("{}", name); // Display impl
let s: &str = name.as_str(); // Deref to &str
// Invalid inputs are rejected
assert!(PortableFilename::new("foo/bar").is_err()); // path separator
assert!(PortableFilename::new("-rf").is_err()); // leading hyphen
assert!(PortableFilename::new("foo bar").is_err()); // space
assert!(PortableFilename::new("..").is_err()); // reserved
Ok(())
}
Parse, don't validate. Once you have a PortableFilename, you know it's valid. No need to re-check at every use site. The type system enforces the invariant.
POSIX.1-2008 defines the Portable Filename Character Set:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
a b c d e f g h i j k l m n o p q r s t u v w x y z
0 1 2 3 4 5 6 7 8 9 . _ -
Additionally:
- since this risks confusion with command-line options.. and .. are reserved for directory traversalThis crate provides two optional features: arbitrary and serde.
arbitraryImplements Arbitrary for PortableFilename, enabling structure-aware fuzzing with cargo-fuzz. Downstream crates can fuzz code that handles filenames without wasting cycles on invalid inputs.
# Cargo.toml
[dependencies]
posix-portable-filename = { version = "0.2", features = ["arbitrary"] }
// fuzz/fuzz_targets/my_target.rs
libfuzzer_sys::fuzz_target!(|filename: PortableFilename| {
my_function_that_takes_filename(filename);
});
serdeSerialize/deserialize with validation on deserialization
# Cargo.toml
[dependencies]
posix-portable-filename = { version = "0.2", features = ["serde"] }
Since this crates has optional features, use:
cargo test --all-features
This library contains no unsafe code (#![forbid(unsafe_code)]) and has a small, auditable validation path. The validation logic is ~30 lines of straightforward character checking; see PortableFilename::new().
Testing includes:
* The fuzz harness feeds arbitrary byte sequences to the parser; coverage saturation means all reachable code paths have been exercised.
After you install [cargo fuzz] as recommended (which involves using Nightly Rust), then you can run fuzz testing with:
cargo +nightly fuzz run fuzz_new
Note: "rwsv" stands for "read, write, seek, validate" -- the methods exercised by the fuzzer. [cargo fuzz]: https://github.com/rust-fuzz/cargo-fuzz
If you want to have local documentation that shows feature badges; e.g.:
Available on crate feature serde only.
Then use this magic incantation:
RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --open --all-features --no-deps
Read more about this at thread #64577 on Rust programming language users forum.
Dual-licensed under MIT or Apache 2.0, at your option.