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.virtual_join("")
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.virtual_join("")
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]
158 pub fn try_into_root(self) -> crate::validator::virtual_root::VirtualRoot<Marker> {
159 self.inner.try_into_boundary().virtualize()
160 }
161
162 #[inline]
165 pub fn try_into_root_create(self) -> crate::validator::virtual_root::VirtualRoot<Marker> {
166 let boundary = self.inner.try_into_boundary();
167 if !boundary.exists() {
168 let _ = std::fs::create_dir_all(boundary.as_ref());
170 }
171 boundary.virtualize()
172 }
173
174 #[inline]
177 pub fn as_unvirtual(&self) -> &StrictPath<Marker> {
178 &self.inner
179 }
180
181 #[inline]
184 pub fn interop_path(&self) -> &OsStr {
185 self.inner.interop_path()
186 }
187
188 #[inline]
191 pub fn virtual_join<P: AsRef<Path>>(&self, path: P) -> Result<Self> {
192 let candidate = self.virtual_path.join(path.as_ref());
194 let anchored = crate::validator::path_history::PathHistory::new(candidate)
195 .canonicalize_anchored(self.inner.boundary())?;
196 let boundary_path = clamp(self.inner.boundary(), anchored)?;
197 Ok(VirtualPath::new(boundary_path))
198 }
199
200 pub fn virtualpath_parent(&self) -> Result<Option<Self>> {
206 match self.virtual_path.parent() {
207 Some(parent_virtual_path) => {
208 let anchored = crate::validator::path_history::PathHistory::new(
209 parent_virtual_path.to_path_buf(),
210 )
211 .canonicalize_anchored(self.inner.boundary())?;
212 let validated_path = clamp(self.inner.boundary(), anchored)?;
213 Ok(Some(VirtualPath::new(validated_path)))
214 }
215 None => Ok(None),
216 }
217 }
218
219 #[inline]
222 pub fn virtualpath_with_file_name<S: AsRef<OsStr>>(&self, file_name: S) -> Result<Self> {
223 let candidate = self.virtual_path.with_file_name(file_name);
224 let anchored = crate::validator::path_history::PathHistory::new(candidate)
225 .canonicalize_anchored(self.inner.boundary())?;
226 let validated_path = clamp(self.inner.boundary(), anchored)?;
227 Ok(VirtualPath::new(validated_path))
228 }
229
230 pub fn virtualpath_with_extension<S: AsRef<OsStr>>(&self, extension: S) -> Result<Self> {
233 if self.virtual_path.file_name().is_none() {
234 return Err(StrictPathError::path_escapes_boundary(
235 self.virtual_path.clone(),
236 self.inner.boundary().path().to_path_buf(),
237 ));
238 }
239
240 let candidate = self.virtual_path.with_extension(extension);
241 let anchored = crate::validator::path_history::PathHistory::new(candidate)
242 .canonicalize_anchored(self.inner.boundary())?;
243 let validated_path = clamp(self.inner.boundary(), anchored)?;
244 Ok(VirtualPath::new(validated_path))
245 }
246
247 #[inline]
250 pub fn virtualpath_file_name(&self) -> Option<&OsStr> {
251 self.virtual_path.file_name()
252 }
253
254 #[inline]
257 pub fn virtualpath_file_stem(&self) -> Option<&OsStr> {
258 self.virtual_path.file_stem()
259 }
260
261 #[inline]
264 pub fn virtualpath_extension(&self) -> Option<&OsStr> {
265 self.virtual_path.extension()
266 }
267
268 #[inline]
271 pub fn virtualpath_starts_with<P: AsRef<Path>>(&self, p: P) -> bool {
272 self.virtual_path.starts_with(p)
273 }
274
275 #[inline]
278 pub fn virtualpath_ends_with<P: AsRef<Path>>(&self, p: P) -> bool {
279 self.virtual_path.ends_with(p)
280 }
281
282 #[inline]
285 pub fn virtualpath_display(&self) -> VirtualPathDisplay<'_, Marker> {
286 VirtualPathDisplay(self)
287 }
288
289 #[inline]
292 pub fn exists(&self) -> bool {
293 self.inner.exists()
294 }
295
296 #[inline]
299 pub fn is_file(&self) -> bool {
300 self.inner.is_file()
301 }
302
303 #[inline]
306 pub fn is_dir(&self) -> bool {
307 self.inner.is_dir()
308 }
309
310 #[inline]
313 pub fn metadata(&self) -> std::io::Result<std::fs::Metadata> {
314 self.inner.metadata()
315 }
316
317 #[inline]
320 pub fn read_to_string(&self) -> std::io::Result<String> {
321 self.inner.read_to_string()
322 }
323
324 #[deprecated(since = "0.1.0-alpha.5", note = "Use read() instead")]
326 #[inline]
327 pub fn read_bytes(&self) -> std::io::Result<Vec<u8>> {
328 self.inner.read()
329 }
330
331 #[deprecated(since = "0.1.0-alpha.5", note = "Use write(...) instead")]
333 #[inline]
334 pub fn write_bytes(&self, data: &[u8]) -> std::io::Result<()> {
335 self.inner.write(data)
336 }
337
338 #[deprecated(since = "0.1.0-alpha.5", note = "Use write(...) instead")]
340 #[inline]
341 pub fn write_string(&self, data: &str) -> std::io::Result<()> {
342 self.inner.write(data)
343 }
344
345 #[inline]
348 pub fn read(&self) -> std::io::Result<Vec<u8>> {
349 self.inner.read()
350 }
351
352 pub fn read_dir(&self) -> std::io::Result<std::fs::ReadDir> {
355 self.inner.read_dir()
356 }
357
358 #[inline]
361 pub fn write<C: AsRef<[u8]>>(&self, contents: C) -> std::io::Result<()> {
362 self.inner.write(contents)
363 }
364
365 #[inline]
368 pub fn create_dir_all(&self) -> std::io::Result<()> {
369 self.inner.create_dir_all()
370 }
371
372 #[inline]
375 pub fn create_dir(&self) -> std::io::Result<()> {
376 self.inner.create_dir()
377 }
378
379 #[inline]
382 pub fn create_parent_dir(&self) -> std::io::Result<()> {
383 match self.virtualpath_parent() {
384 Ok(Some(parent)) => parent.create_dir(),
385 Ok(None) => Ok(()),
386 Err(crate::StrictPathError::PathEscapesBoundary { .. }) => Ok(()),
387 Err(e) => Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
388 }
389 }
390
391 #[inline]
394 pub fn create_parent_dir_all(&self) -> std::io::Result<()> {
395 match self.virtualpath_parent() {
396 Ok(Some(parent)) => parent.create_dir_all(),
397 Ok(None) => Ok(()),
398 Err(crate::StrictPathError::PathEscapesBoundary { .. }) => Ok(()),
399 Err(e) => Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
400 }
401 }
402
403 #[inline]
406 pub fn remove_file(&self) -> std::io::Result<()> {
407 self.inner.remove_file()
408 }
409
410 #[inline]
413 pub fn remove_dir(&self) -> std::io::Result<()> {
414 self.inner.remove_dir()
415 }
416
417 #[inline]
420 pub fn remove_dir_all(&self) -> std::io::Result<()> {
421 self.inner.remove_dir_all()
422 }
423
424 pub fn virtual_symlink(&self, link_path: &Self) -> std::io::Result<()> {
427 if self.inner.boundary().path() != link_path.inner.boundary().path() {
428 let err = StrictPathError::path_escapes_boundary(
429 link_path.inner.path().to_path_buf(),
430 self.inner.boundary().path().to_path_buf(),
431 );
432 return Err(std::io::Error::new(std::io::ErrorKind::Other, err));
433 }
434
435 self.inner.strict_symlink(&link_path.inner)
436 }
437
438 pub fn virtual_hard_link(&self, link_path: &Self) -> std::io::Result<()> {
441 if self.inner.boundary().path() != link_path.inner.boundary().path() {
442 let err = StrictPathError::path_escapes_boundary(
443 link_path.inner.path().to_path_buf(),
444 self.inner.boundary().path().to_path_buf(),
445 );
446 return Err(std::io::Error::new(std::io::ErrorKind::Other, err));
447 }
448
449 self.inner.strict_hard_link(&link_path.inner)
450 }
451
452 pub fn virtual_rename<P: AsRef<Path>>(&self, dest: P) -> std::io::Result<()> {
456 let dest_ref = dest.as_ref();
457 let dest_v = if dest_ref.is_absolute() {
458 match self.virtual_join(dest_ref) {
459 Ok(p) => p,
460 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
461 }
462 } else {
463 let parent = match self.virtualpath_parent() {
465 Ok(Some(p)) => p,
466 Ok(None) => match self.virtual_join("") {
467 Ok(root) => root,
468 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
469 },
470 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
471 };
472 match parent.virtual_join(dest_ref) {
473 Ok(p) => p,
474 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
475 }
476 };
477
478 self.inner.strict_rename(dest_v.inner.path())
480 }
481
482 pub fn virtual_copy<P: AsRef<Path>>(&self, dest: P) -> std::io::Result<u64> {
486 let dest_ref = dest.as_ref();
487 let dest_v = if dest_ref.is_absolute() {
488 match self.virtual_join(dest_ref) {
489 Ok(p) => p,
490 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
491 }
492 } else {
493 let parent = match self.virtualpath_parent() {
495 Ok(Some(p)) => p,
496 Ok(None) => match self.virtual_join("") {
497 Ok(root) => root,
498 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
499 },
500 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
501 };
502 match parent.virtual_join(dest_ref) {
503 Ok(p) => p,
504 Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
505 }
506 };
507
508 std::fs::copy(self.inner.path(), dest_v.inner.path())
510 }
511}
512
513pub struct VirtualPathDisplay<'a, Marker>(&'a VirtualPath<Marker>);
514
515impl<'a, Marker> fmt::Display for VirtualPathDisplay<'a, Marker> {
516 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
517 let s_lossy = self.0.virtual_path.to_string_lossy();
519 let s_norm: std::borrow::Cow<'_, str> = {
520 #[cfg(windows)]
521 {
522 std::borrow::Cow::Owned(s_lossy.replace('\\', "/"))
523 }
524 #[cfg(not(windows))]
525 {
526 std::borrow::Cow::Borrowed(&s_lossy)
527 }
528 };
529 if s_norm.starts_with('/') {
530 write!(f, "{s_norm}")
531 } else {
532 write!(f, "/{s_norm}")
533 }
534 }
535}
536
537impl<Marker> fmt::Debug for VirtualPath<Marker> {
538 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
539 f.debug_struct("VirtualPath")
540 .field("system_path", &self.inner.path())
541 .field("virtual", &format!("{}", self.virtualpath_display()))
542 .field("boundary", &self.inner.boundary().path())
543 .field("marker", &std::any::type_name::<Marker>())
544 .finish()
545 }
546}
547
548impl<Marker> PartialEq for VirtualPath<Marker> {
549 #[inline]
550 fn eq(&self, other: &Self) -> bool {
551 self.inner.path() == other.inner.path()
552 }
553}
554
555impl<Marker> Eq for VirtualPath<Marker> {}
556
557impl<Marker> Hash for VirtualPath<Marker> {
558 #[inline]
559 fn hash<H: Hasher>(&self, state: &mut H) {
560 self.inner.path().hash(state);
561 }
562}
563
564impl<Marker> PartialEq<crate::path::strict_path::StrictPath<Marker>> for VirtualPath<Marker> {
565 #[inline]
566 fn eq(&self, other: &crate::path::strict_path::StrictPath<Marker>) -> bool {
567 self.inner.path() == other.path()
568 }
569}
570
571impl<T: AsRef<Path>, Marker> PartialEq<T> for VirtualPath<Marker> {
572 #[inline]
573 fn eq(&self, other: &T) -> bool {
574 let virtual_str = format!("{}", self.virtualpath_display());
577 let other_str = other.as_ref().to_string_lossy();
578
579 let normalized_virtual = virtual_str.as_str();
581
582 #[cfg(windows)]
583 let other_normalized = other_str.replace('\\', "/");
584 #[cfg(not(windows))]
585 let other_normalized = other_str.to_string();
586
587 let normalized_other = if other_normalized.starts_with('/') {
588 other_normalized
589 } else {
590 format!("/{}", other_normalized)
591 };
592
593 normalized_virtual == normalized_other
594 }
595}
596
597impl<T: AsRef<Path>, Marker> PartialOrd<T> for VirtualPath<Marker> {
598 #[inline]
599 fn partial_cmp(&self, other: &T) -> Option<std::cmp::Ordering> {
600 let virtual_str = format!("{}", self.virtualpath_display());
602 let other_str = other.as_ref().to_string_lossy();
603
604 let normalized_virtual = virtual_str.as_str();
606
607 #[cfg(windows)]
608 let other_normalized = other_str.replace('\\', "/");
609 #[cfg(not(windows))]
610 let other_normalized = other_str.to_string();
611
612 let normalized_other = if other_normalized.starts_with('/') {
613 other_normalized
614 } else {
615 format!("/{}", other_normalized)
616 };
617
618 Some(normalized_virtual.cmp(&normalized_other))
619 }
620}
621
622impl<Marker> PartialOrd for VirtualPath<Marker> {
623 #[inline]
624 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
625 Some(self.cmp(other))
626 }
627}
628
629impl<Marker> Ord for VirtualPath<Marker> {
630 #[inline]
631 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
632 self.inner.path().cmp(other.inner.path())
633 }
634}
635
636#[cfg(feature = "serde")]
637impl<Marker> serde::Serialize for VirtualPath<Marker> {
638 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
639 where
640 S: serde::Serializer,
641 {
642 serializer.serialize_str(&format!("{}", self.virtualpath_display()))
643 }
644}