libpna/entry/
name.rs

1use crate::util::{str::join_with_capacity, utf8path::normalize_utf8path};
2use camino::{Utf8Component, Utf8Path};
3use std::borrow::Cow;
4use std::error::Error;
5use std::ffi::{OsStr, OsString};
6use std::fmt::{self, Display, Formatter};
7use std::path::{Path, PathBuf};
8use std::str::{self, Utf8Error};
9
10/// A UTF-8 encoded entry name.
11///
12/// # Examples
13///
14/// ```rust
15/// use libpna::EntryName;
16///
17/// assert_eq!("uer/bin", EntryName::from("uer/bin"));
18/// assert_eq!("user/bin", EntryName::from("/user/bin"));
19/// assert_eq!("user/bin", EntryName::from("/user/bin/"));
20/// assert_eq!("user/bin", EntryName::from("../user/bin/"));
21/// assert_eq!("", EntryName::from("/"));
22/// ```
23#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
24pub struct EntryName(String);
25
26impl EntryName {
27    #[inline]
28    fn new_from_utf8(name: &str) -> Self {
29        Self::from_utf8_preserve_root(name).sanitize()
30    }
31
32    #[inline]
33    fn new_from_path(name: &Path) -> Result<Self, EntryNameError> {
34        let name = str::from_utf8(name.as_os_str().as_encoded_bytes())?;
35        Ok(Self::new_from_utf8(name))
36    }
37
38    /// Creates an [`EntryName`] from a struct impl <code>[Into]<[PathBuf]></code>.
39    ///
40    /// Any non-Unicode sequences are replaced with
41    /// [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD] and
42    /// any path components not match with [Component::Normal] are removed.
43    ///
44    /// [U+FFFD]: core::char::REPLACEMENT_CHARACTER
45    /// [Component::Normal]: std::path::Component::Normal
46    /// # Examples
47    /// ```rust
48    /// use libpna::EntryName;
49    ///
50    /// assert_eq!("foo.txt", EntryName::from_lossy("foo.txt"));
51    /// assert_eq!("foo.txt", EntryName::from_lossy("/foo.txt"));
52    /// assert_eq!("foo.txt", EntryName::from_lossy("./foo.txt"));
53    /// assert_eq!("foo.txt", EntryName::from_lossy("../foo.txt"));
54    /// ```
55    #[inline]
56    pub fn from_lossy<T: Into<PathBuf>>(p: T) -> Self {
57        Self::from_path_lossy(&p.into())
58    }
59
60    #[inline]
61    fn from_path_lossy(p: &Path) -> Self {
62        Self::from_path_lossy_preserve_root(p).sanitize()
63    }
64
65    /// Creates an [EntryName] from a path, preserving absolute path components.
66    ///
67    /// This method is similar to the `From` implementations for path-like types, but preserves absolute path components.
68    ///
69    /// # Examples
70    ///
71    /// ```rust
72    /// use libpna::EntryName;
73    ///
74    /// assert_eq!("foo.txt", EntryName::from_utf8_preserve_root("foo.txt"));
75    /// assert_eq!("/foo.txt", EntryName::from_utf8_preserve_root("/foo.txt"));
76    /// assert_eq!("./foo.txt", EntryName::from_utf8_preserve_root("./foo.txt"));
77    /// assert_eq!("../foo.txt", EntryName::from_utf8_preserve_root("../foo.txt"));
78    /// assert_eq!("bar/../foo.txt", EntryName::from_utf8_preserve_root("bar/../foo.txt"));
79    /// ```
80    #[inline]
81    pub fn from_utf8_preserve_root(path: &str) -> Self {
82        Self::new_preserve_root(path.into())
83    }
84
85    #[inline]
86    fn new_preserve_root(path: String) -> Self {
87        Self(path)
88    }
89
90    /// Creates an [EntryName] from a path, preserving absolute path components.
91    ///
92    /// This method is similar to the `From` implementations for path-like types, but preserves absolute path components.
93    ///
94    /// # Examples
95    ///
96    /// ```rust
97    /// use libpna::EntryName;
98    ///
99    /// assert_eq!("foo.txt", EntryName::from_path_preserve_root("foo.txt".as_ref()).unwrap());
100    /// assert_eq!("./foo.txt", EntryName::from_path_preserve_root("./foo.txt".as_ref()).unwrap());
101    /// assert_eq!("bar/../foo.txt", EntryName::from_path_preserve_root("bar/../foo.txt".as_ref()).unwrap());
102    /// ```
103    ///
104    /// # Errors
105    ///
106    /// Returns [`EntryNameError`] if the path cannot be represented as valid UTF-8.
107    #[inline]
108    pub fn from_path_preserve_root(name: &Path) -> Result<Self, EntryNameError> {
109        let path = str::from_utf8(name.as_os_str().as_encoded_bytes())?;
110        Ok(Self::from_utf8_preserve_root(path))
111    }
112
113    /// Creates an [EntryName] from a path, preserving absolute path components.
114    ///
115    /// This method is similar to the `From` implementations for path-like types, but preserves absolute path components.
116    ///
117    /// # Errors
118    ///
119    /// Returns an [`EntryNameError`] if it cannot be represented as valid UTF-8.
120    ///
121    /// # Examples
122    ///
123    /// ```rust
124    /// use libpna::EntryName;
125    ///
126    /// assert_eq!("foo.txt", EntryName::from_path_lossy_preserve_root("foo.txt".as_ref()));
127    /// assert_eq!("./foo.txt", EntryName::from_path_lossy_preserve_root("./foo.txt".as_ref()));
128    /// assert_eq!("bar/../foo.txt", EntryName::from_path_lossy_preserve_root("bar/../foo.txt".as_ref()));
129    /// ```
130    #[inline]
131    pub fn from_path_lossy_preserve_root(name: &Path) -> Self {
132        Self::new_preserve_root(name.to_string_lossy().into())
133    }
134
135    /// Returns a sanitized copy of this entry name that contains only normal components.
136    ///
137    /// Sanitization discards prefixes, root separators, `.` and `..` segments so the
138    /// resulting entry name is always relative and safe to embed in an archive.
139    ///
140    /// # Examples
141    ///
142    /// ```rust
143    /// use libpna::EntryName;
144    ///
145    /// let name = EntryName::from_utf8_preserve_root("/var/../tmp/./log");
146    /// assert_eq!("tmp/log", name.sanitize());
147    /// ```
148    #[inline]
149    pub fn sanitize(&self) -> Self {
150        let path = normalize_utf8path(Utf8Path::new(&self.0));
151        Self(join_with_capacity(
152            path.components()
153                .filter(|c| matches!(c, Utf8Component::Normal(_))),
154            "/",
155            path.as_str().len(),
156        ))
157    }
158
159    #[inline]
160    pub(crate) fn as_bytes(&self) -> &[u8] {
161        self.0.as_bytes()
162    }
163
164    /// Extracts a string slice containing the entire [EntryName].
165    ///
166    /// # Examples
167    ///
168    /// ```
169    /// use libpna::EntryName;
170    ///
171    /// let name = EntryName::from("foo");
172    /// assert_eq!("foo", name.as_str());
173    /// ```
174    #[inline]
175    pub fn as_str(&self) -> &str {
176        self.0.as_str()
177    }
178
179    /// Converts to an [`OsStr`] slice.
180    ///
181    /// # Examples
182    ///
183    /// ```
184    /// use libpna::EntryName;
185    /// use std::ffi::OsStr;
186    ///
187    /// let entry_name = EntryName::from("foo.txt");
188    /// let os_str = OsStr::new("foo.txt");
189    /// assert_eq!(entry_name.as_os_str(), os_str);
190    /// ```
191    #[inline]
192    pub fn as_os_str(&self) -> &OsStr {
193        self.0.as_ref()
194    }
195
196    /// Coerces to a [`Path`] slice.
197    ///
198    /// # Examples
199    ///
200    /// ```
201    /// use libpna::EntryName;
202    /// use std::path::Path;
203    ///
204    /// let entry_name = EntryName::from("test/foo.txt");
205    /// assert_eq!(Path::new("test/foo.txt"), entry_name.as_path());
206    /// ```
207    #[inline]
208    pub fn as_path(&self) -> &Path {
209        self.0.as_ref()
210    }
211}
212
213impl From<String> for EntryName {
214    #[inline]
215    fn from(value: String) -> Self {
216        Self::new_from_utf8(&value)
217    }
218}
219
220impl From<&String> for EntryName {
221    #[inline]
222    fn from(value: &String) -> Self {
223        Self::new_from_utf8(value)
224    }
225}
226
227impl From<&str> for EntryName {
228    /// # Examples
229    ///
230    /// ```
231    /// use libpna::EntryName;
232    ///
233    /// assert_eq!("test.txt", EntryName::from("test.txt"));
234    /// assert_eq!("test.txt", EntryName::from("/test.txt"));
235    /// assert_eq!("test.txt", EntryName::from("./test.txt"));
236    /// assert_eq!("test.txt", EntryName::from("../test.txt"));
237    /// ```
238    #[inline]
239    fn from(value: &str) -> Self {
240        Self::new_from_utf8(value)
241    }
242}
243
244impl From<Cow<'_, str>> for EntryName {
245    /// # Examples
246    ///
247    /// ```
248    /// use libpna::EntryName;
249    /// use std::borrow::Cow;
250    ///
251    /// assert_eq!("test.txt", EntryName::from(Cow::from("test.txt")));
252    /// ```
253    #[inline]
254    fn from(value: Cow<'_, str>) -> Self {
255        Self::new_from_utf8(&value)
256    }
257}
258
259impl From<&Cow<'_, str>> for EntryName {
260    #[inline]
261    fn from(value: &Cow<'_, str>) -> Self {
262        Self::new_from_utf8(value)
263    }
264}
265
266impl TryFrom<&OsStr> for EntryName {
267    type Error = EntryNameError;
268
269    #[inline]
270    fn try_from(value: &OsStr) -> Result<Self, Self::Error> {
271        Self::new_from_path(Path::new(value))
272    }
273}
274
275impl TryFrom<OsString> for EntryName {
276    type Error = EntryNameError;
277
278    #[inline]
279    fn try_from(value: OsString) -> Result<Self, Self::Error> {
280        Self::new_from_path(Path::new(&value))
281    }
282}
283
284impl TryFrom<&OsString> for EntryName {
285    type Error = EntryNameError;
286
287    #[inline]
288    fn try_from(value: &OsString) -> Result<Self, Self::Error> {
289        Self::new_from_path(Path::new(value))
290    }
291}
292
293impl TryFrom<Cow<'_, OsStr>> for EntryName {
294    type Error = EntryNameError;
295
296    #[inline]
297    fn try_from(value: Cow<'_, OsStr>) -> Result<Self, Self::Error> {
298        Self::new_from_path(Path::new(&value))
299    }
300}
301
302impl TryFrom<&Path> for EntryName {
303    type Error = EntryNameError;
304
305    /// # Examples
306    ///
307    /// ```
308    /// use libpna::EntryName;
309    /// use std::path::Path;
310    ///
311    /// let p = Path::new("path/to/file");
312    /// assert_eq!("path/to/file", EntryName::try_from(p).unwrap());
313    /// ```
314    #[inline]
315    fn try_from(value: &Path) -> Result<Self, Self::Error> {
316        Self::new_from_path(value)
317    }
318}
319
320impl TryFrom<PathBuf> for EntryName {
321    type Error = EntryNameError;
322
323    /// # Examples
324    ///
325    /// ```
326    /// use libpna::EntryName;
327    /// use std::path::PathBuf;
328    ///
329    /// let p = PathBuf::from("path/to/file");
330    /// assert_eq!("path/to/file", EntryName::try_from(p).unwrap());
331    /// ```
332    #[inline]
333    fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
334        Self::new_from_path(&value)
335    }
336}
337
338impl TryFrom<&PathBuf> for EntryName {
339    type Error = EntryNameError;
340
341    /// # Examples
342    ///
343    /// ```
344    /// use libpna::EntryName;
345    /// use std::path::PathBuf;
346    ///
347    /// let p = PathBuf::from("path/to/file");
348    /// assert_eq!("path/to/file", EntryName::try_from(&p).unwrap());
349    /// ```
350    #[inline]
351    fn try_from(value: &PathBuf) -> Result<Self, Self::Error> {
352        Self::new_from_path(value)
353    }
354}
355
356impl TryFrom<Cow<'_, Path>> for EntryName {
357    type Error = EntryNameError;
358
359    /// # Examples
360    ///
361    /// ```
362    /// use libpna::EntryName;
363    /// use std::borrow::Cow;
364    /// use std::path::PathBuf;
365    ///
366    /// let p = Cow::from(PathBuf::from("path/to/file"));
367    /// assert_eq!("path/to/file", EntryName::try_from(p).unwrap());
368    /// ```
369    #[inline]
370    fn try_from(value: Cow<'_, Path>) -> Result<Self, Self::Error> {
371        Self::new_from_path(&value)
372    }
373}
374
375impl TryFrom<&[u8]> for EntryName {
376    type Error = EntryNameError;
377
378    #[inline]
379    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
380        Ok(Self::from(str::from_utf8(value)?))
381    }
382}
383
384impl Display for EntryName {
385    #[inline]
386    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
387        Display::fmt(&self.0, f)
388    }
389}
390
391impl AsRef<str> for EntryName {
392    #[inline]
393    fn as_ref(&self) -> &str {
394        self.as_str()
395    }
396}
397
398impl AsRef<OsStr> for EntryName {
399    #[inline]
400    fn as_ref(&self) -> &OsStr {
401        self.as_os_str()
402    }
403}
404
405impl AsRef<Path> for EntryName {
406    #[inline]
407    fn as_ref(&self) -> &Path {
408        self.as_path()
409    }
410}
411
412impl PartialEq<str> for EntryName {
413    #[inline]
414    fn eq(&self, other: &str) -> bool {
415        PartialEq::eq(self.as_str(), other)
416    }
417}
418
419impl PartialEq<&str> for EntryName {
420    /// # Examples
421    ///
422    /// ```
423    /// use libpna::EntryName;
424    ///
425    /// assert_eq!(EntryName::from("test.txt"), "test.txt");
426    /// ```
427    #[inline]
428    fn eq(&self, other: &&str) -> bool {
429        PartialEq::eq(self.as_str(), *other)
430    }
431}
432
433impl PartialEq<EntryName> for str {
434    #[inline]
435    fn eq(&self, other: &EntryName) -> bool {
436        PartialEq::eq(self, other.as_str())
437    }
438}
439
440impl PartialEq<EntryName> for &str {
441    /// # Examples
442    ///
443    /// ```
444    /// use libpna::EntryName;
445    ///
446    /// assert_eq!("test.txt", EntryName::from("test.txt"));
447    /// ```
448    #[inline]
449    fn eq(&self, other: &EntryName) -> bool {
450        PartialEq::eq(self, &other.as_str())
451    }
452}
453
454/// Error of invalid [EntryName].
455#[derive(Clone, Eq, PartialEq, Debug)]
456pub struct EntryNameError(Utf8Error);
457
458impl Error for EntryNameError {}
459
460impl Display for EntryNameError {
461    #[inline]
462    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
463        Display::fmt(&self.0, f)
464    }
465}
466
467impl From<Utf8Error> for EntryNameError {
468    #[inline]
469    fn from(value: Utf8Error) -> Self {
470        Self(value)
471    }
472}
473
474#[cfg(test)]
475mod tests {
476    use super::*;
477    #[cfg(unix)]
478    use std::os::unix::ffi::OsStrExt;
479    #[cfg(all(target_family = "wasm", target_os = "unknown"))]
480    use wasm_bindgen_test::wasm_bindgen_test as test;
481
482    #[test]
483    fn remove_root() {
484        assert_eq!("test.txt", EntryName::from("/test.txt"));
485        assert_eq!("test/test.txt", EntryName::from("/test/test.txt"));
486    }
487
488    #[test]
489    fn remove_last() {
490        assert_eq!("test", EntryName::from("test/"));
491        assert_eq!("test/test", EntryName::from("test/test/"));
492    }
493
494    #[cfg(target_os = "windows")]
495    #[test]
496    fn remove_prefix() {
497        assert_eq!("test.txt", EntryName::from("C:\\test.txt"));
498        assert_eq!("test/test.txt", EntryName::from("C:\\test\\test.txt"));
499    }
500
501    #[test]
502    fn basic_string_conversion() {
503        // String conversion
504        assert_eq!("test.txt", EntryName::from(String::from("test.txt")));
505        assert_eq!("test.txt", EntryName::from(&String::from("test.txt")));
506
507        // &str conversion
508        assert_eq!("test.txt", EntryName::from("test.txt"));
509
510        // Cow conversion
511        assert_eq!("test.txt", EntryName::from(Cow::from("test.txt")));
512        assert_eq!("test.txt", EntryName::from(&Cow::from("test.txt")));
513    }
514
515    #[test]
516    fn special_characters() {
517        // Unicode characters
518        assert_eq!("日本語.txt", EntryName::from("日本語.txt"));
519        assert_eq!("test/日本語.txt", EntryName::from("test/日本語.txt"));
520        assert_eq!("日本語/テスト.txt", EntryName::from("日本語/テスト.txt"));
521
522        // Special characters
523        assert_eq!("test@example.com", EntryName::from("test@example.com"));
524        assert_eq!("test#123", EntryName::from("test#123"));
525        assert_eq!("test$123", EntryName::from("test$123"));
526        assert_eq!("test+123", EntryName::from("test+123"));
527        assert_eq!("test-123", EntryName::from("test-123"));
528        assert_eq!("test_123", EntryName::from("test_123"));
529    }
530
531    #[test]
532    fn path_normalization() {
533        // Current directory
534        assert_eq!("test.txt", EntryName::from("./test.txt"));
535        assert_eq!("test/test.txt", EntryName::from("./test/test.txt"));
536
537        // Parent directory
538        assert_eq!("test.txt", EntryName::from("../test.txt"));
539        assert_eq!("test/test.txt", EntryName::from("../test/test.txt"));
540        assert_eq!("test.txt", EntryName::from("test/../test.txt"));
541
542        // Multiple slashes
543        assert_eq!("test/test.txt", EntryName::from("test//test.txt"));
544        assert_eq!("test/test.txt", EntryName::from("test///test.txt"));
545        assert_eq!("test/test.txt", EntryName::from("///test///test.txt"));
546    }
547
548    #[test]
549    fn error_cases() {
550        // Invalid UTF-8
551        let invalid_utf8: &[u8] = &[0x74, 0x65, 0x73, 0x74, 0xFF, 0x2E, 0x74, 0x78, 0x74];
552        assert!(EntryName::try_from(invalid_utf8).is_err());
553    }
554
555    #[cfg(unix)]
556    #[test]
557    fn unix_error_cases() {
558        let invalid_bytes = [0x74, 0x65, 0x73, 0x74, 0xFF, 0x2E, 0x74, 0x78, 0x74];
559        let invalid_os_str = OsStr::from_bytes(&invalid_bytes);
560        assert!(EntryName::try_from(invalid_os_str).is_err());
561    }
562
563    #[test]
564    fn preserve_root_keeps_unsafe_components() {
565        assert_eq!(
566            "/../foo",
567            EntryName::from_utf8_preserve_root("/../foo").as_str()
568        );
569        assert_eq!(
570            "bar/../foo",
571            EntryName::from_utf8_preserve_root("bar/../foo").as_str()
572        );
573        assert_eq!(
574            "../foo",
575            EntryName::from_utf8_preserve_root("../foo").as_str()
576        );
577    }
578
579    #[test]
580    fn preserve_root_edge_cases() {
581        // Empty string
582        assert_eq!("", EntryName::from_utf8_preserve_root(""));
583        // Only parent dir
584        assert_eq!("..", EntryName::from_utf8_preserve_root(".."));
585        // Only current dir
586        assert_eq!(".", EntryName::from_utf8_preserve_root("."));
587        // Only root
588        assert_eq!("/", EntryName::from_utf8_preserve_root("/"));
589        // Multiple parent dirs
590        assert_eq!("../../..", EntryName::from_utf8_preserve_root("../../.."));
591    }
592
593    #[test]
594    fn sanitize_edge_cases() {
595        // Empty string remains empty
596        assert_eq!("", EntryName::from_utf8_preserve_root("").sanitize());
597        // Only parent dir becomes empty
598        assert_eq!("", EntryName::from_utf8_preserve_root("..").sanitize());
599        // Only current dir becomes empty
600        assert_eq!("", EntryName::from_utf8_preserve_root(".").sanitize());
601        // Only root becomes empty
602        assert_eq!("", EntryName::from_utf8_preserve_root("/").sanitize());
603        // Multiple parent dirs become empty
604        assert_eq!(
605            "",
606            EntryName::from_utf8_preserve_root("../../..").sanitize()
607        );
608        // Mixed with normal component
609        assert_eq!(
610            "foo",
611            EntryName::from_utf8_preserve_root("/../foo").sanitize()
612        );
613        assert_eq!(
614            "foo",
615            EntryName::from_utf8_preserve_root("./foo").sanitize()
616        );
617    }
618
619    #[test]
620    fn type_conversions() {
621        // Path conversions
622        let path = Path::new("test.txt");
623        assert_eq!("test.txt", EntryName::try_from(path).unwrap());
624
625        let path_buf = PathBuf::from("test.txt");
626        assert_eq!("test.txt", EntryName::try_from(&path_buf).unwrap());
627
628        // OsStr conversions
629        let os_str = OsStr::new("test.txt");
630        assert_eq!("test.txt", EntryName::try_from(os_str).unwrap());
631
632        let os_string = OsString::from("test.txt");
633        assert_eq!("test.txt", EntryName::try_from(&os_string).unwrap());
634    }
635
636    #[test]
637    fn comparisons() {
638        let name1 = EntryName::from("test.txt");
639        let name2 = EntryName::from("test.txt");
640        let name3 = EntryName::from("other.txt");
641
642        // Equality
643        assert_eq!(name1, name2);
644        assert_eq!(name1, "test.txt");
645        assert_eq!("test.txt", name1);
646
647        // Inequality
648        assert_ne!(name1, name3);
649        assert_ne!(name1, "other.txt");
650        assert_ne!("other.txt", name1);
651    }
652
653    #[cfg(unix)]
654    #[test]
655    fn unix_lossy_conversion() {
656        // Test with invalid UTF-8 sequence
657        let invalid_bytes = [0x74, 0x65, 0x73, 0x74, 0xFF, 0x2E, 0x74, 0x78, 0x74];
658        let invalid_path = PathBuf::from(OsStr::from_bytes(&invalid_bytes));
659        let name = EntryName::from_lossy(invalid_path);
660        assert_eq!("test\u{FFFD}.txt", name.as_str());
661
662        // Test with multiple invalid UTF-8 sequences
663        let invalid_bytes = [0x74, 0x65, 0x73, 0x74, 0xFF, 0xFF, 0x2E, 0x74, 0x78, 0x74];
664        let invalid_path = PathBuf::from(OsStr::from_bytes(&invalid_bytes));
665        let name = EntryName::from_lossy(invalid_path);
666        assert_eq!("test\u{FFFD}\u{FFFD}.txt", name.as_str());
667
668        // Test with invalid UTF-8 sequence at the start
669        let invalid_bytes = [0xFF, 0x74, 0x65, 0x73, 0x74, 0x2E, 0x74, 0x78, 0x74];
670        let invalid_path = PathBuf::from(OsStr::from_bytes(&invalid_bytes));
671        let name = EntryName::from_lossy(invalid_path);
672        assert_eq!("\u{FFFD}test.txt", name.as_str());
673
674        // Test with invalid UTF-8 sequence at the end
675        let invalid_bytes = [0x74, 0x65, 0x73, 0x74, 0x2E, 0x74, 0x78, 0x74, 0xFF];
676        let invalid_path = PathBuf::from(OsStr::from_bytes(&invalid_bytes));
677        let name = EntryName::from_lossy(invalid_path);
678        assert_eq!("test.txt\u{FFFD}", name.as_str());
679    }
680
681    #[test]
682    fn as_ref_implementations() {
683        let name = EntryName::from("test.txt");
684
685        // AsRef<str>
686        let str_ref: &str = name.as_ref();
687        assert_eq!("test.txt", str_ref);
688
689        // AsRef<OsStr>
690        let os_str_ref: &OsStr = name.as_ref();
691        assert_eq!(OsStr::new("test.txt"), os_str_ref);
692
693        // AsRef<Path>
694        let path_ref: &Path = name.as_ref();
695        assert_eq!(Path::new("test.txt"), path_ref);
696    }
697}