web: Fix cors issues with http (close ruffle-rs#1486)

This commit is contained in:
Sam Morrow 2020-12-11 11:41:44 +00:00 committed by Mike Welsh
parent 8f34023ad2
commit c4d7b24629
7 changed files with 86 additions and 18 deletions

View File

@ -198,6 +198,12 @@ pub trait NavigatorBackend {
/// current document's base URL, while the most obvious base for a desktop /// current document's base URL, while the most obvious base for a desktop
/// client would be the file-URL form of the current path. /// client would be the file-URL form of the current path.
fn resolve_relative_url<'a>(&mut self, url: &'a str) -> Cow<'a, str>; fn resolve_relative_url<'a>(&mut self, url: &'a str) -> Cow<'a, str>;
/// Handle any context specific pre-processing
///
/// Changing http -> https for example. This function may alter any part of the
/// URL (generally only if configured to do so by the user).
fn pre_process_url(&self, url: Url) -> Url;
} }
/// A null implementation of an event loop that only supports blocking. /// A null implementation of an event loop that only supports blocking.
@ -378,4 +384,8 @@ impl NavigatorBackend for NullNavigatorBackend {
url.into() url.into()
} }
} }
fn pre_process_url(&self, url: Url) -> Url {
url
}
} }

View File

@ -76,6 +76,10 @@ struct Opt {
/// (Optional) Proxy to use when loading movies via URL /// (Optional) Proxy to use when loading movies via URL
#[clap(long, case_insensitive = true)] #[clap(long, case_insensitive = true)]
proxy: Option<Url>, proxy: Option<Url>,
/// (Optional) Replace all embedded http URLs with https
#[clap(long, case_insensitive = true, takes_value = false)]
upgrade_to_https: bool,
} }
#[cfg(feature = "render_trace")] #[cfg(feature = "render_trace")]
@ -207,6 +211,7 @@ fn run_player(opt: Opt) -> Result<(), Box<dyn std::error::Error>> {
chan, chan,
event_loop.create_proxy(), event_loop.create_proxy(),
opt.proxy, opt.proxy,
opt.upgrade_to_https,
)); //TODO: actually implement this backend type )); //TODO: actually implement this backend type
let input = Box::new(input::WinitInputBackend::new(window.clone())); let input = Box::new(input::WinitInputBackend::new(window.clone()));
let storage = Box::new(DiskStorageBackend::new( let storage = Box::new(DiskStorageBackend::new(

View File

@ -34,6 +34,8 @@ pub struct ExternalNavigatorBackend {
// Client to use for network requests // Client to use for network requests
client: Option<Rc<HttpClient>>, client: Option<Rc<HttpClient>>,
upgrade_to_https: bool,
} }
impl ExternalNavigatorBackend { impl ExternalNavigatorBackend {
@ -44,6 +46,7 @@ impl ExternalNavigatorBackend {
channel: Sender<OwnedFuture<(), Error>>, channel: Sender<OwnedFuture<(), Error>>,
event_loop: EventLoopProxy<RuffleEvent>, event_loop: EventLoopProxy<RuffleEvent>,
proxy: Option<Url>, proxy: Option<Url>,
upgrade_to_https: bool,
) -> Self { ) -> Self {
let proxy = proxy.and_then(|url| url.as_str().parse().ok()); let proxy = proxy.and_then(|url| url.as_str().parse().ok());
let builder = HttpClient::builder() let builder = HttpClient::builder()
@ -58,6 +61,7 @@ impl ExternalNavigatorBackend {
client, client,
movie_url, movie_url,
start_time: Instant::now(), start_time: Instant::now(),
upgrade_to_https,
} }
} }
} }
@ -96,36 +100,37 @@ impl NavigatorBackend for ExternalNavigatorBackend {
} }
} }
parsed_url.into_string() parsed_url
} }
None => url, None => parsed_url,
}; };
match webbrowser::open(&modified_url) { let processed_url = self.pre_process_url(modified_url);
match webbrowser::open(&processed_url.to_string()) {
Ok(_output) => {} Ok(_output) => {}
Err(e) => log::error!("Could not open URL {}: {}", modified_url, e), Err(e) => log::error!("Could not open URL {}: {}", processed_url.as_str(), e),
}; };
} }
fn time_since_launch(&mut self) -> Duration {
Instant::now().duration_since(self.start_time)
}
fn fetch(&self, url: &str, options: RequestOptions) -> OwnedFuture<Vec<u8>, Error> { fn fetch(&self, url: &str, options: RequestOptions) -> OwnedFuture<Vec<u8>, Error> {
// TODO: honor sandbox type (local-with-filesystem, local-with-network, remote, ...) // TODO: honor sandbox type (local-with-filesystem, local-with-network, remote, ...)
let full_url = self.movie_url.clone().join(url).unwrap(); let full_url = self.movie_url.clone().join(url).unwrap();
let processed_url = self.pre_process_url(full_url);
let client = self.client.clone(); let client = self.client.clone();
match full_url.scheme() {
match processed_url.scheme() {
"file" => Box::pin(async move { "file" => Box::pin(async move {
fs::read(full_url.to_file_path().unwrap()).map_err(Error::NetworkError) fs::read(processed_url.to_file_path().unwrap()).map_err(Error::NetworkError)
}), }),
_ => Box::pin(async move { _ => Box::pin(async move {
let client = client.ok_or(Error::NetworkUnavailable)?; let client = client.ok_or(Error::NetworkUnavailable)?;
let request = match options.method() { let request = match options.method() {
NavigationMethod::GET => Request::get(full_url.to_string()), NavigationMethod::GET => Request::get(processed_url.to_string()),
NavigationMethod::POST => Request::post(full_url.to_string()), NavigationMethod::POST => Request::post(processed_url.to_string()),
}; };
let (body_data, _) = options.body().clone().unwrap_or_default(); let (body_data, _) = options.body().clone().unwrap_or_default();
@ -143,6 +148,10 @@ impl NavigatorBackend for ExternalNavigatorBackend {
} }
} }
fn time_since_launch(&mut self) -> Duration {
Instant::now().duration_since(self.start_time)
}
fn spawn_future(&mut self, future: OwnedFuture<(), Error>) { fn spawn_future(&mut self, future: OwnedFuture<(), Error>) {
self.channel.send(future).expect("working channel send"); self.channel.send(future).expect("working channel send");
@ -161,6 +170,13 @@ impl NavigatorBackend for ExternalNavigatorBackend {
url.into() url.into()
} }
} }
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
}
} }
fn response_to_bytes(res: Response<Body>) -> Result<Vec<u8>, std::io::Error> { fn response_to_bytes(res: Response<Body>) -> Result<Vec<u8>, std::io::Error> {

View File

@ -69,6 +69,19 @@ export interface BaseLoadOptions {
* @default UnmuteOverlay.Visible * @default UnmuteOverlay.Visible
*/ */
unmuteOverlay?: UnmuteOverlay; unmuteOverlay?: UnmuteOverlay;
/**
* Whether or not to auto-upgrade all embedded URLs to https.
*
* Flash content that embeds http urls will be blocked from
* accessing those urls by the browser when Ruffle is loaded
* in a https context. Set to `true` to automatically change
* `http://` to `https://` for all embedded URLs when Ruffle is
* loaded in an https context.
*
* @default true
*/
upgradeToHttps?: boolean;
} }
/** /**

View File

@ -314,7 +314,9 @@ export class RufflePlayer extends HTMLElement {
this.instance = new ruffleConstructor( this.instance = new ruffleConstructor(
this.container, this.container,
this, this,
this.allowScriptAccess this.allowScriptAccess,
config.upgradeToHttps !== false &&
window.location.protocol === "https:"
); );
console.log("New Ruffle instance created."); console.log("New Ruffle instance created.");

View File

@ -115,6 +115,7 @@ impl Ruffle {
parent: HtmlElement, parent: HtmlElement,
js_player: JavascriptPlayer, js_player: JavascriptPlayer,
allow_script_access: bool, allow_script_access: bool,
upgrade_to_https: bool,
) -> Result<Ruffle, JsValue> { ) -> Result<Ruffle, JsValue> {
if RUFFLE_GLOBAL_PANIC.is_completed() { if RUFFLE_GLOBAL_PANIC.is_completed() {
// If an actual panic happened, then we can't trust the state it left us in. // If an actual panic happened, then we can't trust the state it left us in.
@ -122,7 +123,7 @@ impl Ruffle {
return Err("Ruffle is panicking!".into()); return Err("Ruffle is panicking!".into());
} }
set_panic_handler(); set_panic_handler();
Ruffle::new_internal(parent, js_player, allow_script_access) Ruffle::new_internal(parent, js_player, allow_script_access, upgrade_to_https)
.map_err(|_| "Error creating player".into()) .map_err(|_| "Error creating player".into())
} }
@ -284,6 +285,7 @@ impl Ruffle {
parent: HtmlElement, parent: HtmlElement,
js_player: JavascriptPlayer, js_player: JavascriptPlayer,
allow_script_access: bool, allow_script_access: bool,
upgrade_to_https: bool,
) -> Result<Ruffle, Box<dyn Error>> { ) -> Result<Ruffle, Box<dyn Error>> {
let _ = console_log::init_with_level(log::Level::Trace); let _ = console_log::init_with_level(log::Level::Trace);
@ -296,7 +298,7 @@ impl Ruffle {
.into_js_result()?; .into_js_result()?;
let audio = Box::new(WebAudioBackend::new()?); let audio = Box::new(WebAudioBackend::new()?);
let navigator = Box::new(WebNavigatorBackend::new()); let navigator = Box::new(WebNavigatorBackend::new(upgrade_to_https));
let input = Box::new(WebInputBackend::new(&canvas)); let input = Box::new(WebInputBackend::new(&canvas));
let locale = Box::new(WebLocaleBackend::new()); let locale = Box::new(WebLocaleBackend::new());

View File

@ -1,5 +1,4 @@
//! Navigator backend for web //! Navigator backend for web
use js_sys::{Array, ArrayBuffer, Uint8Array}; use js_sys::{Array, ArrayBuffer, Uint8Array};
use ruffle_core::backend::navigator::{ use ruffle_core::backend::navigator::{
url_from_relative_url, NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions, url_from_relative_url, NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions,
@ -8,6 +7,7 @@ use ruffle_core::indexmap::IndexMap;
use ruffle_core::loader::Error; use ruffle_core::loader::Error;
use std::borrow::Cow; use std::borrow::Cow;
use std::time::Duration; use std::time::Duration;
use url::Url;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use wasm_bindgen_futures::{spawn_local, JsFuture}; use wasm_bindgen_futures::{spawn_local, JsFuture};
use web_sys::{window, Blob, BlobPropertyBag, Performance, Request, RequestInit, Response}; use web_sys::{window, Blob, BlobPropertyBag, Performance, Request, RequestInit, Response};
@ -15,16 +15,18 @@ use web_sys::{window, Blob, BlobPropertyBag, Performance, Request, RequestInit,
pub struct WebNavigatorBackend { pub struct WebNavigatorBackend {
performance: Performance, performance: Performance,
start_time: f64, start_time: f64,
upgrade_to_https: bool,
} }
impl WebNavigatorBackend { impl WebNavigatorBackend {
pub fn new() -> Self { pub fn new(upgrade_to_https: bool) -> Self {
let window = web_sys::window().expect("window()"); let window = web_sys::window().expect("window()");
let performance = window.performance().expect("window.performance()"); let performance = window.performance().expect("window.performance()");
WebNavigatorBackend { WebNavigatorBackend {
start_time: performance.now(), start_time: performance.now(),
performance, performance,
upgrade_to_https,
} }
} }
} }
@ -37,6 +39,12 @@ impl NavigatorBackend for WebNavigatorBackend {
vars_method: Option<(NavigationMethod, IndexMap<String, String>)>, vars_method: Option<(NavigationMethod, IndexMap<String, String>)>,
) { ) {
if let Some(window) = window() { if let Some(window) = window() {
let url = if let Ok(parsed_url) = Url::parse(&url) {
self.pre_process_url(parsed_url).to_string()
} else {
url.to_string()
};
//TODO: Should we return a result for failed opens? Does Flash care? //TODO: Should we return a result for failed opens? Does Flash care?
#[allow(unused_must_use)] #[allow(unused_must_use)]
match (vars_method, window_spec) { match (vars_method, window_spec) {
@ -95,7 +103,12 @@ impl NavigatorBackend for WebNavigatorBackend {
} }
fn fetch(&self, url: &str, options: RequestOptions) -> OwnedFuture<Vec<u8>, Error> { fn fetch(&self, url: &str, options: RequestOptions) -> OwnedFuture<Vec<u8>, Error> {
let url = url.to_string(); let url = if let Ok(parsed_url) = Url::parse(url) {
self.pre_process_url(parsed_url).to_string()
} else {
url.to_string()
};
Box::pin(async move { Box::pin(async move {
let mut init = RequestInit::new(); let mut init = RequestInit::new();
@ -172,4 +185,11 @@ impl NavigatorBackend for WebNavigatorBackend {
url.into() url.into()
} }
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
}
} }