Add missing frame-ancestors.

This commit is contained in:
Tomasz Drwięga 2017-07-04 16:04:09 +02:00
parent cc718bb108
commit 949086baa5
No known key found for this signature in database
GPG Key ID: D066F497E62CAF66
16 changed files with 142 additions and 85 deletions

View File

@ -25,6 +25,7 @@ use util::sha3::sha3;
use page::{LocalPageEndpoint, PageCache};
use handlers::{ContentValidator, ValidatorResponse};
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest};
use Embeddable;
type OnDone = Box<Fn(Option<LocalPageEndpoint>) + Send>;
@ -116,16 +117,16 @@ pub struct Dapp {
id: String,
dapps_path: PathBuf,
on_done: OnDone,
embeddable_on: Option<(String, u16)>,
embeddable_on: Embeddable,
}
impl Dapp {
pub fn new(id: String, dapps_path: PathBuf, on_done: OnDone, embeddable_on: Option<(String, u16)>) -> Self {
pub fn new(id: String, dapps_path: PathBuf, on_done: OnDone, embeddable_on: Embeddable) -> Self {
Dapp {
id: id,
dapps_path: dapps_path,
on_done: on_done,
embeddable_on: embeddable_on,
id,
dapps_path,
on_done,
embeddable_on,
}
}

View File

@ -31,7 +31,7 @@ use parity_reactor::Remote;
use hyper;
use hyper::status::StatusCode;
use {SyncStatus, random_filename};
use {Embeddable, SyncStatus, random_filename};
use util::Mutex;
use page::LocalPageEndpoint;
use handlers::{ContentHandler, ContentFetcherHandler};
@ -52,7 +52,7 @@ pub struct ContentFetcher<F: Fetch = FetchClient, R: URLHint + 'static = URLHint
resolver: R,
cache: Arc<Mutex<ContentCache>>,
sync: Arc<SyncStatus>,
embeddable_on: Option<(String, u16)>,
embeddable_on: Embeddable,
remote: Remote,
fetch: F,
only_content: bool,
@ -93,22 +93,22 @@ impl<R: URLHint + 'static, F: Fetch> ContentFetcher<F, R> {
self
}
pub fn embeddable_on(mut self, embeddable_on: Option<(String, u16)>) -> Self {
pub fn embeddable_on(mut self, embeddable_on: Embeddable) -> Self {
self.embeddable_on = embeddable_on;
self
}
fn still_syncing(address: Option<(String, u16)>) -> Box<Handler> {
fn still_syncing(embeddable: Embeddable) -> Box<Handler> {
Box::new(ContentHandler::error(
StatusCode::ServiceUnavailable,
"Sync In Progress",
"Your node is still syncing. We cannot resolve any content before it's fully synced.",
Some("<a href=\"javascript:window.location.reload()\">Refresh</a>"),
address,
embeddable,
))
}
fn dapps_disabled(address: Option<(String, u16)>) -> Box<Handler> {
fn dapps_disabled(address: Embeddable) -> Box<Handler> {
Box::new(ContentHandler::error(
StatusCode::ServiceUnavailable,
"Network Dapps Not Available",

View File

@ -22,6 +22,7 @@ use std::path::{Path, PathBuf};
use page::{LocalPageEndpoint, PageCache};
use endpoint::{Endpoint, EndpointInfo};
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest};
use Embeddable;
struct LocalDapp {
id: String,
@ -60,14 +61,14 @@ fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo {
/// Returns Dapp Id and Local Dapp Endpoint for given filesystem path.
/// Parses the path to extract last component (for name).
/// `None` is returned when path is invalid or non-existent.
pub fn local_endpoint<P: AsRef<Path>>(path: P, signer_address: Option<(String, u16)>) -> Option<(String, Box<LocalPageEndpoint>)> {
pub fn local_endpoint<P: AsRef<Path>>(path: P, embeddable: Embeddable) -> Option<(String, Box<LocalPageEndpoint>)> {
let path = path.as_ref().to_owned();
path.canonicalize().ok().and_then(|path| {
let name = path.file_name().and_then(|name| name.to_str());
name.map(|name| {
let dapp = local_dapp(name.into(), path.clone());
(dapp.id, Box::new(LocalPageEndpoint::new(
dapp.path, dapp.info, PageCache::Disabled, signer_address.clone())
dapp.path, dapp.info, PageCache::Disabled, embeddable.clone())
))
})
})
@ -86,12 +87,12 @@ fn local_dapp(name: String, path: PathBuf) -> LocalDapp {
/// Returns endpoints for Local Dapps found for given filesystem path.
/// Scans the directory and collects `LocalPageEndpoints`.
pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, signer_address: Option<(String, u16)>) -> BTreeMap<String, Box<Endpoint>> {
pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, embeddable: Embeddable) -> BTreeMap<String, Box<Endpoint>> {
let mut pages = BTreeMap::<String, Box<Endpoint>>::new();
for dapp in local_dapps(dapps_path.as_ref()) {
pages.insert(
dapp.id,
Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_address.clone()))
Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, embeddable.clone()))
);
}
pages

View File

@ -26,7 +26,7 @@ use fetch::Fetch;
use parity_dapps::WebApp;
use parity_reactor::Remote;
use parity_ui;
use {WebProxyTokens};
use {WebProxyTokens, ParentFrameSettings, as_embeddable};
mod app;
mod cache;
@ -52,8 +52,8 @@ pub fn ui() -> Box<Endpoint> {
Box::new(PageEndpoint::with_fallback_to_index(parity_ui::App::default()))
}
pub fn ui_redirection(ui_address: Option<(String, u16)>) -> Box<Endpoint> {
Box::new(ui::Redirection::new(ui_address))
pub fn ui_redirection(ui_address: Option<(String, u16)>, dapps_domain: String) -> Box<Endpoint> {
Box::new(ui::Redirection::new(as_embeddable(ui_address, dapps_domain)))
}
pub fn all_endpoints<F: Fetch>(
@ -65,10 +65,11 @@ pub fn all_endpoints<F: Fetch>(
remote: Remote,
fetch: F,
) -> Endpoints {
let embeddable = as_embeddable(ui_address.clone(), dapps_domain.clone());
// fetch fs dapps at first to avoid overwriting builtins
let mut pages = fs::local_endpoints(dapps_path, ui_address.clone());
let mut pages = fs::local_endpoints(dapps_path, embeddable.clone());
for path in extra_dapps {
if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), ui_address.clone()) {
if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), embeddable.clone()) {
pages.insert(id, endpoint);
} else {
warn!(target: "dapps", "Ignoring invalid dapp at {}", path.display());
@ -76,9 +77,9 @@ pub fn all_endpoints<F: Fetch>(
}
// NOTE [ToDr] Dapps will be currently embeded on 8180
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(ui_address.clone()));
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(embeddable.clone()));
pages.insert("proxy".into(), ProxyPac::boxed(ui_address.clone(), dapps_domain));
pages.insert(WEB_PATH.into(), Web::boxed(ui_address.clone(), web_proxy_tokens.clone(), remote.clone(), fetch.clone()));
pages.insert(WEB_PATH.into(), Web::boxed(embeddable.clone(), web_proxy_tokens.clone(), remote.clone(), fetch.clone()));
Arc::new(pages)
}
@ -91,7 +92,7 @@ fn insert<T : WebApp + Default + 'static>(pages: &mut BTreeMap<String, Box<Endpo
}
enum Embeddable {
Yes(Option<(String, u16)>),
Yes(Option<ParentFrameSettings>),
#[allow(dead_code)]
No,
}

View File

@ -19,28 +19,28 @@
use hyper::{Control, StatusCode};
use endpoint::{Endpoint, Handler, EndpointPath};
use {address, handlers};
use {handlers, Embeddable};
/// Redirection to UI server.
pub struct Redirection {
signer_address: Option<(String, u16)>,
embeddable_on: Embeddable,
}
impl Redirection {
pub fn new(
signer_address: Option<(String, u16)>,
embeddable_on: Embeddable,
) -> Self {
Redirection {
signer_address: signer_address,
embeddable_on,
}
}
}
impl Endpoint for Redirection {
fn to_async_handler(&self, _path: EndpointPath, _control: Control) -> Box<Handler> {
if let Some(ref signer_address) = self.signer_address {
if let Some(ref frame) = self.embeddable_on {
trace!(target: "dapps", "Redirecting to signer interface.");
handlers::Redirection::boxed(&format!("http://{}", address(signer_address)))
handlers::Redirection::boxed(&format!("http://{}:{}", &frame.host, frame.port))
} else {
trace!(target: "dapps", "Signer disabled, returning 404.");
Box::new(handlers::ContentHandler::error(
@ -48,7 +48,7 @@ impl Endpoint for Redirection {
"404 Not Found",
"Your homepage is not available when Trusted Signer is disabled.",
Some("You can still access dapps by writing a correct address, though. Re-enable Signer to get your homepage back."),
self.signer_address.clone(),
None,
))
}
}

View File

@ -24,6 +24,7 @@ use hyper::status::StatusCode;
use util::version;
use handlers::add_security_headers;
use Embeddable;
#[derive(Clone)]
pub struct ContentHandler {
@ -31,7 +32,7 @@ pub struct ContentHandler {
content: String,
mimetype: Mime,
write_pos: usize,
safe_to_embed_on: Option<(String, u16)>,
safe_to_embed_on: Embeddable,
}
impl ContentHandler {
@ -43,11 +44,17 @@ impl ContentHandler {
Self::new(StatusCode::NotFound, content, mimetype)
}
pub fn html(code: StatusCode, content: String, embeddable_on: Option<(String, u16)>) -> Self {
pub fn html(code: StatusCode, content: String, embeddable_on: Embeddable) -> Self {
Self::new_embeddable(code, content, mime!(Text/Html), embeddable_on)
}
pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_on: Option<(String, u16)>) -> Self {
pub fn error(
code: StatusCode,
title: &str,
message: &str,
details: Option<&str>,
embeddable_on: Embeddable,
) -> Self {
Self::html(code, format!(
include_str!("../error_tpl.html"),
title=title,
@ -61,13 +68,18 @@ impl ContentHandler {
Self::new_embeddable(code, content, mimetype, None)
}
pub fn new_embeddable(code: StatusCode, content: String, mimetype: Mime, embeddable_on: Option<(String, u16)>) -> Self {
pub fn new_embeddable(
code: StatusCode,
content: String,
mimetype: Mime,
safe_to_embed_on: Embeddable,
) -> Self {
ContentHandler {
code: code,
content: content,
mimetype: mimetype,
code,
content,
mimetype,
write_pos: 0,
safe_to_embed_on: embeddable_on,
safe_to_embed_on,
}
}
}
@ -84,7 +96,7 @@ impl server::Handler<HttpStream> for ContentHandler {
fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(self.code);
res.headers_mut().set(header::ContentType(self.mimetype.clone()));
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone());
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.take());
Next::write()
}

View File

@ -33,6 +33,7 @@ use hyper::status::StatusCode;
use endpoint::EndpointPath;
use handlers::{ContentHandler, StreamingHandler};
use page::{LocalPageEndpoint, PageHandlerWaiting};
use {Embeddable};
const FETCH_TIMEOUT: u64 = 300;
@ -179,7 +180,7 @@ impl server::Handler<HttpStream> for WaitingHandler {
#[derive(Clone)]
struct Errors {
embeddable_on: Option<(String, u16)>,
embeddable_on: Embeddable,
}
impl Errors {
@ -241,20 +242,20 @@ impl<H: ContentValidator, F: Fetch> ContentFetcherHandler<H, F> {
path: EndpointPath,
control: Control,
installer: H,
embeddable_on: Option<(String, u16)>,
embeddable_on: Embeddable,
remote: Remote,
fetch: F,
) -> Self {
ContentFetcherHandler {
fetch_control: FetchControl::default(),
control: control,
remote: remote,
fetch: fetch,
control,
remote,
fetch,
status: FetchState::NotStarted(url),
installer: Some(installer),
path: path,
path,
errors: Errors {
embeddable_on: embeddable_on,
embeddable_on,
},
}
}

View File

@ -30,20 +30,15 @@ pub use self::streaming::StreamingHandler;
use url::Url;
use hyper::{server, header, net, uri};
use address;
use {apps, address, Embeddable};
/// Adds security-related headers to the Response.
pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option<(String, u16)>) {
pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Embeddable) {
headers.set_raw("X-XSS-Protection", vec![b"1; mode=block".to_vec()]);
headers.set_raw("X-Content-Type-Options", vec![b"nosniff".to_vec()]);
// Embedding header:
if let Some(ref embeddable_on) = embeddable_on {
headers.set_raw("X-Frame-Options", vec![
format!("ALLOW-FROM http://{}", address(embeddable_on)).into_bytes()
]);
} else {
// TODO [ToDr] Should we be more strict here (DENY?)?
if let None = embeddable_on {
headers.set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]);
}
@ -60,7 +55,7 @@ pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option
b"child-src 'self' http: https:;".to_vec(),
// We allow data: blob: and HTTP(s) images.
// We could get rid of wildcarding HTTP and only allow RPC server URL.
// (http require for local dapps icons)
// (http required for local dapps icons)
b"img-src 'self' 'unsafe-inline' data: blob: http: https:;".to_vec(),
// Allow style from data: blob: and HTTPS.
b"style-src 'self' 'unsafe-inline' data: blob: https:;".to_vec(),
@ -78,10 +73,27 @@ pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option
b"block-all-mixed-content;".to_vec(),
// Specify if the site can be embedded.
match embeddable_on {
Some((ref host, ref port)) if host == "127.0.0.1" => {
format!("frame-ancestors {} {};", address(&(host.to_owned(), *port)), address(&("localhost".to_owned(), *port)))
Some(ref embed) => {
let std = address(&embed.host, embed.port);
let proxy = format!("{}.{}", apps::HOME_PAGE, embed.dapps_domain);
let domain = format!("*.{}:{}", embed.dapps_domain, embed.port);
if embed.host == "127.0.0.1" {
let localhost = address("localhost", embed.port);
format!("frame-ancestors {} {} {} {};",
std,
localhost,
domain,
proxy,
)
} else {
format!("frame-ancestors {} {} {};",
std,
domain,
proxy,
)
}
},
Some(ref embed) => format!("frame-ancestors {};", address(embed)),
None => format!("frame-ancestors 'self';"),
}.into_bytes(),
]);

View File

@ -24,6 +24,7 @@ use hyper::mime::Mime;
use hyper::status::StatusCode;
use handlers::add_security_headers;
use Embeddable;
const BUFFER_SIZE: usize = 1024;
@ -33,11 +34,11 @@ pub struct StreamingHandler<R: io::Read> {
status: StatusCode,
content: io::BufReader<R>,
mimetype: Mime,
safe_to_embed_on: Option<(String, u16)>,
safe_to_embed_on: Embeddable,
}
impl<R: io::Read> StreamingHandler<R> {
pub fn new(content: R, status: StatusCode, mimetype: Mime, embeddable_on: Option<(String, u16)>) -> Self {
pub fn new(content: R, status: StatusCode, mimetype: Mime, embeddable_on: Embeddable) -> Self {
StreamingHandler {
buffer: [0; BUFFER_SIZE],
buffer_leftover: 0,
@ -68,7 +69,7 @@ impl<R: io::Read> server::Handler<HttpStream> for StreamingHandler<R> {
fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(self.status);
res.headers_mut().set(header::ContentType(self.mimetype.clone()));
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone());
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.take());
Next::write()
}

View File

@ -171,12 +171,13 @@ impl Middleware {
web_proxy_tokens: Arc<WebProxyTokens>,
fetch: F,
) -> Self {
let embeddable = as_embeddable(ui_address.clone(), dapps_domain.clone());
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(
hash_fetch::urlhint::URLHintContract::new(registrar),
sync_status,
remote.clone(),
fetch.clone(),
).embeddable_on(ui_address.clone()).allow_dapps(true));
).embeddable_on(embeddable.clone()).allow_dapps(true));
let endpoints = apps::all_endpoints(
dapps_path,
extra_dapps,
@ -189,7 +190,10 @@ impl Middleware {
let special = {
let mut special = special_endpoints(content_fetcher.clone());
special.insert(router::SpecialEndpoint::Home, Some(apps::ui_redirection(ui_address.clone())));
special.insert(
router::SpecialEndpoint::Home,
Some(apps::ui_redirection(ui_address.clone(), dapps_domain.clone())),
);
special
};
@ -197,7 +201,7 @@ impl Middleware {
content_fetcher,
Some(endpoints.clone()),
special,
ui_address,
embeddable,
dapps_domain,
);
@ -222,8 +226,12 @@ fn special_endpoints(content_fetcher: Arc<apps::fetcher::Fetcher>) -> HashMap<ro
special
}
fn address(address: &(String, u16)) -> String {
format!("{}:{}", address.0, address.1)
fn address(host: &str, port: u16) -> String {
format!("{}:{}", host, port)
}
fn as_embeddable(ui_address: Option<(String, u16)>, dapps_domain: String) -> Option<ParentFrameSettings> {
ui_address.map(|(host, port)| ParentFrameSettings { host, port, dapps_domain, })
}
/// Random filename
@ -232,3 +240,16 @@ fn random_filename() -> String {
let mut rng = ::rand::OsRng::new().unwrap();
rng.gen_ascii_chars().take(12).collect()
}
type Embeddable = Option<ParentFrameSettings>;
/// Parent frame host and port allowed to embed the content.
#[derive(Debug, Clone)]
pub struct ParentFrameSettings {
/// Hostname
pub host: String,
/// Port
pub port: u16,
/// Dapps Domain (web3.site)
pub dapps_domain: String,
}

View File

@ -18,6 +18,7 @@ use page::{handler, PageCache};
use std::sync::Arc;
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
use parity_dapps::{WebApp, File, Info};
use Embeddable;
pub struct PageEndpoint<T : WebApp + 'static> {
/// Content of the files
@ -25,7 +26,7 @@ pub struct PageEndpoint<T : WebApp + 'static> {
/// Prefix to strip from the path (when `None` deducted from `app_id`)
pub prefix: Option<String>,
/// Safe to be loaded in frame by other origin. (use wisely!)
safe_to_embed_on: Option<(String, u16)>,
safe_to_embed_on: Embeddable,
info: EndpointInfo,
fallback_to_index_html: bool,
}
@ -73,7 +74,7 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
/// Creates new `PageEndpoint` which can be safely used in iframe
/// even from different origin. It might be dangerous (clickjacking).
/// Use wisely!
pub fn new_safe_to_embed(app: T, address: Option<(String, u16)>) -> Self {
pub fn new_safe_to_embed(app: T, address: Embeddable) -> Self {
let info = app.info();
PageEndpoint {
app: Arc::new(app),

View File

@ -24,6 +24,7 @@ use hyper::status::StatusCode;
use hyper::{Decoder, Encoder, Next};
use endpoint::EndpointPath;
use handlers::{ContentHandler, add_security_headers};
use {Embeddable};
/// Represents a file that can be sent to client.
/// Implementation should keep track of bytes already sent internally.
@ -59,7 +60,7 @@ pub enum ServedFile<T: Dapp> {
}
impl<T: Dapp> ServedFile<T> {
pub fn new(embeddable_on: Option<(String, u16)>) -> Self {
pub fn new(embeddable_on: Embeddable) -> Self {
ServedFile::Error(ContentHandler::error(
StatusCode::NotFound,
"404 Not Found",
@ -102,7 +103,7 @@ pub struct PageHandler<T: Dapp> {
/// Requested path.
pub path: EndpointPath,
/// Flag indicating if the file can be safely embeded (put in iframe).
pub safe_to_embed_on: Option<(String, u16)>,
pub safe_to_embed_on: Embeddable,
/// Cache settings for this page.
pub cache: PageCache,
}
@ -174,7 +175,7 @@ impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
}
// Security headers:
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone());
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.take());
Next::write()
},
ServedFile::Error(ref mut handler) => {

View File

@ -21,6 +21,7 @@ use std::path::{Path, PathBuf};
use page::handler::{self, PageCache, PageHandlerWaiting};
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
use mime::Mime;
use Embeddable;
#[derive(Debug, Clone)]
pub struct LocalPageEndpoint {
@ -28,11 +29,11 @@ pub struct LocalPageEndpoint {
mime: Option<Mime>,
info: Option<EndpointInfo>,
cache: PageCache,
embeddable_on: Option<(String, u16)>,
embeddable_on: Embeddable,
}
impl LocalPageEndpoint {
pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Option<(String, u16)>) -> Self {
pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Embeddable) -> Self {
LocalPageEndpoint {
path: path,
mime: None,

View File

@ -39,7 +39,7 @@ impl Endpoint for ProxyPac {
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
let signer = self.signer_address
.as_ref()
.map(address)
.map(|&(ref host, port)| address(host, port))
.unwrap_or_else(|| format!("{}:{}", path.host, path.port));
let content = format!(

View File

@ -30,6 +30,7 @@ use apps;
use apps::fetcher::Fetcher;
use endpoint::{Endpoint, Endpoints, EndpointPath, Handler};
use handlers;
use Embeddable;
/// Special endpoints are accessible on every domain (every dapp)
#[derive(Debug, PartialEq, Hash, Eq)]
@ -45,7 +46,7 @@ pub struct Router {
endpoints: Option<Endpoints>,
fetch: Arc<Fetcher>,
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
embeddable_on: Option<(String, u16)>,
embeddable_on: Embeddable,
dapps_domain: String,
}
@ -148,7 +149,7 @@ impl Router {
content_fetcher: Arc<Fetcher>,
endpoints: Option<Endpoints>,
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
embeddable_on: Option<(String, u16)>,
embeddable_on: Embeddable,
dapps_domain: String,
) -> Self {
Router {

View File

@ -31,9 +31,7 @@ use handlers::{
StreamingHandler, extract_url,
};
use url::Url;
use WebProxyTokens;
pub type Embeddable = Option<(String, u16)>;
use {Embeddable, WebProxyTokens};
pub struct Web<F> {
embeddable_on: Embeddable,
@ -43,12 +41,17 @@ pub struct Web<F> {
}
impl<F: Fetch> Web<F> {
pub fn boxed(embeddable_on: Embeddable, web_proxy_tokens: Arc<WebProxyTokens>, remote: Remote, fetch: F) -> Box<Endpoint> {
pub fn boxed(
embeddable_on: Embeddable,
web_proxy_tokens: Arc<WebProxyTokens>,
remote: Remote,
fetch: F,
) -> Box<Endpoint> {
Box::new(Web {
embeddable_on: embeddable_on,
web_proxy_tokens: web_proxy_tokens,
remote: remote,
fetch: fetch,
embeddable_on,
web_proxy_tokens,
remote,
fetch,
})
}
}