1use crate::error::StrictPathError;
3use crate::path::strict_path::StrictPath;
4use crate::validator::path_history::{Canonicalized, PathHistory};
5use crate::PathBoundary;
6use crate::Result;
7use std::ffi::OsStr;
8use std::fmt;
9use std::hash::{Hash, Hasher};
10use std::path::{Path, PathBuf};
11
12#[derive(Clone)]
20pub struct VirtualPath<Marker = ()> {
21 inner: StrictPath<Marker>,
22 virtual_path: PathBuf,
23}
24
25#[inline]
26fn clamp<Marker, H>(
27 restriction: &PathBoundary<Marker>,
28 anchored: PathHistory<(H, Canonicalized)>,
29) -> crate::Result<crate::path::strict_path::StrictPath<Marker>> {
30 restriction.strict_join(anchored.into_inner())
31}
32
33impl<Marker> VirtualPath<Marker> {
34 pub fn with_root<P: AsRef<Path>>(root: P) -> Result<Self> {
37 let vroot = crate::validator::virtual_root::VirtualRoot::try_new(root)?;
38 vroot.into_virtualpath()
39 }
40
41 pub fn with_root_create<P: AsRef<Path>>(root: P) -> Result<Self> {
44 let vroot = crate::validator::virtual_root::VirtualRoot::try_new_create(root)?;
45 vroot.into_virtualpath()
46 }
47 #[inline]
48 pub(crate) fn new(strict_path: StrictPath<Marker>) -> Self {
49 fn compute_virtual<Marker>(
50 system_path: &std::path::Path,
51 restriction: &crate::PathBoundary<Marker>,
52 ) -> std::path::PathBuf {
53 use std::ffi::OsString;
54 use std::path::Component;
55
56 #[cfg(windows)]
57 fn strip_verbatim(p: &std::path::Path) -> std::path::PathBuf {
58 let s = p.as_os_str().to_string_lossy();
59 if let Some(trimmed) = s.strip_prefix("\\\\?\\") {
60 return std::path::PathBuf::from(trimmed);
61 }
62 if let Some(trimmed) = s.strip_prefix("\\\\.\\") {
63 return std::path::PathBuf::from(trimmed);
64 }
65 std::path::PathBuf::from(s.to_string())
66 }
67
68 #[cfg(not(windows))]
69 fn strip_verbatim(p: &std::path::Path) -> std::path::PathBuf {
70 p.to_path_buf()
71 }
72
73 let system_norm = strip_verbatim(system_path);
74 let jail_norm = strip_verbatim(restriction.path());
75
76 if let Ok(stripped) = system_norm.strip_prefix(&jail_norm) {
77 let mut cleaned = std::path::PathBuf::new();
78 for comp in stripped.components() {
79 if let Component::Normal(name) = comp {
80 let s = name.to_string_lossy();
81 let cleaned_s = s.replace(['\n', ';'], "_");
82 if cleaned_s == s {
83 cleaned.push(name);
84 } else {
85 cleaned.push(OsString::from(cleaned_s));
86 }
87 }
88 }
89 return cleaned;
90 }
91
92 let mut strictpath_comps: Vec<_> = system_norm
93 .components()
94 .filter(|c| !matches!(c, Component::Prefix(_) | Component::RootDir))
95 .collect();
96 let mut boundary_comps: Vec<_> = jail_norm
97 .components()
98 .filter(|c| !matches!(c, Component::Prefix(_) | Component::RootDir))
99 .collect();
100
101 #[cfg(windows)]
102 fn comp_eq(a: &Component, b: &Component) -> bool {
103 match (a, b) {
104 (Component::Normal(x), Component::Normal(y)) => {
105 x.to_string_lossy().to_ascii_lowercase()
106 == y.to_string_lossy().to_ascii_lowercase()
107 }
108 _ => false,
109 }
110 }
111
112 #[cfg(not(windows))]
113 fn comp_eq(a: &Component, b: &Component) -> bool {
114 a == b
115 }
116
117 while !strictpath_comps.is_empty()
118 && !boundary_comps.is_empty()
119 && comp_eq(&strictpath_comps[0], &boundary_comps[0])
120 {
121 strictpath_comps.remove(0);
122 boundary_comps.remove(0);
123 }
124
125 let mut vb = std::path::PathBuf::new();
126 for c in strictpath_comps {
127 if let Component::Normal(name) = c {
128 let s = name.to_string_lossy();
129 let cleaned = s.replace(['\n', ';'], "_");
130 if cleaned == s {
131 vb.push(name);
132 } else {
133 vb.push(OsString::from(cleaned));
134 }
135 }
136 }
137 vb
138 }
139
140 let virtual_path = compute_virtual(strict_path.path(), strict_path.boundary());
141
142 Self {
143 inner: strict_path,
144 virtual_path,
145 }
146 }
147
148 #[inline]
151 pub fn unvirtual(self) -> StrictPath<Marker> {
152 self.inner
153 }
154
155 #[inline]
224 pub fn change_marker<NewMarker>(self) -> VirtualPath<NewMarker> {
225 let VirtualPath {
226 inner,
227 virtual_path,
228 } = self;
229
230 VirtualPath {
231 inner: inner.change_marker(),
232 virtual_path,
233 }
234 }
235
236 #[inline]
246 pub fn try_into_root(self) -> Result<crate::validator::virtual_root::VirtualRoot<Marker>> {
247 Ok(self.inner.try_into_boundary()?.virtualize())
248 }
249
250 #[inline]
261 pub fn try_into_root_create(
262 self,
263 ) -> Result<crate::validator::virtual_root::VirtualRoot<Marker>> {
264 let strict_path = self.inner;
265 let boundary = strict_path.try_into_boundary_create()?;
266 Ok(boundary.virtualize())
267 }
268
269 #[inline]
272 pub fn as_unvirtual(&self) -> &StrictPath<Marker> {
273 &self.inner
274 }
275
276 #[inline]
279 pub fn interop_path(&self) -> &OsStr {
280 self.inner.interop_path()
281 }
282
283 #[inline]
286 pub fn virtual_join<P: AsRef<Path>>(&self, path: P) -> Result<Self> {
287 let candidate = self.virtual_path.join(path.as_ref());
289 let anchored = crate::validator::path_history::PathHistory::new(candidate)
290 .canonicalize_anchored(self.inner.boundary())?;
291 let boundary_path = clamp(self.inner.boundary(), anchored)?;
292 Ok(VirtualPath::new(boundary_path))
293 }
294
295 pub fn virtualpath_parent(&self) -> Result<Option<Self>> {
301 match self.virtual_path.parent() {
302 Some(parent_virtual_path) => {
303 let anchored = crate::validator::path_history::PathHistory::new(
304 parent_virtual_path.to_path_buf(),
305 )
306 .canonicalize_anchored(self.inner.boundary())?;
307 let validated_path = clamp(self.inner.boundary(), anchored)?;
308 Ok(Some(VirtualPath::new(validated_path)))
309 }
310 None => Ok(None),
311 }
312 }
313
314 #[inline]
317 pub fn virtualpath_with_file_name<S: AsRef<OsStr>>(&self, file_name: S) -> Result<Self> {
318 let candidate = self.virtual_path.with_file_name(file_name);
319 let anchored = crate::validator::path_history::PathHistory::new(candidate)
320 .canonicalize_anchored(self.inner.boundary())?;
321 let validated_path = clamp(self.inner.boundary(), anchored)?;
322 Ok(VirtualPath::new(validated_path))
323 }
324
325 pub fn virtualpath_with_extension<S: AsRef<OsStr>>(&self, extension: S) -> Result<Self> {
328 if self.virtual_path.file_name().is_none() {
329 return Err(StrictPathError::path_escapes_boundary(
330 self.virtual_path.clone(),
331 self.inner.boundary().path().to_path_buf(),
332 ));
333 }
334
335 let candidate = self.virtual_path.with_extension(extension);
336 let anchored = crate::validator::path_history::PathHistory::new(candidate)
337 .canonicalize_anchored(self.inner.boundary())?;
338 let validated_path = clamp(self.inner.boundary(), anchored)?;
339 Ok(VirtualPath::new(validated_path))
340 }
341
342 #[inline]
345 pub fn virtualpath_file_name(&self) -> Option<&OsStr> {
346 self.virtual_path.file_name()
347 }
348
349 #[inline]
352 pub fn virtualpath_file_stem(&self) -> Option<&OsStr> {
353 self.virtual_path.file_stem()
354 }
355
356 #[inline]
359 pub fn virtualpath_extension(&self) -> Option<&OsStr> {
360 self.virtual_path.extension()
361 }
362
363 #[inline]
366 pub fn virtualpath_starts_with<P: AsRef<Path>>(&self, p: P) -> bool {
367 self.virtual_path.starts_with(p)
368 }
369
370 #[inline]
373 pub fn virtualpath_ends_with<P: AsRef<Path>>(&self, p: P) -> bool {
374 self.virtual_path.ends_with(p)
375 }
376
377 #[inline]
380 pub fn virtualpath_display(&self) -> VirtualPathDisplay<'_, Marker> {
381 VirtualPathDisplay(self)
382 }
383
384 #[inline]
387 pub fn exists(&self) -> bool {
388 self.inner.exists()
389 }
390
391 #[inline]
394 pub fn is_file(&self) -> bool {
395 self.inner.is_file()
396 }
397
398 #[inline]
401 pub fn is_dir(&self) -> bool {
402 self.inner.is_dir()
403 }
404
405 #[inline]
408 pub fn metadata(&self) -> std::io::Result<std::fs::Metadata> {
409 self.inner.metadata()
410 }
411
412 #[inline]
415 pub fn read_to_string(&self) -> std::io::Result<String> {
416 self.inner.read_to_string()
417 }
418
419 #[deprecated(since = "0.1.0-alpha.5", note = "Use read() instead")]
421 #[inline]
422 pub fn read_bytes(&self) -> std::io::Result<Vec<u8>> {
423 self.inner.read()
424 }
425
426 #[deprecated(since = "0.1.0-alpha.5", note = "Use write(...) instead")]
428 #[inline]
429 pub fn write_bytes(&self, data: &[u8]) -> std::io::Result<()> {
430 self.inner.write(data)
431 }
432
433 #[deprecated(since = "0.1.0-alpha.5", note = "Use write(...) instead")]
435 #[inline]
436 pub fn write_string(&self, data: &str) -> std::io::Result<()> {
437 self.inner.write(data)
438 }
439
440 #[inline]
443 pub fn read(&self) -> std::io::Result<Vec<u8>> {
444 self.inner.read()
445 }
446
447 pub fn read_dir(&self) -> std::io::Result<std::fs::ReadDir> {
450 self.inner.read_dir()
451 }
452
453 #[inline]
456 pub fn write<C: AsRef<[u8]>>(&self, contents: C) -> std::io::Result<()> {
457 self.inner.write(contents)
458 }
459
460 #[inline]
487 pub fn create_file(&self) -> std::io::Result<std::fs::File> {
488 self.inner.create_file()
489 }
490
491 #[inline]
521 pub fn open_file(&self) -> std::io::Result<std::fs::File> {
522 self.inner.open_file()
523 }
524
525 #[inline]
528 pub fn create_dir_all(&self) -> std::io::Result<()> {
529 self.inner.create_dir_all()
530 }
531
532 #[inline]
535 pub fn create_dir(&self) -> std::io::Result<()> {
536 self.inner.create_dir()
537 }
538
539 #[inline]
542 pub fn create_parent_dir(&self) -> std::io::Result<()> {
543 match self.virtualpath_parent() {
544 Ok(Some(parent)) => parent.create_dir(),
545 Ok(None) => Ok(()),
546 Err(crate::StrictPathError::PathEscapesBoundary { .. }) => Ok(()),
547 Err(e) => Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
548 }
549 }
550
551 #[inline]
554 pub fn create_parent_dir_all(&self) -> std::io::Result<()> {
555 match self.virtualpath_parent() {
556 Ok(Some(parent)) => parent.create_dir_all(),
557 Ok(None) => Ok(()),
558 Err(crate::StrictPathError::PathEscapesBoundary { .. }) => Ok(()),
559 Err(e) => Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
560 }
561 }
562
563 #[inline]
566 pub fn remove_file(&self) -> std::io::Result<()> {
567 self.inner.remove_file()
568 }
569
570 #[inline]
573 pub fn remove_dir(&self) -> std::io::Result<()> {
574 self.inner.remove_dir()
575 }
576
577 #[inline]
580 pub fn remove_dir_all(&self) -> std::io::Result<()> {
581 self.inner.remove_dir_all()
582 }
583
584 pub fn virtual_symlink(&self, link_path: &Self) -> std::io::Result<()> {
587 if self.inner.boundary().path() != link_path.inner.boundary().path() {
588 let err = StrictPathError::path_escapes_boundary(
589 link_path.inner.path().to_path_buf(),
590 self.inner.boundary().path().to_path_buf(),
591 );
592 return Err(std::io::Error::new(std::io::ErrorKind::Other, err));
593 }
594
595 self.inner.strict_symlink(&link_path.inner)
596 }
597
598 pub fn virtual_hard_link(&self, link_path: &Self) -> std::io::Result<()> {
601 if self.inner.boundary().path() != link_path.inner.boundary().path() {
602 let err = StrictPathError::path_escapes_boundary(
603 link_path.inner.path().to_path_buf(),
604 self.inner.boundary().path().to_path_buf(),
605 );
606 return Err(std::io::Error::new(std::io::ErrorKind::Other, err));
607 }
608
609 self.inner.strict_hard_link(&link_path.inner)
610 }
611
612 pub fn virtual_rename<P: AsRef<Path>>(&self, dest: P) -> std::io::Result<()> {
616 let dest_ref = dest.as_ref();
617 let dest_v = if dest_ref.is_absolute() {
618 match self.virtual_join(dest_ref) {
619 Ok(p) => p,
620 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
621 }
622 } else {
623 let parent = match self.virtualpath_parent() {
625 Ok(Some(p)) => p,
626 Ok(None) => match self
627 .inner
628 .boundary()
629 .clone()
630 .virtualize()
631 .into_virtualpath()
632 {
633 Ok(root) => root,
634 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
635 },
636 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
637 };
638 match parent.virtual_join(dest_ref) {
639 Ok(p) => p,
640 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
641 }
642 };
643
644 self.inner.strict_rename(dest_v.inner.path())
646 }
647
648 pub fn virtual_copy<P: AsRef<Path>>(&self, dest: P) -> std::io::Result<u64> {
652 let dest_ref = dest.as_ref();
653 let dest_v = if dest_ref.is_absolute() {
654 match self.virtual_join(dest_ref) {
655 Ok(p) => p,
656 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
657 }
658 } else {
659 let parent = match self.virtualpath_parent() {
661 Ok(Some(p)) => p,
662 Ok(None) => match self
663 .inner
664 .boundary()
665 .clone()
666 .virtualize()
667 .into_virtualpath()
668 {
669 Ok(root) => root,
670 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
671 },
672 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
673 };
674 match parent.virtual_join(dest_ref) {
675 Ok(p) => p,
676 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
677 }
678 };
679
680 std::fs::copy(self.inner.path(), dest_v.inner.path())
682 }
683}
684
685pub struct VirtualPathDisplay<'a, Marker>(&'a VirtualPath<Marker>);
686
687impl<'a, Marker> fmt::Display for VirtualPathDisplay<'a, Marker> {
688 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
689 let s_lossy = self.0.virtual_path.to_string_lossy();
691 let s_norm: std::borrow::Cow<'_, str> = {
692 #[cfg(windows)]
693 {
694 std::borrow::Cow::Owned(s_lossy.replace('\\', "/"))
695 }
696 #[cfg(not(windows))]
697 {
698 std::borrow::Cow::Borrowed(&s_lossy)
699 }
700 };
701 if s_norm.starts_with('/') {
702 write!(f, "{s_norm}")
703 } else {
704 write!(f, "/{s_norm}")
705 }
706 }
707}
708
709impl<Marker> fmt::Debug for VirtualPath<Marker> {
710 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
711 f.debug_struct("VirtualPath")
712 .field("system_path", &self.inner.path())
713 .field("virtual", &format!("{}", self.virtualpath_display()))
714 .field("boundary", &self.inner.boundary().path())
715 .field("marker", &std::any::type_name::<Marker>())
716 .finish()
717 }
718}
719
720impl<Marker> PartialEq for VirtualPath<Marker> {
721 #[inline]
722 fn eq(&self, other: &Self) -> bool {
723 self.inner.path() == other.inner.path()
724 }
725}
726
727impl<Marker> Eq for VirtualPath<Marker> {}
728
729impl<Marker> Hash for VirtualPath<Marker> {
730 #[inline]
731 fn hash<H: Hasher>(&self, state: &mut H) {
732 self.inner.path().hash(state);
733 }
734}
735
736impl<Marker> PartialEq<crate::path::strict_path::StrictPath<Marker>> for VirtualPath<Marker> {
737 #[inline]
738 fn eq(&self, other: &crate::path::strict_path::StrictPath<Marker>) -> bool {
739 self.inner.path() == other.path()
740 }
741}
742
743impl<T: AsRef<Path>, Marker> PartialEq<T> for VirtualPath<Marker> {
744 #[inline]
745 fn eq(&self, other: &T) -> bool {
746 let virtual_str = format!("{}", self.virtualpath_display());
749 let other_str = other.as_ref().to_string_lossy();
750
751 let normalized_virtual = virtual_str.as_str();
753
754 #[cfg(windows)]
755 let other_normalized = other_str.replace('\\', "/");
756 #[cfg(not(windows))]
757 let other_normalized = other_str.to_string();
758
759 let normalized_other = if other_normalized.starts_with('/') {
760 other_normalized
761 } else {
762 format!("/{}", other_normalized)
763 };
764
765 normalized_virtual == normalized_other
766 }
767}
768
769impl<T: AsRef<Path>, Marker> PartialOrd<T> for VirtualPath<Marker> {
770 #[inline]
771 fn partial_cmp(&self, other: &T) -> Option<std::cmp::Ordering> {
772 let virtual_str = format!("{}", self.virtualpath_display());
774 let other_str = other.as_ref().to_string_lossy();
775
776 let normalized_virtual = virtual_str.as_str();
778
779 #[cfg(windows)]
780 let other_normalized = other_str.replace('\\', "/");
781 #[cfg(not(windows))]
782 let other_normalized = other_str.to_string();
783
784 let normalized_other = if other_normalized.starts_with('/') {
785 other_normalized
786 } else {
787 format!("/{}", other_normalized)
788 };
789
790 Some(normalized_virtual.cmp(&normalized_other))
791 }
792}
793
794impl<Marker> PartialOrd for VirtualPath<Marker> {
795 #[inline]
796 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
797 Some(self.cmp(other))
798 }
799}
800
801impl<Marker> Ord for VirtualPath<Marker> {
802 #[inline]
803 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
804 self.inner.path().cmp(other.inner.path())
805 }
806}
807
808#[cfg(feature = "serde")]
809impl<Marker> serde::Serialize for VirtualPath<Marker> {
810 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
811 where
812 S: serde::Serializer,
813 {
814 serializer.serialize_str(&format!("{}", self.virtualpath_display()))
815 }
816}