1use bstr::ByteSlice;
3use std::{
4 borrow::Borrow,
5 fmt::{self, Debug, Display},
6 mem,
7 ops::Deref,
8 str::FromStr,
9};
10
11mod component;
12pub use component::{PathComponent, PathComponentError};
13
14#[derive(Eq, Hash, PartialEq)]
18#[repr(transparent)] pub struct Path {
20 inner: [u8],
23}
24
25#[allow(dead_code)]
26impl Path {
27 pub const ROOT: &'static Path = unsafe { Path::from_bytes_unchecked(&[]) };
29
30 const unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Path {
32 unsafe { mem::transmute(bytes) }
34 }
35
36 pub fn from_bytes(bytes: &[u8]) -> Option<&Path> {
39 if !bytes.is_empty() {
40 for component in bytes.split_str(b"/") {
42 if component::validate_name(component).is_err() {
43 return None;
44 }
45 }
46 }
47
48 Some(unsafe { Path::from_bytes_unchecked(bytes) })
50 }
51
52 pub fn into_boxed_bytes(self: Box<Path>) -> Box<[u8]> {
53 unsafe { mem::transmute(self) }
55 }
56
57 pub fn parent(&self) -> Option<&Path> {
62 if self.inner.is_empty() {
64 return None;
65 }
66
67 Some(
68 if let Some((parent, _file_name)) = self.inner.rsplit_once_str(b"/") {
69 unsafe { Path::from_bytes_unchecked(parent) }
71 } else {
72 Path::ROOT
74 },
75 )
76 }
77
78 pub fn try_join(&self, name: &[u8]) -> Result<PathBuf, std::io::Error> {
80 let mut v = PathBuf::with_capacity(self.inner.len() + name.len() + 1);
81 v.inner.extend_from_slice(&self.inner);
82 v.try_push(name)?;
83
84 Ok(v)
85 }
86
87 pub fn components(&self) -> impl Iterator<Item = PathComponent> + '_ {
91 let mut iter = self.inner.split_str(&b"/");
92
93 if self.inner.is_empty() {
95 let _ = iter.next();
96 }
97
98 iter.map(|b| PathComponent {
99 inner: bytes::Bytes::copy_from_slice(b),
100 })
101 }
102
103 pub fn components_bytes(&self) -> impl Iterator<Item = &[u8]> {
107 let mut iter = self.inner.split_str(&b"/");
108
109 if self.inner.is_empty() {
111 let _ = iter.next();
112 }
113
114 iter
115 }
116
117 pub fn file_name(&self) -> Option<PathComponent> {
120 self.components().last()
121 }
122
123 pub fn file_name_bytes(&self) -> Option<&[u8]> {
125 self.components_bytes().last()
126 }
127
128 pub fn extension(&self) -> Option<&[u8]> {
130 let file_name = match self.inner[..].rsplit_once_str(b"/") {
131 Some((_, r)) => r,
132 None => &self.inner[..],
133 };
134 let mut iter = file_name.rsplitn(2, |b| *b == b'.');
135 let e = iter.next();
136 iter.next()?;
138
139 e
140 }
141
142 pub fn as_bytes(&self) -> &[u8] {
143 &self.inner
144 }
145}
146
147impl Debug for Path {
148 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
149 Debug::fmt(self.inner.as_bstr(), f)
150 }
151}
152
153impl Display for Path {
154 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
155 Display::fmt(self.inner.as_bstr(), f)
156 }
157}
158
159impl AsRef<Path> for Path {
160 fn as_ref(&self) -> &Path {
161 self
162 }
163}
164
165#[derive(Clone, Default, Eq, Hash, PartialEq)]
169pub struct PathBuf {
170 inner: Vec<u8>,
171}
172
173impl Deref for PathBuf {
174 type Target = Path;
175
176 fn deref(&self) -> &Self::Target {
177 unsafe { Path::from_bytes_unchecked(&self.inner) }
179 }
180}
181
182impl AsRef<Path> for PathBuf {
183 fn as_ref(&self) -> &Path {
184 self
185 }
186}
187
188impl ToOwned for Path {
189 type Owned = PathBuf;
190
191 fn to_owned(&self) -> Self::Owned {
192 PathBuf {
193 inner: self.inner.to_owned(),
194 }
195 }
196}
197
198impl Borrow<Path> for PathBuf {
199 fn borrow(&self) -> &Path {
200 self
201 }
202}
203
204impl From<Box<Path>> for PathBuf {
205 fn from(value: Box<Path>) -> Self {
206 unsafe { PathBuf::from_bytes_unchecked(value.into_boxed_bytes().into_vec()) }
208 }
209}
210
211impl From<&Path> for PathBuf {
212 fn from(value: &Path) -> Self {
213 value.to_owned()
214 }
215}
216
217impl FromStr for PathBuf {
218 type Err = std::io::Error;
219
220 fn from_str(s: &str) -> Result<PathBuf, Self::Err> {
221 Ok(Path::from_bytes(s.as_bytes())
222 .ok_or(std::io::ErrorKind::InvalidData)?
223 .to_owned())
224 }
225}
226
227impl Debug for PathBuf {
228 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
229 Debug::fmt(&**self, f)
230 }
231}
232
233impl Display for PathBuf {
234 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
235 Display::fmt(&**self, f)
236 }
237}
238
239impl PathBuf {
240 pub fn new() -> PathBuf {
241 Self::default()
242 }
243
244 pub fn with_capacity(capacity: usize) -> PathBuf {
245 Self {
247 inner: Vec::with_capacity(capacity),
248 }
249 }
250
251 pub fn try_push(&mut self, name: &[u8]) -> Result<(), std::io::Error> {
253 if component::validate_name(name).is_err() {
254 return Err(std::io::ErrorKind::InvalidData.into());
255 }
256
257 if !self.inner.is_empty() {
258 self.inner.push(b'/');
259 }
260
261 self.inner.extend_from_slice(name);
262
263 Ok(())
264 }
265
266 unsafe fn from_bytes_unchecked(bytes: Vec<u8>) -> PathBuf {
268 PathBuf { inner: bytes }
269 }
270
271 #[cfg(unix)]
286 pub fn from_host_path(
287 host_path: &std::path::Path,
288 canonicalize_dotdot: bool,
289 ) -> Result<Self, std::io::Error> {
290 let mut p = PathBuf::with_capacity(host_path.as_os_str().len());
291
292 for component in host_path.components() {
293 match component {
294 std::path::Component::Prefix(_) | std::path::Component::RootDir => {
295 return Err(std::io::Error::new(
296 std::io::ErrorKind::InvalidData,
297 "found disallowed prefix or rootdir",
298 ));
299 }
300 std::path::Component::CurDir => continue, std::path::Component::ParentDir => {
302 if canonicalize_dotdot {
303 p = p
306 .parent()
307 .ok_or_else(|| {
308 std::io::Error::new(
309 std::io::ErrorKind::InvalidData,
310 "found .. going too far up",
311 )
312 })?
313 .to_owned();
314 } else {
315 return Err(std::io::Error::new(
316 std::io::ErrorKind::InvalidData,
317 "found disallowed ..",
318 ));
319 }
320 }
321 std::path::Component::Normal(s) => {
322 p.try_push(s.as_encoded_bytes()).map_err(|_| {
324 std::io::Error::new(
325 std::io::ErrorKind::InvalidData,
326 "encountered invalid node in sub_path component",
327 )
328 })?
329 }
330 }
331 }
332
333 Ok(p)
334 }
335
336 pub fn into_boxed_path(self) -> Box<Path> {
337 unsafe { mem::transmute(self.inner.into_boxed_slice()) }
340 }
341
342 pub fn into_bytes(self) -> Vec<u8> {
343 self.inner
344 }
345}
346
347#[cfg(test)]
348mod test {
349 use super::{Path, PathBuf};
350 use bstr::ByteSlice;
351 use rstest::rstest;
352
353 #[rstest]
357 #[case::empty("", 0)]
358 #[case("a", 1)]
359 #[case("a/b", 2)]
360 #[case("a/b/c", 3)]
361 #[case::cursed("C:\\a/b", 2)]
366 #[case::cursed("\\\\snix-store", 1)]
367 pub fn from_str(#[case] s: &str, #[case] num_components: usize) {
368 let p: PathBuf = s.parse().expect("must parse");
369
370 assert_eq!(s.as_bytes(), p.as_bytes(), "inner bytes mismatch");
371 assert_eq!(
372 num_components,
373 p.components_bytes().count(),
374 "number of components mismatch"
375 );
376 }
377
378 #[rstest]
379 #[case::absolute("/a/b")]
380 #[case::two_forward_slashes_start("//a/b")]
381 #[case::two_forward_slashes_middle("a/b//c/d")]
382 #[case::trailing_slash("a/b/")]
383 #[case::dot(".")]
384 #[case::dotdot("..")]
385 #[case::dot_start("./a")]
386 #[case::dotdot_start("../a")]
387 #[case::dot_middle("a/./b")]
388 #[case::dotdot_middle("a/../b")]
389 #[case::dot_end("a/b/.")]
390 #[case::dotdot_end("a/b/..")]
391 #[case::null("fo\0o")]
392 pub fn from_str_fail(#[case] s: &str) {
393 s.parse::<PathBuf>().expect_err("must fail");
394 }
395
396 #[rstest]
397 #[case("foo", "")]
398 #[case("foo/bar", "foo")]
399 #[case("foo2/bar2", "foo2")]
400 #[case("foo/bar/baz", "foo/bar")]
401 pub fn parent(#[case] p: PathBuf, #[case] exp_parent: PathBuf) {
402 assert_eq!(Some(&*exp_parent), p.parent());
403 }
404
405 #[rstest]
406 pub fn no_parent() {
407 assert!(Path::ROOT.parent().is_none());
408 }
409
410 #[rstest]
411 #[case("a", "b", "a/b")]
412 #[case("a", "b", "a/b")]
413 pub fn join_push(#[case] mut p: PathBuf, #[case] name: &str, #[case] exp_p: PathBuf) {
414 assert_eq!(exp_p, p.try_join(name.as_bytes()).expect("join failed"));
415 p.try_push(name.as_bytes()).expect("push failed");
416 assert_eq!(exp_p, p);
417 }
418
419 #[rstest]
420 #[case("a", "/")]
421 #[case("a", "")]
422 #[case("a", "b/c")]
423 #[case("", "/")]
424 #[case("", "")]
425 #[case("", "b/c")]
426 #[case("", ".")]
427 #[case("", "..")]
428 pub fn join_push_fail(#[case] mut p: PathBuf, #[case] name: &str) {
429 p.try_join(name.as_bytes())
430 .expect_err("join succeeded unexpectedly");
431 p.try_push(name.as_bytes())
432 .expect_err("push succeeded unexpectedly");
433 }
434
435 #[rstest]
436 #[case::empty("", vec![])]
437 #[case("a", vec!["a"])]
438 #[case("a/b", vec!["a", "b"])]
439 #[case("a/b/c", vec!["a","b", "c"])]
440 pub fn components_bytes(#[case] p: PathBuf, #[case] exp_components: Vec<&str>) {
441 assert_eq!(
442 exp_components,
443 p.components_bytes()
444 .map(|x| x.to_str().unwrap())
445 .collect::<Vec<_>>()
446 );
447 }
448
449 #[rstest]
450 #[case::empty("", "", false)]
451 #[case::path("a", "a", false)]
452 #[case::path2("a/b", "a/b", false)]
453 #[case::double_slash_middle("a//b", "a/b", false)]
454 #[case::dot(".", "", false)]
455 #[case::dot_start("./a/b", "a/b", false)]
456 #[case::dot_middle("a/./b", "a/b", false)]
457 #[case::dot_end("a/b/.", "a/b", false)]
458 #[case::trailing_slash("a/b/", "a/b", false)]
459 #[case::dotdot_canonicalize("a/..", "", true)]
460 #[case::dotdot_canonicalize2("a/../b", "b", true)]
461 #[cfg_attr(unix, case::faux_prefix("\\\\nix-store", "\\\\nix-store", false))]
462 #[cfg_attr(unix, case::faux_letter("C:\\foo.txt", "C:\\foo.txt", false))]
463 pub fn from_host_path(
464 #[case] host_path: std::path::PathBuf,
465 #[case] exp_path: PathBuf,
466 #[case] canonicalize_dotdot: bool,
467 ) {
468 let p = PathBuf::from_host_path(&host_path, canonicalize_dotdot).expect("must succeed");
469
470 assert_eq!(exp_path, p);
471 }
472
473 #[rstest]
474 #[case::absolute("/", false)]
475 #[case::dotdot_root("..", false)]
476 #[case::dotdot_root_canonicalize("..", true)]
477 #[case::dotdot_root_no_canonicalize("a/..", false)]
478 #[case::invalid_name("foo/bar\0", false)]
479 pub fn from_host_path_fail(
482 #[case] host_path: std::path::PathBuf,
483 #[case] canonicalize_dotdot: bool,
484 ) {
485 PathBuf::from_host_path(&host_path, canonicalize_dotdot).expect_err("must fail");
486 }
487
488 #[rstest]
489 #[case::without_dot(PathBuf { inner: "foo".into()}, None)]
490 #[case::simple(PathBuf { inner: "foo.txt".into()}, Some(&b"txt"[..]))]
491 #[case::empty(PathBuf { inner: "foo.".into()}, Some(&b""[..]))]
492 #[case::multiple(PathBuf { inner: "foo.bar.txt".into()}, Some(&b"txt"[..]))]
493 #[case::with_components(PathBuf { inner: "foo/foo.txt".into()}, Some(&b"txt"[..]))]
494 #[case::path(PathBuf { inner: "foo.a/foo".into()}, None)]
495 fn extension(#[case] p: PathBuf, #[case] exp_extension: Option<&[u8]>) {
496 assert_eq!(exp_extension, p.extension())
497 }
498}