From d3662e487d9ff94a09a2fa96598bf0a41666a7f2 Mon Sep 17 00:00:00 2001 From: Kitson Kelly Date: Mon, 1 Nov 2021 15:29:46 +1100 Subject: feat(ext/fetch): support fetching local files (#12545) Closes #11925 Closes #2150 Co-authored-by: Bert Belder --- ext/fetch/fs_fetch_handler.rs | 52 ++++++++++++++++++++++++++ ext/fetch/lib.rs | 87 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 ext/fetch/fs_fetch_handler.rs (limited to 'ext/fetch') diff --git a/ext/fetch/fs_fetch_handler.rs b/ext/fetch/fs_fetch_handler.rs new file mode 100644 index 000000000..82cbc4ecb --- /dev/null +++ b/ext/fetch/fs_fetch_handler.rs @@ -0,0 +1,52 @@ +// 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::futures::FutureExt; +use deno_core::futures::TryFutureExt; +use deno_core::url::Url; +use deno_core::CancelFuture; +use reqwest::StatusCode; +use std::rc::Rc; +use tokio_util::io::ReaderStream; + +/// 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_file( + &mut self, + url: Url, + ) -> ( + CancelableResponseFuture, + Option, + Option>, + ) { + let cancel_handle = CancelHandle::new_rc(); + let response_fut = async move { + let path = url.to_file_path()?; + let file = tokio::fs::File::open(path).map_err(|_| ()).await?; + let stream = ReaderStream::new(file); + let body = reqwest::Body::wrap_stream(stream); + let response = http::Response::builder() + .status(StatusCode::OK) + .body(body) + .map_err(|_| ())? + .into(); + Ok::<_, ()>(response) + } + .map_err(move |_| { + type_error("NetworkError when attempting to fetch resource.") + }) + .or_cancel(&cancel_handle) + .boxed_local(); + + (response_fut, None, Some(cancel_handle)) + } +} diff --git a/ext/fetch/lib.rs b/ext/fetch/lib.rs index 13adae1a7..b4bffb6de 100644 --- a/ext/fetch/lib.rs +++ b/ext/fetch/lib.rs @@ -1,5 +1,7 @@ // 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; @@ -52,14 +54,21 @@ use tokio_util::io::StreamReader; pub use data_url; pub use reqwest; -pub fn init( +pub use fs_fetch_handler::FsFetchHandler; + +pub fn init( user_agent: String, root_cert_store: Option, proxy: Option, request_builder_hook: Option RequestBuilder>, unsafely_ignore_certificate_errors: Option>, client_cert_chain_and_key: Option<(String, String)>, -) -> Extension { + file_fetch_handler: FH, +) -> Extension +where + FP: FetchPermissions + 'static, + FH: FetchHandler + 'static, +{ Extension::builder() .js(include_js_files!( prefix "deno:ext/fetch", @@ -73,13 +82,13 @@ pub fn init( "26_fetch.js", )) .ops(vec![ - ("op_fetch", op_sync(op_fetch::

)), + ("op_fetch", op_sync(op_fetch::)), ("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_fetch_custom_client", - op_sync(op_fetch_custom_client::

), + op_sync(op_fetch_custom_client::), ), ]) .state(move |state| { @@ -103,6 +112,7 @@ pub fn init( .clone(), client_cert_chain_and_key: client_cert_chain_and_key.clone(), }); + state.put::(file_fetch_handler.clone()); Ok(()) }) .build() @@ -117,6 +127,45 @@ pub struct HttpClientDefaults { pub client_cert_chain_and_key: Option<(String, String)>, } +pub type CancelableResponseFuture = + Pin>>; + +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_file( + &mut self, + url: Url, + ) -> ( + CancelableResponseFuture, + Option, + Option>, + ); +} + +/// A default implementation which will error for every request. +#[derive(Clone)] +pub struct DefaultFileFetchHandler; + +impl FetchHandler for DefaultFileFetchHandler { + fn fetch_file( + &mut self, + _url: Url, + ) -> ( + CancelableResponseFuture, + Option, + Option>, + ) { + let fut = async move { + Ok(Err(type_error( + "NetworkError when attempting to fetch resource.", + ))) + }; + (Box::pin(fut), None, None) + } +} + pub trait FetchPermissions { fn check_net_url(&mut self, _url: &Url) -> Result<(), AnyError>; fn check_read(&mut self, _p: &Path) -> Result<(), AnyError>; @@ -145,13 +194,14 @@ pub struct FetchReturn { cancel_handle_rid: Option, } -pub fn op_fetch( +pub fn op_fetch( state: &mut OpState, args: FetchArgs, data: Option, ) -> Result where FP: FetchPermissions + 'static, + FH: FetchHandler + 'static, { let client = if let Some(rid) = args.client_rid { let r = state.resource_table.get::(rid)?; @@ -167,6 +217,31 @@ where // Check scheme before asking for net permission let scheme = url.scheme(); let (request_rid, request_body_rid, cancel_handle_rid) = match scheme { + "file" => { + let path = url.to_file_path().map_err(|_| { + type_error("NetworkError when attempting to fetch resource.") + })?; + let permissions = state.borrow_mut::(); + permissions.check_read(&path)?; + + if method != Method::GET { + return Err(type_error(format!( + "Fetching files only supports the GET method. Received {}.", + method + ))); + } + + let file_fetch_handler = state.borrow_mut::(); + let (request, maybe_request_body, maybe_cancel_handle) = + file_fetch_handler.fetch_file(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, maybe_request_body_rid, maybe_cancel_handle_rid) + } "http" | "https" => { let permissions = state.borrow_mut::(); permissions.check_net_url(&url)?; @@ -400,7 +475,7 @@ impl Resource for FetchCancelHandle { } } -struct FetchRequestBodyResource { +pub struct FetchRequestBodyResource { body: AsyncRefCell>>>, cancel: CancelHandle, } -- cgit v1.2.3