@@ -7,7 +7,7 @@ use std::{
77 fmt,
88 path:: { Path , PathBuf , MAIN_SEPARATOR } ,
99 sync:: {
10- atomic:: { AtomicU32 , Ordering } ,
10+ atomic:: { AtomicBool , AtomicU32 , Ordering } ,
1111 Arc , Mutex ,
1212 } ,
1313} ;
@@ -29,19 +29,34 @@ pub enum Event {
2929
3030type EventListener = Box < dyn Fn ( & Event ) + Send > ;
3131
32+ enum Pending {
33+ Unlisten ( ScopeEventId ) ,
34+ Listen {
35+ id : ScopeEventId ,
36+ handler : EventListener ,
37+ } ,
38+ Emit ( Event ) ,
39+ }
40+
41+ struct ScopeInner {
42+ allowed_patterns : Mutex < HashSet < Pattern > > ,
43+ forbidden_patterns : Mutex < HashSet < Pattern > > ,
44+ match_options : glob:: MatchOptions ,
45+ event_listeners : Mutex < HashMap < ScopeEventId , EventListener > > ,
46+ pending : Mutex < Vec < Pending > > ,
47+ emitting : AtomicBool ,
48+ next_event_id : AtomicU32 ,
49+ }
50+
3251/// Scope for filesystem access.
3352#[ derive( Clone ) ]
3453pub struct Scope {
35- allowed_patterns : Arc < Mutex < HashSet < Pattern > > > ,
36- forbidden_patterns : Arc < Mutex < HashSet < Pattern > > > ,
37- event_listeners : Arc < Mutex < HashMap < ScopeEventId , EventListener > > > ,
38- match_options : glob:: MatchOptions ,
39- next_event_id : Arc < AtomicU32 > ,
54+ inner : Arc < ScopeInner > ,
4055}
4156
4257impl Scope {
4358 fn next_event_id ( & self ) -> u32 {
44- self . next_event_id . fetch_add ( 1 , Ordering :: Relaxed )
59+ self . inner . next_event_id . fetch_add ( 1 , Ordering :: Relaxed )
4560 }
4661}
4762
@@ -51,6 +66,7 @@ impl fmt::Debug for Scope {
5166 . field (
5267 "allowed_patterns" ,
5368 & self
69+ . inner
5470 . allowed_patterns
5571 . lock ( )
5672 . unwrap ( )
@@ -61,6 +77,7 @@ impl fmt::Debug for Scope {
6177 . field (
6278 "forbidden_patterns" ,
6379 & self
80+ . inner
6481 . forbidden_patterns
6582 . lock ( )
6683 . unwrap ( )
@@ -208,66 +225,122 @@ impl Scope {
208225 } ;
209226
210227 Ok ( Self {
211- allowed_patterns : Arc :: new ( Mutex :: new ( allowed_patterns) ) ,
212- forbidden_patterns : Arc :: new ( Mutex :: new ( forbidden_patterns) ) ,
213- event_listeners : Default :: default ( ) ,
214- next_event_id : Default :: default ( ) ,
215- match_options : glob:: MatchOptions {
216- // this is needed so `/dir/*` doesn't match files within subdirectories such as `/dir/subdir/file.txt`
217- // see: <https://github.com/tauri-apps/tauri/security/advisories/GHSA-6mv3-wm7j-h4w5>
218- require_literal_separator : true ,
219- require_literal_leading_dot,
220- ..Default :: default ( )
221- } ,
228+ inner : Arc :: new ( ScopeInner {
229+ allowed_patterns : Mutex :: new ( allowed_patterns) ,
230+ forbidden_patterns : Mutex :: new ( forbidden_patterns) ,
231+ event_listeners : Default :: default ( ) ,
232+ pending : Default :: default ( ) ,
233+ emitting : AtomicBool :: new ( false ) ,
234+ next_event_id : Default :: default ( ) ,
235+ match_options : glob:: MatchOptions {
236+ // this is needed so `/dir/*` doesn't match files within subdirectories such as `/dir/subdir/file.txt`
237+ // see: <https://github.com/tauri-apps/tauri/security/advisories/GHSA-6mv3-wm7j-h4w5>
238+ require_literal_separator : true ,
239+ require_literal_leading_dot,
240+ ..Default :: default ( )
241+ } ,
242+ } ) ,
222243 } )
223244 }
224245
225246 /// The list of allowed patterns.
226247 pub fn allowed_patterns ( & self ) -> HashSet < Pattern > {
227- self . allowed_patterns . lock ( ) . unwrap ( ) . clone ( )
248+ self . inner . allowed_patterns . lock ( ) . unwrap ( ) . clone ( )
228249 }
229250
230251 /// The list of forbidden patterns.
231252 pub fn forbidden_patterns ( & self ) -> HashSet < Pattern > {
232- self . forbidden_patterns . lock ( ) . unwrap ( ) . clone ( )
253+ self . inner . forbidden_patterns . lock ( ) . unwrap ( ) . clone ( )
233254 }
234255
235256 /// Listen to an event on this scope.
236257 pub fn listen < F : Fn ( & Event ) + Send + ' static > ( & self , f : F ) -> ScopeEventId {
237258 let id = self . next_event_id ( ) ;
238- self . listen_with_id ( id, f ) ;
259+ self . listen_with_id ( id, Box :: new ( f ) ) ;
239260 id
240261 }
241262
242- fn listen_with_id < F : Fn ( & Event ) + Send + ' static > ( & self , id : ScopeEventId , f : F ) {
243- self . event_listeners . lock ( ) . unwrap ( ) . insert ( id, Box :: new ( f) ) ;
263+ fn listen_with_id ( & self , id : ScopeEventId , handler : EventListener ) {
264+ if self . inner . emitting . load ( Ordering :: Relaxed ) {
265+ self
266+ . inner
267+ . pending
268+ . lock ( )
269+ . unwrap ( )
270+ . push ( Pending :: Listen { id, handler } ) ;
271+ } else {
272+ self
273+ . inner
274+ . event_listeners
275+ . lock ( )
276+ . unwrap ( )
277+ . insert ( id, handler) ;
278+ }
244279 }
245280
246281 /// Listen to an event on this scope and immediately unlisten.
247282 pub fn once < F : FnOnce ( & Event ) + Send + ' static > ( & self , f : F ) -> ScopeEventId {
248- let listerners = self . event_listeners . clone ( ) ;
283+ let self_ = self . clone ( ) ;
249284 let handler = std:: cell:: Cell :: new ( Some ( f) ) ;
250285 let id = self . next_event_id ( ) ;
251- self . listen_with_id ( id, move |event| {
252- listerners. lock ( ) . unwrap ( ) . remove ( & id) ;
253- let handler = handler
254- . take ( )
255- . expect ( "attempted to call handler more than once" ) ;
256- handler ( event)
257- } ) ;
286+ self . listen_with_id (
287+ id,
288+ Box :: new ( move |event| {
289+ self_. unlisten ( id) ;
290+ let handler = handler
291+ . take ( )
292+ . expect ( "attempted to call handler more than once" ) ;
293+ handler ( event) ;
294+ } ) ,
295+ ) ;
258296 id
259297 }
260298
261299 /// Removes an event listener on this scope.
262300 pub fn unlisten ( & self , id : ScopeEventId ) {
263- self . event_listeners . lock ( ) . unwrap ( ) . remove ( & id) ;
301+ if self . inner . emitting . load ( Ordering :: Relaxed ) {
302+ self
303+ . inner
304+ . pending
305+ . lock ( )
306+ . unwrap ( )
307+ . push ( Pending :: Unlisten ( id) ) ;
308+ } else {
309+ self . inner . event_listeners . lock ( ) . unwrap ( ) . remove ( & id) ;
310+ }
264311 }
265312
266313 fn emit ( & self , event : Event ) {
267- let listeners = self . event_listeners . lock ( ) . unwrap ( ) ;
268- let handlers = listeners. values ( ) ;
269- for listener in handlers {
270- listener ( & event) ;
314+ let was_emitting = self . inner . emitting . swap ( true , Ordering :: Relaxed ) ;
315+ if was_emitting {
316+ self
317+ . inner
318+ . pending
319+ . lock ( )
320+ . unwrap ( )
321+ . push ( Pending :: Emit ( event) ) ;
322+ return ;
323+ }
324+
325+ {
326+ let listeners = self . inner . event_listeners . lock ( ) . unwrap ( ) ;
327+ let handlers = listeners. values ( ) ;
328+ for listener in handlers {
329+ listener ( & event) ;
330+ }
331+ self . inner . emitting . store ( false , Ordering :: Relaxed ) ;
332+ }
333+
334+ let pending = {
335+ let mut lock = self . inner . pending . lock ( ) . unwrap ( ) ;
336+ std:: mem:: take ( & mut * lock)
337+ } ;
338+ for action in pending {
339+ match action {
340+ Pending :: Unlisten ( id) => self . unlisten ( id) ,
341+ Pending :: Listen { id, handler } => self . listen_with_id ( id, handler) ,
342+ Pending :: Emit ( event) => self . emit ( event) ,
343+ }
271344 }
272345 }
273346
@@ -278,7 +351,7 @@ impl Scope {
278351 pub fn allow_directory < P : AsRef < Path > > ( & self , path : P , recursive : bool ) -> crate :: Result < ( ) > {
279352 let path = path. as_ref ( ) ;
280353 {
281- let mut list = self . allowed_patterns . lock ( ) . unwrap ( ) ;
354+ let mut list = self . inner . allowed_patterns . lock ( ) . unwrap ( ) ;
282355
283356 // allow the directory to be read
284357 push_pattern ( & mut list, path, escaped_pattern) ?;
@@ -297,7 +370,7 @@ impl Scope {
297370 pub fn allow_file < P : AsRef < Path > > ( & self , path : P ) -> crate :: Result < ( ) > {
298371 let path = path. as_ref ( ) ;
299372 push_pattern (
300- & mut self . allowed_patterns . lock ( ) . unwrap ( ) ,
373+ & mut self . inner . allowed_patterns . lock ( ) . unwrap ( ) ,
301374 path,
302375 escaped_pattern,
303376 ) ?;
@@ -311,7 +384,7 @@ impl Scope {
311384 pub fn forbid_directory < P : AsRef < Path > > ( & self , path : P , recursive : bool ) -> crate :: Result < ( ) > {
312385 let path = path. as_ref ( ) ;
313386 {
314- let mut list = self . forbidden_patterns . lock ( ) . unwrap ( ) ;
387+ let mut list = self . inner . forbidden_patterns . lock ( ) . unwrap ( ) ;
315388
316389 // allow the directory to be read
317390 push_pattern ( & mut list, path, escaped_pattern) ?;
@@ -330,7 +403,7 @@ impl Scope {
330403 pub fn forbid_file < P : AsRef < Path > > ( & self , path : P ) -> crate :: Result < ( ) > {
331404 let path = path. as_ref ( ) ;
332405 push_pattern (
333- & mut self . forbidden_patterns . lock ( ) . unwrap ( ) ,
406+ & mut self . inner . forbidden_patterns . lock ( ) . unwrap ( ) ,
334407 path,
335408 escaped_pattern,
336409 ) ?;
@@ -349,21 +422,23 @@ impl Scope {
349422 if let Ok ( path) = path {
350423 let path: PathBuf = path. components ( ) . collect ( ) ;
351424 let forbidden = self
425+ . inner
352426 . forbidden_patterns
353427 . lock ( )
354428 . unwrap ( )
355429 . iter ( )
356- . any ( |p| p. matches_path_with ( & path, self . match_options ) ) ;
430+ . any ( |p| p. matches_path_with ( & path, self . inner . match_options ) ) ;
357431
358432 if forbidden {
359433 false
360434 } else {
361435 let allowed = self
436+ . inner
362437 . allowed_patterns
363438 . lock ( )
364439 . unwrap ( )
365440 . iter ( )
366- . any ( |p| p. matches_path_with ( & path, self . match_options ) ) ;
441+ . any ( |p| p. matches_path_with ( & path, self . inner . match_options ) ) ;
367442
368443 allowed
369444 }
@@ -381,11 +456,12 @@ impl Scope {
381456 if let Ok ( path) = path {
382457 let path: PathBuf = path. components ( ) . collect ( ) ;
383458 self
459+ . inner
384460 . forbidden_patterns
385461 . lock ( )
386462 . unwrap ( )
387463 . iter ( )
388- . any ( |p| p. matches_path_with ( & path, self . match_options ) )
464+ . any ( |p| p. matches_path_with ( & path, self . inner . match_options ) )
389465 } else {
390466 true
391467 }
@@ -424,29 +500,35 @@ fn escaped_pattern_with(p: &str, append: &str) -> Result<Pattern, glob::PatternE
424500
425501#[ cfg( test) ]
426502mod tests {
427- use std:: collections:: HashSet ;
503+ use std:: { collections:: HashSet , sync :: Arc } ;
428504
429505 use glob:: Pattern ;
430506
507+ use crate :: fs:: ScopeInner ;
508+
431509 use super :: { push_pattern, Scope } ;
432510
433511 fn new_scope ( ) -> Scope {
434512 Scope {
435- allowed_patterns : Default :: default ( ) ,
436- forbidden_patterns : Default :: default ( ) ,
437- event_listeners : Default :: default ( ) ,
438- next_event_id : Default :: default ( ) ,
439- match_options : glob:: MatchOptions {
440- // this is needed so `/dir/*` doesn't match files within subdirectories such as `/dir/subdir/file.txt`
441- // see: <https://github.com/tauri-apps/tauri/security/advisories/GHSA-6mv3-wm7j-h4w5>
442- require_literal_separator : true ,
443- // dotfiles are not supposed to be exposed by default on unix
444- #[ cfg( unix) ]
445- require_literal_leading_dot : true ,
446- #[ cfg( windows) ]
447- require_literal_leading_dot : false ,
448- ..Default :: default ( )
449- } ,
513+ inner : Arc :: new ( ScopeInner {
514+ allowed_patterns : Default :: default ( ) ,
515+ forbidden_patterns : Default :: default ( ) ,
516+ event_listeners : Default :: default ( ) ,
517+ pending : Default :: default ( ) ,
518+ emitting : Default :: default ( ) ,
519+ next_event_id : Default :: default ( ) ,
520+ match_options : glob:: MatchOptions {
521+ // this is needed so `/dir/*` doesn't match files within subdirectories such as `/dir/subdir/file.txt`
522+ // see: <https://github.com/tauri-apps/tauri/security/advisories/GHSA-6mv3-wm7j-h4w5>
523+ require_literal_separator : true ,
524+ // dotfiles are not supposed to be exposed by default on unix
525+ #[ cfg( unix) ]
526+ require_literal_leading_dot : true ,
527+ #[ cfg( windows) ]
528+ require_literal_leading_dot : false ,
529+ ..Default :: default ( )
530+ } ,
531+ } ) ,
450532 }
451533 }
452534
@@ -645,4 +727,15 @@ mod tests {
645727 assert_pattern ! ( patterns, "\\ \\ ?\\ C:\\ path\\ to\\ dir\\ **" ) ;
646728 }
647729 }
730+
731+ #[ test]
732+ fn event_no_deadlocks ( ) {
733+ let scope = new_scope ( ) ;
734+ let scope_clone = scope. clone ( ) ;
735+ scope. once ( move |event| {
736+ assert ! ( matches!( event, super :: Event :: PathAllowed ( _) ) ) ;
737+ scope_clone. allow_file ( "/another-test-path" ) . unwrap ( ) ;
738+ } ) ;
739+ scope. allow_file ( "/test-path" ) . unwrap ( ) ;
740+ }
648741}
0 commit comments