2019-09-02 21:23:26 +00:00
//! Navigator backend for web
2020-01-29 00:23:02 +00:00
use crate ::custom_event ::RuffleEvent ;
2022-06-11 08:55:17 +00:00
use isahc ::{
config ::RedirectPolicy , prelude ::* , AsyncReadResponseExt , HttpClient , Request as IsahcRequest ,
} ;
2020-01-18 04:11:09 +00:00
use ruffle_core ::backend ::navigator ::{
2022-06-11 08:55:17 +00:00
NavigationMethod , NavigatorBackend , OwnedFuture , Request , Response ,
2020-01-18 04:11:09 +00:00
} ;
2020-08-28 17:16:13 +00:00
use ruffle_core ::indexmap ::IndexMap ;
2020-06-18 08:36:04 +00:00
use ruffle_core ::loader ::Error ;
2020-10-05 17:45:59 +00:00
use std ::rc ::Rc ;
2020-01-29 00:23:02 +00:00
use std ::sync ::mpsc ::Sender ;
2020-07-23 22:30:39 +00:00
use url ::Url ;
2020-04-21 13:32:50 +00:00
use winit ::event_loop ::EventLoopProxy ;
2019-09-02 21:23:26 +00:00
/// Implementation of `NavigatorBackend` for non-web environments that can call
/// out to a web browser.
2020-01-29 00:23:02 +00:00
pub struct ExternalNavigatorBackend {
/// Sink for tasks sent to us through `spawn_future`.
2020-06-18 08:36:04 +00:00
channel : Sender < OwnedFuture < ( ) , Error > > ,
2020-01-29 00:23:02 +00:00
/// Event sink to trigger a new task poll.
event_loop : EventLoopProxy < RuffleEvent > ,
2020-02-24 09:49:28 +00:00
2020-10-05 17:45:59 +00:00
/// The url to use for all relative fetches.
2022-10-25 10:26:19 +00:00
base_url : Url ,
2020-03-29 05:28:07 +00:00
2020-10-05 17:45:59 +00:00
// Client to use for network requests
client : Option < Rc < HttpClient > > ,
2020-12-11 11:41:44 +00:00
upgrade_to_https : bool ,
2020-01-29 00:23:02 +00:00
}
2019-09-02 21:23:26 +00:00
impl ExternalNavigatorBackend {
2020-02-24 09:49:28 +00:00
/// Construct a navigator backend with fetch and async capability.
2020-10-05 17:45:59 +00:00
pub fn new (
movie_url : Url ,
2020-06-18 08:36:04 +00:00
channel : Sender < OwnedFuture < ( ) , Error > > ,
2020-02-24 09:49:28 +00:00
event_loop : EventLoopProxy < RuffleEvent > ,
2020-10-20 08:20:11 +00:00
proxy : Option < Url > ,
2020-12-11 11:41:44 +00:00
upgrade_to_https : bool ,
2020-02-24 09:49:28 +00:00
) -> Self {
2020-10-20 08:20:11 +00:00
let proxy = proxy . and_then ( | url | url . as_str ( ) . parse ( ) . ok ( ) ) ;
2020-10-20 12:09:04 +00:00
let builder = HttpClient ::builder ( )
. proxy ( proxy )
. redirect_policy ( RedirectPolicy ::Follow ) ;
2020-02-24 09:49:28 +00:00
2020-10-05 17:45:59 +00:00
let client = builder . build ( ) . ok ( ) . map ( Rc ::new ) ;
2022-10-25 10:26:19 +00:00
let mut base_url = movie_url ;
// Force replace the last segment with empty. //
base_url
. path_segments_mut ( )
. unwrap ( )
. pop_if_empty ( )
. pop ( )
. push ( " " ) ;
2020-02-24 09:49:28 +00:00
Self {
channel ,
event_loop ,
2020-10-05 17:45:59 +00:00
client ,
2022-10-25 10:26:19 +00:00
base_url ,
2020-12-11 11:41:44 +00:00
upgrade_to_https ,
2020-01-29 00:23:02 +00:00
}
2019-09-02 21:23:26 +00:00
}
}
impl NavigatorBackend for ExternalNavigatorBackend {
2019-09-17 03:37:11 +00:00
fn navigate_to_url (
& self ,
url : String ,
2022-08-05 13:29:12 +00:00
_target : String ,
2020-08-28 17:16:13 +00:00
vars_method : Option < ( NavigationMethod , IndexMap < String , String > ) > ,
2019-09-17 03:37:11 +00:00
) {
2019-09-02 21:23:26 +00:00
//TODO: Should we return a result for failed opens? Does Flash care?
2019-09-17 03:37:11 +00:00
2019-09-14 03:30:38 +00:00
//NOTE: Flash desktop players / projectors ignore the window parameter,
// unless it's a `_layer`, and we shouldn't handle that anyway.
let mut parsed_url = match Url ::parse ( & url ) {
Ok ( parsed_url ) = > parsed_url ,
Err ( e ) = > {
2019-09-17 03:37:11 +00:00
log ::error! (
" Could not parse URL because of {}, the corrupt URL was: {} " ,
e ,
url
) ;
2019-09-14 03:30:38 +00:00
return ;
}
} ;
let modified_url = match vars_method {
Some ( ( _ , query_pairs ) ) = > {
2019-09-17 03:37:11 +00:00
{
//lifetime limiter because we don't have NLL yet
2019-09-14 03:30:38 +00:00
let mut modifier = parsed_url . query_pairs_mut ( ) ;
for ( k , v ) in query_pairs . iter ( ) {
modifier . append_pair ( k , v ) ;
}
}
2020-12-11 11:41:44 +00:00
parsed_url
2019-09-17 03:37:11 +00:00
}
2020-12-11 11:41:44 +00:00
None = > parsed_url ,
2019-09-14 03:30:38 +00:00
} ;
2019-09-17 03:37:11 +00:00
2020-12-11 11:41:44 +00:00
let processed_url = self . pre_process_url ( modified_url ) ;
2022-05-22 06:01:34 +00:00
match webbrowser ::open ( processed_url . as_ref ( ) ) {
2019-09-17 03:37:11 +00:00
Ok ( _output ) = > { }
2020-12-11 11:41:44 +00:00
Err ( e ) = > log ::error! ( " Could not open URL {}: {} " , processed_url . as_str ( ) , e ) ,
2019-09-02 21:23:26 +00:00
} ;
}
2019-11-07 19:34:38 +00:00
2022-06-11 08:55:17 +00:00
fn fetch ( & self , request : Request ) -> OwnedFuture < Response , Error > {
2020-10-05 17:45:59 +00:00
// TODO: honor sandbox type (local-with-filesystem, local-with-network, remote, ...)
2022-10-25 10:26:19 +00:00
let full_url = match self . base_url . join ( request . url ( ) ) {
2020-12-31 21:59:32 +00:00
Ok ( url ) = > url ,
Err ( e ) = > {
2022-06-11 08:55:17 +00:00
let msg = format! ( " Invalid URL {} : {} " , request . url ( ) , e ) ;
2020-12-31 21:59:32 +00:00
return Box ::pin ( async move { Err ( Error ::FetchError ( msg ) ) } ) ;
}
} ;
2020-10-05 17:45:59 +00:00
2020-12-11 11:41:44 +00:00
let processed_url = self . pre_process_url ( full_url ) ;
2020-10-05 17:45:59 +00:00
let client = self . client . clone ( ) ;
2020-12-11 11:41:44 +00:00
match processed_url . scheme ( ) {
2020-10-05 17:45:59 +00:00
" file " = > Box ::pin ( async move {
2022-03-30 02:23:07 +00:00
let path = processed_url . to_file_path ( ) . unwrap_or_default ( ) ;
2022-04-08 13:08:13 +00:00
let url = processed_url . into ( ) ;
2022-04-08 12:40:11 +00:00
let body = std ::fs ::read ( & path ) . or_else ( | e | {
2022-04-08 20:40:55 +00:00
if cfg! ( feature = " sandbox " ) {
use rfd ::{ FileDialog , MessageButtons , MessageDialog , MessageLevel } ;
use std ::io ::ErrorKind ;
2022-03-30 02:23:07 +00:00
2022-04-08 20:40:55 +00:00
if e . kind ( ) = = ErrorKind ::PermissionDenied {
let attempt_sandbox_open = MessageDialog ::new ( )
. set_level ( MessageLevel ::Warning )
. set_description ( & format! ( " The current movie is attempting to read files stored in {} . \n \n To allow it to do so, click Yes, and then Open to grant read access to that directory. \n \n Otherwise, click No to deny access. " , path . parent ( ) . unwrap ( ) . to_string_lossy ( ) ) )
. set_buttons ( MessageButtons ::YesNo )
. show ( ) ;
2022-03-30 02:23:07 +00:00
2022-04-08 20:40:55 +00:00
if attempt_sandbox_open {
FileDialog ::new ( ) . set_directory ( & path ) . pick_folder ( ) ;
2022-03-30 02:23:07 +00:00
2022-04-08 12:40:11 +00:00
return std ::fs ::read ( & path ) ;
2022-04-08 20:40:55 +00:00
}
2022-03-30 02:23:07 +00:00
}
}
2022-04-08 20:40:55 +00:00
Err ( e )
2022-04-08 12:40:11 +00:00
} ) . map_err ( | e | Error ::FetchError ( e . to_string ( ) ) ) ? ;
2022-04-08 13:08:13 +00:00
Ok ( Response { url , body } )
2020-10-05 17:45:59 +00:00
} ) ,
_ = > Box ::pin ( async move {
2022-03-17 23:38:31 +00:00
let client =
client . ok_or_else ( | | Error ::FetchError ( " Network unavailable " . to_string ( ) ) ) ? ;
2020-10-05 17:45:59 +00:00
2022-06-11 08:55:17 +00:00
let isahc_request = match request . method ( ) {
NavigationMethod ::Get = > IsahcRequest ::get ( processed_url . to_string ( ) ) ,
NavigationMethod ::Post = > IsahcRequest ::post ( processed_url . to_string ( ) ) ,
2020-10-05 17:45:59 +00:00
} ;
2022-06-11 08:55:17 +00:00
let ( body_data , _ ) = request . body ( ) . clone ( ) . unwrap_or_default ( ) ;
let body = isahc_request
2020-10-05 17:45:59 +00:00
. body ( body_data )
. map_err ( | e | Error ::FetchError ( e . to_string ( ) ) ) ? ;
2021-01-01 19:36:21 +00:00
let mut response = client
2020-10-05 17:45:59 +00:00
. send_async ( body )
. await
. map_err ( | e | Error ::FetchError ( e . to_string ( ) ) ) ? ;
2021-03-26 01:07:50 +00:00
if ! response . status ( ) . is_success ( ) {
2021-03-27 19:43:43 +00:00
return Err ( Error ::FetchError ( format! (
" HTTP status is not ok, got {} " ,
2021-03-27 19:55:07 +00:00
response . status ( )
2021-03-27 19:43:43 +00:00
) ) ) ;
2021-03-26 01:07:50 +00:00
}
2022-04-08 13:08:13 +00:00
let url = if let Some ( uri ) = response . effective_uri ( ) {
uri . to_string ( )
} else {
processed_url . into ( )
} ;
2022-04-08 12:40:11 +00:00
let mut body = vec! [ ] ;
2021-01-01 19:36:21 +00:00
response
2022-04-08 12:40:11 +00:00
. copy_to ( & mut body )
2021-01-01 19:36:21 +00:00
. await
. map_err ( | e | Error ::FetchError ( e . to_string ( ) ) ) ? ;
2022-04-08 12:40:11 +00:00
2022-04-08 13:08:13 +00:00
Ok ( Response { url , body } )
2020-10-05 17:45:59 +00:00
} ) ,
}
2019-11-07 19:34:38 +00:00
}
2020-06-18 08:36:04 +00:00
fn spawn_future ( & mut self , future : OwnedFuture < ( ) , Error > ) {
2020-01-29 00:23:02 +00:00
self . channel . send ( future ) . expect ( " working channel send " ) ;
if self . event_loop . send_event ( RuffleEvent ::TaskPoll ) . is_err ( ) {
log ::warn! (
" A task was queued on an event loop that has already ended. It will not be polled. "
) ;
}
2019-11-07 19:34:38 +00:00
}
2020-07-23 04:00:41 +00:00
2020-12-11 11:41:44 +00:00
fn pre_process_url ( & self , mut url : Url ) -> Url {
if self . upgrade_to_https & & url . scheme ( ) = = " http " & & url . set_scheme ( " https " ) . is_err ( ) {
log ::error! ( " Url::set_scheme failed on: {} " , url ) ;
}
url
}
2019-09-17 03:37:11 +00:00
}