Skip to content

Commit

Permalink
refactor to trait
Browse files Browse the repository at this point in the history
  • Loading branch information
kitsonk committed Oct 28, 2021
1 parent 061da6f commit 4883f66
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 42 deletions.
61 changes: 61 additions & 0 deletions ext/fetch/fs_fetch_handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

use crate::CancelHandle;
use crate::CancelableResponseFuture;
use crate::FetchHandler;
use crate::FetchRequestBodyResource;

use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::futures::FutureExt;
use deno_core::url::Url;
use std::rc::Rc;

/// An implementation which tries to read file URLs from the file system via
/// tokio::fs
#[derive(Clone)]
pub struct FsFetchHandler;

impl FetchHandler for FsFetchHandler {
fn fetch_url(
&mut self,
url: Url,
) -> (
CancelableResponseFuture,
Option<FetchRequestBodyResource>,
Option<Rc<CancelHandle>>,
) {
let path = url.to_file_path().unwrap();
let response_fut = async move {
let response = {
match tokio::fs::read(&path).await {
Ok(body) => match http::Response::builder()
.status(http::StatusCode::OK)
.body(reqwest::Body::from(body))
{
Ok(response) => Ok(reqwest::Response::from(response)),
Err(err) => Err(err.into()),
},
Err(err) => Err(err.into()),
}
};
Ok(response)
}
.boxed_local();

(response_fut, None, None)
}

fn validate_url(&mut self, url: &Url) -> Result<(), AnyError> {
// Error messages are kept intentionally generic in order to discourage
// probing, and attempting to discern something from the environment.
let path = url
.to_file_path()
.map_err(|_| type_error(format!("Unable to fetch \"{}\".", url)))?;
if !path.is_file() {
Err(type_error(format!("Unable to fetch \"{}\".", url)))
} else {
Ok(())
}
}
}
101 changes: 65 additions & 36 deletions ext/fetch/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

mod fs_fetch_handler;

use data_url::DataUrl;
use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::futures::Future;
use deno_core::futures::FutureExt;
use deno_core::futures::Stream;
use deno_core::futures::StreamExt;
use deno_core::include_js_files;
Expand Down Expand Up @@ -53,15 +54,21 @@ use tokio_util::io::StreamReader;
pub use data_url;
pub use reqwest;

pub fn init<P: FetchPermissions + 'static>(
pub use fs_fetch_handler::FsFetchHandler;

