mirror of
https://github.com/tauri-apps/tauri.git
synced 2025-01-01 23:42:33 +03:00
feat(core): expose SafePathBuf
(#6713)
This commit is contained in:
parent
09376af594
commit
22a7633816
5
.changes/safepathbuf-refactor.md
Normal file
5
.changes/safepathbuf-refactor.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": patch
|
||||
---
|
||||
|
||||
Expose `SafePathBuf` type in `tauri::path`.
|
@ -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() {
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user