use std::{
io::{self, Cursor, Read},
sync::Arc,
};
use async_trait::async_trait;
use http::{Request, Response};
use crate::{
maybe_send_sync::{MaybeSend, MaybeSync},
Result,
};
mod reqwest;
mod ureq;
mod wasi;
pub mod restricted;
pub use http;
pub trait SyncHttpResolver: MaybeSend + MaybeSync {
fn http_resolve(
&self,
request: Request<Vec<u8>>,
) -> Result<Response<Box<dyn Read>>, HttpResolverError>;
}
impl<T: SyncHttpResolver + ?Sized> SyncHttpResolver for Arc<T> {
fn http_resolve(
&self,
request: Request<Vec<u8>>,
) -> Result<Response<Box<dyn Read>>, HttpResolverError> {
(**self).http_resolve(request)
}
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
pub trait AsyncHttpResolver: MaybeSend + MaybeSync {
async fn http_resolve_async(
&self,
request: Request<Vec<u8>>,
) -> Result<Response<Box<dyn Read>>, HttpResolverError>;
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl<T: AsyncHttpResolver + ?Sized> AsyncHttpResolver for Arc<T> {
async fn http_resolve_async(
&self,
request: Request<Vec<u8>>,
) -> Result<Response<Box<dyn Read>>, HttpResolverError> {
(**self).http_resolve_async(request).await
}
}
pub struct SyncGenericResolver {
inner: sync_resolver::Impl,
}
impl SyncGenericResolver {
pub fn new() -> Self {
Self {
inner: sync_resolver::new(),
}
}
pub fn with_redirects() -> Option<Self> {
sync_resolver::with_redirects().map(|inner| Self { inner })
}
}
impl Default for SyncGenericResolver {
fn default() -> Self {
Self::new()
}
}
impl SyncHttpResolver for SyncGenericResolver {
fn http_resolve(
&self,
request: Request<Vec<u8>>,
) -> Result<Response<Box<dyn Read>>, HttpResolverError> {
self.inner.http_resolve(request)
}
}
pub struct AsyncGenericResolver {
inner: async_resolver::Impl,
max_response_body_size: Option<u64>,
}
impl AsyncGenericResolver {
pub fn new() -> Self {
Self {
inner: async_resolver::new(),
max_response_body_size: None,
}
}
pub fn with_redirects() -> Option<Self> {
async_resolver::with_redirects().map(|inner| Self {
inner,
max_response_body_size: None,
})
}
pub fn with_max_response_body_size(mut self, limit: u64) -> Self {
self.max_response_body_size = Some(limit);
self
}
}
impl Default for AsyncGenericResolver {
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl AsyncHttpResolver for AsyncGenericResolver {
async fn http_resolve_async(
&self,
request: Request<Vec<u8>>,
) -> Result<Response<Box<dyn Read>>, HttpResolverError> {
let response = self.inner.http_resolve_async(request).await?;
if let Some(limit) = self.max_response_body_size {
let reported_len = response
.headers()
.get(http::header::CONTENT_LENGTH)
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse::<u64>().ok());
if let Some(len) = reported_len {
if len > limit {
return Err(HttpResolverError::ResponseTooLarge);
}
}
let (parts, body) = response.into_parts();
let mut buf = Vec::new();
body.take(limit + 1).read_to_end(&mut buf)?;
if buf.len() as u64 > limit {
return Err(HttpResolverError::ResponseTooLarge);
}
return Ok(http::Response::from_parts(
parts,
Box::new(Cursor::new(buf)) as Box<dyn Read>,
));
}
Ok(response)
}
}
#[derive(Debug, thiserror::Error)]
pub enum HttpResolverError {
#[error(transparent)]
Http(#[from] http::Error),
#[error(transparent)]
Io(#[from] io::Error),
#[error("the sync http resolver is not implemented")]
SyncHttpResolverNotImplemented,
#[error("the async http resolver is not implemented")]
AsyncHttpResolverNotImplemented,
#[error("remote URI \"{uri}\" is not permitted by the allowed list")]
UriDisallowed { uri: String },
#[error("response body exceeded maximum allowed size")]
ResponseTooLarge,
#[error("an error occurred from the underlying http resolver")]
Other(Box<dyn std::error::Error + Send + Sync>),
}
#[cfg(all(
not(target_arch = "wasm32"),
feature = "http_reqwest_blocking",
not(feature = "http_ureq")
))]
mod sync_resolver {
pub use crate::http::reqwest::sync_impl::{new, with_redirects, Impl};
}
#[cfg(all(not(target_arch = "wasm32"), feature = "http_ureq"))]
mod sync_resolver {
pub use crate::http::ureq::sync_impl::{new, with_redirects, Impl};
}
#[cfg(all(target_os = "wasi", feature = "http_wasi"))]
mod sync_resolver {
pub use crate::http::wasi::sync_impl::{new, with_redirects, Impl};
}
#[cfg(not(any(
all(target_os = "wasi", feature = "http_wasi"),
all(
not(target_arch = "wasm32"),
any(feature = "http_ureq", feature = "http_reqwest_blocking")
)
)))]
mod sync_resolver {
use super::*;
pub type Impl = SyncNoopResolver;
pub fn new() -> Impl {
SyncNoopResolver
}
pub fn with_redirects() -> Option<Impl> {
Some(SyncNoopResolver)
}
pub struct SyncNoopResolver;
impl SyncHttpResolver for SyncNoopResolver {
fn http_resolve(
&self,
_request: Request<Vec<u8>>,
) -> Result<Response<Box<dyn Read>>, HttpResolverError> {
Err(HttpResolverError::SyncHttpResolverNotImplemented)
}
}
}
#[cfg(all(not(target_os = "wasi"), feature = "http_reqwest"))]
mod async_resolver {
pub use crate::http::reqwest::async_impl::{new, with_redirects, Impl};
}
#[cfg(all(target_os = "wasi", feature = "http_wstd"))]
mod async_resolver {
pub use crate::http::wasi::async_impl::{new, with_redirects, Impl};
}
#[cfg(not(any(
feature = "http_reqwest",
all(target_os = "wasi", feature = "http_wstd")
)))]
mod async_resolver {
use super::*;
pub type Impl = AsyncNoopResolver;
pub fn new() -> Impl {
AsyncNoopResolver
}
pub fn with_redirects() -> Option<Impl> {
Some(AsyncNoopResolver)
}
pub struct AsyncNoopResolver;
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl AsyncHttpResolver for AsyncNoopResolver {
async fn http_resolve_async(
&self,
_request: Request<Vec<u8>>,
) -> Result<Response<Box<dyn Read>>, HttpResolverError> {
Err(HttpResolverError::AsyncHttpResolverNotImplemented)
}
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(test)]
pub mod tests {
#![allow(clippy::unwrap_used)]
use async_generic::async_generic;
use super::*;
fn mock_server<'a>(server: &'a httpmock::MockServer) -> httpmock::Mock<'a> {
server.mock(|when, then| {
when.method(httpmock::Method::GET);
then.status(200).body([1, 2, 3]);
})
}
fn redirect_mock_server<'a>(server: &'a httpmock::MockServer) -> httpmock::Mock<'a> {
server.mock(|when, then| {
when.method(httpmock::Method::GET).path("/redirect");
then.status(302).header("Location", "/").body([3, 2, 1]);
})
}
#[async_generic(async_signature(resolver: impl AsyncHttpResolver))]
pub fn assert_http_resolver(resolver: impl SyncHttpResolver) {
use httpmock::MockServer;
let server = MockServer::start();
let mock = mock_server(&server);
let request = Request::get(server.base_url()).body(vec![1, 2, 3]).unwrap();
let response = if _sync {
resolver.http_resolve(request).unwrap()
} else {
resolver.http_resolve_async(request).await.unwrap()
};
let mut response_body = Vec::new();
response
.into_body()
.read_to_end(&mut response_body)
.unwrap();
assert_eq!(&response_body, &[1, 2, 3]);
mock.assert();
}
#[async_generic(async_signature(resolver: impl AsyncHttpResolver))]
pub fn assert_http_resolver_with_redirects(resolver: impl SyncHttpResolver) {
use httpmock::MockServer;
let server = MockServer::start();
let redirect = redirect_mock_server(&server);
let target = mock_server(&server);
let request = Request::get(format!("{}/redirect", server.base_url()))
.body(vec![3, 2, 1])
.unwrap();
let response = if _sync {
resolver.http_resolve(request).unwrap()
} else {
resolver.http_resolve_async(request).await.unwrap()
};
assert_eq!(response.status(), 200);
redirect.assert_calls(1);
target.assert_calls(1);
}
}