1#![forbid(unsafe_code)]
47#![warn(missing_docs)]
48
49use std::io;
50use std::path::{Path, PathBuf};
51
52#[cfg(target_os = "linux")]
53use std::path::Component;
54
55#[cfg(target_os = "linux")]
57const MAX_SYMLINK_FOLLOWS: u32 = 40;
58
59pub fn canonicalize(path: impl AsRef<Path>) -> io::Result<PathBuf> {
104 canonicalize_impl(path.as_ref())
105}
106
107#[cfg(target_os = "linux")]
108fn canonicalize_impl(path: &Path) -> io::Result<PathBuf> {
109 if let Some((namespace_prefix, remainder)) = find_namespace_boundary(path) {
111 std::fs::metadata(&namespace_prefix)?;
115
116 if remainder.as_os_str().is_empty() {
117 Ok(namespace_prefix)
119 } else {
120 let resolved_prefix = std::fs::canonicalize(&namespace_prefix)?;
126
127 let full_path = namespace_prefix.join(&remainder);
130 let canonicalized = std::fs::canonicalize(full_path)?;
131
132 if let Ok(suffix) = canonicalized.strip_prefix(&resolved_prefix) {
135 Ok(namespace_prefix.join(suffix))
137 } else {
138 Ok(canonicalized)
142 }
143 }
144 } else {
145 if let Some(magic_path) = detect_indirect_proc_magic_link(path)? {
155 return canonicalize_impl(&magic_path);
158 }
159
160 std::fs::canonicalize(path)
162 }
163}
164
165#[cfg(not(target_os = "linux"))]
166fn canonicalize_impl(path: &Path) -> io::Result<PathBuf> {
167 #[cfg(all(feature = "dunce", windows))]
169 {
170 dunce::canonicalize(path)
171 }
172 #[cfg(not(all(feature = "dunce", windows)))]
173 {
174 std::fs::canonicalize(path)
175 }
176}
177
178#[cfg(target_os = "linux")]
186fn find_namespace_boundary(path: &Path) -> Option<(PathBuf, PathBuf)> {
187 let mut components = path.components();
188
189 if components.next() != Some(Component::RootDir) {
191 return None;
192 }
193
194 match components.next() {
196 Some(Component::Normal(s)) if s == "proc" => {}
197 _ => return None,
198 }
199
200 let pid_component = match components.next() {
202 Some(Component::Normal(s)) => s,
203 _ => return None,
204 };
205
206 let pid_str = pid_component.to_string_lossy();
207 let is_valid_pid = pid_str == "self"
208 || pid_str == "thread-self"
209 || (!pid_str.is_empty() && pid_str.chars().all(|c| c.is_ascii_digit()));
210
211 if !is_valid_pid {
212 return None;
213 }
214
215 let next_component = match components.next() {
217 Some(Component::Normal(s)) => s,
218 _ => return None,
219 };
220
221 if next_component == "root" || next_component == "cwd" {
222 let mut prefix = PathBuf::from("/proc");
224 prefix.push(pid_component);
225 prefix.push(next_component);
226
227 let remainder: PathBuf = components.collect();
229 Some((prefix, remainder))
230 } else if next_component == "task" {
231 let tid_component = match components.next() {
235 Some(Component::Normal(s)) => s,
236 _ => return None,
237 };
238
239 let tid_str = tid_component.to_string_lossy();
240 if tid_str.is_empty() || !tid_str.chars().all(|c| c.is_ascii_digit()) {
241 return None;
242 }
243
244 let ns_type = match components.next() {
246 Some(Component::Normal(s)) if s == "root" || s == "cwd" => s,
247 _ => return None,
248 };
249
250 let mut prefix = PathBuf::from("/proc");
251 prefix.push(pid_component);
252 prefix.push("task");
253 prefix.push(tid_component);
254 prefix.push(ns_type);
255
256 let remainder: PathBuf = components.collect();
258 Some((prefix, remainder))
259 } else {
260 None
261 }
262}
263
264#[cfg(target_os = "linux")]
273fn is_proc_magic_path(path: &Path) -> bool {
274 find_namespace_boundary(path).is_some()
275}
276
277#[cfg(target_os = "linux")]
284fn detect_indirect_proc_magic_link(path: &Path) -> io::Result<Option<PathBuf>> {
285 let mut current_path = if path.is_absolute() {
286 path.to_path_buf()
287 } else {
288 std::env::current_dir()?.join(path)
289 };
290
291 let mut iterations = 0;
292
293 'scan: loop {
295 if iterations >= MAX_SYMLINK_FOLLOWS {
296 return Ok(None);
297 }
298
299 if is_proc_magic_path(¤t_path) {
309 return Ok(Some(current_path));
310 }
311
312 let mut accumulated = PathBuf::new();
313 let mut components = current_path.components().peekable();
314
315 if let Some(Component::RootDir) = components.peek() {
316 accumulated.push("/");
317 components.next();
318 }
319
320 while let Some(component) = components.next() {
321 match component {
322 Component::RootDir => {
323 accumulated.push("/");
324 }
325 Component::CurDir => {}
326 Component::ParentDir => {
327 accumulated.pop();
328 if is_proc_magic_path(&accumulated) {
330 let remainder: PathBuf = components.collect();
332 return Ok(Some(accumulated.join(remainder)));
333 }
334 }
335 Component::Normal(name) => {
336 let next_path = accumulated.join(name);
337
338 let metadata = match std::fs::symlink_metadata(&next_path) {
340 Ok(m) => m,
341 Err(_) => {
342 accumulated.push(name);
343 continue;
344 }
345 };
346
347 if metadata.is_symlink() {
348 iterations += 1;
350 let target = std::fs::read_link(&next_path)?;
351
352 let parent = next_path.parent().unwrap_or(Path::new("/"));
354 let remainder: PathBuf = components.collect();
355
356 let resolved = if target.is_relative() {
357 parent.join(target)
358 } else {
359 target
360 };
361
362 current_path = resolved.join(remainder);
363 continue 'scan; }
365
366 accumulated.push(name);
367 }
368 Component::Prefix(_) => unreachable!("Linux paths don't have prefixes"),
369 }
370 }
371
372 if is_proc_magic_path(&accumulated) {
376 return Ok(Some(accumulated));
377 }
378
379 return Ok(None);
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386
387 #[cfg(target_os = "linux")]
388 mod linux {
389 use super::*;
390
391 #[test]
398 fn test_find_namespace_boundary_proc_pid_root() {
399 let (prefix, remainder) =
402 find_namespace_boundary(Path::new("/proc/1234/root/etc/passwd")).unwrap();
403 assert_eq!(prefix, PathBuf::from("/proc/1234/root"));
404 assert_eq!(remainder, PathBuf::from("etc/passwd"));
405 }
406
407 #[test]
408 fn test_find_namespace_boundary_proc_pid_cwd() {
409 let (prefix, remainder) =
412 find_namespace_boundary(Path::new("/proc/5678/cwd/some/file.txt")).unwrap();
413 assert_eq!(prefix, PathBuf::from("/proc/5678/cwd"));
414 assert_eq!(remainder, PathBuf::from("some/file.txt"));
415 }
416
417 #[test]
418 fn test_find_namespace_boundary_proc_self_root() {
419 let (prefix, remainder) =
422 find_namespace_boundary(Path::new("/proc/self/root/etc/passwd")).unwrap();
423 assert_eq!(prefix, PathBuf::from("/proc/self/root"));
424 assert_eq!(remainder, PathBuf::from("etc/passwd"));
425 }
426
427 #[test]
428 fn test_find_namespace_boundary_proc_thread_self_root() {
429 let (prefix, remainder) =
431 find_namespace_boundary(Path::new("/proc/thread-self/root/app/config")).unwrap();
432 assert_eq!(prefix, PathBuf::from("/proc/thread-self/root"));
433 assert_eq!(remainder, PathBuf::from("app/config"));
434 }
435
436 #[test]
437 fn test_find_namespace_boundary_just_prefix_no_remainder() {
438 let (prefix, remainder) =
440 find_namespace_boundary(Path::new("/proc/1234/root")).unwrap();
441 assert_eq!(prefix, PathBuf::from("/proc/1234/root"));
442 assert_eq!(remainder, PathBuf::from(""));
443 }
444
445 #[test]
446 fn test_find_namespace_boundary_normal_path_returns_none() {
447 assert!(find_namespace_boundary(Path::new("/home/user/file.txt")).is_none());
449 }
450
451 #[test]
452 fn test_find_namespace_boundary_proc_other_files_not_namespace() {
453 assert!(find_namespace_boundary(Path::new("/proc/1234/status")).is_none());
456 assert!(find_namespace_boundary(Path::new("/proc/1234/exe")).is_none());
457 assert!(find_namespace_boundary(Path::new("/proc/1234/fd/0")).is_none());
458 }
459
460 #[test]
461 fn test_find_namespace_boundary_relative_path_rejected() {
462 assert!(find_namespace_boundary(Path::new("proc/1234/root")).is_none());
465 }
466
467 #[test]
468 fn test_find_namespace_boundary_invalid_pid_rejected() {
469 assert!(find_namespace_boundary(Path::new("/proc/abc/root")).is_none());
472 assert!(find_namespace_boundary(Path::new("/proc/123abc/root")).is_none());
473 assert!(find_namespace_boundary(Path::new("/proc//root")).is_none());
474 }
475
476 #[test]
481 fn reading_container_file_from_host() {
482 let container_pid = std::process::id(); let container_root = format!("/proc/{container_pid}/root");
485 let file_inside_container = format!("{container_root}/etc");
486
487 let canonical_path = canonicalize(file_inside_container).unwrap();
488
489 assert!(canonical_path.starts_with(&container_root));
491 }
492
493 #[test]
494 fn validating_path_stays_in_container() {
495 let container_pid = std::process::id();
497 let container_root = format!("/proc/{container_pid}/root");
498 let user_requested_file = format!("{container_root}/etc/passwd");
499
500 let canonical = canonicalize(user_requested_file).unwrap();
501
502 let is_inside_container = canonical.starts_with(&container_root);
504 assert!(is_inside_container);
505 }
506
507 #[test]
508 fn proc_self_root_preserved_not_resolved_to_slash() {
509 let path = "/proc/self/root";
510
511 let our_result = canonicalize(path).unwrap();
512 let std_result = std::fs::canonicalize(path).unwrap();
513
514 assert_eq!(std_result, Path::new("/"));
516
517 assert_eq!(our_result, Path::new("/proc/self/root"));
519 }
520
521 #[test]
522 fn proc_self_cwd_preserved() {
523 let path = "/proc/self/cwd";
524
525 let result = canonicalize(path).unwrap();
526
527 assert_eq!(result, Path::new("/proc/self/cwd"));
528 }
529
530 #[test]
531 fn explicit_pid_root_preserved() {
532 let my_pid = std::process::id();
533 let path = format!("/proc/{my_pid}/root");
534
535 let our_result = canonicalize(&path).unwrap();
536 let std_result = std::fs::canonicalize(&path).unwrap();
537
538 assert_eq!(std_result, Path::new("/"));
539 assert_eq!(our_result, Path::new(&path));
540 }
541
542 #[test]
543 fn subpath_through_namespace_preserves_prefix() {
544 let path = "/proc/self/root/etc";
545
546 let result = canonicalize(path).unwrap();
547
548 assert!(result.starts_with("/proc/self/root"));
549 assert!(result.ends_with("etc"));
550 }
551
552 #[test]
553 fn normal_paths_behave_like_std() {
554 let path = std::env::temp_dir();
555
556 let our_result = canonicalize(&path).unwrap();
557 let std_result = std::fs::canonicalize(&path).unwrap();
558
559 assert_eq!(our_result, std_result);
560 }
561
562 #[test]
567 fn nonexistent_file_returns_not_found() {
568 let path = "/proc/self/root/this_file_does_not_exist_12345";
569
570 let result = canonicalize(path);
571
572 assert!(result.is_err());
573 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
574 }
575
576 #[test]
577 fn nonexistent_pid_returns_not_found() {
578 let path = "/proc/4294967295/root"; let result = canonicalize(path);
581
582 assert!(result.is_err());
583 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
584 }
585
586 #[test]
587 fn empty_path_returns_error() {
588 let result = canonicalize("");
589
590 assert!(result.is_err());
591 }
592
593 #[test]
598 fn dotdot_stays_inside_root_namespace() {
599 let path = "/proc/self/root/tmp/../etc";
600
601 let result = canonicalize(path);
602
603 if let Ok(canonical) = result {
604 assert!(canonical.starts_with("/proc/self/root"));
605 }
606 }
607
608 #[test]
609 fn dot_is_normalized_out() {
610 let path = "/proc/self/root/./etc";
611
612 let result = canonicalize(path);
613
614 if let Ok(canonical) = result {
615 assert!(canonical.starts_with("/proc/self/root"));
616 assert!(!canonical.to_string_lossy().contains("/./"));
617 }
618 }
619
620 #[test]
621 fn deep_path_preserves_namespace() {
622 let path = "/proc/self/root/usr/share/doc";
623
624 let result = canonicalize(path);
625
626 if let Ok(canonical) = result {
627 assert!(canonical.starts_with("/proc/self/root"));
628 }
629 }
630
631 #[test]
632 fn trailing_slash_works() {
633 let with_slash = canonicalize("/proc/self/root/");
634 let without_slash = canonicalize("/proc/self/root");
635
636 if let (Ok(a), Ok(b)) = (with_slash, without_slash) {
637 assert!(a.starts_with("/proc/self/root"));
638 assert!(b.starts_with("/proc/self/root"));
639 }
640 }
641
642 #[test]
643 fn thread_self_root_preserved() {
644 let path = "/proc/thread-self/root";
645
646 if let Ok(result) = canonicalize(path) {
647 assert_eq!(result, PathBuf::from("/proc/thread-self/root"));
648 }
649 }
650
651 #[test]
656 fn boundary_detection_handles_trailing_slash() {
657 let (prefix, _remainder) =
658 find_namespace_boundary(Path::new("/proc/1234/root/")).unwrap();
659 assert_eq!(prefix, PathBuf::from("/proc/1234/root"));
660 }
661
662 #[test]
663 fn boundary_detection_handles_dot_components() {
664 let (prefix, _remainder) =
665 find_namespace_boundary(Path::new("/proc/1234/root/./etc/../etc")).unwrap();
666 assert_eq!(prefix, PathBuf::from("/proc/1234/root"));
667 }
668
669 #[test]
674 fn pid_1_root_requires_permission_or_preserves_prefix() {
675 let path = "/proc/1/root";
676
677 match canonicalize(path) {
678 Ok(result) => {
679 assert_eq!(result, PathBuf::from("/proc/1/root"));
681 assert_eq!(std::fs::canonicalize(path).unwrap(), PathBuf::from("/"));
683 }
684 Err(e) => {
685 assert!(matches!(
687 e.kind(),
688 io::ErrorKind::PermissionDenied | io::ErrorKind::NotFound
689 ));
690 }
691 }
692 }
693
694 #[test]
695 fn pid_1_subpath_preserves_prefix_when_accessible() {
696 let path = "/proc/1/root/etc/hostname";
697
698 match canonicalize(path) {
699 Ok(result) => {
700 assert!(
701 result.starts_with("/proc/1/root"),
702 "must preserve /proc/1/root prefix, got: {:?}",
703 result
704 );
705 }
706 Err(e) => {
707 assert!(matches!(
708 e.kind(),
709 io::ErrorKind::PermissionDenied | io::ErrorKind::NotFound
710 ));
711 }
712 }
713 }
714
715 #[test]
716 fn pid_1_cwd_preserves_prefix_when_accessible() {
717 let path = "/proc/1/cwd";
718
719 match canonicalize(path) {
720 Ok(result) => assert_eq!(result, PathBuf::from("/proc/1/cwd")),
721 Err(e) => {
722 assert!(matches!(
723 e.kind(),
724 io::ErrorKind::PermissionDenied | io::ErrorKind::NotFound
725 ));
726 }
727 }
728 }
729
730 #[test]
731 fn self_and_explicit_pid_both_work() {
732 let my_pid = std::process::id();
733
734 let self_result = canonicalize("/proc/self/root").unwrap();
735 let pid_result = canonicalize(format!("/proc/{my_pid}/root")).unwrap();
736
737 assert_eq!(self_result, Path::new("/proc/self/root"));
738 assert_eq!(pid_result, Path::new(&format!("/proc/{my_pid}/root")));
739 }
740
741 mod indirect_symlink_tests {
746 use super::*;
747 use std::os::unix::fs::symlink;
748
749 #[test]
750 fn symlink_to_proc_self_root_preserves_namespace() {
751 let temp = tempfile::tempdir().unwrap();
752 let link = temp.path().join("link");
753
754 symlink("/proc/self/root", &link).unwrap();
755
756 let result = canonicalize(&link).unwrap();
757
758 assert_ne!(result, Path::new("/")); assert_eq!(result, Path::new("/proc/self/root"));
760 }
761
762 #[test]
763 fn symlink_then_subpath_preserves_namespace() {
764 let temp = tempfile::tempdir().unwrap();
765 let link = temp.path().join("container");
766
767 symlink("/proc/self/root", &link).unwrap();
768
769 let result = canonicalize(link.join("etc")).unwrap();
770
771 assert!(result.starts_with("/proc/self/root"));
772 }
773
774 #[test]
775 fn chained_symlinks_all_followed() {
776 let temp = tempfile::tempdir().unwrap();
777 let link1 = temp.path().join("link1");
778 let link2 = temp.path().join("link2");
779
780 symlink("/proc/self/root", &link2).unwrap();
781 symlink(&link2, &link1).unwrap();
782
783 let result = canonicalize(&link1).unwrap();
784
785 assert_eq!(result, Path::new("/proc/self/root"));
786 }
787
788 #[test]
789 fn symlink_to_explicit_pid_root_preserved() {
790 let my_pid = std::process::id();
791 let target = format!("/proc/{my_pid}/root");
792 let temp = tempfile::tempdir().unwrap();
793 let link = temp.path().join("link");
794
795 symlink(&target, &link).unwrap();
796
797 let result = canonicalize(&link).unwrap();
798
799 assert_ne!(result, Path::new("/"));
800 assert_eq!(result, Path::new(&target));
801 }
802
803 #[test]
804 fn symlink_to_cwd_preserved() {
805 let temp = tempfile::tempdir().unwrap();
806 let link = temp.path().join("link");
807
808 symlink("/proc/self/cwd", &link).unwrap();
809
810 let result = canonicalize(&link).unwrap();
811
812 assert!(result.starts_with("/proc/self/cwd"));
813 }
814
815 #[test]
816 fn normal_symlinks_work_like_std() {
817 let temp = tempfile::tempdir().unwrap();
818 let target = temp.path().join("target");
819 let link = temp.path().join("link");
820
821 std::fs::create_dir(&target).unwrap();
822 symlink(&target, &link).unwrap();
823
824 let our_result = canonicalize(&link).unwrap();
825 let std_result = std::fs::canonicalize(&link).unwrap();
826
827 assert_eq!(our_result, std_result);
828 }
829
830 #[test]
831 fn symlink_loop_returns_error_not_hang() {
832 let temp = tempfile::tempdir().unwrap();
833 let link_a = temp.path().join("a");
834 let link_b = temp.path().join("b");
835
836 symlink(&link_b, &link_a).unwrap();
837 symlink(&link_a, &link_b).unwrap();
838
839 let result = canonicalize(&link_a);
840
841 assert!(result.is_err());
842 }
843
844 #[test]
845 fn symlink_to_thread_self_root_preserved() {
846 let temp = tempfile::tempdir().unwrap();
847 let link = temp.path().join("thread_link");
848
849 symlink("/proc/thread-self/root", &link).unwrap();
850
851 if let Ok(result) = canonicalize(&link) {
853 assert!(result.starts_with("/proc/thread-self/root"));
854 }
855 }
856 }
857
858 mod security_tests {
863 use super::*;
864
865 #[test]
866 fn excessive_dotdot_cannot_escape_root_namespace() {
867 let path = "/proc/self/root/../../../../../../../etc/passwd";
868
869 if let Ok(result) = canonicalize(path) {
870 assert!(result.starts_with("/proc/self/root"));
871 }
872 }
873
874 #[test]
875 fn idempotent_canonicalization() {
876 let paths = ["/proc/self/root", "/proc/self/root/etc", "/proc/self/cwd"];
877
878 for path in &paths {
879 if let Ok(first) = canonicalize(path) {
880 if let Ok(second) = canonicalize(&first) {
881 assert_eq!(first, second);
882 }
883 }
884 }
885 }
886
887 #[test]
888 fn uppercase_proc_not_magic() {
889 let result = canonicalize("/PROC/self/root");
890
891 match result {
892 Ok(path) => assert!(!path.starts_with("/proc/")),
893 Err(e) => assert_eq!(e.kind(), io::ErrorKind::NotFound),
894 }
895 }
896
897 #[test]
898 fn double_slashes_normalized() {
899 if let Ok(normal) = canonicalize("/proc/self/root") {
900 if let Ok(doubled) = canonicalize("//proc//self//root") {
901 assert_eq!(normal, doubled);
902 }
903 }
904 }
905
906 #[test]
907 fn relative_proc_path_not_magic() {
908 let _ = canonicalize("proc/self/root"); }
911
912 #[test]
913 fn missing_pid_not_namespace() {
914 let result = find_namespace_boundary(Path::new("/proc/root"));
915 assert!(result.is_none());
916 }
917
918 #[test]
919 fn invalid_special_names_not_namespace() {
920 for name in &["parent", "init", "current", "me"] {
921 let path = format!("/proc/{name}/root");
922 assert!(find_namespace_boundary(Path::new(&path)).is_none());
923 }
924 }
925
926 #[test]
927 fn long_numeric_pid_accepted() {
928 let long_pid = "9".repeat(100);
929 let path = format!("/proc/{long_pid}/root");
930 assert!(find_namespace_boundary(Path::new(&path)).is_some());
931 }
932
933 #[test]
934 fn pid_zero_syntactically_valid() {
935 assert!(find_namespace_boundary(Path::new("/proc/0/root")).is_some());
936 assert!(canonicalize("/proc/0/root").is_err()); }
938
939 #[test]
940 fn negative_pid_not_valid() {
941 assert!(find_namespace_boundary(Path::new("/proc/-1/root")).is_none());
942 }
943
944 #[test]
945 fn leading_zeros_in_pid_accepted() {
946 assert!(find_namespace_boundary(Path::new("/proc/0001234/root")).is_some());
947 }
948
949 #[test]
950 fn symlink_to_deep_proc_path_preserves_prefix() {
951 use std::os::unix::fs::symlink;
952
953 let temp = tempfile::tempdir().unwrap();
954 let link = temp.path().join("link");
955
956 symlink("/proc/self/root/etc", &link).unwrap();
957
958 if let Ok(result) = canonicalize(&link) {
959 assert!(result.starts_with("/proc/self/root"));
960 }
961 }
962
963 #[test]
964 fn relative_symlink_looking_like_proc_not_magic() {
965 use std::os::unix::fs::symlink;
966
967 let temp = tempfile::tempdir().unwrap();
968 let fake_proc = temp.path().join("proc/self/root");
969 std::fs::create_dir_all(fake_proc).unwrap();
970
971 let link = temp.path().join("link");
972 symlink("proc/self/root", &link).unwrap();
973
974 let result = canonicalize(&link).unwrap();
975
976 assert!(!result.starts_with("/proc/self/root"));
977 assert!(result.starts_with(temp.path()));
978 }
979
980 #[test]
981 fn relative_symlink_escape_behaves_like_std() {
982 use std::os::unix::fs::symlink;
985
986 let temp = tempfile::tempdir().unwrap();
987 let subdir = temp.path().join("subdir");
988 std::fs::create_dir(&subdir).unwrap();
989
990 let escape_link = subdir.join("escape");
991 symlink("../../../../../../etc", &escape_link).unwrap();
992
993 let our_result = canonicalize(&escape_link);
994 let std_result = std::fs::canonicalize(&escape_link);
995
996 match (our_result, std_result) {
997 (Ok(ours), Ok(stds)) => assert_eq!(ours, stds),
998 (Err(_), Err(_)) => {} _ => panic!("Behavior should match std"),
1000 }
1001 }
1002 }
1003 }
1004
1005 #[cfg(not(target_os = "linux"))]
1006 mod non_linux {
1007 use super::*;
1008
1009 #[test]
1010 fn test_canonicalize_is_std_on_non_linux() {
1011 let tmp = std::env::temp_dir();
1013 let our_result = canonicalize(&tmp).expect("should succeed");
1014 let std_result = std::fs::canonicalize(&tmp).expect("should succeed");
1015 #[cfg(all(feature = "dunce", windows))]
1017 {
1018 let our_str = our_result.to_string_lossy();
1019 let std_str = std_result.to_string_lossy();
1020 assert!(!our_str.starts_with(r"\\?\"), "dunce should simplify path");
1022 assert!(std_str.starts_with(r"\\?\"), "std returns UNC format");
1023 assert_eq!(our_str.as_ref(), std_str.trim_start_matches(r"\\?\"));
1025 }
1026 #[cfg(not(all(feature = "dunce", windows)))]
1028 {
1029 assert_eq!(our_result, std_result);
1030 }
1031 }
1032 }
1033}