i am making it worse

pull/2070/head
FabianLars 8 months ago
parent caac44d6ee
commit f323eb8f56
No known key found for this signature in database

@ -14,7 +14,7 @@ rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.platforms.support]
windows = { level = "full", notes = "No write access to `$RESOURCES` folder with MSI installer and NSIS installers in `perMachine` or `both` mode" }
windows = { level = "full", notes = "Apps installed via MSI or NSIS in `perMachine` and `both` mode require admin permissions for write acces in `$RESOURCES` folder" }
linux = { level = "full", notes = "No write access to `$RESOURCES` folder" }
macos = { level = "full", notes = "No write access to `$RESOURCES` folder" }
android = { level = "partial", notes = "Access is restricted to Application folder by default" }

@ -7,7 +7,7 @@ use std::{
path::{Path, PathBuf},
};
#[path = "src/scope.rs"]
#[path = "src/entryraw.rs"]
#[allow(dead_code)]
mod scope;

@ -16,7 +16,7 @@ use std::{
borrow::Cow,
fs::File,
io::{BufReader, Lines, Read, Write},
path::PathBuf,
path::{Path, PathBuf},
str::FromStr,
sync::Mutex,
time::{SystemTime, UNIX_EPOCH},
@ -1002,37 +1002,88 @@ pub fn resolve_path<R: Runtime>(
let scope = tauri::scope::fs::Scope::new(
webview,
&FsScope::Scope {
allow: webview
.fs_scope()
.allowed
.lock()
.unwrap()
.clone()
.into_iter()
.chain(global_scope.allows().iter().filter_map(|e| e.path.clone()))
allow: global_scope
.allows()
.iter()
.filter_map(|e| e.path.clone())
.chain(command_scope.allows().iter().filter_map(|e| e.path.clone()))
.collect(),
deny: webview
.fs_scope()
.denied
.lock()
.unwrap()
.clone()
.into_iter()
.chain(global_scope.denies().iter().filter_map(|e| e.path.clone()))
deny: global_scope
.denies()
.iter()
.filter_map(|e| e.path.clone())
.chain(command_scope.denies().iter().filter_map(|e| e.path.clone()))
.collect(),
require_literal_leading_dot: webview.fs_scope().require_literal_leading_dot,
},
)?;
if scope.is_allowed(&path) {
let fs_scope = webview.fs_scope();
let require_literal_leading_dot = fs_scope.require_literal_leading_dot.unwrap_or(cfg!(unix));
if fs_scope
.scope
.as_ref()
.map(|s| is_forbidden(s, &path, require_literal_leading_dot))
.unwrap_or(false)
|| is_forbidden(&scope, &path, require_literal_leading_dot)
{
return Err(CommandError::Plugin(Error::PathForbidden(path)));
}
if fs_scope
.scope
.as_ref()
.map(|s| s.is_allowed(&path))
.unwrap_or(false)
|| scope.is_allowed(&path)
{
Ok(path)
} else {
Err(CommandError::Plugin(Error::PathForbidden(path)))
}
}
fn is_forbidden<P: AsRef<Path>>(
scope: &tauri::fs::Scope,
path: P,
require_literal_leading_dot: bool,
) -> bool {
let path = path.as_ref();
let path = if path.is_symlink() {
match std::fs::read_link(path) {
Ok(p) => p,
Err(_) => return false,
}
} else {
path.to_path_buf()
};
let path = if !path.exists() {
crate::Result::Ok(path)
} else {
std::fs::canonicalize(path).map_err(Into::into)
};
if let Ok(path) = path {
let path: PathBuf = path.components().collect();
scope.forbidden_patterns().iter().any(|p| {
p.matches_path_with(
&path,
glob::MatchOptions {
// this is needed so `/dir/*` doesn't match files within subdirectories such as `/dir/subdir/file.txt`
// see: <https://github.com/tauri-apps/tauri/security/advisories/GHSA-6mv3-wm7j-h4w5>
require_literal_separator: true,
require_literal_leading_dot,
..Default::default()
},
)
})
} else {
false
}
}
struct StdFileResource(Mutex<File>);
impl StdFileResource {

@ -0,0 +1,14 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::path::PathBuf;
use serde::Deserialize;
#[derive(Deserialize)]
#[serde(untagged)]
pub(crate) enum EntryRaw {
Value(PathBuf),
Object { path: PathBuf },
}

@ -15,7 +15,7 @@ use serde::Deserialize;
use tauri::{
ipc::ScopeObject,
plugin::{Builder as PluginBuilder, TauriPlugin},
utils::acl::Value,
utils::{acl::Value, config::FsScope},
AppHandle, DragDropEvent, Manager, RunEvent, Runtime, WindowEvent,
};
@ -23,6 +23,7 @@ mod commands;
mod config;
#[cfg(not(target_os = "android"))]
mod desktop;
mod entryraw;
mod error;
mod file_path;
#[cfg(target_os = "android")]
@ -352,8 +353,8 @@ impl ScopeObject for scope::Entry {
raw: Value,
) -> std::result::Result<Self, Self::Error> {
let path = serde_json::from_value(raw.into()).map(|raw| match raw {
scope::EntryRaw::Value(path) => path,
scope::EntryRaw::Object { path } => path,
entryraw::EntryRaw::Value(path) => path,
entryraw::EntryRaw::Object { path } => path,
})?;
match app.path().parse(path) {
@ -419,11 +420,13 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<config::Config>> {
watcher::unwatch
])
.setup(|app, api| {
let mut scope = Scope::default();
scope.require_literal_leading_dot = api
.config()
.as_ref()
.and_then(|c| c.require_literal_leading_dot);
let scope = Scope {
require_literal_leading_dot: api
.config()
.as_ref()
.and_then(|c| c.require_literal_leading_dot),
scope: Some(tauri::fs::Scope::new(app, &FsScope::default())?),
};
#[cfg(target_os = "android")]
{

@ -3,30 +3,16 @@
// SPDX-License-Identifier: MIT
use std::{
collections::HashMap,
collections::HashSet,
path::{Path, PathBuf},
sync::{
atomic::{AtomicU32, Ordering},
Mutex,
},
};
use serde::Deserialize;
#[derive(Deserialize)]
#[serde(untagged)]
pub(crate) enum EntryRaw {
Value(PathBuf),
Object { path: PathBuf },
}
#[derive(Debug)]
pub struct Entry {
pub path: Option<PathBuf>,
}
pub type EventId = u32;
type EventListener = Box<dyn Fn(&Event) + Send>;
/// Scope change event.
#[derive(Debug, Clone)]
@ -39,10 +25,8 @@ pub enum Event {
#[derive(Default)]
pub struct Scope {
pub(crate) allowed: Mutex<Vec<PathBuf>>,
pub(crate) denied: Mutex<Vec<PathBuf>>,
event_listeners: Mutex<HashMap<EventId, EventListener>>,
next_event_id: AtomicU32,
// TODO: Remove Option in v2, just used to keep Default
pub(crate) scope: Option<tauri::fs::Scope>,
pub(crate) require_literal_leading_dot: Option<bool>,
}
@ -51,113 +35,93 @@ impl Scope {
///
/// After this function has been called, the frontend will be able to use the Tauri API to read
/// the directory and all of its files. If `recursive` is `true`, subdirectories will be accessible too.
// TODO: Return Result
pub fn allow_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) {
let path = path.as_ref();
{
let mut allowed = self.allowed.lock().unwrap();
let p = path.to_string_lossy();
allowed.push(escape(&p));
allowed.push(PathBuf::from(if recursive { "**" } else { "*" }));
if let Some(scope) = &self.scope {
let _ = scope.allow_directory(path, recursive);
}
self.emit(Event::PathAllowed(path.to_path_buf()));
}
/// Extend the allowed patterns with the given file path.
///
/// After this function has been called, the frontend will be able to use the Tauri API to read the contents of this file.
// TODO: Return Result
pub fn allow_file<P: AsRef<Path>>(&self, path: P) {
let path = path.as_ref();
self.allowed
.lock()
.unwrap()
.push(escape(&path.to_string_lossy()));
self.emit(Event::PathAllowed(path.to_path_buf()));
if let Some(scope) = &self.scope {
let _ = scope.allow_file(path);
}
}
/// Set the given directory path to be forbidden by this scope.
///
/// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.
// TODO: Return Result
pub fn forbid_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) {
let path = path.as_ref();
{
let mut denied = self.denied.lock().unwrap();
let p = path.to_string_lossy();
denied.push(escape(&p));
denied.push(PathBuf::from(if recursive { "**" } else { "*" }));
if let Some(scope) = &self.scope {
let _ = scope.forbid_directory(path, recursive);
}
self.emit(Event::PathForbidden(path.to_path_buf()));
}
/// Set the given file path to be forbidden by this scope.
///
/// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.
// TODO: Return Result
pub fn forbid_file<P: AsRef<Path>>(&self, path: P) {
let path = path.as_ref();
self.denied
.lock()
.unwrap()
.push(escape(&path.to_string_lossy()));
self.emit(Event::PathForbidden(path.to_path_buf()));
if let Some(scope) = &self.scope {
let _ = scope.forbid_file(path);
}
}
/// List of allowed paths.
#[deprecated(since = "2.1.0", note = "use `allowed_patterns` instead")]
pub fn allowed(&self) -> Vec<PathBuf> {
self.allowed.lock().unwrap().clone()
self.scope
.as_ref()
.map(|s| s.allowed_patterns().clone())
.unwrap_or_default()
.iter()
.map(|p| PathBuf::from(p.as_str()))
.collect()
}
/// List of forbidden paths.
pub fn forbidden(&self) -> Vec<PathBuf> {
self.denied.lock().unwrap().clone()
/// List of allowed patterns. Note that this does not include paths defined in capabilites.
pub fn allowed_patterns(&self) -> HashSet<tauri::fs::Pattern> {
self.scope
.as_ref()
.map(|s| s.allowed_patterns().clone())
.unwrap_or_default()
}
fn next_event_id(&self) -> u32 {
self.next_event_id.fetch_add(1, Ordering::Relaxed)
/// List of forbidden paths.
#[deprecated(since = "2.1.0", note = "use `forbidden_patterns` instead")]
pub fn forbidden(&self) -> Vec<PathBuf> {
self.scope
.as_ref()
.map(|s| s.forbidden_patterns().clone())
.unwrap_or_default()
.iter()
.map(|p| PathBuf::from(p.as_str()))
.collect()
}
fn emit(&self, event: Event) {
let listeners = self.event_listeners.lock().unwrap();
let handlers = listeners.values();
for listener in handlers {
listener(&event);
}
/// List of forbidden patterns. Note that this does not include paths defined in capabilites.
pub fn forbidden_patterns(&self) -> HashSet<tauri::fs::Pattern> {
self.scope
.as_ref()
.map(|s| s.forbidden_patterns())
.unwrap_or_default()
}
/// Listen to an event on this scope.
/// Silently fails and returns `0` until v3 if `Scope` was constructed manually instead of getting it via `app.fs_scope()`.
pub fn listen<F: Fn(&Event) + Send + 'static>(&self, f: F) -> EventId {
let id = self.next_event_id();
self.event_listeners.lock().unwrap().insert(id, Box::new(f));
id
}
}
// taken from https://github.com/rust-lang/glob/blob/master/src/lib.rs#L717C5-L737C6
/// Escape metacharacters within the given string by surrounding them in
/// brackets. The resulting string will, when compiled into a `Pattern`,
/// match the input string and nothing else.
pub fn escape(s: &str) -> PathBuf {
let mut escaped = String::new();
for c in s.chars() {
match c {
// note that ! does not need escaping because it is only special
// inside brackets
/* disabled to not break paths '?' | */
'*' | '[' | ']' => {
escaped.push('[');
escaped.push(c);
escaped.push(']');
}
c => {
escaped.push(c);
}
if let Some(scope) = &self.scope {
scope.listen(move |e| match e {
tauri::fs::Event::PathAllowed(p) => f(&Event::PathAllowed(p.to_owned())),
tauri::fs::Event::PathForbidden(p) => f(&Event::PathForbidden(p.to_owned())),
})
} else {
0
}
}
PathBuf::from(escaped)
}

Loading…
Cancel
Save