Skip to content

Commit 22a7633

Browse files
authored
feat(core): expose SafePathBuf (#6713)
1 parent 09376af commit 22a7633

File tree

6 files changed

+73
-71
lines changed

6 files changed

+73
-71
lines changed

.changes/safepathbuf-refactor.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Expose `SafePathBuf` type in `tauri::path`.

core/tauri/src/api/file.rs

Lines changed: 1 addition & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -8,59 +8,12 @@
88
mod extract;
99
mod file_move;
1010

11-
use std::{
12-
fs,
13-
path::{Display, Path},
14-
};
11+
use std::{fs, path::Path};
1512

1613
#[cfg(feature = "fs-extract-api")]
1714
pub use extract::*;
1815
pub use file_move::*;
1916

20-
use serde::{de::Error as DeError, Deserialize, Deserializer};
21-
22-
#[derive(Clone, Debug)]
23-
pub(crate) struct SafePathBuf(std::path::PathBuf);
24-
25-
impl SafePathBuf {
26-
pub fn new(path: std::path::PathBuf) -> Result<Self, &'static str> {
27-
if path
28-
.components()
29-
.any(|x| matches!(x, std::path::Component::ParentDir))
30-
{
31-
Err("cannot traverse directory, rewrite the path without the use of `../`")
32-
} else {
33-
Ok(Self(path))
34-
}
35-
}
36-
37-
#[allow(dead_code)]
38-
pub unsafe fn new_unchecked(path: std::path::PathBuf) -> Self {
39-
Self(path)
40-
}
41-
42-
#[allow(dead_code)]
43-
pub fn display(&self) -> Display<'_> {
44-
self.0.display()
45-
}
46-
}
47-
48-
impl AsRef<Path> for SafePathBuf {
49-
fn as_ref(&self) -> &Path {
50-
self.0.as_ref()
51-
}
52-
}
53-
54-
impl<'de> Deserialize<'de> for SafePathBuf {
55-
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
56-
where
57-
D: Deserializer<'de>,
58-
{
59-
let path = std::path::PathBuf::deserialize(deserializer)?;
60-
SafePathBuf::new(path).map_err(DeError::custom)
61-
}
62-
}
63-
6417
/// Reads the entire contents of a file into a string.
6518
pub fn read_string<P: AsRef<Path>>(file: P) -> crate::api::Result<String> {
6619
fs::read_to_string(file).map_err(Into::into)
@@ -76,19 +29,6 @@ mod test {
7629
use super::*;
7730
#[cfg(not(windows))]
7831
use crate::api::Error;
79-
use quickcheck::{Arbitrary, Gen};
80-
81-
use std::path::PathBuf;
82-
83-
impl Arbitrary for super::SafePathBuf {
84-
fn arbitrary(g: &mut Gen) -> Self {
85-
Self(PathBuf::arbitrary(g))
86-
}
87-
88-
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
89-
Box::new(self.0.shrink().map(SafePathBuf))
90-
}
91-
}
9232

9333
#[test]
9434
fn check_read_string() {

core/tauri/src/endpoints/file_system.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@
55
#![allow(unused_imports)]
66

77
use crate::{
8-
api::{
9-
dir,
10-
file::{self, SafePathBuf},
11-
},
12-
path::BaseDirectory,
8+
api::{dir, file},
9+
path::{BaseDirectory, SafePathBuf},
1310
scope::Scopes,
1411
Config, Env, Manager, PackageInfo, Runtime, Window,
1512
};

core/tauri/src/endpoints/http.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,7 @@ impl Cmd {
102102
..
103103
} = value
104104
{
105-
if crate::api::file::SafePathBuf::new(path.clone()).is_err()
106-
|| !scopes.fs.is_allowed(path)
107-
{
105+
if crate::path::SafePathBuf::new(path.clone()).is_err() || !scopes.fs.is_allowed(path) {
108106
return Err(crate::Error::PathNotAllowed(path.clone()).into_anyhow());
109107
}
110108
}

core/tauri/src/manager.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ impl<R: Runtime> WindowManager<R> {
513513

514514
#[cfg(protocol_asset)]
515515
if !registered_scheme_protocols.contains(&"asset".into()) {
516-
use crate::api::file::SafePathBuf;
516+
use crate::path::SafePathBuf;
517517
use tokio::io::{AsyncReadExt, AsyncSeekExt};
518518
use url::Position;
519519
let asset_scope = self.state().get::<crate::Scopes>().asset_protocol.clone();

core/tauri/src/path/mod.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44

55
use std::{
66
env::temp_dir,
7-
path::{Component, Path, PathBuf},
7+
path::{Component, Display, Path, PathBuf},
88
};
99

1010
use crate::{
1111
plugin::{Builder, TauriPlugin},
1212
Manager, Runtime,
1313
};
1414

15+
use serde::{de::Error as DeError, Deserialize, Deserializer};
1516
use serde_repr::{Deserialize_repr, Serialize_repr};
1617

1718
#[cfg(path_all)]
@@ -29,6 +30,49 @@ pub(crate) use android::PathResolver;
2930
#[cfg(not(target_os = "android"))]
3031
pub(crate) use desktop::PathResolver;
3132

33+
/// A wrapper for [`PathBuf`] that prevents path traversal.
34+
#[derive(Clone, Debug)]
35+
pub struct SafePathBuf(PathBuf);
36+
37+
impl SafePathBuf {
38+
/// Validates the path for directory traversal vulnerabilities and returns a new [`SafePathBuf`] instance if it is safe.
39+
pub fn new(path: PathBuf) -> std::result::Result<Self, &'static str> {
40+
if path.components().any(|x| matches!(x, Component::ParentDir)) {
41+
Err("cannot traverse directory, rewrite the path without the use of `../`")
42+
} else {
43+
Ok(Self(path))
44+
}
45+
}
46+
47+
#[allow(dead_code)]
48+
pub(crate) unsafe fn new_unchecked(path: PathBuf) -> Self {
49+
Self(path)
50+
}
51+
52+
/// Returns an object that implements [`std::fmt::Display`] for safely printing paths.
53+
///
54+
/// See [`PathBuf#method.display`] for more information.
55+
pub fn display(&self) -> Display<'_> {
56+
self.0.display()
57+
}
58+
}
59+
60+
impl AsRef<Path> for SafePathBuf {
61+
fn as_ref(&self) -> &Path {
62+
self.0.as_ref()
63+
}
64+
}
65+
66+
impl<'de> Deserialize<'de> for SafePathBuf {
67+
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
68+
where
69+
D: Deserializer<'de>,
70+
{
71+
let path = PathBuf::deserialize(deserializer)?;
72+
SafePathBuf::new(path).map_err(DeError::custom)
73+
}
74+
}
75+
3276
/// A base directory to be used in [`resolve_directory`].
3377
///
3478
/// The base directory is the optional root of a file system operation.
@@ -332,3 +376,21 @@ pub(crate) fn init<R: Runtime>() -> TauriPlugin<R> {
332376
})
333377
.build()
334378
}
379+
380+
#[cfg(test)]
381+
mod test {
382+
use super::SafePathBuf;
383+
use quickcheck::{Arbitrary, Gen};
384+
385+
use std::path::PathBuf;
386+
387+
impl Arbitrary for SafePathBuf {
388+
fn arbitrary(g: &mut Gen) -> Self {
389+
Self(PathBuf::arbitrary(g))
390+
}
391+
392+
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
393+
Box::new(self.0.shrink().map(SafePathBuf))
394+
}
395+
}
396+
}

0 commit comments

Comments
 (0)