@ -3,30 +3,16 @@
// SPDX-License-Identifier: MIT
use std ::{
collections ::Hash Map ,
collections ::Hash Set ,
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 )
}