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 ;
2021-01-01 19:36:21 +00:00
use isahc ::{ config ::RedirectPolicy , prelude ::* , AsyncReadResponseExt , HttpClient , Request } ;
2020-01-18 04:11:09 +00:00
use ruffle_core ::backend ::navigator ::{
2020-10-05 17:45:59 +00:00
NavigationMethod , NavigatorBackend , OwnedFuture , RequestOptions ,
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-07-23 04:00:41 +00:00
use std ::borrow ::Cow ;
2020-02-24 09:49:28 +00:00
use std ::fs ;
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.
movie_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 ) ;
2020-02-24 09:49:28 +00:00
Self {
channel ,
event_loop ,
2020-10-05 17:45:59 +00:00
client ,
movie_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 ,
_window_spec : Option < 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 ) ;
match webbrowser ::open ( & processed_url . to_string ( ) ) {
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
2020-10-05 17:45:59 +00:00
fn fetch ( & self , url : & str , options : RequestOptions ) -> OwnedFuture < Vec < u8 > , Error > {
// TODO: honor sandbox type (local-with-filesystem, local-with-network, remote, ...)
2020-12-31 21:59:32 +00:00
let full_url = match self . movie_url . clone ( ) . join ( url ) {
Ok ( url ) = > url ,
Err ( e ) = > {
let msg = format! ( " Invalid URL {} : {} " , url , e ) ;
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 ( ) {
2022-03-30 02:23:07 +00:00
#[ cfg(not(feature = " sandbox " )) ]
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 ( ) ;
fs ::read ( path ) . map_err ( | e | Error ::FetchError ( e . to_string ( ) ) )
} ) ,
#[ cfg(feature = " sandbox " ) ]
" file " = > Box ::pin ( async move {
use rfd ::{ FileDialog , MessageButtons , MessageDialog , MessageLevel } ;
use std ::io ::ErrorKind ;
let path = processed_url . to_file_path ( ) . unwrap_or_default ( ) ;
fs ::read ( path . clone ( ) ) . or_else ( | e | {
if matches! ( e . kind ( ) , ErrorKind ::PermissionDenied ) {
let mut display_dir = path . clone ( ) ;
display_dir . pop ( ) ;
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. " , display_dir . into_os_string ( ) . to_string_lossy ( ) ) )
. set_buttons ( MessageButtons ::YesNo )
. show ( ) ;
if attempt_sandbox_open {
FileDialog ::new ( ) . set_directory ( path . clone ( ) ) . pick_folder ( ) ;
return fs ::read ( path ) . map_err ( | e | Error ::FetchError ( e . to_string ( ) ) ) ;
}
}
Err ( Error ::FetchError ( e . to_string ( ) ) )
} )
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
let request = match options . method ( ) {
2021-02-12 13:03:17 +00:00
NavigationMethod ::Get = > Request ::get ( processed_url . to_string ( ) ) ,
NavigationMethod ::Post = > Request ::post ( processed_url . to_string ( ) ) ,
2020-10-05 17:45:59 +00:00
} ;
let ( body_data , _ ) = options . body ( ) . clone ( ) . unwrap_or_default ( ) ;
let body = request
. 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
}
2021-01-01 19:36:21 +00:00
let mut buffer = vec! [ ] ;
response
. copy_to ( & mut buffer )
. await
. map_err ( | e | Error ::FetchError ( e . to_string ( ) ) ) ? ;
Ok ( buffer )
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
2021-09-07 04:44:24 +00:00
fn resolve_relative_url < ' a > ( & self , url : & ' a str ) -> Cow < ' a , str > {
2020-10-05 17:45:59 +00:00
let relative = self . movie_url . join ( url ) ;
2020-07-23 22:30:39 +00:00
if let Ok ( relative ) = relative {
2021-05-08 03:00:22 +00:00
String ::from ( relative ) . into ( )
2020-07-23 22:30:39 +00:00
} else {
url . into ( )
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
}