pub fn init<FP, FH>(
user_agent: String,
root_cert_store: Option<RootCertStore>,
proxy: Option<Proxy>,
request_builder_hook: Option<fn(RequestBuilder) -> RequestBuilder>,
unsafely_ignore_certificate_errors: Option<Vec<String>>,
client_cert_chain_and_key: Option<(String, String)>,
enable_file_fetch: bool,
) -> Extension {
file_fetch_handler: FH,
) -> Extension
where
FP: FetchPermissions + 'static,
FH: FetchHandler + 'static,
{
Extension::builder()
.js(include_js_files!(
prefix "deno:ext/fetch",
Expand All @@ -75,11 +82,14 @@ pub fn init<P: FetchPermissions + 'static>(
"26_fetch.js",
))
.ops(vec![
("op_fetch", op_sync(op_fetch::<P>)),
("op_fetch", op_sync(op_fetch::<FP, FH>)),
("op_fetch_send", op_async(op_fetch_send)),
("op_fetch_request_write", op_async(op_fetch_request_write)),
("op_fetch_response_read", op_async(op_fetch_response_read)),
("op_create_http_client", op_sync(op_create_http_client::<P>)),
(
"op_create_http_client",
op_sync(op_create_http_client::<FP>),
),
])
.state(move |state| {
state.put::<reqwest::Client>({
Expand All @@ -102,7 +112,7 @@ pub fn init<P: FetchPermissions + 'static>(
.clone(),
client_cert_chain_and_key: client_cert_chain_and_key.clone(),
});
state.put::<FetchFeatures>(FetchFeatures { enable_file_fetch });
state.put::<FH>(file_fetch_handler.clone());
Ok(())
})
.build()
Expand All @@ -117,10 +127,40 @@ pub struct HttpClientDefaults {
pub client_cert_chain_and_key: Option<(String, String)>,
}

struct FetchFeatures {
enable_file_fetch: bool,
pub type CancelableResponseFuture =
Pin<Box<dyn Future<Output = CancelableResponseResult>>>;

pub trait FetchHandler: Clone {
// Return the result of the fetch request consisting of a tuple of the
// cancelable response result, the optional fetch body resource and the
// optional cancel handle.
fn fetch_url(
&mut self,
url: Url,
) -> (
CancelableResponseFuture,
Option<FetchRequestBodyResource>,
Option<Rc<CancelHandle>>,
) {
let fut = async move {
Ok(Err(type_error(format!("Unable to fetch \"{}\".", url))))
};
(Box::pin(fut), None, None)
}

// Determine if a given URL is valid, returning an early error to the client
fn validate_url(&mut self, url: &Url) -> Result<(), AnyError> {
Err(type_error(format!("Unable to fetch \"{}\".", url)))
}
}

/// A default implementation which leverages the trait's default method
/// implementations.
#[derive(Clone)]
pub struct DefaultFileFetchHandler;

impl FetchHandler for DefaultFileFetchHandler {}

pub trait FetchPermissions {
fn check_net_url(&mut self, _url: &Url) -> Result<(), AnyError>;
fn check_read(&mut self, _p: &Path) -> Result<(), AnyError>;
Expand Down Expand Up @@ -149,13 +189,14 @@ pub struct FetchReturn {
cancel_handle_rid: Option<ResourceId>,
}

pub fn op_fetch<FP>(
pub fn op_fetch<FP, FH>(
state: &mut OpState,
args: FetchArgs,
data: Option<ZeroCopyBuf>,
) -> Result<FetchReturn, AnyError>
where
FP: FetchPermissions + 'static,
FH: FetchHandler + 'static,
{
let client = if let Some(rid) = args.client_rid {
let r = state.resource_table.get::<HttpClientResource>(rid)?;
Expand All @@ -164,51 +205,39 @@ where
let client = state.borrow::<reqwest::Client>();
client.clone()
};
let features = state.borrow::<FetchFeatures>();

let method = Method::from_bytes(&args.method)?;
let url = Url::parse(&args.url)?;

// Check scheme before asking for net permission
let scheme = url.scheme();
let (request_rid, request_body_rid, cancel_handle_rid) = match scheme {
"file" if features.enable_file_fetch => {
let permissions = state.borrow_mut::<FP>();
"file" => {
let path = url
.to_file_path()
.map_err(|_| type_error(format!("Invalid file URL: {}", url)))?;
let permissions = state.borrow_mut::<FP>();
permissions.check_read(&path)?;

if method != Method::GET {
return Err(type_error(format!(
"Fetching files only supports the GET method. Received {}.",
method
)));
}
if !path.is_file() {
return Err(type_error(format!("Unable to fetch \"{}\".", url)));
}

let fut = async move {
let response = {
match tokio::fs::read(&path).await {
Ok(body) => match http::Response::builder()
.status(http::StatusCode::OK)
.body(reqwest::Body::from(body))
{
Ok(response) => Ok(Response::from(response)),
Err(err) => Err(err.into()),
},
Err(err) => Err(err.into()),
}
};

Ok(response)
}
.boxed_local();
let file_fetch_handler = state.borrow_mut::<FH>();
file_fetch_handler.validate_url(&url)?;

let request_rid = state.resource_table.add(FetchRequestResource(fut));
let (request, maybe_request_body, maybe_cancel_handle) =
file_fetch_handler.fetch_url(url);
let request_rid = state.resource_table.add(FetchRequestResource(request));
let maybe_request_body_rid =
maybe_request_body.map(|r| state.resource_table.add(r));
let maybe_cancel_handle_rid = maybe_cancel_handle
.map(|ch| state.resource_table.add(FetchCancelHandle(ch)));

(request_rid, None, None)
(request_rid, maybe_request_body_rid, maybe_cancel_handle_rid)
}
"http" | "https" => {
let permissions = state.borrow_mut::<FP>();
Expand Down Expand Up @@ -443,7 +472,7 @@ impl Resource for FetchCancelHandle {
}
}

struct FetchRequestBodyResource {
pub struct FetchRequestBodyResource {
body: AsyncRefCell<mpsc::Sender<std::io::Result<Vec<u8>>>>,
cancel: CancelHandle,
}
Expand Down
4 changes: 2 additions & 2 deletions runtime/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,14 @@ mod not_docs {
deno_url::init(),
deno_tls::init(),
deno_web::init(deno_web::BlobStore::default(), Default::default()),
deno_fetch::init::<Permissions>(
deno_fetch::init::<Permissions, deno_fetch::DefaultFileFetchHandler>(
"".to_owned(),
None,
None,
None,
None,
None,
false, // No enable_file_fetch
deno_fetch::DefaultFileFetchHandler, // No enable_file_fetch
),
deno_websocket::init::<Permissions>("".to_owned(), None, None),
deno_webstorage::init(None),
Expand Down
4 changes: 2 additions & 2 deletions runtime/web_worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,14 +317,14 @@ impl WebWorker {
deno_console::init(),
deno_url::init(),
deno_web::init(options.blob_store.clone(), Some(main_module.clone())),
deno_fetch::init::<Permissions>(
deno_fetch::init::<Permissions, deno_fetch::FsFetchHandler>(
options.user_agent.clone(),
options.root_cert_store.clone(),
None,
None,
options.unsafely_ignore_certificate_errors.clone(),
None,
true, // enable_file_fetch
deno_fetch::FsFetchHandler,
),
deno_websocket::init::<Permissions>(
options.user_agent.clone(),
Expand Down
4 changes: 2 additions & 2 deletions runtime/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,14 @@ impl MainWorker {
options.blob_store.clone(),
options.bootstrap.location.clone(),
),
deno_fetch::init::<Permissions>(
deno_fetch::init::<Permissions, deno_fetch::FsFetchHandler>(
options.user_agent.clone(),
options.root_cert_store.clone(),
None,
None,
options.unsafely_ignore_certificate_errors.clone(),
None,
true, // enabled_file_fetch
deno_fetch::FsFetchHandler,
),
deno_websocket::init::<Permissions>(
options.user_agent.clone(),
Expand Down

0 comments on commit 4883f66

Please sign in to comment.