1 //! Path newtypes which are always guaranteed to be canonical
2 //!
3 //! In the same way a `str` "guarantees" a `&[u8]` contains only valid UTF-8 data,
4 //! `CanonicalPath` and `CanonicalPathBuf` guarantee that the paths they represent
5 //! are canonical, or at least, were canonical at the time they were created.
6 
7 #![deny(
8     warnings,
9     missing_docs,
10     trivial_numeric_casts,
11     unused_import_braces,
12     unused_qualifications
13 )]
14 #![doc(html_root_url = "https://docs.rs/canonical-path/2.0.2")]
15 
16 use std::{
17     borrow::Borrow,
18     env,
19     ffi::{OsStr, OsString},
20     fs::{Metadata, ReadDir},
21     io::{Error, ErrorKind, Result},
22     path::{Components, Display, Iter, Path, PathBuf},
23 };
24 
25 /// Common methods of `CanonicalPath` and `CanonicalPathBuf`
26 macro_rules! impl_path {
27     () => {
28         /// Return a `Path` reference.
29         #[inline]
30         pub fn as_path(&self) -> &Path {
31             self.0.as_ref()
32         }
33 
34         /// Return an `OsStr` reference.
35         #[inline]
36         pub fn as_os_str(&self) -> &OsStr {
37             self.0.as_os_str()
38         }
39 
40         /// Yields a `&str` slice if the path is valid unicode.
41         #[inline]
42         pub fn to_str(&self) -> Option<&str> {
43             self.0.to_str()
44         }
45 
46         /// Return a canonical parent path of this path, or `io::Error` if the
47         /// path is the root directory or another canonicalization error occurs.
48         pub fn parent(&self) -> Result<CanonicalPathBuf> {
49             CanonicalPathBuf::new(&self.0
50                 .parent()
51                 .ok_or_else(|| Error::new(ErrorKind::InvalidInput, "can't get parent of '/'"))?)
52         }
53 
54         /// Returns the final component of the path, if there is one.
55         #[inline]
56         pub fn file_name(&self) -> Option<&OsStr> {
57             self.0.file_name()
58         }
59 
60         /// Determines whether base is a prefix of self.
61         #[inline]
62         pub fn starts_with<P: AsRef<Path>>(&self, base: P) -> bool {
63             self.0.starts_with(base)
64         }
65 
66         /// Determines whether child is a suffix of self.
67         #[inline]
68         pub fn ends_with<P: AsRef<Path>>(&self, child: P) -> bool {
69             self.0.ends_with(child)
70         }
71 
72         /// Extracts the stem (non-extension) portion of `self.file_name`.
73         #[inline]
74         pub fn file_stem(&self) -> Option<&OsStr> {
75             self.0.file_stem()
76         }
77 
78         /// Extracts the extension of `self.file_name`, if possible.
79         #[inline]
80         pub fn extension(&self) -> Option<&OsStr> {
81             self.0.extension()
82         }
83 
84         /// Creates an owned `CanonicalPathBuf` like self but with the given file name.
85         #[inline]
86         pub fn with_file_name<S: AsRef<OsStr>>(&self, file_name: S) -> Result<CanonicalPathBuf> {
87             CanonicalPathBuf::new(&self.0.with_file_name(file_name))
88         }
89 
90         /// Creates an owned `CanonicalPathBuf` like self but with the given extension.
91         #[inline]
92         pub fn with_extension<S: AsRef<OsStr>>(&self, extension: S) -> Result<CanonicalPathBuf> {
93             CanonicalPathBuf::new(&self.0.with_extension(extension))
94         }
95 
96         /// Produces an iterator over the `Component`s of a path
97         #[inline]
98         pub fn components(&self) -> Components {
99             self.0.components()
100         }
101 
102         /// Produces an iterator over the path's components viewed as
103         /// `OsStr` slices.
104          #[inline]
105         pub fn iter(&self) -> Iter {
106             self.0.iter()
107         }
108 
109         /// Returns an object that implements `Display` for safely printing
110         /// paths that may contain non-Unicode data.
111         #[inline]
112         pub fn display(&self) -> Display {
113             self.0.display()
114         }
115 
116         /// Queries the file system to get information about a file, directory, etc.
117         ///
118         /// Unlike the `std` version of this method, it will not follow symlinks,
119         /// since as a canonical path we should be symlink-free.
120         #[inline]
121         pub fn metadata(&self) -> Result<Metadata> {
122             // Counterintuitively this is the version of this method which
123             // does not traverse symlinks
124             self.0.symlink_metadata()
125         }
126 
127         /// Join a path onto a canonical path, returning a `CanonicalPathBuf`.
128         #[inline]
129         pub fn join<P: AsRef<Path>>(&self, path: P) -> Result<CanonicalPathBuf> {
130             CanonicalPathBuf::new(&self.0.join(path))
131         }
132 
133         /// Returns an iterator over the entries within a directory.
134         ///
135         /// The iterator will yield instances of io::Result<DirEntry>. New
136         /// errors may be encountered after an iterator is initially
137         /// constructed.
138         ///
139         /// This is an alias to fs::read_dir.
140         #[inline]
141         pub fn read_dir(&self) -> Result<ReadDir> {
142             self.0.read_dir()
143         }
144 
145         /// Does this path exist?
146         #[inline]
147         pub fn exists(&self) -> bool {
148             self.0.exists()
149         }
150 
151         /// Is this path a file?
152         #[inline]
153         pub fn is_file(&self) -> bool {
154             self.0.is_file()
155         }
156 
157         /// Is this path a directory?
158         #[inline]
159         pub fn is_dir(&self) -> bool {
160             self.0.is_file()
161         }
162     }
163 }
164 
165 /// An owned path on the filesystem which is guaranteed to be canonical.
166 ///
167 /// More specifically: it is at least guaranteed to be canonical at
168 /// the time it is created. There are potential TOCTTOU problems if the
169 /// underlying filesystem structure changes after path canonicalization.
170 #[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
171 pub struct CanonicalPathBuf(PathBuf);
172 
173 impl CanonicalPathBuf {
174     /// Create a canonical path by first canonicalizing the given path.
canonicalize<P>(path: P) -> Result<Self> where P: AsRef<Path>,175     pub fn canonicalize<P>(path: P) -> Result<Self>
176     where
177         P: AsRef<Path>,
178     {
179         Ok(CanonicalPathBuf(path.as_ref().canonicalize()?))
180     }
181 
182     /// Create a canonical path, returning error if the supplied path is not canonical.
183     // TODO: rename this to `from_path` or `try_new` to satisfy clippy? (breaking API change)
184     #[allow(clippy::new_ret_no_self)]
new<P>(path: P) -> Result<Self> where P: AsRef<Path>,185     pub fn new<P>(path: P) -> Result<Self>
186     where
187         P: AsRef<Path>,
188     {
189         let p = path.as_ref();
190         let canonical_path_buf = Self::canonicalize(p)?;
191 
192         if canonical_path_buf.as_path() != p {
193             return Err(Error::new(
194                 ErrorKind::InvalidInput,
195                 format!("non-canonical input path: {}", p.display()),
196             ));
197         }
198 
199         Ok(canonical_path_buf)
200     }
201 
202     /// Return a `CanonicalPath` reference.
203     #[inline]
as_canonical_path(&self) -> &CanonicalPath204     pub fn as_canonical_path(&self) -> &CanonicalPath {
205         unsafe { CanonicalPath::from_path_unchecked(&self.0) }
206     }
207 
208     /// Updates `self`'s filename ala the same method on `PathBuf`
set_file_name<S: AsRef<OsStr>>(&mut self, file_name: S)209     pub fn set_file_name<S: AsRef<OsStr>>(&mut self, file_name: S) {
210         self.0.set_file_name(file_name);
211     }
212 
213     /// Updates `self.extension` to extension.
214     ///
215     /// Returns `false` and does nothing if `self.file_name` is `None`,
216     /// returns `true` and updates the extension otherwise.
217     /// If `self.extension` is `None`, the extension is added; otherwise it is replaced.
set_extension<S: AsRef<OsStr>>(&mut self, extension: S) -> bool218     pub fn set_extension<S: AsRef<OsStr>>(&mut self, extension: S) -> bool {
219         self.0.set_extension(extension)
220     }
221 
222     /// Consumes the `CanonicalPathBuf`, yielding its internal `PathBuf` storage.
into_path_buf(self) -> PathBuf223     pub fn into_path_buf(self) -> PathBuf {
224         self.0
225     }
226 
227     /// Consumes the `CanonicalPathBuf`, yielding its internal `OsString` storage.
into_os_string(self) -> OsString228     pub fn into_os_string(self) -> OsString {
229         self.0.into_os_string()
230     }
231 
232     impl_path!();
233 }
234 
235 impl AsRef<Path> for CanonicalPathBuf {
as_ref(&self) -> &Path236     fn as_ref(&self) -> &Path {
237         self.as_path()
238     }
239 }
240 
241 impl AsRef<CanonicalPath> for CanonicalPathBuf {
as_ref(&self) -> &CanonicalPath242     fn as_ref(&self) -> &CanonicalPath {
243         self.as_canonical_path()
244     }
245 }
246 
247 impl AsRef<OsStr> for CanonicalPathBuf {
as_ref(&self) -> &OsStr248     fn as_ref(&self) -> &OsStr {
249         self.as_os_str()
250     }
251 }
252 
253 impl Borrow<CanonicalPath> for CanonicalPathBuf {
borrow(&self) -> &CanonicalPath254     fn borrow(&self) -> &CanonicalPath {
255         self.as_canonical_path()
256     }
257 }
258 
259 /// A reference type for a canonical filesystem path
260 ///
261 /// More specifically: it is at least guaranteed to be canonical at
262 /// the time it is created. There are potential TOCTTOU problems if the
263 /// underlying filesystem structure changes after path canonicalization.
264 #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
265 pub struct CanonicalPath(Path);
266 
267 impl CanonicalPath {
268     /// Create a canonical path, returning error if the supplied path is not canonical
new<P>(path: &P) -> Result<&Self> where P: AsRef<Path> + ?Sized,269     pub fn new<P>(path: &P) -> Result<&Self>
270     where
271         P: AsRef<Path> + ?Sized,
272     {
273         let p = path.as_ref();
274 
275         // TODO: non-allocating check that `P` is canonical
276         //
277         // This seems tricky as realpath(3) is our only real way of checking
278         // that a path is canonical. It's also slightly terrifying in that,
279         // at least in glibc, it is over 200 lines long and full of complex
280         // logic and error handling:
281         //
282         // https://sourceware.org/git/?p=glibc.git;a=blob;f=stdlib/canonicalize.c;hb=HEAD
283         if p != p.canonicalize()? {
284             return Err(Error::new(
285                 ErrorKind::InvalidInput,
286                 format!("non-canonical input path: {}", p.display()),
287             ));
288         }
289 
290         Ok(unsafe { Self::from_path_unchecked(p) })
291     }
292 
293     /// Create a canonical path from a path, skipping the canonicalization check
294     ///
295     /// This uses the same unsafe reference conversion tricks as `std` to
296     /// convert from `AsRef<Path>` to `AsRef<CanonicalPath>`, i.e. `&CanonicalPath`
297     /// is a newtype for `&Path` in the same way `&Path` is a newtype for `&OsStr`.
from_path_unchecked<P>(path: &P) -> &Self where P: AsRef<Path> + ?Sized,298     pub unsafe fn from_path_unchecked<P>(path: &P) -> &Self
299     where
300         P: AsRef<Path> + ?Sized,
301     {
302         &*(path.as_ref() as *const Path as *const CanonicalPath)
303     }
304 
305     /// Convert a canonical path reference into an owned `CanonicalPathBuf`
to_canonical_path_buf(&self) -> CanonicalPathBuf306     pub fn to_canonical_path_buf(&self) -> CanonicalPathBuf {
307         CanonicalPathBuf(self.0.to_owned())
308     }
309 
310     impl_path!();
311 }
312 
313 impl AsRef<Path> for CanonicalPath {
as_ref(&self) -> &Path314     fn as_ref(&self) -> &Path {
315         &self.0
316     }
317 }
318 
319 impl ToOwned for CanonicalPath {
320     type Owned = CanonicalPathBuf;
321 
to_owned(&self) -> CanonicalPathBuf322     fn to_owned(&self) -> CanonicalPathBuf {
323         self.to_canonical_path_buf()
324     }
325 }
326 
327 /// Returns the full, canonicalized filesystem path of the current running
328 /// executable.
current_exe() -> Result<CanonicalPathBuf>329 pub fn current_exe() -> Result<CanonicalPathBuf> {
330     let p = env::current_exe()?;
331     Ok(CanonicalPathBuf::canonicalize(p)?)
332 }
333 
334 // TODO: test on Windows
335 #[cfg(all(test, not(windows)))]
336 mod tests {
337     use std::fs::File;
338     use std::os::unix::fs;
339     use std::path::PathBuf;
340 
341     use super::{CanonicalPath, CanonicalPathBuf};
342     use tempfile::TempDir;
343 
344     // We create a test file with this name
345     const CANONICAL_FILENAME: &str = "canonical-file";
346 
347     // We create a symlink to "canonical-file" with this name
348     const NON_CANONICAL_FILENAME: &str = "non-canonical-file";
349 
350     /// A directory full of test fixtures
351     struct TestFixtureDir {
352         /// The temporary directory itself (i.e. root directory of our tests)
353         pub tempdir: TempDir,
354 
355         /// Canonical path to the test directory
356         pub base_path: PathBuf,
357 
358         /// Path to a canonical file in our test fixture directory
359         pub canonical_path: PathBuf,
360 
361         /// Path to a symlink in our test fixture directory
362         pub symlink_path: PathBuf,
363     }
364 
365     impl TestFixtureDir {
new() -> Self366         pub fn new() -> Self {
367             let tempdir = TempDir::new().unwrap();
368             let base_path = tempdir.path().canonicalize().unwrap();
369 
370             let canonical_path = base_path.join(CANONICAL_FILENAME);
371             File::create(&canonical_path).unwrap();
372 
373             let symlink_path = base_path.join(NON_CANONICAL_FILENAME);
374             fs::symlink(&canonical_path, &symlink_path).unwrap();
375 
376             Self {
377                 tempdir,
378                 base_path,
379                 canonical_path,
380                 symlink_path,
381             }
382         }
383     }
384 
385     #[test]
create_canonical_path()386     fn create_canonical_path() {
387         let test_fixtures = TestFixtureDir::new();
388         let canonical_path = CanonicalPath::new(&test_fixtures.canonical_path).unwrap();
389         assert_eq!(
390             canonical_path.as_path(),
391             test_fixtures.canonical_path.as_path()
392         );
393     }
394 
395     #[test]
create_canonical_path_buf()396     fn create_canonical_path_buf() {
397         let test_fixtures = TestFixtureDir::new();
398         let canonical_path_buf = CanonicalPathBuf::new(&test_fixtures.canonical_path).unwrap();
399         assert_eq!(
400             canonical_path_buf.as_path(),
401             test_fixtures.canonical_path.as_path()
402         );
403     }
404 
405     #[test]
reject_canonical_path_symlinks()406     fn reject_canonical_path_symlinks() {
407         let test_fixtures = TestFixtureDir::new();
408         let result = CanonicalPath::new(&test_fixtures.symlink_path);
409         assert!(result.is_err(), "symlinks aren't canonical paths!");
410     }
411 
412     #[test]
reject_canonical_path_buf_symlinks()413     fn reject_canonical_path_buf_symlinks() {
414         let test_fixtures = TestFixtureDir::new();
415         let result = CanonicalPathBuf::new(&test_fixtures.symlink_path);
416         assert!(result.is_err(), "symlinks aren't canonical paths!");
417     }
418 }
419