feat(core): expose SafePathBuf (#6713)

This commit is contained in:
Lucas Fernandes Nogueira 2023-04-15 11:44:05 -07:00 committed by GitHub
parent 09376af594
commit 22a7633816
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 73 additions and 71 deletions

View File

@ -0,0 +1,5 @@
---
"tauri": patch
---
Expose `SafePathBuf` type in `tauri::path`.

View File

@ -8,59 +8,12 @@
mod extract;
mod file_move;
use std::{
fs,
path::{Display, Path},
};
use std::{fs, path::Path};
#[cfg(feature = "fs-extract-api")]
pub use extract::*;
pub use file_move::*;
use serde::{de::Error as DeError, Deserialize, Deserializer};
#[derive(Clone, Debug)]
pub(crate) struct SafePathBuf(std::path::PathBuf);
impl SafePathBuf {
pub fn new(path: std::path::PathBuf) -> Result<Self, &'static str> {
if path
.components()
.any(|x| matches!(x, std::path::Component::ParentDir))
{
Err("cannot traverse directory, rewrite the path without the use of `../`")
} else {
Ok(Self(path))
}
}
#[allow(dead_code)]
pub unsafe fn new_unchecked(path: std::path::PathBuf) -> Self {
Self(path)
}
#[allow(dead_code)]
pub fn display(&self) -> Display<'_> {
self.0.display()
}
}
impl AsRef<Path> for SafePathBuf {
fn as_ref(&self) -> &Path {
self.0.as_ref()
}
}
impl<'de> Deserialize<'de> for SafePathBuf {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let path = std::path::PathBuf::deserialize(deserializer)?;
SafePathBuf::new(path).map_err(DeError::custom)
}
}
/// Reads the entire contents of a file into a string.
pub fn read_string<P: AsRef<Path>>(file: P) -> crate::api::Result<String> {
fs::read_to_string(file).map_err(Into::into)
@ -76,19 +29,6 @@ mod test {
use super::*;
#[cfg(not(windows))]
use crate::api::Error;
use quickcheck::{Arbitrary, Gen};
use std::path::PathBuf;
impl Arbitrary for super::SafePathBuf {
fn arbitrary(g: &mut Gen) -> Self {
Self(PathBuf::arbitrary(g))
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(self.0.shrink().map(SafePathBuf))
}
}
#[test]
fn check_read_string() {

View File

@ -5,11 +5,8 @@
#![allow(unused_imports)]
use crate::{
api::{
dir,
file::{self, SafePathBuf},
},
path::BaseDirectory,
api::{dir, file},
path::{BaseDirectory, SafePathBuf},
scope::Scopes,
Config, Env, Manager, PackageInfo, Runtime, Window,
};

View File

@ -102,9 +102,7 @@ impl Cmd {
..
} = value
{
if crate::api::file::SafePathBuf::new(path.clone()).is_err()
|| !scopes.fs.is_allowed(path)
{
if crate::path::SafePathBuf::new(path.clone()).is_err() || !scopes.fs.is_allowed(path) {
return Err(crate::Error::PathNotAllowed(path.clone()).into_anyhow());
}
}

View File

@ -513,7 +513,7 @@ impl<R: Runtime> WindowManager<R> {
#[cfg(protocol_asset)]
if !registered_scheme_protocols.contains(&"asset".into()) {
use crate::api::file::SafePathBuf;
use crate::path::SafePathBuf;
use tokio::io::{AsyncReadExt, AsyncSeekExt};
use url::Position;
let asset_scope = self.state().get::<crate::Scopes>().asset_protocol.clone();

View File

@ -4,7 +4,7 @@
use std::{
env::temp_dir,
path::{Component, Path, PathBuf},
path::{Component, Display, Path, PathBuf},
};
use crate::{
@ -12,6 +12,7 @@ use crate::{
Manager, Runtime,
};
use serde::{de::Error as DeError, Deserialize, Deserializer};
use serde_repr::{Deserialize_repr, Serialize_repr};
#[cfg(path_all)]
@ -29,6 +30,49 @@ pub(crate) use android::PathResolver;
#[cfg(not(target_os = "android"))]
pub(crate) use desktop::PathResolver;
/// A wrapper for [`PathBuf`] that prevents path traversal.
#[derive(Clone, Debug)]
pub struct SafePathBuf(PathBuf);
impl SafePathBuf {
/// Validates the path for directory traversal vulnerabilities and returns a new [`SafePathBuf`] instance if it is safe.
pub fn new(path: PathBuf) -> std::result::Result<Self, &'static str> {
if path.components().any(|x| matches!(x, Component::ParentDir)) {
Err("cannot traverse directory, rewrite the path without the use of `../`")
} else {
Ok(Self(path))
}
}
#[allow(dead_code)]
pub(crate) unsafe fn new_unchecked(path: PathBuf) -> Self {
Self(path)
}
/// Returns an object that implements [`std::fmt::Display`] for safely printing paths.
///
/// See [`PathBuf#method.display`] for more information.
pub fn display(&self) -> Display<'_> {
self.0.display()
}
}
impl AsRef<Path> for SafePathBuf {
fn as_ref(&self) -> &Path {
self.0.as_ref()
}
}
impl<'de> Deserialize<'de> for SafePathBuf {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let path = PathBuf::deserialize(deserializer)?;
SafePathBuf::new(path).map_err(DeError::custom)
}
}
/// A base directory to be used in [`resolve_directory`].
///
/// The base directory is the optional root of a file system operation.
@ -332,3 +376,21 @@ pub(crate) fn init<R: Runtime>() -> TauriPlugin<R> {
})
.build()
}
#[cfg(test)]
mod test {
use super::SafePathBuf;
use quickcheck::{Arbitrary, Gen};
use std::path::PathBuf;
impl Arbitrary for SafePathBuf {
fn arbitrary(g: &mut Gen) -> Self {
Self(PathBuf::arbitrary(g))
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(self.0.shrink().map(SafePathBuf))
}
}
}