chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

484
vendor/reqwest/src/async_impl/body.rs vendored Normal file
View File

@@ -0,0 +1,484 @@
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::task::{ready, Context, Poll};
use std::time::Duration;
use bytes::Bytes;
use http_body::Body as HttpBody;
use http_body_util::combinators::BoxBody;
use pin_project_lite::pin_project;
#[cfg(feature = "stream")]
use tokio::fs::File;
use tokio::time::Sleep;
#[cfg(feature = "stream")]
use tokio_util::io::ReaderStream;
/// An asynchronous request body.
pub struct Body {
inner: Inner,
}
enum Inner {
Reusable(Bytes),
Streaming(BoxBody<Bytes, Box<dyn std::error::Error + Send + Sync>>),
}
pin_project! {
/// A body with a total timeout.
///
/// The timeout does not reset upon each chunk, but rather requires the whole
/// body be streamed before the deadline is reached.
pub(crate) struct TotalTimeoutBody<B> {
#[pin]
inner: B,
timeout: Pin<Box<Sleep>>,
}
}
pin_project! {
pub(crate) struct ReadTimeoutBody<B> {
#[pin]
inner: B,
#[pin]
sleep: Option<Sleep>,
timeout: Duration,
}
}
impl Body {
/// Returns a reference to the internal data of the `Body`.
///
/// `None` is returned, if the underlying data is a stream.
pub fn as_bytes(&self) -> Option<&[u8]> {
match &self.inner {
Inner::Reusable(bytes) => Some(bytes.as_ref()),
Inner::Streaming(..) => None,
}
}
/// Wrap a futures `Stream` in a box inside `Body`.
///
/// # Example
///
/// ```
/// # use reqwest::Body;
/// # use futures_util;
/// # fn main() {
/// let chunks: Vec<Result<_, ::std::io::Error>> = vec![
/// Ok("hello"),
/// Ok(" "),
/// Ok("world"),
/// ];
///
/// let stream = futures_util::stream::iter(chunks);
///
/// let body = Body::wrap_stream(stream);
/// # }
/// ```
///
/// # Optional
///
/// This requires the `stream` feature to be enabled.
#[cfg(feature = "stream")]
#[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
pub fn wrap_stream<S>(stream: S) -> Body
where
S: futures_core::stream::TryStream + Send + 'static,
S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
Bytes: From<S::Ok>,
{
Body::stream(stream)
}
#[cfg(any(feature = "stream", feature = "multipart", feature = "blocking"))]
pub(crate) fn stream<S>(stream: S) -> Body
where
S: futures_core::stream::TryStream + Send + 'static,
S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
Bytes: From<S::Ok>,
{
use futures_util::TryStreamExt;
use http_body::Frame;
use http_body_util::StreamBody;
let body = http_body_util::BodyExt::boxed(StreamBody::new(sync_wrapper::SyncStream::new(
stream
.map_ok(|d| Frame::data(Bytes::from(d)))
.map_err(Into::into),
)));
Body {
inner: Inner::Streaming(body),
}
}
pub(crate) fn empty() -> Body {
Body::reusable(Bytes::new())
}
pub(crate) fn reusable(chunk: Bytes) -> Body {
Body {
inner: Inner::Reusable(chunk),
}
}
/// Wrap a [`HttpBody`] in a box inside `Body`.
///
/// # Example
///
/// ```
/// # use reqwest::Body;
/// # use futures_util;
/// # fn main() {
/// let content = "hello,world!".to_string();
///
/// let body = Body::wrap(content);
/// # }
/// ```
pub fn wrap<B>(inner: B) -> Body
where
B: HttpBody + Send + Sync + 'static,
B::Data: Into<Bytes>,
B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
use http_body_util::BodyExt;
let boxed = IntoBytesBody { inner }.map_err(Into::into).boxed();
Body {
inner: Inner::Streaming(boxed),
}
}
pub(crate) fn try_clone(&self) -> Option<Body> {
match self.inner {
Inner::Reusable(ref chunk) => Some(Body::reusable(chunk.clone())),
Inner::Streaming { .. } => None,
}
}
#[cfg(feature = "multipart")]
pub(crate) fn content_length(&self) -> Option<u64> {
match self.inner {
Inner::Reusable(ref bytes) => Some(bytes.len() as u64),
Inner::Streaming(ref body) => body.size_hint().exact(),
}
}
}
impl Default for Body {
#[inline]
fn default() -> Body {
Body::empty()
}
}
/*
impl From<hyper::Body> for Body {
#[inline]
fn from(body: hyper::Body) -> Body {
Self {
inner: Inner::Streaming {
body: Box::pin(WrapHyper(body)),
},
}
}
}
*/
impl From<Bytes> for Body {
#[inline]
fn from(bytes: Bytes) -> Body {
Body::reusable(bytes)
}
}
impl From<Vec<u8>> for Body {
#[inline]
fn from(vec: Vec<u8>) -> Body {
Body::reusable(vec.into())
}
}
impl From<&'static [u8]> for Body {
#[inline]
fn from(s: &'static [u8]) -> Body {
Body::reusable(Bytes::from_static(s))
}
}
impl From<String> for Body {
#[inline]
fn from(s: String) -> Body {
Body::reusable(s.into())
}
}
impl From<&'static str> for Body {
#[inline]
fn from(s: &'static str) -> Body {
s.as_bytes().into()
}
}
#[cfg(feature = "stream")]
#[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
impl From<File> for Body {
#[inline]
fn from(file: File) -> Body {
Body::wrap_stream(ReaderStream::new(file))
}
}
impl fmt::Debug for Body {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Body").finish()
}
}
impl HttpBody for Body {
type Data = Bytes;
type Error = crate::Error;
fn poll_frame(
mut self: Pin<&mut Self>,
cx: &mut Context,
) -> Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
match self.inner {
Inner::Reusable(ref mut bytes) => {
let out = bytes.split_off(0);
if out.is_empty() {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(hyper::body::Frame::data(out))))
}
}
Inner::Streaming(ref mut body) => Poll::Ready(
ready!(Pin::new(body).poll_frame(cx))
.map(|opt_chunk| opt_chunk.map_err(crate::error::body)),
),
}
}
fn size_hint(&self) -> http_body::SizeHint {
match self.inner {
Inner::Reusable(ref bytes) => http_body::SizeHint::with_exact(bytes.len() as u64),
Inner::Streaming(ref body) => body.size_hint(),
}
}
fn is_end_stream(&self) -> bool {
match self.inner {
Inner::Reusable(ref bytes) => bytes.is_empty(),
Inner::Streaming(ref body) => body.is_end_stream(),
}
}
}
// ===== impl TotalTimeoutBody =====
pub(crate) fn total_timeout<B>(body: B, timeout: Pin<Box<Sleep>>) -> TotalTimeoutBody<B> {
TotalTimeoutBody {
inner: body,
timeout,
}
}
pub(crate) fn with_read_timeout<B>(body: B, timeout: Duration) -> ReadTimeoutBody<B> {
ReadTimeoutBody {
inner: body,
sleep: None,
timeout,
}
}
impl<B> hyper::body::Body for TotalTimeoutBody<B>
where
B: hyper::body::Body,
B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
type Data = B::Data;
type Error = crate::Error;
fn poll_frame(
self: Pin<&mut Self>,
cx: &mut Context,
) -> Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
let this = self.project();
if let Poll::Ready(()) = this.timeout.as_mut().poll(cx) {
return Poll::Ready(Some(Err(crate::error::body(crate::error::TimedOut))));
}
Poll::Ready(
ready!(this.inner.poll_frame(cx))
.map(|opt_chunk| opt_chunk.map_err(crate::error::body)),
)
}
#[inline]
fn size_hint(&self) -> http_body::SizeHint {
self.inner.size_hint()
}
#[inline]
fn is_end_stream(&self) -> bool {
self.inner.is_end_stream()
}
}
impl<B> hyper::body::Body for ReadTimeoutBody<B>
where
B: hyper::body::Body,
B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
type Data = B::Data;
type Error = crate::Error;
fn poll_frame(
self: Pin<&mut Self>,
cx: &mut Context,
) -> Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
let mut this = self.project();
// Start the `Sleep` if not active.
let sleep_pinned = if let Some(some) = this.sleep.as_mut().as_pin_mut() {
some
} else {
this.sleep.set(Some(tokio::time::sleep(*this.timeout)));
this.sleep.as_mut().as_pin_mut().unwrap()
};
// Error if the timeout has expired.
if let Poll::Ready(()) = sleep_pinned.poll(cx) {
return Poll::Ready(Some(Err(crate::error::body(crate::error::TimedOut))));
}
let item = ready!(this.inner.poll_frame(cx))
.map(|opt_chunk| opt_chunk.map_err(crate::error::body));
// a ready frame means timeout is reset
this.sleep.set(None);
Poll::Ready(item)
}
#[inline]
fn size_hint(&self) -> http_body::SizeHint {
self.inner.size_hint()
}
#[inline]
fn is_end_stream(&self) -> bool {
self.inner.is_end_stream()
}
}
pub(crate) type ResponseBody =
http_body_util::combinators::BoxBody<Bytes, Box<dyn std::error::Error + Send + Sync>>;
pub(crate) fn boxed<B>(body: B) -> ResponseBody
where
B: hyper::body::Body<Data = Bytes> + Send + Sync + 'static,
B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
use http_body_util::BodyExt;
body.map_err(box_err).boxed()
}
pub(crate) fn response<B>(
body: B,
deadline: Option<Pin<Box<Sleep>>>,
read_timeout: Option<Duration>,
) -> ResponseBody
where
B: hyper::body::Body<Data = Bytes> + Send + Sync + 'static,
B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
use http_body_util::BodyExt;
match (deadline, read_timeout) {
(Some(total), Some(read)) => {
let body = with_read_timeout(body, read).map_err(box_err);
total_timeout(body, total).map_err(box_err).boxed()
}
(Some(total), None) => total_timeout(body, total).map_err(box_err).boxed(),
(None, Some(read)) => with_read_timeout(body, read).map_err(box_err).boxed(),
(None, None) => body.map_err(box_err).boxed(),
}
}
fn box_err<E>(err: E) -> Box<dyn std::error::Error + Send + Sync>
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
err.into()
}
// ===== impl IntoBytesBody =====
pin_project! {
struct IntoBytesBody<B> {
#[pin]
inner: B,
}
}
// We can't use `map_frame()` because that loses the hint data (for good reason).
// But we aren't transforming the data.
impl<B> hyper::body::Body for IntoBytesBody<B>
where
B: hyper::body::Body,
B::Data: Into<Bytes>,
{
type Data = Bytes;
type Error = B::Error;
fn poll_frame(
self: Pin<&mut Self>,
cx: &mut Context,
) -> Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
match ready!(self.project().inner.poll_frame(cx)) {
Some(Ok(f)) => Poll::Ready(Some(Ok(f.map_data(Into::into)))),
Some(Err(e)) => Poll::Ready(Some(Err(e))),
None => Poll::Ready(None),
}
}
#[inline]
fn size_hint(&self) -> http_body::SizeHint {
self.inner.size_hint()
}
#[inline]
fn is_end_stream(&self) -> bool {
self.inner.is_end_stream()
}
}
#[cfg(test)]
mod tests {
use http_body::Body as _;
use super::Body;
#[test]
fn test_as_bytes() {
let test_data = b"Test body";
let body = Body::from(&test_data[..]);
assert_eq!(body.as_bytes(), Some(&test_data[..]));
}
#[test]
fn body_exact_length() {
let empty_body = Body::empty();
assert!(empty_body.is_end_stream());
assert_eq!(empty_body.size_hint().exact(), Some(0));
let bytes_body = Body::reusable("abc".into());
assert!(!bytes_body.is_end_stream());
assert_eq!(bytes_body.size_hint().exact(), Some(3));
// can delegate even when wrapped
let stream_body = Body::wrap(empty_body);
assert!(stream_body.is_end_stream());
assert_eq!(stream_body.size_hint().exact(), Some(0));
}
}

3144
vendor/reqwest/src/async_impl/client.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,144 @@
use crate::async_impl::h3_client::dns::resolve;
use crate::dns::DynResolver;
use crate::error::BoxError;
use bytes::Bytes;
use h3::client::SendRequest;
use h3_quinn::{Connection, OpenStreams};
use http::Uri;
use hyper_util::client::legacy::connect::dns::Name;
use quinn::crypto::rustls::QuicClientConfig;
use quinn::{ClientConfig, Endpoint, TransportConfig};
use std::net::{IpAddr, SocketAddr};
use std::str::FromStr;
use std::sync::Arc;
type H3Connection = (
h3::client::Connection<Connection, Bytes>,
SendRequest<OpenStreams, Bytes>,
);
/// H3 Client Config
#[derive(Clone)]
pub(crate) struct H3ClientConfig {
/// Set the maximum HTTP/3 header size this client is willing to accept.
///
/// See [header size constraints] section of the specification for details.
///
/// [header size constraints]: https://www.rfc-editor.org/rfc/rfc9114.html#name-header-size-constraints
///
/// Please see docs in [`Builder`] in [`h3`].
///
/// [`Builder`]: https://docs.rs/h3/latest/h3/client/struct.Builder.html#method.max_field_section_size
pub(crate) max_field_section_size: Option<u64>,
/// Enable whether to send HTTP/3 protocol grease on the connections.
///
/// Just like in HTTP/2, HTTP/3 also uses the concept of "grease"
///
/// to prevent potential interoperability issues in the future.
/// In HTTP/3, the concept of grease is used to ensure that the protocol can evolve
/// and accommodate future changes without breaking existing implementations.
///
/// Please see docs in [`Builder`] in [`h3`].
///
/// [`Builder`]: https://docs.rs/h3/latest/h3/client/struct.Builder.html#method.send_grease
pub(crate) send_grease: Option<bool>,
}
impl Default for H3ClientConfig {
fn default() -> Self {
Self {
max_field_section_size: None,
send_grease: None,
}
}
}
#[derive(Clone)]
pub(crate) struct H3Connector {
resolver: DynResolver,
endpoint: Endpoint,
client_config: H3ClientConfig,
}
impl H3Connector {
pub fn new(
resolver: DynResolver,
tls: rustls::ClientConfig,
local_addr: Option<IpAddr>,
transport_config: TransportConfig,
client_config: H3ClientConfig,
) -> Result<H3Connector, BoxError> {
let quic_client_config = Arc::new(QuicClientConfig::try_from(tls)?);
let mut config = ClientConfig::new(quic_client_config);
// FIXME: Replace this when there is a setter.
config.transport_config(Arc::new(transport_config));
let socket_addr = match local_addr {
Some(ip) => SocketAddr::new(ip, 0),
None => "[::]:0".parse::<SocketAddr>().unwrap(),
};
let mut endpoint = Endpoint::client(socket_addr)?;
endpoint.set_default_client_config(config);
Ok(Self {
resolver,
endpoint,
client_config,
})
}
pub async fn connect(&mut self, dest: Uri) -> Result<H3Connection, BoxError> {
let host = dest
.host()
.ok_or("destination must have a host")?
.trim_start_matches('[')
.trim_end_matches(']');
let port = dest.port_u16().unwrap_or(443);
let addrs = if let Some(addr) = IpAddr::from_str(host).ok() {
// If the host is already an IP address, skip resolving.
vec![SocketAddr::new(addr, port)]
} else {
let addrs = resolve(&mut self.resolver, Name::from_str(host)?).await?;
let addrs = addrs.map(|mut addr| {
addr.set_port(port);
addr
});
addrs.collect()
};
self.remote_connect(addrs, host).await
}
async fn remote_connect(
&mut self,
addrs: Vec<SocketAddr>,
server_name: &str,
) -> Result<H3Connection, BoxError> {
let mut err = None;
for addr in addrs {
match self.endpoint.connect(addr, server_name)?.await {
Ok(new_conn) => {
let quinn_conn = Connection::new(new_conn);
let mut h3_client_builder = h3::client::builder();
if let Some(max_field_section_size) = self.client_config.max_field_section_size
{
h3_client_builder.max_field_section_size(max_field_section_size);
}
if let Some(send_grease) = self.client_config.send_grease {
h3_client_builder.send_grease(send_grease);
}
return Ok(h3_client_builder.build(quinn_conn).await?);
}
Err(e) => err = Some(e),
}
}
match err {
Some(e) => Err(Box::new(e) as BoxError),
None => Err("failed to establish connection for HTTP/3 request".into()),
}
}
}

View File

@@ -0,0 +1,43 @@
use core::task;
use hyper_util::client::legacy::connect::dns::Name;
use std::future::Future;
use std::net::SocketAddr;
use std::task::Poll;
use tower_service::Service;
// Trait from hyper to implement DNS resolution for HTTP/3 client.
pub trait Resolve {
type Addrs: Iterator<Item = SocketAddr>;
type Error: Into<Box<dyn std::error::Error + Send + Sync>>;
type Future: Future<Output = Result<Self::Addrs, Self::Error>>;
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>>;
fn resolve(&mut self, name: Name) -> Self::Future;
}
impl<S> Resolve for S
where
S: Service<Name>,
S::Response: Iterator<Item = SocketAddr>,
S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
type Addrs = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
Service::poll_ready(self, cx)
}
fn resolve(&mut self, name: Name) -> Self::Future {
Service::call(self, name)
}
}
pub(super) async fn resolve<R>(resolver: &mut R, name: Name) -> Result<R::Addrs, R::Error>
where
R: Resolve,
{
std::future::poll_fn(|cx| resolver.poll_ready(cx)).await?;
resolver.resolve(name).await
}

View File

@@ -0,0 +1,116 @@
#![cfg(feature = "http3")]
pub(crate) mod connect;
pub(crate) mod dns;
mod pool;
use crate::async_impl::body::ResponseBody;
use crate::async_impl::h3_client::pool::{Key, Pool, PoolClient};
use crate::error::{BoxError, Error, Kind};
use crate::{error, Body};
use connect::H3Connector;
use http::{Request, Response};
use log::trace;
use std::future::{self, Future};
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
use sync_wrapper::SyncWrapper;
use tower::Service;
#[derive(Clone)]
pub(crate) struct H3Client {
pool: Pool,
connector: H3Connector,
}
impl H3Client {
pub fn new(connector: H3Connector, pool_timeout: Option<Duration>) -> Self {
H3Client {
pool: Pool::new(pool_timeout),
connector,
}
}
async fn get_pooled_client(&mut self, key: Key) -> Result<PoolClient, BoxError> {
if let Some(client) = self.pool.try_pool(&key) {
trace!("getting client from pool with key {key:?}");
return Ok(client);
}
trace!("did not find connection {key:?} in pool so connecting...");
let dest = pool::domain_as_uri(key.clone());
let lock = match self.pool.connecting(&key) {
pool::Connecting::InProgress(waiter) => {
trace!("connecting to {key:?} is already in progress, subscribing...");
match waiter.receive().await {
Some(client) => return Ok(client),
None => return Err("failed to establish connection for HTTP/3 request".into()),
}
}
pool::Connecting::Acquired(lock) => lock,
};
trace!("connecting to {key:?}...");
let (driver, tx) = self.connector.connect(dest).await?;
trace!("saving new pooled connection to {key:?}");
Ok(self.pool.new_connection(lock, driver, tx))
}
async fn send_request(
mut self,
key: Key,
req: Request<Body>,
) -> Result<Response<ResponseBody>, Error> {
let mut pooled = match self.get_pooled_client(key).await {
Ok(client) => client,
Err(e) => return Err(error::request(e)),
};
pooled
.send_request(req)
.await
.map_err(|e| Error::new(Kind::Request, Some(e)))
}
pub fn request(&self, mut req: Request<Body>) -> H3ResponseFuture {
let pool_key = match pool::extract_domain(req.uri_mut()) {
Ok(s) => s,
Err(e) => {
return H3ResponseFuture {
inner: SyncWrapper::new(Box::pin(future::ready(Err(e)))),
}
}
};
H3ResponseFuture {
inner: SyncWrapper::new(Box::pin(self.clone().send_request(pool_key, req))),
}
}
}
impl Service<Request<Body>> for H3Client {
type Response = Response<ResponseBody>;
type Error = Error;
type Future = H3ResponseFuture;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
self.request(req)
}
}
pub(crate) struct H3ResponseFuture {
inner: SyncWrapper<Pin<Box<dyn Future<Output = Result<Response<ResponseBody>, Error>> + Send>>>,
}
impl Future for H3ResponseFuture {
type Output = Result<Response<ResponseBody>, Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.inner.get_mut().as_mut().poll(cx)
}
}

View File

@@ -0,0 +1,372 @@
use bytes::Bytes;
use std::collections::HashMap;
use std::future;
use std::pin::Pin;
use std::sync::mpsc::{Receiver, TryRecvError};
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use std::time::Duration;
use tokio::sync::{oneshot, watch};
use tokio::time::Instant;
use crate::async_impl::body::ResponseBody;
use crate::error::{BoxError, Error, Kind};
use crate::Body;
use bytes::Buf;
use h3::client::SendRequest;
use h3_quinn::{Connection, OpenStreams};
use http::uri::{Authority, Scheme};
use http::{Request, Response, Uri};
use log::{error, trace};
pub(super) type Key = (Scheme, Authority);
#[derive(Clone)]
pub struct Pool {
inner: Arc<Mutex<PoolInner>>,
}
struct ConnectingLockInner {
key: Key,
pool: Arc<Mutex<PoolInner>>,
}
/// A lock that ensures only one HTTP/3 connection is established per host at a
/// time. The lock is automatically released when dropped.
pub struct ConnectingLock(Option<ConnectingLockInner>);
/// A waiter that allows subscribers to receive updates when a new connection is
/// established or when the connection attempt fails. For example, when
/// connection lock is dropped due to an error.
pub struct ConnectingWaiter {
receiver: watch::Receiver<Option<PoolClient>>,
}
pub enum Connecting {
/// A connection attempt is already in progress.
/// You must subscribe to updates instead of initiating a new connection.
InProgress(ConnectingWaiter),
/// The connection lock has been acquired, allowing you to initiate a
/// new connection.
Acquired(ConnectingLock),
}
impl ConnectingLock {
fn new(key: Key, pool: Arc<Mutex<PoolInner>>) -> Self {
Self(Some(ConnectingLockInner { key, pool }))
}
/// Forget the lock and return corresponding Key
fn forget(mut self) -> Key {
// Unwrap is safe because the Option can be None only after dropping the
// lock
self.0.take().unwrap().key
}
}
impl Drop for ConnectingLock {
fn drop(&mut self) {
if let Some(ConnectingLockInner { key, pool }) = self.0.take() {
let mut pool = pool.lock().unwrap();
pool.connecting.remove(&key);
trace!("HTTP/3 connecting lock for {:?} is dropped", key);
}
}
}
impl ConnectingWaiter {
pub async fn receive(mut self) -> Option<PoolClient> {
match self.receiver.wait_for(Option::is_some).await {
// unwrap because we already checked that option is Some
Ok(ok) => Some(ok.as_ref().unwrap().to_owned()),
Err(_) => None,
}
}
}
impl Pool {
pub fn new(timeout: Option<Duration>) -> Self {
Self {
inner: Arc::new(Mutex::new(PoolInner {
connecting: HashMap::new(),
idle_conns: HashMap::new(),
timeout,
})),
}
}
/// Acquire a connecting lock. This is to ensure that we have only one HTTP3
/// connection per host.
pub fn connecting(&self, key: &Key) -> Connecting {
let mut inner = self.inner.lock().unwrap();
if let Some(sender) = inner.connecting.get(key) {
Connecting::InProgress(ConnectingWaiter {
receiver: sender.subscribe(),
})
} else {
let (tx, _) = watch::channel(None);
inner.connecting.insert(key.clone(), tx);
Connecting::Acquired(ConnectingLock::new(key.clone(), Arc::clone(&self.inner)))
}
}
pub fn try_pool(&self, key: &Key) -> Option<PoolClient> {
let mut inner = self.inner.lock().unwrap();
let timeout = inner.timeout;
if let Some(conn) = inner.idle_conns.get(&key) {
// We check first if the connection still valid
// and if not, we remove it from the pool.
if conn.is_invalid() {
trace!("pooled HTTP/3 connection is invalid so removing it...");
inner.idle_conns.remove(&key);
return None;
}
if let Some(duration) = timeout {
if Instant::now().saturating_duration_since(conn.idle_timeout) > duration {
trace!("pooled connection expired");
return None;
}
}
}
inner
.idle_conns
.get_mut(&key)
.and_then(|conn| Some(conn.pool()))
}
pub fn new_connection(
&mut self,
lock: ConnectingLock,
mut driver: h3::client::Connection<Connection, Bytes>,
tx: SendRequest<OpenStreams, Bytes>,
) -> PoolClient {
let (close_tx, close_rx) = std::sync::mpsc::channel();
tokio::spawn(async move {
let e = future::poll_fn(|cx| driver.poll_close(cx)).await;
trace!("poll_close returned error {e:?}");
close_tx.send(e).ok();
});
let mut inner = self.inner.lock().unwrap();
// We clean up "connecting" here so we don't have to acquire the lock again.
let key = lock.forget();
let Some(notifier) = inner.connecting.remove(&key) else {
unreachable!("there should be one connecting lock at a time");
};
let client = PoolClient::new(tx);
// Send the client to all our awaiters
let pool_client = if let Err(watch::error::SendError(Some(unsent_client))) =
notifier.send(Some(client.clone()))
{
// If there are no awaiters, the client is returned to us. As a
// micro optimisation, let's reuse it and avoid cloning.
unsent_client
} else {
client.clone()
};
let conn = PoolConnection::new(pool_client, close_rx);
inner.insert(key, conn);
client
}
}
struct PoolInner {
connecting: HashMap<Key, watch::Sender<Option<PoolClient>>>,
idle_conns: HashMap<Key, PoolConnection>,
timeout: Option<Duration>,
}
impl PoolInner {
fn insert(&mut self, key: Key, conn: PoolConnection) {
if self.idle_conns.contains_key(&key) {
trace!("connection already exists for key {key:?}");
}
self.idle_conns.insert(key, conn);
}
}
#[derive(Clone)]
pub struct PoolClient {
inner: SendRequest<OpenStreams, Bytes>,
}
impl PoolClient {
pub fn new(tx: SendRequest<OpenStreams, Bytes>) -> Self {
Self { inner: tx }
}
pub async fn send_request(
&mut self,
req: Request<Body>,
) -> Result<Response<ResponseBody>, BoxError> {
use hyper::body::Body as _;
let (head, mut req_body) = req.into_parts();
let mut req = Request::from_parts(head, ());
if let Some(n) = req_body.size_hint().exact() {
if n > 0 {
req.headers_mut()
.insert(http::header::CONTENT_LENGTH, n.into());
}
}
let (mut send, mut recv) = self.inner.send_request(req).await?.split();
let (tx, mut rx) = oneshot::channel::<Result<(), BoxError>>();
tokio::spawn(async move {
let mut req_body = Pin::new(&mut req_body);
loop {
match std::future::poll_fn(|cx| req_body.as_mut().poll_frame(cx)).await {
Some(Ok(frame)) => {
if let Ok(b) = frame.into_data() {
if let Err(e) = send.send_data(Bytes::copy_from_slice(&b)).await {
if let Err(e) = tx.send(Err(e.into())) {
error!("Failed to communicate send.send_data() error: {e:?}");
}
return;
}
}
}
Some(Err(e)) => {
if let Err(e) = tx.send(Err(e.into())) {
error!("Failed to communicate req_body read error: {e:?}");
}
return;
}
None => break,
}
}
if let Err(e) = send.finish().await {
if let Err(e) = tx.send(Err(e.into())) {
error!("Failed to communicate send.finish read error: {e:?}");
}
return;
}
let _ = tx.send(Ok(()));
});
tokio::select! {
Ok(Err(e)) = &mut rx => Err(e),
resp = recv.recv_response() => {
let resp = resp?;
let resp_body = crate::async_impl::body::boxed(Incoming::new(recv, resp.headers(), rx));
Ok(resp.map(|_| resp_body))
}
}
}
}
pub struct PoolConnection {
// This receives errors from polling h3 driver.
close_rx: Receiver<h3::error::ConnectionError>,
client: PoolClient,
idle_timeout: Instant,
}
impl PoolConnection {
pub fn new(client: PoolClient, close_rx: Receiver<h3::error::ConnectionError>) -> Self {
Self {
close_rx,
client,
idle_timeout: Instant::now(),
}
}
pub fn pool(&mut self) -> PoolClient {
self.idle_timeout = Instant::now();
self.client.clone()
}
pub fn is_invalid(&self) -> bool {
match self.close_rx.try_recv() {
Err(TryRecvError::Empty) => false,
Err(TryRecvError::Disconnected) => true,
Ok(_) => true,
}
}
}
struct Incoming<S, B> {
inner: h3::client::RequestStream<S, B>,
content_length: Option<u64>,
send_rx: oneshot::Receiver<Result<(), BoxError>>,
}
impl<S, B> Incoming<S, B> {
fn new(
stream: h3::client::RequestStream<S, B>,
headers: &http::header::HeaderMap,
send_rx: oneshot::Receiver<Result<(), BoxError>>,
) -> Self {
Self {
inner: stream,
content_length: headers
.get(http::header::CONTENT_LENGTH)
.and_then(|h| h.to_str().ok())
.and_then(|v| v.parse().ok()),
send_rx,
}
}
}
impl<S, B> http_body::Body for Incoming<S, B>
where
S: h3::quic::RecvStream,
{
type Data = Bytes;
type Error = crate::error::Error;
fn poll_frame(
mut self: Pin<&mut Self>,
cx: &mut Context,
) -> Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
if let Ok(Err(e)) = self.send_rx.try_recv() {
return Poll::Ready(Some(Err(crate::error::body(e))));
}
match futures_core::ready!(self.inner.poll_recv_data(cx)) {
Ok(Some(mut b)) => Poll::Ready(Some(Ok(hyper::body::Frame::data(
b.copy_to_bytes(b.remaining()),
)))),
Ok(None) => Poll::Ready(None),
Err(e) => Poll::Ready(Some(Err(crate::error::body(e)))),
}
}
fn size_hint(&self) -> hyper::body::SizeHint {
if let Some(content_length) = self.content_length {
hyper::body::SizeHint::with_exact(content_length)
} else {
hyper::body::SizeHint::default()
}
}
}
pub(crate) fn extract_domain(uri: &mut Uri) -> Result<Key, Error> {
let uri_clone = uri.clone();
match (uri_clone.scheme(), uri_clone.authority()) {
(Some(scheme), Some(auth)) => Ok((scheme.clone(), auth.clone())),
_ => Err(Error::new(Kind::Request, None::<Error>)),
}
}
pub(crate) fn domain_as_uri((scheme, auth): Key) -> Uri {
http::uri::Builder::new()
.scheme(scheme)
.authority(auth)
.path_and_query("/")
.build()
.expect("domain is valid Uri")
}

14
vendor/reqwest/src/async_impl/mod.rs vendored Normal file
View File

@@ -0,0 +1,14 @@
pub use self::body::Body;
pub use self::client::{Client, ClientBuilder};
pub use self::request::{Request, RequestBuilder};
pub use self::response::Response;
pub use self::upgrade::Upgraded;
pub mod body;
pub mod client;
pub mod h3_client;
#[cfg(feature = "multipart")]
pub mod multipart;
pub(crate) mod request;
mod response;
mod upgrade;

View File

@@ -0,0 +1,754 @@
//! multipart/form-data
use std::borrow::Cow;
use std::fmt;
use std::pin::Pin;
#[cfg(feature = "stream")]
use std::io;
#[cfg(feature = "stream")]
use std::path::Path;
use bytes::Bytes;
use mime_guess::Mime;
use percent_encoding::{self, AsciiSet, NON_ALPHANUMERIC};
#[cfg(feature = "stream")]
use tokio::fs::File;
use futures_core::Stream;
use futures_util::{future, stream, StreamExt};
use http_body_util::BodyExt;
use super::Body;
use crate::header::HeaderMap;
/// An async multipart/form-data request.
pub struct Form {
inner: FormParts<Part>,
}
/// A field in a multipart form.
pub struct Part {
meta: PartMetadata,
value: Body,
body_length: Option<u64>,
}
pub(crate) struct FormParts<P> {
pub(crate) boundary: String,
pub(crate) computed_headers: Vec<Vec<u8>>,
pub(crate) fields: Vec<(Cow<'static, str>, P)>,
pub(crate) percent_encoding: PercentEncoding,
}
pub(crate) struct PartMetadata {
mime: Option<Mime>,
file_name: Option<Cow<'static, str>>,
pub(crate) headers: HeaderMap,
}
pub(crate) trait PartProps {
fn value_len(&self) -> Option<u64>;
fn metadata(&self) -> &PartMetadata;
}
// ===== impl Form =====
impl Default for Form {
fn default() -> Self {
Self::new()
}
}
impl Form {
/// Creates a new async Form without any content.
pub fn new() -> Form {
Form {
inner: FormParts::new(),
}
}
/// Get the boundary that this form will use.
#[inline]
pub fn boundary(&self) -> &str {
self.inner.boundary()
}
/// Add a data field with supplied name and value.
///
/// # Examples
///
/// ```
/// let form = reqwest::multipart::Form::new()
/// .text("username", "seanmonstar")
/// .text("password", "secret");
/// ```
pub fn text<T, U>(self, name: T, value: U) -> Form
where
T: Into<Cow<'static, str>>,
U: Into<Cow<'static, str>>,
{
self.part(name, Part::text(value))
}
/// Adds a file field.
///
/// The path will be used to try to guess the filename and mime.
///
/// # Examples
///
/// ```no_run
/// # async fn run() -> std::io::Result<()> {
/// let form = reqwest::multipart::Form::new()
/// .file("key", "/path/to/file").await?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
///
/// Errors when the file cannot be opened.
#[cfg(feature = "stream")]
#[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
pub async fn file<T, U>(self, name: T, path: U) -> io::Result<Form>
where
T: Into<Cow<'static, str>>,
U: AsRef<Path>,
{
Ok(self.part(name, Part::file(path).await?))
}
/// Adds a customized Part.
pub fn part<T>(self, name: T, part: Part) -> Form
where
T: Into<Cow<'static, str>>,
{
self.with_inner(move |inner| inner.part(name, part))
}
/// Configure this `Form` to percent-encode using the `path-segment` rules.
pub fn percent_encode_path_segment(self) -> Form {
self.with_inner(|inner| inner.percent_encode_path_segment())
}
/// Configure this `Form` to percent-encode using the `attr-char` rules.
pub fn percent_encode_attr_chars(self) -> Form {
self.with_inner(|inner| inner.percent_encode_attr_chars())
}
/// Configure this `Form` to skip percent-encoding
pub fn percent_encode_noop(self) -> Form {
self.with_inner(|inner| inner.percent_encode_noop())
}
/// Consume this instance and transform into an instance of Body for use in a request.
pub(crate) fn stream(self) -> Body {
if self.inner.fields.is_empty() {
return Body::empty();
}
Body::stream(self.into_stream())
}
/// Produce a stream of the bytes in this `Form`, consuming it.
pub fn into_stream(mut self) -> impl Stream<Item = Result<Bytes, crate::Error>> + Send + Sync {
if self.inner.fields.is_empty() {
let empty_stream: Pin<
Box<dyn Stream<Item = Result<Bytes, crate::Error>> + Send + Sync>,
> = Box::pin(futures_util::stream::empty());
return empty_stream;
}
// create initial part to init reduce chain
let (name, part) = self.inner.fields.remove(0);
let start = Box::pin(self.part_stream(name, part))
as Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>;
let fields = self.inner.take_fields();
// for each field, chain an additional stream
let stream = fields.into_iter().fold(start, |memo, (name, part)| {
let part_stream = self.part_stream(name, part);
Box::pin(memo.chain(part_stream))
as Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>
});
// append special ending boundary
let last = stream::once(future::ready(Ok(
format!("--{}--\r\n", self.boundary()).into()
)));
Box::pin(stream.chain(last))
}
/// Generate a hyper::Body stream for a single Part instance of a Form request.
pub(crate) fn part_stream<T>(
&mut self,
name: T,
part: Part,
) -> impl Stream<Item = Result<Bytes, crate::Error>>
where
T: Into<Cow<'static, str>>,
{
// start with boundary
let boundary = stream::once(future::ready(Ok(
format!("--{}\r\n", self.boundary()).into()
)));
// append headers
let header = stream::once(future::ready(Ok({
let mut h = self
.inner
.percent_encoding
.encode_headers(&name.into(), &part.meta);
h.extend_from_slice(b"\r\n\r\n");
h.into()
})));
// then append form data followed by terminating CRLF
boundary
.chain(header)
.chain(part.value.into_data_stream())
.chain(stream::once(future::ready(Ok("\r\n".into()))))
}
pub(crate) fn compute_length(&mut self) -> Option<u64> {
self.inner.compute_length()
}
fn with_inner<F>(self, func: F) -> Self
where
F: FnOnce(FormParts<Part>) -> FormParts<Part>,
{
Form {
inner: func(self.inner),
}
}
}
impl fmt::Debug for Form {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt_fields("Form", f)
}
}
// ===== impl Part =====
impl Part {
/// Makes a text parameter.
pub fn text<T>(value: T) -> Part
where
T: Into<Cow<'static, str>>,
{
let body = match value.into() {
Cow::Borrowed(slice) => Body::from(slice),
Cow::Owned(string) => Body::from(string),
};
Part::new(body, None)
}
/// Makes a new parameter from arbitrary bytes.
pub fn bytes<T>(value: T) -> Part
where
T: Into<Cow<'static, [u8]>>,
{
let body = match value.into() {
Cow::Borrowed(slice) => Body::from(slice),
Cow::Owned(vec) => Body::from(vec),
};
Part::new(body, None)
}
/// Makes a new parameter from an arbitrary stream.
pub fn stream<T: Into<Body>>(value: T) -> Part {
Part::new(value.into(), None)
}
/// Makes a new parameter from an arbitrary stream with a known length. This is particularly
/// useful when adding something like file contents as a stream, where you can know the content
/// length beforehand.
pub fn stream_with_length<T: Into<Body>>(value: T, length: u64) -> Part {
Part::new(value.into(), Some(length))
}
/// Makes a file parameter.
///
/// # Errors
///
/// Errors when the file cannot be opened.
#[cfg(feature = "stream")]
#[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
pub async fn file<T: AsRef<Path>>(path: T) -> io::Result<Part> {
let path = path.as_ref();
let file_name = path
.file_name()
.map(|filename| filename.to_string_lossy().into_owned());
let ext = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
let mime = mime_guess::from_ext(ext).first_or_octet_stream();
let file = File::open(path).await?;
let len = file.metadata().await.map(|m| m.len()).ok();
let field = match len {
Some(len) => Part::stream_with_length(file, len),
None => Part::stream(file),
}
.mime(mime);
Ok(if let Some(file_name) = file_name {
field.file_name(file_name)
} else {
field
})
}
fn new(value: Body, body_length: Option<u64>) -> Part {
Part {
meta: PartMetadata::new(),
value,
body_length,
}
}
/// Tries to set the mime of this part.
pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
}
// Re-export when mime 0.4 is available, with split MediaType/MediaRange.
fn mime(self, mime: Mime) -> Part {
self.with_inner(move |inner| inner.mime(mime))
}
/// Sets the filename, builder style.
pub fn file_name<T>(self, filename: T) -> Part
where
T: Into<Cow<'static, str>>,
{
self.with_inner(move |inner| inner.file_name(filename))
}
/// Sets custom headers for the part.
pub fn headers(self, headers: HeaderMap) -> Part {
self.with_inner(move |inner| inner.headers(headers))
}
fn with_inner<F>(self, func: F) -> Self
where
F: FnOnce(PartMetadata) -> PartMetadata,
{
Part {
meta: func(self.meta),
..self
}
}
}
impl fmt::Debug for Part {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut dbg = f.debug_struct("Part");
dbg.field("value", &self.value);
self.meta.fmt_fields(&mut dbg);
dbg.finish()
}
}
impl PartProps for Part {
fn value_len(&self) -> Option<u64> {
if self.body_length.is_some() {
self.body_length
} else {
self.value.content_length()
}
}
fn metadata(&self) -> &PartMetadata {
&self.meta
}
}
// ===== impl FormParts =====
impl<P: PartProps> FormParts<P> {
pub(crate) fn new() -> Self {
FormParts {
boundary: gen_boundary(),
computed_headers: Vec::new(),
fields: Vec::new(),
percent_encoding: PercentEncoding::PathSegment,
}
}
pub(crate) fn boundary(&self) -> &str {
&self.boundary
}
/// Adds a customized Part.
pub(crate) fn part<T>(mut self, name: T, part: P) -> Self
where
T: Into<Cow<'static, str>>,
{
self.fields.push((name.into(), part));
self
}
/// Configure this `Form` to percent-encode using the `path-segment` rules.
pub(crate) fn percent_encode_path_segment(mut self) -> Self {
self.percent_encoding = PercentEncoding::PathSegment;
self
}
/// Configure this `Form` to percent-encode using the `attr-char` rules.
pub(crate) fn percent_encode_attr_chars(mut self) -> Self {
self.percent_encoding = PercentEncoding::AttrChar;
self
}
/// Configure this `Form` to skip percent-encoding
pub(crate) fn percent_encode_noop(mut self) -> Self {
self.percent_encoding = PercentEncoding::NoOp;
self
}
// If predictable, computes the length the request will have
// The length should be predictable if only String and file fields have been added,
// but not if a generic reader has been added;
pub(crate) fn compute_length(&mut self) -> Option<u64> {
let mut length = 0u64;
for &(ref name, ref field) in self.fields.iter() {
match field.value_len() {
Some(value_length) => {
// We are constructing the header just to get its length. To not have to
// construct it again when the request is sent we cache these headers.
let header = self.percent_encoding.encode_headers(name, field.metadata());
let header_length = header.len();
self.computed_headers.push(header);
// The additions mimic the format string out of which the field is constructed
// in Reader. Not the cleanest solution because if that format string is
// ever changed then this formula needs to be changed too which is not an
// obvious dependency in the code.
length += 2
+ self.boundary().len() as u64
+ 2
+ header_length as u64
+ 4
+ value_length
+ 2
}
_ => return None,
}
}
// If there is at least one field there is a special boundary for the very last field.
if !self.fields.is_empty() {
length += 2 + self.boundary().len() as u64 + 4
}
Some(length)
}
/// Take the fields vector of this instance, replacing with an empty vector.
fn take_fields(&mut self) -> Vec<(Cow<'static, str>, P)> {
std::mem::replace(&mut self.fields, Vec::new())
}
}
impl<P: fmt::Debug> FormParts<P> {
pub(crate) fn fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct(ty_name)
.field("boundary", &self.boundary)
.field("parts", &self.fields)
.finish()
}
}
// ===== impl PartMetadata =====
impl PartMetadata {
pub(crate) fn new() -> Self {
PartMetadata {
mime: None,
file_name: None,
headers: HeaderMap::default(),
}
}
pub(crate) fn mime(mut self, mime: Mime) -> Self {
self.mime = Some(mime);
self
}
pub(crate) fn file_name<T>(mut self, filename: T) -> Self
where
T: Into<Cow<'static, str>>,
{
self.file_name = Some(filename.into());
self
}
pub(crate) fn headers<T>(mut self, headers: T) -> Self
where
T: Into<HeaderMap>,
{
self.headers = headers.into();
self
}
}
impl PartMetadata {
pub(crate) fn fmt_fields<'f, 'fa, 'fb>(
&self,
debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>,
) -> &'f mut fmt::DebugStruct<'fa, 'fb> {
debug_struct
.field("mime", &self.mime)
.field("file_name", &self.file_name)
.field("headers", &self.headers)
}
}
// https://url.spec.whatwg.org/#fragment-percent-encode-set
const FRAGMENT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS
.add(b' ')
.add(b'"')
.add(b'<')
.add(b'>')
.add(b'`');
// https://url.spec.whatwg.org/#path-percent-encode-set
const PATH_ENCODE_SET: &AsciiSet = &FRAGMENT_ENCODE_SET.add(b'#').add(b'?').add(b'{').add(b'}');
const PATH_SEGMENT_ENCODE_SET: &AsciiSet = &PATH_ENCODE_SET.add(b'/').add(b'%');
// https://tools.ietf.org/html/rfc8187#section-3.2.1
const ATTR_CHAR_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
.remove(b'!')
.remove(b'#')
.remove(b'$')
.remove(b'&')
.remove(b'+')
.remove(b'-')
.remove(b'.')
.remove(b'^')
.remove(b'_')
.remove(b'`')
.remove(b'|')
.remove(b'~');
pub(crate) enum PercentEncoding {
PathSegment,
AttrChar,
NoOp,
}
impl PercentEncoding {
pub(crate) fn encode_headers(&self, name: &str, field: &PartMetadata) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(b"Content-Disposition: form-data; ");
match self.percent_encode(name) {
Cow::Borrowed(value) => {
// nothing has been percent encoded
buf.extend_from_slice(b"name=\"");
buf.extend_from_slice(value.as_bytes());
buf.extend_from_slice(b"\"");
}
Cow::Owned(value) => {
// something has been percent encoded
buf.extend_from_slice(b"name*=utf-8''");
buf.extend_from_slice(value.as_bytes());
}
}
// According to RFC7578 Section 4.2, `filename*=` syntax is invalid.
// See https://github.com/seanmonstar/reqwest/issues/419.
if let Some(filename) = &field.file_name {
buf.extend_from_slice(b"; filename=\"");
let legal_filename = filename
.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\r', "\\\r")
.replace('\n', "\\\n");
buf.extend_from_slice(legal_filename.as_bytes());
buf.extend_from_slice(b"\"");
}
if let Some(mime) = &field.mime {
buf.extend_from_slice(b"\r\nContent-Type: ");
buf.extend_from_slice(mime.as_ref().as_bytes());
}
for (k, v) in field.headers.iter() {
buf.extend_from_slice(b"\r\n");
buf.extend_from_slice(k.as_str().as_bytes());
buf.extend_from_slice(b": ");
buf.extend_from_slice(v.as_bytes());
}
buf
}
fn percent_encode<'a>(&self, value: &'a str) -> Cow<'a, str> {
use percent_encoding::utf8_percent_encode as percent_encode;
match self {
Self::PathSegment => percent_encode(value, PATH_SEGMENT_ENCODE_SET).into(),
Self::AttrChar => percent_encode(value, ATTR_CHAR_ENCODE_SET).into(),
Self::NoOp => value.into(),
}
}
}
fn gen_boundary() -> String {
use crate::util::fast_random as random;
let a = random();
let b = random();
let c = random();
let d = random();
format!("{a:016x}-{b:016x}-{c:016x}-{d:016x}")
}
#[cfg(test)]
mod tests {
use super::*;
use futures_util::stream;
use futures_util::TryStreamExt;
use std::future;
use tokio::{self, runtime};
#[test]
fn form_empty() {
let form = Form::new();
let rt = runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("new rt");
let body = form.stream().into_data_stream();
let s = body.map_ok(|try_c| try_c.to_vec()).try_concat();
let out = rt.block_on(s);
assert!(out.unwrap().is_empty());
}
#[test]
fn stream_to_end() {
let mut form = Form::new()
.part(
"reader1",
Part::stream(Body::stream(stream::once(future::ready::<
Result<String, crate::Error>,
>(Ok(
"part1".to_owned()
))))),
)
.part("key1", Part::text("value1"))
.part(
"key2",
Part::text("value2").mime(mime_guess::mime::IMAGE_BMP),
)
.part(
"reader2",
Part::stream(Body::stream(stream::once(future::ready::<
Result<String, crate::Error>,
>(Ok(
"part2".to_owned()
))))),
)
.part("key3", Part::text("value3").file_name("filename"));
form.inner.boundary = "boundary".to_string();
let expected = "--boundary\r\n\
Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
part1\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
value1\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key2\"\r\n\
Content-Type: image/bmp\r\n\r\n\
value2\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
part2\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
value3\r\n--boundary--\r\n";
let rt = runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("new rt");
let body = form.stream().into_data_stream();
let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
let out = rt.block_on(s).unwrap();
// These prints are for debug purposes in case the test fails
println!(
"START REAL\n{}\nEND REAL",
std::str::from_utf8(&out).unwrap()
);
println!("START EXPECTED\n{expected}\nEND EXPECTED");
assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
}
#[test]
fn stream_to_end_with_header() {
let mut part = Part::text("value2").mime(mime_guess::mime::IMAGE_BMP);
let mut headers = HeaderMap::new();
headers.insert("Hdr3", "/a/b/c".parse().unwrap());
part = part.headers(headers);
let mut form = Form::new().part("key2", part);
form.inner.boundary = "boundary".to_string();
let expected = "--boundary\r\n\
Content-Disposition: form-data; name=\"key2\"\r\n\
Content-Type: image/bmp\r\n\
hdr3: /a/b/c\r\n\
\r\n\
value2\r\n\
--boundary--\r\n";
let rt = runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("new rt");
let body = form.stream().into_data_stream();
let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
let out = rt.block_on(s).unwrap();
// These prints are for debug purposes in case the test fails
println!(
"START REAL\n{}\nEND REAL",
std::str::from_utf8(&out).unwrap()
);
println!("START EXPECTED\n{expected}\nEND EXPECTED");
assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
}
#[test]
fn correct_content_length() {
// Setup an arbitrary data stream
let stream_data = b"just some stream data";
let stream_len = stream_data.len();
let stream_data = stream_data
.chunks(3)
.map(|c| Ok::<_, std::io::Error>(Bytes::from(c)));
let the_stream = futures_util::stream::iter(stream_data);
let bytes_data = b"some bytes data".to_vec();
let bytes_len = bytes_data.len();
let stream_part = Part::stream_with_length(Body::stream(the_stream), stream_len as u64);
let body_part = Part::bytes(bytes_data);
// A simple check to make sure we get the configured body length
assert_eq!(stream_part.value_len().unwrap(), stream_len as u64);
// Make sure it delegates to the underlying body if length is not specified
assert_eq!(body_part.value_len().unwrap(), bytes_len as u64);
}
#[test]
fn header_percent_encoding() {
let name = "start%'\"\r\nßend";
let field = Part::text("");
assert_eq!(
PercentEncoding::PathSegment.encode_headers(name, &field.meta),
&b"Content-Disposition: form-data; name*=utf-8''start%25'%22%0D%0A%C3%9Fend"[..]
);
assert_eq!(
PercentEncoding::AttrChar.encode_headers(name, &field.meta),
&b"Content-Disposition: form-data; name*=utf-8''start%25%27%22%0D%0A%C3%9Fend"[..]
);
}
}

1143
vendor/reqwest/src/async_impl/request.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,505 @@
use std::fmt;
use std::net::SocketAddr;
use std::pin::Pin;
use std::time::Duration;
use bytes::Bytes;
use http_body_util::BodyExt;
use hyper::{HeaderMap, StatusCode, Version};
use hyper_util::client::legacy::connect::HttpInfo;
#[cfg(feature = "json")]
use serde::de::DeserializeOwned;
#[cfg(feature = "json")]
use serde_json;
use tokio::time::Sleep;
use url::Url;
use super::body::Body;
use crate::async_impl::body::ResponseBody;
#[cfg(feature = "cookies")]
use crate::cookie;
#[cfg(feature = "charset")]
use encoding_rs::{Encoding, UTF_8};
#[cfg(feature = "charset")]
use mime::Mime;
/// A Response to a submitted `Request`.
pub struct Response {
pub(super) res: hyper::Response<ResponseBody>,
// Boxed to save space (11 words to 1 word), and it's not accessed
// frequently internally.
url: Box<Url>,
}
impl Response {
pub(super) fn new(
res: hyper::Response<ResponseBody>,
url: Url,
total_timeout: Option<Pin<Box<Sleep>>>,
read_timeout: Option<Duration>,
) -> Response {
let (parts, body) = res.into_parts();
let res = hyper::Response::from_parts(
parts,
super::body::response(body, total_timeout, read_timeout),
);
Response {
res,
url: Box::new(url),
}
}
/// Get the `StatusCode` of this `Response`.
#[inline]
pub fn status(&self) -> StatusCode {
self.res.status()
}
/// Get the HTTP `Version` of this `Response`.
#[inline]
pub fn version(&self) -> Version {
self.res.version()
}
/// Get the `Headers` of this `Response`.
#[inline]
pub fn headers(&self) -> &HeaderMap {
self.res.headers()
}
/// Get a mutable reference to the `Headers` of this `Response`.
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
self.res.headers_mut()
}
/// Get the content length of the response, if it is known.
///
/// This value does not directly represents the value of the `Content-Length`
/// header, but rather the size of the response's body. To read the header's
/// value, please use the [`Response::headers`] method instead.
///
/// Reasons it may not be known:
///
/// - The response does not include a body (e.g. it responds to a `HEAD`
/// request).
/// - The response is gzipped and automatically decoded (thus changing the
/// actual decoded length).
pub fn content_length(&self) -> Option<u64> {
use hyper::body::Body;
Body::size_hint(self.res.body()).exact()
}
/// Retrieve the cookies contained in the response.
///
/// Note that invalid 'Set-Cookie' headers will be ignored.
///
/// # Optional
///
/// This requires the optional `cookies` feature to be enabled.
#[cfg(feature = "cookies")]
#[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a {
cookie::extract_response_cookies(self.res.headers()).filter_map(Result::ok)
}
/// Get the final `Url` of this `Response`.
#[inline]
pub fn url(&self) -> &Url {
&self.url
}
/// Get the remote address used to get this `Response`.
pub fn remote_addr(&self) -> Option<SocketAddr> {
self.res
.extensions()
.get::<HttpInfo>()
.map(|info| info.remote_addr())
}
/// Returns a reference to the associated extensions.
pub fn extensions(&self) -> &http::Extensions {
self.res.extensions()
}
/// Returns a mutable reference to the associated extensions.
pub fn extensions_mut(&mut self) -> &mut http::Extensions {
self.res.extensions_mut()
}
// body methods
/// Get the full response text.
///
/// This method decodes the response body with BOM sniffing
/// and with malformed sequences replaced with the
/// [`char::REPLACEMENT_CHARACTER`].
/// Encoding is determined from the `charset` parameter of `Content-Type` header,
/// and defaults to `utf-8` if not presented.
///
/// Note that the BOM is stripped from the returned String.
///
/// # Note
///
/// If the `charset` feature is disabled the method will only attempt to decode the
/// response as UTF-8, regardless of the given `Content-Type`
///
/// # Example
///
/// ```
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let content = reqwest::get("http://httpbin.org/range/26")
/// .await?
/// .text()
/// .await?;
///
/// println!("text: {content:?}");
/// # Ok(())
/// # }
/// ```
pub async fn text(self) -> crate::Result<String> {
#[cfg(feature = "charset")]
{
self.text_with_charset("utf-8").await
}
#[cfg(not(feature = "charset"))]
{
let full = self.bytes().await?;
let text = String::from_utf8_lossy(&full);
Ok(text.into_owned())
}
}
/// Get the full response text given a specific encoding.
///
/// This method decodes the response body with BOM sniffing
/// and with malformed sequences replaced with the [`char::REPLACEMENT_CHARACTER`].
/// You can provide a default encoding for decoding the raw message, while the
/// `charset` parameter of `Content-Type` header is still prioritized. For more information
/// about the possible encoding name, please go to [`encoding_rs`] docs.
///
/// Note that the BOM is stripped from the returned String.
///
/// [`encoding_rs`]: https://docs.rs/encoding_rs/0.8/encoding_rs/#relationship-with-windows-code-pages
///
/// # Optional
///
/// This requires the optional `encoding_rs` feature enabled.
///
/// # Example
///
/// ```
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let content = reqwest::get("http://httpbin.org/range/26")
/// .await?
/// .text_with_charset("utf-8")
/// .await?;
///
/// println!("text: {content:?}");
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "charset")]
#[cfg_attr(docsrs, doc(cfg(feature = "charset")))]
pub async fn text_with_charset(self, default_encoding: &str) -> crate::Result<String> {
let content_type = self
.headers()
.get(crate::header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.and_then(|value| value.parse::<Mime>().ok());
let encoding_name = content_type
.as_ref()
.and_then(|mime| mime.get_param("charset").map(|charset| charset.as_str()))
.unwrap_or(default_encoding);
let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8);
let full = self.bytes().await?;
let (text, _, _) = encoding.decode(&full);
Ok(text.into_owned())
}
/// Try to deserialize the response body as JSON.
///
/// # Optional
///
/// This requires the optional `json` feature enabled.
///
/// # Examples
///
/// ```
/// # extern crate reqwest;
/// # extern crate serde;
/// #
/// # use reqwest::Error;
/// # use serde::Deserialize;
/// #
/// // This `derive` requires the `serde` dependency.
/// #[derive(Deserialize)]
/// struct Ip {
/// origin: String,
/// }
///
/// # async fn run() -> Result<(), Error> {
/// let ip = reqwest::get("http://httpbin.org/ip")
/// .await?
/// .json::<Ip>()
/// .await?;
///
/// println!("ip: {}", ip.origin);
/// # Ok(())
/// # }
/// #
/// # fn main() { }
/// ```
///
/// # Errors
///
/// This method fails whenever the response body is not in JSON format,
/// or it cannot be properly deserialized to target type `T`. For more
/// details please see [`serde_json::from_reader`].
///
/// [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
pub async fn json<T: DeserializeOwned>(self) -> crate::Result<T> {
let full = self.bytes().await?;
serde_json::from_slice(&full).map_err(crate::error::decode)
}
/// Get the full response body as `Bytes`.
///
/// # Example
///
/// ```
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let bytes = reqwest::get("http://httpbin.org/ip")
/// .await?
/// .bytes()
/// .await?;
///
/// println!("bytes: {bytes:?}");
/// # Ok(())
/// # }
/// ```
pub async fn bytes(self) -> crate::Result<Bytes> {
use http_body_util::BodyExt;
BodyExt::collect(self.res.into_body())
.await
.map(|buf| buf.to_bytes())
.map_err(crate::error::decode)
}
/// Stream a chunk of the response body.
///
/// When the response body has been exhausted, this will return `None`.
///
/// # Example
///
/// ```
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let mut res = reqwest::get("https://hyper.rs").await?;
///
/// while let Some(chunk) = res.chunk().await? {
/// println!("Chunk: {chunk:?}");
/// }
/// # Ok(())
/// # }
/// ```
pub async fn chunk(&mut self) -> crate::Result<Option<Bytes>> {
use http_body_util::BodyExt;
// loop to ignore unrecognized frames
loop {
if let Some(res) = self.res.body_mut().frame().await {
let frame = res.map_err(crate::error::decode)?;
if let Ok(buf) = frame.into_data() {
return Ok(Some(buf));
}
// else continue
} else {
return Ok(None);
}
}
}
/// Convert the response into a `Stream` of `Bytes` from the body.
///
/// # Example
///
/// ```
/// use futures_util::StreamExt;
///
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let mut stream = reqwest::get("http://httpbin.org/ip")
/// .await?
/// .bytes_stream();
///
/// while let Some(item) = stream.next().await {
/// println!("Chunk: {:?}", item?);
/// }
/// # Ok(())
/// # }
/// ```
///
/// # Optional
///
/// This requires the optional `stream` feature to be enabled.
#[cfg(feature = "stream")]
#[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
pub fn bytes_stream(self) -> impl futures_core::Stream<Item = crate::Result<Bytes>> {
http_body_util::BodyDataStream::new(self.res.into_body().map_err(crate::error::decode))
}
// util methods
/// Turn a response into an error if the server returned an error.
///
/// # Example
///
/// ```
/// # use reqwest::Response;
/// fn on_response(res: Response) {
/// match res.error_for_status() {
/// Ok(_res) => (),
/// Err(err) => {
/// // asserting a 400 as an example
/// // it could be any status between 400...599
/// assert_eq!(
/// err.status(),
/// Some(reqwest::StatusCode::BAD_REQUEST)
/// );
/// }
/// }
/// }
/// # fn main() {}
/// ```
pub fn error_for_status(self) -> crate::Result<Self> {
let status = self.status();
let reason = self.extensions().get::<hyper::ext::ReasonPhrase>().cloned();
if status.is_client_error() || status.is_server_error() {
Err(crate::error::status_code(*self.url, status, reason))
} else {
Ok(self)
}
}
/// Turn a reference to a response into an error if the server returned an error.
///
/// # Example
///
/// ```
/// # use reqwest::Response;
/// fn on_response(res: &Response) {
/// match res.error_for_status_ref() {
/// Ok(_res) => (),
/// Err(err) => {
/// // asserting a 400 as an example
/// // it could be any status between 400...599
/// assert_eq!(
/// err.status(),
/// Some(reqwest::StatusCode::BAD_REQUEST)
/// );
/// }
/// }
/// }
/// # fn main() {}
/// ```
pub fn error_for_status_ref(&self) -> crate::Result<&Self> {
let status = self.status();
let reason = self.extensions().get::<hyper::ext::ReasonPhrase>().cloned();
if status.is_client_error() || status.is_server_error() {
Err(crate::error::status_code(*self.url.clone(), status, reason))
} else {
Ok(self)
}
}
// private
// The Response's body is an implementation detail.
// You no longer need to get a reference to it, there are async methods
// on the `Response` itself.
//
// This method is just used by the blocking API.
#[cfg(feature = "blocking")]
pub(crate) fn body_mut(&mut self) -> &mut ResponseBody {
self.res.body_mut()
}
}
impl fmt::Debug for Response {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Response")
.field("url", &self.url().as_str())
.field("status", &self.status())
.field("headers", self.headers())
.finish()
}
}
/// A `Response` can be piped as the `Body` of another request.
impl From<Response> for Body {
fn from(r: Response) -> Body {
Body::wrap(r.res.into_body())
}
}
// I'm not sure this conversion is that useful... People should be encouraged
// to use `http::Response`, not `reqwest::Response`.
impl<T: Into<Body>> From<http::Response<T>> for Response {
fn from(r: http::Response<T>) -> Response {
use crate::response::ResponseUrl;
let (mut parts, body) = r.into_parts();
let body: crate::async_impl::body::Body = body.into();
let url = parts
.extensions
.remove::<ResponseUrl>()
.unwrap_or_else(|| ResponseUrl(Url::parse("http://no.url.provided.local").unwrap()));
let url = url.0;
let res = hyper::Response::from_parts(parts, ResponseBody::new(body.map_err(Into::into)));
Response {
res,
url: Box::new(url),
}
}
}
/// A `Response` can be converted into a `http::Response`.
// It's supposed to be the inverse of the conversion above.
impl From<Response> for http::Response<Body> {
fn from(r: Response) -> http::Response<Body> {
let (parts, body) = r.res.into_parts();
let body = Body::wrap(body);
http::Response::from_parts(parts, body)
}
}
#[cfg(test)]
mod tests {
use super::Response;
use crate::ResponseBuilderExt;
use http::response::Builder;
use url::Url;
#[test]
fn test_from_http_response() {
let url = Url::parse("http://example.com").unwrap();
let response = Builder::new()
.status(200)
.url(url.clone())
.body("foo")
.unwrap();
let response = Response::from(response);
assert_eq!(response.status(), 200);
assert_eq!(*response.url(), url);
}
}

View File

@@ -0,0 +1,75 @@
use std::pin::Pin;
use std::task::{self, Poll};
use std::{fmt, io};
use hyper_util::rt::TokioIo;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
/// An upgraded HTTP connection.
pub struct Upgraded {
inner: TokioIo<hyper::upgrade::Upgraded>,
}
impl AsyncRead for Upgraded {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut task::Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
Pin::new(&mut self.inner).poll_read(cx, buf)
}
}
impl AsyncWrite for Upgraded {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut task::Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
Pin::new(&mut self.inner).poll_write(cx, buf)
}
fn poll_write_vectored(
mut self: Pin<&mut Self>,
cx: &mut task::Context<'_>,
bufs: &[io::IoSlice<'_>],
) -> Poll<io::Result<usize>> {
Pin::new(&mut self.inner).poll_write_vectored(cx, bufs)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.inner).poll_flush(cx)
}
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.inner).poll_shutdown(cx)
}
fn is_write_vectored(&self) -> bool {
self.inner.is_write_vectored()
}
}
impl fmt::Debug for Upgraded {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Upgraded").finish()
}
}
impl From<hyper::upgrade::Upgraded> for Upgraded {
fn from(inner: hyper::upgrade::Upgraded) -> Self {
Upgraded {
inner: TokioIo::new(inner),
}
}
}
impl super::response::Response {
/// Consumes the response and returns a future for a possible HTTP upgrade.
pub async fn upgrade(self) -> crate::Result<Upgraded> {
hyper::upgrade::on(self.res)
.await
.map(Upgraded::from)
.map_err(crate::error::upgrade)
}
}

372
vendor/reqwest/src/blocking/body.rs vendored Normal file
View File

@@ -0,0 +1,372 @@
use std::fmt;
use std::fs::File;
use std::future::Future;
#[cfg(feature = "multipart")]
use std::io::Cursor;
use std::io::{self, Read};
use std::mem::{self, MaybeUninit};
use std::ptr;
use bytes::Bytes;
use futures_channel::mpsc;
use crate::async_impl;
/// The body of a `Request`.
///
/// In most cases, this is not needed directly, as the
/// [`RequestBuilder.body`][builder] method uses `Into<Body>`, which allows
/// passing many things (like a string or vector of bytes).
///
/// [builder]: ./struct.RequestBuilder.html#method.body
#[derive(Debug)]
pub struct Body {
kind: Kind,
}
impl Body {
/// Instantiate a `Body` from a reader.
///
/// # Note
///
/// While allowing for many types to be used, these bodies do not have
/// a way to reset to the beginning and be reused. This means that when
/// encountering a 307 or 308 status code, instead of repeating the
/// request at the new location, the `Response` will be returned with
/// the redirect status code set.
///
/// ```rust
/// # use std::fs::File;
/// # use reqwest::blocking::Body;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let file = File::open("national_secrets.txt")?;
/// let body = Body::new(file);
/// # Ok(())
/// # }
/// ```
///
/// If you have a set of bytes, like `String` or `Vec<u8>`, using the
/// `From` implementations for `Body` will store the data in a manner
/// it can be reused.
///
/// ```rust
/// # use reqwest::blocking::Body;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let s = "A stringy body";
/// let body = Body::from(s);
/// # Ok(())
/// # }
/// ```
pub fn new<R: Read + Send + 'static>(reader: R) -> Body {
Body {
kind: Kind::Reader(Box::from(reader), None),
}
}
/// Create a `Body` from a `Read` where the size is known in advance
/// but the data should not be fully loaded into memory. This will
/// set the `Content-Length` header and stream from the `Read`.
///
/// ```rust
/// # use std::fs::File;
/// # use reqwest::blocking::Body;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let file = File::open("a_large_file.txt")?;
/// let file_size = file.metadata()?.len();
/// let body = Body::sized(file, file_size);
/// # Ok(())
/// # }
/// ```
pub fn sized<R: Read + Send + 'static>(reader: R, len: u64) -> Body {
Body {
kind: Kind::Reader(Box::from(reader), Some(len)),
}
}
/// Returns the body as a byte slice if the body is already buffered in
/// memory. For streamed requests this method returns `None`.
pub fn as_bytes(&self) -> Option<&[u8]> {
match self.kind {
Kind::Reader(_, _) => None,
Kind::Bytes(ref bytes) => Some(bytes.as_ref()),
}
}
/// Converts streamed requests to their buffered equivalent and
/// returns a reference to the buffer. If the request is already
/// buffered, this has no effect.
///
/// Be aware that for large requests this method is expensive
/// and may cause your program to run out of memory.
pub fn buffer(&mut self) -> Result<&[u8], crate::Error> {
match self.kind {
Kind::Reader(ref mut reader, maybe_len) => {
let mut bytes = if let Some(len) = maybe_len {
Vec::with_capacity(len as usize)
} else {
Vec::new()
};
io::copy(reader, &mut bytes).map_err(crate::error::builder)?;
self.kind = Kind::Bytes(bytes.into());
self.buffer()
}
Kind::Bytes(ref bytes) => Ok(bytes.as_ref()),
}
}
#[cfg(feature = "multipart")]
pub(crate) fn len(&self) -> Option<u64> {
match self.kind {
Kind::Reader(_, len) => len,
Kind::Bytes(ref bytes) => Some(bytes.len() as u64),
}
}
#[cfg(feature = "multipart")]
pub(crate) fn into_reader(self) -> Reader {
match self.kind {
Kind::Reader(r, _) => Reader::Reader(r),
Kind::Bytes(b) => Reader::Bytes(Cursor::new(b)),
}
}
pub(crate) fn into_async(self) -> (Option<Sender>, async_impl::Body, Option<u64>) {
match self.kind {
Kind::Reader(read, len) => {
let (tx, rx) = mpsc::channel(0);
let tx = Sender {
body: (read, len),
tx,
};
(Some(tx), async_impl::Body::stream(rx), len)
}
Kind::Bytes(chunk) => {
let len = chunk.len() as u64;
(None, async_impl::Body::reusable(chunk), Some(len))
}
}
}
pub(crate) fn try_clone(&self) -> Option<Body> {
self.kind.try_clone().map(|kind| Body { kind })
}
}
enum Kind {
Reader(Box<dyn Read + Send>, Option<u64>),
Bytes(Bytes),
}
impl Kind {
fn try_clone(&self) -> Option<Kind> {
match self {
Kind::Reader(..) => None,
Kind::Bytes(v) => Some(Kind::Bytes(v.clone())),
}
}
}
impl From<Vec<u8>> for Body {
#[inline]
fn from(v: Vec<u8>) -> Body {
Body {
kind: Kind::Bytes(v.into()),
}
}
}
impl From<String> for Body {
#[inline]
fn from(s: String) -> Body {
s.into_bytes().into()
}
}
impl From<&'static [u8]> for Body {
#[inline]
fn from(s: &'static [u8]) -> Body {
Body {
kind: Kind::Bytes(Bytes::from_static(s)),
}
}
}
impl From<&'static str> for Body {
#[inline]
fn from(s: &'static str) -> Body {
s.as_bytes().into()
}
}
impl From<File> for Body {
#[inline]
fn from(f: File) -> Body {
let len = f.metadata().map(|m| m.len()).ok();
Body {
kind: Kind::Reader(Box::new(f), len),
}
}
}
impl From<Bytes> for Body {
#[inline]
fn from(b: Bytes) -> Body {
Body {
kind: Kind::Bytes(b),
}
}
}
impl fmt::Debug for Kind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Kind::Reader(_, ref v) => f
.debug_struct("Reader")
.field("length", &DebugLength(v))
.finish(),
Kind::Bytes(ref v) => fmt::Debug::fmt(v, f),
}
}
}
struct DebugLength<'a>(&'a Option<u64>);
impl<'a> fmt::Debug for DebugLength<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self.0 {
Some(ref len) => fmt::Debug::fmt(len, f),
None => f.write_str("Unknown"),
}
}
}
#[cfg(feature = "multipart")]
pub(crate) enum Reader {
Reader(Box<dyn Read + Send>),
Bytes(Cursor<Bytes>),
}
#[cfg(feature = "multipart")]
impl Read for Reader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match *self {
Reader::Reader(ref mut rdr) => rdr.read(buf),
Reader::Bytes(ref mut rdr) => rdr.read(buf),
}
}
}
pub(crate) struct Sender {
body: (Box<dyn Read + Send>, Option<u64>),
tx: mpsc::Sender<Result<Bytes, Abort>>,
}
#[derive(Debug)]
struct Abort;
impl fmt::Display for Abort {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("abort request body")
}
}
impl std::error::Error for Abort {}
async fn send_future(sender: Sender) -> Result<(), crate::Error> {
use bytes::{BufMut, BytesMut};
use futures_util::SinkExt;
use std::cmp;
let con_len = sender.body.1;
let cap = cmp::min(sender.body.1.unwrap_or(8192), 8192);
let mut written = 0;
let mut buf = BytesMut::zeroed(cap as usize);
buf.clear();
let mut body = sender.body.0;
// Put in an option so that it can be consumed on error to call abort()
let mut tx = Some(sender.tx);
loop {
if Some(written) == con_len {
// Written up to content-length, so stop.
return Ok(());
}
// The input stream is read only if the buffer is empty so
// that there is only one read in the buffer at any time.
//
// We need to know whether there is any data to send before
// we check the transmission channel (with poll_ready below)
// because sometimes the receiver disappears as soon as it
// considers the data is completely transmitted, which may
// be true.
//
// The use case is a web server that closes its
// input stream as soon as the data received is valid JSON.
// This behaviour is questionable, but it exists and the
// fact is that there is actually no remaining data to read.
if buf.is_empty() {
if buf.capacity() == buf.len() {
buf.reserve(8192);
// zero out the reserved memory
let uninit = buf.spare_capacity_mut();
let uninit_len = uninit.len();
unsafe {
ptr::write_bytes(uninit.as_mut_ptr().cast::<u8>(), 0, uninit_len);
}
}
let bytes = unsafe {
mem::transmute::<&mut [MaybeUninit<u8>], &mut [u8]>(buf.spare_capacity_mut())
};
match body.read(bytes) {
Ok(0) => {
// The buffer was empty and nothing's left to
// read. Return.
return Ok(());
}
Ok(n) => unsafe {
buf.advance_mut(n);
},
Err(e) => {
let _ = tx
.take()
.expect("tx only taken on error")
.clone()
.try_send(Err(Abort));
return Err(crate::error::body(e));
}
}
}
// The only way to get here is when the buffer is not empty.
// We can check the transmission channel
let buf_len = buf.len() as u64;
tx.as_mut()
.expect("tx only taken on error")
.send(Ok(buf.split().freeze()))
.await
.map_err(crate::error::body)?;
written += buf_len;
}
}
impl Sender {
// A `Future` that may do blocking read calls.
// As a `Future`, this integrates easily with `wait::timeout`.
pub(crate) fn send(self) -> impl Future<Output = Result<(), crate::Error>> {
send_future(self)
}
}
// useful for tests, but not publicly exposed
#[cfg(test)]
pub(crate) fn read_to_string(mut body: Body) -> io::Result<String> {
let mut s = String::new();
match body.kind {
Kind::Reader(ref mut reader, _) => reader.read_to_string(&mut s),
Kind::Bytes(ref mut bytes) => (&**bytes).read_to_string(&mut s),
}
.map(|_| s)
}

1524
vendor/reqwest/src/blocking/client.rs vendored Normal file

File diff suppressed because it is too large Load Diff

108
vendor/reqwest/src/blocking/mod.rs vendored Normal file
View File

@@ -0,0 +1,108 @@
//! A blocking Client API.
//!
//! The blocking `Client` will block the current thread to execute, instead
//! of returning futures that need to be executed on a runtime.
//!
//! Conversely, the functionality in `reqwest::blocking` must *not* be executed
//! within an async runtime, or it will panic when attempting to block. If
//! calling directly from an async function, consider using an async
//! [`reqwest::Client`][crate::Client] instead. If the immediate context is only
//! synchronous, but a transitive caller is async, consider changing that caller
//! to use [`tokio::task::spawn_blocking`] around the calls that need to block.
//!
//! # Optional
//!
//! This requires the optional `blocking` feature to be enabled.
//!
//! # Making a GET request
//!
//! For a single request, you can use the [`get`] shortcut method.
//!
//! ```rust
//! # use reqwest::{Error, Response};
//!
//! # fn run() -> Result<(), Error> {
//! let body = reqwest::blocking::get("https://www.rust-lang.org")?
//! .text()?;
//!
//! println!("body = {body:?}");
//! # Ok(())
//! # }
//! ```
//!
//! Additionally, the blocking [`Response`] struct implements Rust's
//! `Read` trait, so many useful standard library and third party crates will
//! have convenience methods that take a `Response` anywhere `T: Read` is
//! acceptable.
//!
//! **NOTE**: If you plan to perform multiple requests, it is best to create a
//! [`Client`] and reuse it, taking advantage of keep-alive connection pooling.
//!
//! # Making POST requests (or setting request bodies)
//!
//! There are several ways you can set the body of a request. The basic one is
//! by using the `body()` method of a [`RequestBuilder`]. This lets you set the
//! exact raw bytes of what the body should be. It accepts various types,
//! including `String`, `Vec<u8>`, and `File`. If you wish to pass a custom
//! Reader, you can use the `reqwest::blocking::Body::new()` constructor.
//!
//! ```rust
//! # use reqwest::Error;
//! #
//! # fn run() -> Result<(), Error> {
//! let client = reqwest::blocking::Client::new();
//! let res = client.post("http://httpbin.org/post")
//! .body("the exact body that is sent")
//! .send()?;
//! # Ok(())
//! # }
//! ```
//!
//! ## And More
//!
//! Most features available to the asynchronous `Client` are also available,
//! on the blocking `Client`, see those docs for more.
mod body;
mod client;
#[cfg(feature = "multipart")]
pub mod multipart;
mod request;
mod response;
mod wait;
pub use self::body::Body;
pub use self::client::{Client, ClientBuilder};
pub use self::request::{Request, RequestBuilder};
pub use self::response::Response;
/// Shortcut method to quickly make a *blocking* `GET` request.
///
/// **NOTE**: This function creates a new internal `Client` on each call,
/// and so should not be used if making many requests. Create a
/// [`Client`](./struct.Client.html) instead.
///
/// # Examples
///
/// ```rust
/// # fn run() -> Result<(), reqwest::Error> {
/// let body = reqwest::blocking::get("https://www.rust-lang.org")?
/// .text()?;
/// # Ok(())
/// # }
/// # fn main() { }
/// ```
///
/// # Errors
///
/// This function fails if:
///
/// - the native TLS backend cannot be initialized,
/// - the supplied `Url` cannot be parsed,
/// - there was an error while sending request,
/// - a redirect loop was detected,
/// - the redirect limit was exhausted, or
/// - the total download time exceeds 30 seconds.
pub fn get<T: crate::IntoUrl>(url: T) -> crate::Result<Response> {
Client::builder().build()?.get(url).send()
}

494
vendor/reqwest/src/blocking/multipart.rs vendored Normal file
View File

@@ -0,0 +1,494 @@
//! multipart/form-data
//!
//! To send a `multipart/form-data` body, a [`Form`] is built up, adding
//! fields or customized [`Part`]s, and then calling the
//! [`multipart`][builder] method on the `RequestBuilder`.
//!
//! # Example
//!
//! ```
//! use reqwest::blocking::multipart;
//!
//! # fn run() -> Result<(), Box<dyn std::error::Error>> {
//! let form = multipart::Form::new()
//! // Adding just a simple text field...
//! .text("username", "seanmonstar")
//! // And a file...
//! .file("photo", "/path/to/photo.png")?;
//!
//! // Customize all the details of a Part if needed...
//! let bio = multipart::Part::text("hallo peeps")
//! .file_name("bio.txt")
//! .mime_str("text/plain")?;
//!
//! // Add the custom part to our form...
//! let form = form.part("biography", bio);
//!
//! // And finally, send the form
//! let client = reqwest::blocking::Client::new();
//! let resp = client
//! .post("http://localhost:8080/user")
//! .multipart(form)
//! .send()?;
//! # Ok(())
//! # }
//! # fn main() {}
//! ```
//!
//! [builder]: ../struct.RequestBuilder.html#method.multipart
use std::borrow::Cow;
use std::fmt;
use std::fs::File;
use std::io::{self, Cursor, Read};
use std::path::Path;
use mime_guess::{self, Mime};
use super::Body;
use crate::async_impl::multipart::{FormParts, PartMetadata, PartProps};
use crate::header::HeaderMap;
/// A multipart/form-data request.
pub struct Form {
inner: FormParts<Part>,
}
/// A field in a multipart form.
pub struct Part {
meta: PartMetadata,
value: Body,
}
impl Default for Form {
fn default() -> Self {
Self::new()
}
}
impl Form {
/// Creates a new Form without any content.
pub fn new() -> Form {
Form {
inner: FormParts::new(),
}
}
/// Get the boundary that this form will use.
#[inline]
pub fn boundary(&self) -> &str {
self.inner.boundary()
}
/// Add a data field with supplied name and value.
///
/// # Examples
///
/// ```
/// let form = reqwest::blocking::multipart::Form::new()
/// .text("username", "seanmonstar")
/// .text("password", "secret");
/// ```
pub fn text<T, U>(self, name: T, value: U) -> Form
where
T: Into<Cow<'static, str>>,
U: Into<Cow<'static, str>>,
{
self.part(name, Part::text(value))
}
/// Adds a file field.
///
/// The path will be used to try to guess the filename and mime.
///
/// # Examples
///
/// ```no_run
/// # fn run() -> std::io::Result<()> {
/// let form = reqwest::blocking::multipart::Form::new()
/// .file("key", "/path/to/file")?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
///
/// Errors when the file cannot be opened.
pub fn file<T, U>(self, name: T, path: U) -> io::Result<Form>
where
T: Into<Cow<'static, str>>,
U: AsRef<Path>,
{
Ok(self.part(name, Part::file(path)?))
}
/// Adds a customized Part.
pub fn part<T>(self, name: T, part: Part) -> Form
where
T: Into<Cow<'static, str>>,
{
self.with_inner(move |inner| inner.part(name, part))
}
/// Configure this `Form` to percent-encode using the `path-segment` rules.
pub fn percent_encode_path_segment(self) -> Form {
self.with_inner(|inner| inner.percent_encode_path_segment())
}
/// Configure this `Form` to percent-encode using the `attr-char` rules.
pub fn percent_encode_attr_chars(self) -> Form {
self.with_inner(|inner| inner.percent_encode_attr_chars())
}
/// Configure this `Form` to skip percent-encoding
pub fn percent_encode_noop(self) -> Form {
self.with_inner(|inner| inner.percent_encode_noop())
}
pub(crate) fn reader(self) -> Reader {
Reader::new(self)
}
/// Produce a reader over the multipart form data.
pub fn into_reader(self) -> impl Read {
self.reader()
}
// If predictable, computes the length the request will have
// The length should be predictable if only String and file fields have been added,
// but not if a generic reader has been added;
pub(crate) fn compute_length(&mut self) -> Option<u64> {
self.inner.compute_length()
}
fn with_inner<F>(self, func: F) -> Self
where
F: FnOnce(FormParts<Part>) -> FormParts<Part>,
{
Form {
inner: func(self.inner),
}
}
}
impl fmt::Debug for Form {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt_fields("Form", f)
}
}
impl Part {
/// Makes a text parameter.
pub fn text<T>(value: T) -> Part
where
T: Into<Cow<'static, str>>,
{
let body = match value.into() {
Cow::Borrowed(slice) => Body::from(slice),
Cow::Owned(string) => Body::from(string),
};
Part::new(body)
}
/// Makes a new parameter from arbitrary bytes.
pub fn bytes<T>(value: T) -> Part
where
T: Into<Cow<'static, [u8]>>,
{
let body = match value.into() {
Cow::Borrowed(slice) => Body::from(slice),
Cow::Owned(vec) => Body::from(vec),
};
Part::new(body)
}
/// Adds a generic reader.
///
/// Does not set filename or mime.
pub fn reader<T: Read + Send + 'static>(value: T) -> Part {
Part::new(Body::new(value))
}
/// Adds a generic reader with known length.
///
/// Does not set filename or mime.
pub fn reader_with_length<T: Read + Send + 'static>(value: T, length: u64) -> Part {
Part::new(Body::sized(value, length))
}
/// Makes a file parameter.
///
/// # Errors
///
/// Errors when the file cannot be opened.
pub fn file<T: AsRef<Path>>(path: T) -> io::Result<Part> {
let path = path.as_ref();
let file_name = path
.file_name()
.map(|filename| filename.to_string_lossy().into_owned());
let ext = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
let mime = mime_guess::from_ext(ext).first_or_octet_stream();
let file = File::open(path)?;
let field = Part::new(Body::from(file)).mime(mime);
Ok(if let Some(file_name) = file_name {
field.file_name(file_name)
} else {
field
})
}
fn new(value: Body) -> Part {
Part {
meta: PartMetadata::new(),
value,
}
}
/// Tries to set the mime of this part.
pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
}
// Re-export when mime 0.4 is available, with split MediaType/MediaRange.
fn mime(self, mime: Mime) -> Part {
self.with_inner(move |inner| inner.mime(mime))
}
/// Sets the filename, builder style.
pub fn file_name<T>(self, filename: T) -> Part
where
T: Into<Cow<'static, str>>,
{
self.with_inner(move |inner| inner.file_name(filename))
}
/// Sets custom headers for the part.
pub fn headers(self, headers: HeaderMap) -> Part {
self.with_inner(move |inner| inner.headers(headers))
}
fn with_inner<F>(self, func: F) -> Self
where
F: FnOnce(PartMetadata) -> PartMetadata,
{
Part {
meta: func(self.meta),
value: self.value,
}
}
}
impl fmt::Debug for Part {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut dbg = f.debug_struct("Part");
dbg.field("value", &self.value);
self.meta.fmt_fields(&mut dbg);
dbg.finish()
}
}
impl PartProps for Part {
fn value_len(&self) -> Option<u64> {
self.value.len()
}
fn metadata(&self) -> &PartMetadata {
&self.meta
}
}
pub(crate) struct Reader {
form: Form,
active_reader: Option<Box<dyn Read + Send>>,
}
impl fmt::Debug for Reader {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Reader").field("form", &self.form).finish()
}
}
impl Reader {
fn new(form: Form) -> Reader {
let mut reader = Reader {
form,
active_reader: None,
};
reader.next_reader();
reader
}
fn next_reader(&mut self) {
self.active_reader = if !self.form.inner.fields.is_empty() {
// We need to move out of the vector here because we are consuming the field's reader
let (name, field) = self.form.inner.fields.remove(0);
let boundary = Cursor::new(format!("--{}\r\n", self.form.boundary()));
let header = Cursor::new({
// Try to use cached headers created by compute_length
let mut h = if !self.form.inner.computed_headers.is_empty() {
self.form.inner.computed_headers.remove(0)
} else {
self.form
.inner
.percent_encoding
.encode_headers(&name, field.metadata())
};
h.extend_from_slice(b"\r\n\r\n");
h
});
let reader = boundary
.chain(header)
.chain(field.value.into_reader())
.chain(Cursor::new("\r\n"));
// According to https://tools.ietf.org/html/rfc2046#section-5.1.1
// the very last field has a special boundary
if !self.form.inner.fields.is_empty() {
Some(Box::new(reader))
} else {
Some(Box::new(reader.chain(Cursor::new(format!(
"--{}--\r\n",
self.form.boundary()
)))))
}
} else {
None
}
}
}
impl Read for Reader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut total_bytes_read = 0usize;
let mut last_read_bytes;
loop {
match self.active_reader {
Some(ref mut reader) => {
last_read_bytes = reader.read(&mut buf[total_bytes_read..])?;
total_bytes_read += last_read_bytes;
if total_bytes_read == buf.len() {
return Ok(total_bytes_read);
}
}
None => return Ok(total_bytes_read),
};
if last_read_bytes == 0 && !buf.is_empty() {
self.next_reader();
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn form_empty() {
let mut output = Vec::new();
let mut form = Form::new();
let length = form.compute_length();
form.reader().read_to_end(&mut output).unwrap();
assert_eq!(output, b"");
assert_eq!(length.unwrap(), 0);
}
#[test]
fn read_to_end() {
let mut output = Vec::new();
let mut form = Form::new()
.part("reader1", Part::reader(std::io::empty()))
.part("key1", Part::text("value1"))
.part(
"key2",
Part::text("value2").mime(mime_guess::mime::IMAGE_BMP),
)
.part("reader2", Part::reader(std::io::empty()))
.part("key3", Part::text("value3").file_name("filename"));
form.inner.boundary = "boundary".to_string();
let length = form.compute_length();
let expected = "--boundary\r\n\
Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
value1\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key2\"\r\n\
Content-Type: image/bmp\r\n\r\n\
value2\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
value3\r\n--boundary--\r\n";
form.reader().read_to_end(&mut output).unwrap();
// These prints are for debug purposes in case the test fails
println!(
"START REAL\n{}\nEND REAL",
std::str::from_utf8(&output).unwrap()
);
println!("START EXPECTED\n{expected}\nEND EXPECTED");
assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
assert!(length.is_none());
}
#[test]
fn read_to_end_with_length() {
let mut output = Vec::new();
let mut form = Form::new()
.text("key1", "value1")
.part(
"key2",
Part::text("value2").mime(mime_guess::mime::IMAGE_BMP),
)
.part("key3", Part::text("value3").file_name("filename"));
form.inner.boundary = "boundary".to_string();
let length = form.compute_length();
let expected = "--boundary\r\n\
Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
value1\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key2\"\r\n\
Content-Type: image/bmp\r\n\r\n\
value2\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
value3\r\n--boundary--\r\n";
form.reader().read_to_end(&mut output).unwrap();
// These prints are for debug purposes in case the test fails
println!(
"START REAL\n{}\nEND REAL",
std::str::from_utf8(&output).unwrap()
);
println!("START EXPECTED\n{expected}\nEND EXPECTED");
assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
assert_eq!(length.unwrap(), expected.len() as u64);
}
#[test]
fn read_to_end_with_header() {
let mut output = Vec::new();
let mut part = Part::text("value2").mime(mime_guess::mime::IMAGE_BMP);
let mut headers = HeaderMap::new();
headers.insert("Hdr3", "/a/b/c".parse().unwrap());
part = part.headers(headers);
let mut form = Form::new().part("key2", part);
form.inner.boundary = "boundary".to_string();
let expected = "--boundary\r\n\
Content-Disposition: form-data; name=\"key2\"\r\n\
Content-Type: image/bmp\r\n\
hdr3: /a/b/c\r\n\
\r\n\
value2\r\n\
--boundary--\r\n";
form.reader().read_to_end(&mut output).unwrap();
// These prints are for debug purposes in case the test fails
println!(
"START REAL\n{}\nEND REAL",
std::str::from_utf8(&output).unwrap()
);
println!("START EXPECTED\n{expected}\nEND EXPECTED");
assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
}
}

1099
vendor/reqwest/src/blocking/request.rs vendored Normal file

File diff suppressed because it is too large Load Diff

452
vendor/reqwest/src/blocking/response.rs vendored Normal file
View File

@@ -0,0 +1,452 @@
use std::fmt;
use std::io::{self, Read};
use std::mem;
use std::net::SocketAddr;
use std::pin::Pin;
use std::time::Duration;
use bytes::Bytes;
use futures_util::TryStreamExt;
use http;
use http_body_util::BodyExt;
use hyper::header::HeaderMap;
#[cfg(feature = "json")]
use serde::de::DeserializeOwned;
use super::client::KeepCoreThreadAlive;
use super::wait;
#[cfg(feature = "cookies")]
use crate::cookie;
use crate::{async_impl, StatusCode, Url, Version};
/// A Response to a submitted `Request`.
pub struct Response {
inner: async_impl::Response,
body: Option<Pin<Box<dyn futures_util::io::AsyncRead + Send + Sync>>>,
timeout: Option<Duration>,
_thread_handle: KeepCoreThreadAlive,
}
impl fmt::Debug for Response {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.inner, f)
}
}
impl Response {
pub(crate) fn new(
res: async_impl::Response,
timeout: Option<Duration>,
thread: KeepCoreThreadAlive,
) -> Response {
Response {
inner: res,
body: None,
timeout,
_thread_handle: thread,
}
}
/// Get the `StatusCode` of this `Response`.
///
/// # Examples
///
/// Checking for general status class:
///
/// ```rust
/// # #[cfg(feature = "json")]
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let resp = reqwest::blocking::get("http://httpbin.org/get")?;
/// if resp.status().is_success() {
/// println!("success!");
/// } else if resp.status().is_server_error() {
/// println!("server error!");
/// } else {
/// println!("Something else happened. Status: {:?}", resp.status());
/// }
/// # Ok(())
/// # }
/// ```
///
/// Checking for specific status codes:
///
/// ```rust
/// use reqwest::blocking::Client;
/// use reqwest::StatusCode;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let client = Client::new();
///
/// let resp = client.post("http://httpbin.org/post")
/// .body("possibly too large")
/// .send()?;
///
/// match resp.status() {
/// StatusCode::OK => println!("success!"),
/// StatusCode::PAYLOAD_TOO_LARGE => {
/// println!("Request payload is too large!");
/// }
/// s => println!("Received response status: {s:?}"),
/// };
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn status(&self) -> StatusCode {
self.inner.status()
}
/// Get the `Headers` of this `Response`.
///
/// # Example
///
/// Saving an etag when caching a file:
///
/// ```
/// use reqwest::blocking::Client;
/// use reqwest::header::ETAG;
///
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let client = Client::new();
///
/// let mut resp = client.get("http://httpbin.org/cache").send()?;
/// if resp.status().is_success() {
/// if let Some(etag) = resp.headers().get(ETAG) {
/// std::fs::write("etag", etag.as_bytes());
/// }
/// let mut file = std::fs::File::create("file")?;
/// resp.copy_to(&mut file)?;
/// }
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn headers(&self) -> &HeaderMap {
self.inner.headers()
}
/// Get a mutable reference to the `Headers` of this `Response`.
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
self.inner.headers_mut()
}
/// Retrieve the cookies contained in the response.
///
/// Note that invalid 'Set-Cookie' headers will be ignored.
///
/// # Optional
///
/// This requires the optional `cookies` feature to be enabled.
#[cfg(feature = "cookies")]
#[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a {
cookie::extract_response_cookies(self.headers()).filter_map(Result::ok)
}
/// Get the HTTP `Version` of this `Response`.
#[inline]
pub fn version(&self) -> Version {
self.inner.version()
}
/// Get the final `Url` of this `Response`.
///
/// # Example
///
/// ```rust
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let resp = reqwest::blocking::get("http://httpbin.org/redirect/1")?;
/// assert_eq!(resp.url().as_str(), "http://httpbin.org/get");
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn url(&self) -> &Url {
self.inner.url()
}
/// Get the remote address used to get this `Response`.
///
/// # Example
///
/// ```rust
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let resp = reqwest::blocking::get("http://httpbin.org/redirect/1")?;
/// println!("httpbin.org address: {:?}", resp.remote_addr());
/// # Ok(())
/// # }
/// ```
pub fn remote_addr(&self) -> Option<SocketAddr> {
self.inner.remote_addr()
}
/// Returns a reference to the associated extensions.
pub fn extensions(&self) -> &http::Extensions {
self.inner.extensions()
}
/// Returns a mutable reference to the associated extensions.
pub fn extensions_mut(&mut self) -> &mut http::Extensions {
self.inner.extensions_mut()
}
/// Get the content length of the response, if it is known.
///
///
/// This value does not directly represents the value of the `Content-Length`
/// header, but rather the size of the response's body. To read the header's
/// value, please use the [`Response::headers`] method instead.
///
/// Reasons it may not be known:
///
/// - The response does not include a body (e.g. it responds to a `HEAD`
/// request).
/// - The response is gzipped and automatically decoded (thus changing the
/// actual decoded length).
pub fn content_length(&self) -> Option<u64> {
self.inner.content_length()
}
/// Try and deserialize the response body as JSON using `serde`.
///
/// # Optional
///
/// This requires the optional `json` feature enabled.
///
/// # Examples
///
/// ```rust
/// # extern crate reqwest;
/// # extern crate serde;
/// #
/// # use reqwest::Error;
/// # use serde::Deserialize;
/// #
/// // This `derive` requires the `serde` dependency.
/// #[derive(Deserialize)]
/// struct Ip {
/// origin: String,
/// }
///
/// # fn run() -> Result<(), Error> {
/// let json: Ip = reqwest::blocking::get("http://httpbin.org/ip")?.json()?;
/// # Ok(())
/// # }
/// #
/// # fn main() { }
/// ```
///
/// # Errors
///
/// This method fails whenever the response body is not in JSON format,
/// or it cannot be properly deserialized to target type `T`. For more
/// details please see [`serde_json::from_reader`].
///
/// [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
pub fn json<T: DeserializeOwned>(self) -> crate::Result<T> {
wait::timeout(self.inner.json(), self.timeout).map_err(|e| match e {
wait::Waited::TimedOut(e) => crate::error::decode(e),
wait::Waited::Inner(e) => e,
})
}
/// Get the full response body as `Bytes`.
///
/// # Example
///
/// ```
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let bytes = reqwest::blocking::get("http://httpbin.org/ip")?.bytes()?;
///
/// println!("bytes: {bytes:?}");
/// # Ok(())
/// # }
/// ```
pub fn bytes(self) -> crate::Result<Bytes> {
wait::timeout(self.inner.bytes(), self.timeout).map_err(|e| match e {
wait::Waited::TimedOut(e) => crate::error::decode(e),
wait::Waited::Inner(e) => e,
})
}
/// Get the response text.
///
/// This method decodes the response body with BOM sniffing
/// and with malformed sequences replaced with the [`char::REPLACEMENT_CHARACTER`].
/// Encoding is determined from the `charset` parameter of `Content-Type` header,
/// and defaults to `utf-8` if not presented.
///
/// # Note
///
/// If the `charset` feature is disabled the method will only attempt to decode the
/// response as UTF-8, regardless of the given `Content-Type`
///
/// # Example
///
/// ```rust
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let content = reqwest::blocking::get("http://httpbin.org/range/26")?.text()?;
/// # Ok(())
/// # }
/// ```
pub fn text(self) -> crate::Result<String> {
wait::timeout(self.inner.text(), self.timeout).map_err(|e| match e {
wait::Waited::TimedOut(e) => crate::error::decode(e),
wait::Waited::Inner(e) => e,
})
}
/// Get the response text given a specific encoding.
///
/// This method decodes the response body with BOM sniffing
/// and with malformed sequences replaced with the [`char::REPLACEMENT_CHARACTER`].
/// You can provide a default encoding for decoding the raw message, while the
/// `charset` parameter of `Content-Type` header is still prioritized. For more information
/// about the possible encoding name, please go to [`encoding_rs`] docs.
///
/// [`encoding_rs`]: https://docs.rs/encoding_rs/0.8/encoding_rs/#relationship-with-windows-code-pages
///
/// # Optional
///
/// This requires the optional `charset` feature enabled.
///
/// # Example
///
/// ```rust
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let content = reqwest::blocking::get("http://httpbin.org/range/26")?
/// .text_with_charset("utf-8")?;
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "charset")]
#[cfg_attr(docsrs, doc(cfg(feature = "charset")))]
pub fn text_with_charset(self, default_encoding: &str) -> crate::Result<String> {
wait::timeout(self.inner.text_with_charset(default_encoding), self.timeout).map_err(|e| {
match e {
wait::Waited::TimedOut(e) => crate::error::decode(e),
wait::Waited::Inner(e) => e,
}
})
}
/// Copy the response body into a writer.
///
/// This function internally uses [`std::io::copy`] and hence will continuously read data from
/// the body and then write it into writer in a streaming fashion until EOF is met.
///
/// On success, the total number of bytes that were copied to `writer` is returned.
///
/// [`std::io::copy`]: https://doc.rust-lang.org/std/io/fn.copy.html
///
/// # Example
///
/// ```rust
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let mut resp = reqwest::blocking::get("http://httpbin.org/range/5")?;
/// let mut buf: Vec<u8> = vec![];
/// resp.copy_to(&mut buf)?;
/// assert_eq!(b"abcde", buf.as_slice());
/// # Ok(())
/// # }
/// ```
pub fn copy_to<W: ?Sized>(&mut self, w: &mut W) -> crate::Result<u64>
where
W: io::Write,
{
io::copy(self, w).map_err(crate::error::decode_io)
}
/// Turn a response into an error if the server returned an error.
///
/// # Example
///
/// ```rust,no_run
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let res = reqwest::blocking::get("http://httpbin.org/status/400")?
/// .error_for_status();
/// if let Err(err) = res {
/// assert_eq!(err.status(), Some(reqwest::StatusCode::BAD_REQUEST));
/// }
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn error_for_status(self) -> crate::Result<Self> {
let Response {
body,
inner,
timeout,
_thread_handle,
} = self;
inner.error_for_status().map(move |inner| Response {
inner,
body,
timeout,
_thread_handle,
})
}
/// Turn a reference to a response into an error if the server returned an error.
///
/// # Example
///
/// ```rust,no_run
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let res = reqwest::blocking::get("http://httpbin.org/status/400")?;
/// let res = res.error_for_status_ref();
/// if let Err(err) = res {
/// assert_eq!(err.status(), Some(reqwest::StatusCode::BAD_REQUEST));
/// }
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn error_for_status_ref(&self) -> crate::Result<&Self> {
self.inner.error_for_status_ref().and_then(|_| Ok(self))
}
// private
fn body_mut(&mut self) -> Pin<&mut dyn futures_util::io::AsyncRead> {
if self.body.is_none() {
let body = mem::replace(
self.inner.body_mut(),
async_impl::body::boxed(http_body_util::Empty::new()),
);
self.body = Some(Box::pin(
async_impl::body::Body::wrap(body)
.into_data_stream()
.map_err(crate::error::Error::into_io)
.into_async_read(),
));
}
self.body.as_mut().expect("body was init").as_mut()
}
}
impl Read for Response {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
use futures_util::io::AsyncReadExt;
let timeout = self.timeout;
wait::timeout(self.body_mut().read(buf), timeout).map_err(|e| match e {
wait::Waited::TimedOut(e) => crate::error::decode(e).into_io(),
wait::Waited::Inner(e) => e,
})
}
}
impl<T: Into<async_impl::body::Body>> From<http::Response<T>> for Response {
fn from(r: http::Response<T>) -> Response {
let response = async_impl::Response::from(r);
Response::new(response, None, KeepCoreThreadAlive::empty())
}
}

82
vendor/reqwest/src/blocking/wait.rs vendored Normal file
View File

@@ -0,0 +1,82 @@
use std::future::Future;
use std::sync::Arc;
use std::task::{Context, Poll, Wake, Waker};
use std::thread::{self, Thread};
use std::time::Duration;
use tokio::time::Instant;
pub(crate) fn timeout<F, I, E>(fut: F, timeout: Option<Duration>) -> Result<I, Waited<E>>
where
F: Future<Output = Result<I, E>>,
{
enter();
let deadline = timeout.map(|d| {
log::trace!("wait at most {d:?}");
Instant::now() + d
});
let thread = ThreadWaker(thread::current());
// Arc shouldn't be necessary, since `Thread` is reference counted internally,
// but let's just stay safe for now.
let waker = Waker::from(Arc::new(thread));
let mut cx = Context::from_waker(&waker);
futures_util::pin_mut!(fut);
loop {
match fut.as_mut().poll(&mut cx) {
Poll::Ready(Ok(val)) => return Ok(val),
Poll::Ready(Err(err)) => return Err(Waited::Inner(err)),
Poll::Pending => (), // fallthrough
}
if let Some(deadline) = deadline {
let now = Instant::now();
if now >= deadline {
log::trace!("wait timeout exceeded");
return Err(Waited::TimedOut(crate::error::TimedOut));
}
log::trace!(
"({:?}) park timeout {:?}",
thread::current().id(),
deadline - now
);
thread::park_timeout(deadline - now);
} else {
log::trace!("({:?}) park without timeout", thread::current().id());
thread::park();
}
}
}
#[derive(Debug)]
pub(crate) enum Waited<E> {
TimedOut(crate::error::TimedOut),
Inner(E),
}
struct ThreadWaker(Thread);
impl Wake for ThreadWaker {
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &Arc<Self>) {
self.0.unpark();
}
}
fn enter() {
// Check we aren't already in a runtime
#[cfg(debug_assertions)]
{
let _enter = tokio::runtime::Builder::new_current_thread()
.build()
.expect("build shell runtime")
.enter();
}
}

110
vendor/reqwest/src/config.rs vendored Normal file
View File

@@ -0,0 +1,110 @@
//! The `config` module provides a generic mechanism for loading and managing
//! request-scoped configuration.
//!
//! # Design Overview
//!
//! This module is centered around two abstractions:
//!
//! - The [`RequestConfigValue`] trait, used to associate a config key type with its value type.
//! - The [`RequestConfig`] struct, which wraps an optional value of the type linked via [`RequestConfigValue`].
//!
//! Under the hood, the [`RequestConfig`] struct holds a single value for the associated config type.
//! This value can be conveniently accessed, inserted, or mutated using [`http::Extensions`],
//! enabling type-safe configuration storage and retrieval on a per-request basis.
//!
//! # Motivation
//!
//! The key design benefit is the ability to store multiple config types—potentially even with the same
//! value type (e.g., [`Duration`])—without code duplication or ambiguity. By leveraging trait association,
//! each config key is distinct at the type level, while code for storage and access remains totally generic.
//!
//! # Usage
//!
//! Implement [`RequestConfigValue`] for any marker type you wish to use as a config key,
//! specifying the associated value type. Then use [`RequestConfig<T>`] in [`Extensions`]
//! to set or retrieve config values for each key type in a uniform way.
use std::any::type_name;
use std::fmt::Debug;
use std::time::Duration;
use http::Extensions;
/// This trait is empty and is only used to associate a configuration key type with its
/// corresponding value type.
pub(crate) trait RequestConfigValue: Copy + Clone + 'static {
type Value: Clone + Debug + Send + Sync + 'static;
}
/// RequestConfig carries a request-scoped configuration value.
#[derive(Clone, Copy)]
pub(crate) struct RequestConfig<T: RequestConfigValue>(Option<T::Value>);
impl<T: RequestConfigValue> Default for RequestConfig<T> {
fn default() -> Self {
RequestConfig(None)
}
}
impl<T> RequestConfig<T>
where
T: RequestConfigValue,
{
pub(crate) fn new(v: Option<T::Value>) -> Self {
RequestConfig(v)
}
/// format request config value as struct field.
///
/// We provide this API directly to avoid leak internal value to callers.
pub(crate) fn fmt_as_field(&self, f: &mut std::fmt::DebugStruct<'_, '_>) {
if let Some(v) = &self.0 {
f.field(type_name::<T>(), v);
}
}
/// Retrieve the value from the request-scoped configuration.
///
/// If the request specifies a value, use that value; otherwise, attempt to retrieve it from the current instance (typically a client instance).
pub(crate) fn fetch<'client, 'request>(
&'client self,
ext: &'request Extensions,
) -> Option<&'request T::Value>
where
'client: 'request,
{
ext.get::<RequestConfig<T>>()
.and_then(|v| v.0.as_ref())
.or(self.0.as_ref())
}
/// Retrieve the value from the request's Extensions.
pub(crate) fn get(ext: &Extensions) -> Option<&T::Value> {
ext.get::<RequestConfig<T>>().and_then(|v| v.0.as_ref())
}
/// Retrieve the mutable value from the request's Extensions.
pub(crate) fn get_mut(ext: &mut Extensions) -> &mut Option<T::Value> {
let cfg = ext.get_or_insert_default::<RequestConfig<T>>();
&mut cfg.0
}
}
// ================================
//
// The following sections are all configuration types
// provided by reqwest.
//
// To add a new config:
//
// 1. create a new struct for the config key like `RequestTimeout`.
// 2. implement `RequestConfigValue` for the struct, the `Value` is the config value's type.
//
// ================================
#[derive(Clone, Copy)]
pub(crate) struct TotalTimeout;
impl RequestConfigValue for TotalTimeout {
type Value = Duration;
}

2091
vendor/reqwest/src/connect.rs vendored Normal file

File diff suppressed because it is too large Load Diff

293
vendor/reqwest/src/cookie.rs vendored Normal file
View File

@@ -0,0 +1,293 @@
//! HTTP Cookies
use crate::header::{HeaderValue, SET_COOKIE};
use bytes::Bytes;
use std::convert::TryInto;
use std::fmt;
use std::sync::RwLock;
use std::time::SystemTime;
/// Actions for a persistent cookie store providing session support.
pub trait CookieStore: Send + Sync {
/// Store a set of Set-Cookie header values received from `url`
fn set_cookies(&self, cookie_headers: &mut dyn Iterator<Item = &HeaderValue>, url: &url::Url);
/// Get any Cookie values in the store for `url`
fn cookies(&self, url: &url::Url) -> Option<HeaderValue>;
}
/// A single HTTP cookie.
pub struct Cookie<'a>(cookie_crate::Cookie<'a>);
/// A good default `CookieStore` implementation.
///
/// This is the implementation used when simply calling `cookie_store(true)`.
/// This type is exposed to allow creating one and filling it with some
/// existing cookies more easily, before creating a `Client`.
///
/// For more advanced scenarios, such as needing to serialize the store or
/// manipulate it between requests, you may refer to the
/// [reqwest_cookie_store crate](https://crates.io/crates/reqwest_cookie_store).
#[derive(Debug, Default)]
pub struct Jar(RwLock<cookie_store::CookieStore>);
// ===== impl Cookie =====
impl<'a> Cookie<'a> {
fn parse(value: &'a HeaderValue) -> Result<Cookie<'a>, CookieParseError> {
std::str::from_utf8(value.as_bytes())
.map_err(cookie_crate::ParseError::from)
.and_then(cookie_crate::Cookie::parse)
.map_err(CookieParseError)
.map(Cookie)
}
/// The name of the cookie.
pub fn name(&self) -> &str {
self.0.name()
}
/// The value of the cookie.
pub fn value(&self) -> &str {
self.0.value()
}
/// Returns true if the 'HttpOnly' directive is enabled.
pub fn http_only(&self) -> bool {
self.0.http_only().unwrap_or(false)
}
/// Returns true if the 'Secure' directive is enabled.
pub fn secure(&self) -> bool {
self.0.secure().unwrap_or(false)
}
/// Returns true if 'SameSite' directive is 'Lax'.
pub fn same_site_lax(&self) -> bool {
self.0.same_site() == Some(cookie_crate::SameSite::Lax)
}
/// Returns true if 'SameSite' directive is 'Strict'.
pub fn same_site_strict(&self) -> bool {
self.0.same_site() == Some(cookie_crate::SameSite::Strict)
}
/// Returns the path directive of the cookie, if set.
pub fn path(&self) -> Option<&str> {
self.0.path()
}
/// Returns the domain directive of the cookie, if set.
pub fn domain(&self) -> Option<&str> {
self.0.domain()
}
/// Get the Max-Age information.
pub fn max_age(&self) -> Option<std::time::Duration> {
self.0.max_age().map(|d| {
d.try_into()
.expect("time::Duration into std::time::Duration")
})
}
/// The cookie expiration time.
pub fn expires(&self) -> Option<SystemTime> {
match self.0.expires() {
Some(cookie_crate::Expiration::DateTime(offset)) => Some(SystemTime::from(offset)),
None | Some(cookie_crate::Expiration::Session) => None,
}
}
}
impl<'a> fmt::Debug for Cookie<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
pub(crate) fn extract_response_cookie_headers<'a>(
headers: &'a hyper::HeaderMap,
) -> impl Iterator<Item = &'a HeaderValue> + 'a {
headers.get_all(SET_COOKIE).iter()
}
pub(crate) fn extract_response_cookies<'a>(
headers: &'a hyper::HeaderMap,
) -> impl Iterator<Item = Result<Cookie<'a>, CookieParseError>> + 'a {
headers
.get_all(SET_COOKIE)
.iter()
.map(|value| Cookie::parse(value))
}
/// Error representing a parse failure of a 'Set-Cookie' header.
pub(crate) struct CookieParseError(cookie_crate::ParseError);
impl<'a> fmt::Debug for CookieParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl<'a> fmt::Display for CookieParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl std::error::Error for CookieParseError {}
// ===== impl Jar =====
impl Jar {
/// Add a cookie to this jar.
///
/// # Example
///
/// ```
/// use reqwest::{cookie::Jar, Url};
///
/// let cookie = "foo=bar; Domain=yolo.local";
/// let url = "https://yolo.local".parse::<Url>().unwrap();
///
/// let jar = Jar::default();
/// jar.add_cookie_str(cookie, &url);
///
/// // and now add to a `ClientBuilder`?
/// ```
pub fn add_cookie_str(&self, cookie: &str, url: &url::Url) {
let cookies = cookie_crate::Cookie::parse(cookie)
.ok()
.map(|c| c.into_owned())
.into_iter();
self.0.write().unwrap().store_response_cookies(cookies, url);
}
}
impl CookieStore for Jar {
fn set_cookies(&self, cookie_headers: &mut dyn Iterator<Item = &HeaderValue>, url: &url::Url) {
let iter =
cookie_headers.filter_map(|val| Cookie::parse(val).map(|c| c.0.into_owned()).ok());
self.0.write().unwrap().store_response_cookies(iter, url);
}
fn cookies(&self, url: &url::Url) -> Option<HeaderValue> {
let s = self
.0
.read()
.unwrap()
.get_request_values(url)
.map(|(name, value)| format!("{name}={value}"))
.collect::<Vec<_>>()
.join("; ");
if s.is_empty() {
return None;
}
HeaderValue::from_maybe_shared(Bytes::from(s)).ok()
}
}
pub(crate) mod service {
use crate::cookie;
use http::{Request, Response};
use http_body::Body;
use pin_project_lite::pin_project;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::ready;
use std::task::Context;
use std::task::Poll;
use tower::Service;
use url::Url;
/// A [`Service`] that adds cookie support to a lower-level [`Service`].
#[derive(Clone)]
pub struct CookieService<S> {
inner: S,
cookie_store: Option<Arc<dyn cookie::CookieStore>>,
}
impl<S> CookieService<S> {
/// Create a new [`CookieService`].
pub fn new(inner: S, cookie_store: Option<Arc<dyn cookie::CookieStore>>) -> Self {
Self {
inner,
cookie_store,
}
}
}
impl<ReqBody, ResBody, S> Service<Request<ReqBody>> for CookieService<S>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
ReqBody: Body + Default,
{
type Response = Response<ResBody>;
type Error = S::Error;
type Future = ResponseFuture<S, ReqBody>;
#[inline]
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future {
let clone = self.inner.clone();
let mut inner = std::mem::replace(&mut self.inner, clone);
let url = Url::parse(req.uri().to_string().as_str()).expect("invalid URL");
if let Some(cookie_store) = self.cookie_store.as_ref() {
if req.headers().get(crate::header::COOKIE).is_none() {
let headers = req.headers_mut();
crate::util::add_cookie_header(headers, &**cookie_store, &url);
}
}
let cookie_store = self.cookie_store.clone();
ResponseFuture {
future: inner.call(req),
cookie_store,
url,
}
}
}
pin_project! {
#[allow(missing_debug_implementations)]
#[derive(Clone)]
/// A [`Future`] that adds cookie support to a lower-level [`Future`].
pub struct ResponseFuture<S, B>
where
S: Service<Request<B>>,
{
#[pin]
future: S::Future,
cookie_store: Option<Arc<dyn cookie::CookieStore>>,
url: Url,
}
}
impl<S, ReqBody, ResBody> Future for ResponseFuture<S, ReqBody>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
ReqBody: Body + Default,
{
type Output = Result<Response<ResBody>, S::Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let cookie_store = self.cookie_store.clone();
let url = self.url.clone();
let res = ready!(self.project().future.as_mut().poll(cx)?);
if let Some(cookie_store) = cookie_store.as_ref() {
let mut cookies = cookie::extract_response_cookie_headers(res.headers()).peekable();
if cookies.peek().is_some() {
cookie_store.set_cookies(&mut cookies, &url);
}
}
Poll::Ready(Ok(res))
}
}
}

32
vendor/reqwest/src/dns/gai.rs vendored Normal file
View File

@@ -0,0 +1,32 @@
use hyper_util::client::legacy::connect::dns::GaiResolver as HyperGaiResolver;
use tower_service::Service;
use crate::dns::{Addrs, Name, Resolve, Resolving};
use crate::error::BoxError;
#[derive(Debug)]
pub struct GaiResolver(HyperGaiResolver);
impl GaiResolver {
pub fn new() -> Self {
Self(HyperGaiResolver::new())
}
}
impl Default for GaiResolver {
fn default() -> Self {
GaiResolver::new()
}
}
impl Resolve for GaiResolver {
fn resolve(&self, name: Name) -> Resolving {
let mut this = self.0.clone();
Box::pin(async move {
this.call(name.0)
.await
.map(|addrs| Box::new(addrs) as Addrs)
.map_err(|err| Box::new(err) as BoxError)
})
}
}

73
vendor/reqwest/src/dns/hickory.rs vendored Normal file
View File

@@ -0,0 +1,73 @@
//! DNS resolution via the [hickory-resolver](https://github.com/hickory-dns/hickory-dns) crate
use hickory_resolver::{
config::LookupIpStrategy, lookup_ip::LookupIpIntoIter, ResolveError, TokioResolver,
};
use once_cell::sync::OnceCell;
use std::fmt;
use std::net::SocketAddr;
use std::sync::Arc;
use super::{Addrs, Name, Resolve, Resolving};
/// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait.
#[derive(Debug, Default, Clone)]
pub(crate) struct HickoryDnsResolver {
/// Since we might not have been called in the context of a
/// Tokio Runtime in initialization, so we must delay the actual
/// construction of the resolver.
state: Arc<OnceCell<TokioResolver>>,
}
struct SocketAddrs {
iter: LookupIpIntoIter,
}
#[derive(Debug)]
struct HickoryDnsSystemConfError(ResolveError);
impl Resolve for HickoryDnsResolver {
fn resolve(&self, name: Name) -> Resolving {
let resolver = self.clone();
Box::pin(async move {
let resolver = resolver.state.get_or_try_init(new_resolver)?;
let lookup = resolver.lookup_ip(name.as_str()).await?;
let addrs: Addrs = Box::new(SocketAddrs {
iter: lookup.into_iter(),
});
Ok(addrs)
})
}
}
impl Iterator for SocketAddrs {
type Item = SocketAddr;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|ip_addr| SocketAddr::new(ip_addr, 0))
}
}
/// Create a new resolver with the default configuration,
/// which reads from `/etc/resolve.conf`. The options are
/// overridden to look up for both IPv4 and IPv6 addresses
/// to work with "happy eyeballs" algorithm.
fn new_resolver() -> Result<TokioResolver, HickoryDnsSystemConfError> {
let mut builder = TokioResolver::builder_tokio().map_err(HickoryDnsSystemConfError)?;
builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
Ok(builder.build())
}
impl fmt::Display for HickoryDnsSystemConfError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("error reading DNS system conf for hickory-dns")
}
}
impl std::error::Error for HickoryDnsSystemConfError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}

12
vendor/reqwest/src/dns/mod.rs vendored Normal file
View File

@@ -0,0 +1,12 @@
//! DNS resolution
pub use resolve::{Addrs, Name, Resolve, Resolving};
pub(crate) use resolve::{DnsResolverWithOverrides, DynResolver};
#[cfg(docsrs)]
pub use resolve::IntoResolve;
pub(crate) mod gai;
#[cfg(feature = "hickory-dns")]
pub(crate) mod hickory;
pub(crate) mod resolve;

193
vendor/reqwest/src/dns/resolve.rs vendored Normal file
View File

@@ -0,0 +1,193 @@
use hyper_util::client::legacy::connect::dns::Name as HyperName;
use tower_service::Service;
use std::collections::HashMap;
use std::future::Future;
use std::net::SocketAddr;
use std::pin::Pin;
use std::str::FromStr;
use std::sync::Arc;
use std::task::{Context, Poll};
use crate::error::BoxError;
/// Alias for an `Iterator` trait object over `SocketAddr`.
pub type Addrs = Box<dyn Iterator<Item = SocketAddr> + Send>;
/// Alias for the `Future` type returned by a DNS resolver.
pub type Resolving = Pin<Box<dyn Future<Output = Result<Addrs, BoxError>> + Send>>;
/// Trait for customizing DNS resolution in reqwest.
pub trait Resolve: Send + Sync {
/// Performs DNS resolution on a `Name`.
/// The return type is a future containing an iterator of `SocketAddr`.
///
/// It differs from `tower_service::Service<Name>` in several ways:
/// * It is assumed that `resolve` will always be ready to poll.
/// * It does not need a mutable reference to `self`.
/// * Since trait objects cannot make use of associated types, it requires
/// wrapping the returned `Future` and its contained `Iterator` with `Box`.
///
/// Explicitly specified port in the URL will override any port in the resolved `SocketAddr`s.
/// Otherwise, port `0` will be replaced by the conventional port for the given scheme (e.g. 80 for http).
fn resolve(&self, name: Name) -> Resolving;
}
/// A name that must be resolved to addresses.
#[derive(Debug)]
pub struct Name(pub(super) HyperName);
/// A more general trait implemented for types implementing `Resolve`.
///
/// Unnameable, only exported to aid seeing what implements this.
pub trait IntoResolve {
#[doc(hidden)]
fn into_resolve(self) -> Arc<dyn Resolve>;
}
impl Name {
/// View the name as a string.
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl FromStr for Name {
type Err = sealed::InvalidNameError;
fn from_str(host: &str) -> Result<Self, Self::Err> {
HyperName::from_str(host)
.map(Name)
.map_err(|_| sealed::InvalidNameError { _ext: () })
}
}
#[derive(Clone)]
pub(crate) struct DynResolver {
resolver: Arc<dyn Resolve>,
}
impl DynResolver {
pub(crate) fn new(resolver: Arc<dyn Resolve>) -> Self {
Self { resolver }
}
#[cfg(feature = "socks")]
pub(crate) fn gai() -> Self {
Self::new(Arc::new(super::gai::GaiResolver::new()))
}
/// Resolve an HTTP host and port, not just a domain name.
///
/// This does the same thing that hyper-util's HttpConnector does, before
/// calling out to its underlying DNS resolver.
#[cfg(feature = "socks")]
pub(crate) async fn http_resolve(
&self,
target: &http::Uri,
) -> Result<impl Iterator<Item = std::net::SocketAddr>, BoxError> {
let host = target.host().ok_or("missing host")?;
let port = target
.port_u16()
.unwrap_or_else(|| match target.scheme_str() {
Some("https") => 443,
Some("socks4") | Some("socks4a") | Some("socks5") | Some("socks5h") => 1080,
_ => 80,
});
let explicit_port = target.port().is_some();
let addrs = self.resolver.resolve(host.parse()?).await?;
Ok(addrs.map(move |mut addr| {
if explicit_port || addr.port() == 0 {
addr.set_port(port);
}
addr
}))
}
}
impl Service<HyperName> for DynResolver {
type Response = Addrs;
type Error = BoxError;
type Future = Resolving;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, name: HyperName) -> Self::Future {
self.resolver.resolve(Name(name))
}
}
pub(crate) struct DnsResolverWithOverrides {
dns_resolver: Arc<dyn Resolve>,
overrides: Arc<HashMap<String, Vec<SocketAddr>>>,
}
impl DnsResolverWithOverrides {
pub(crate) fn new(
dns_resolver: Arc<dyn Resolve>,
overrides: HashMap<String, Vec<SocketAddr>>,
) -> Self {
DnsResolverWithOverrides {
dns_resolver,
overrides: Arc::new(overrides),
}
}
}
impl Resolve for DnsResolverWithOverrides {
fn resolve(&self, name: Name) -> Resolving {
match self.overrides.get(name.as_str()) {
Some(dest) => {
let addrs: Addrs = Box::new(dest.clone().into_iter());
Box::pin(std::future::ready(Ok(addrs)))
}
None => self.dns_resolver.resolve(name),
}
}
}
impl IntoResolve for Arc<dyn Resolve> {
fn into_resolve(self) -> Arc<dyn Resolve> {
self
}
}
impl<R> IntoResolve for Arc<R>
where
R: Resolve + 'static,
{
fn into_resolve(self) -> Arc<dyn Resolve> {
self
}
}
impl<R> IntoResolve for R
where
R: Resolve + 'static,
{
fn into_resolve(self) -> Arc<dyn Resolve> {
Arc::new(self)
}
}
mod sealed {
use std::fmt;
#[derive(Debug)]
pub struct InvalidNameError {
pub(super) _ext: (),
}
impl fmt::Display for InvalidNameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("invalid DNS name")
}
}
impl std::error::Error for InvalidNameError {}
}

462
vendor/reqwest/src/error.rs vendored Normal file
View File

@@ -0,0 +1,462 @@
#![cfg_attr(target_arch = "wasm32", allow(unused))]
use std::error::Error as StdError;
use std::fmt;
use std::io;
use crate::util::Escape;
use crate::{StatusCode, Url};
/// A `Result` alias where the `Err` case is `reqwest::Error`.
pub type Result<T> = std::result::Result<T, Error>;
/// The Errors that may occur when processing a `Request`.
///
/// Note: Errors may include the full URL used to make the `Request`. If the URL
/// contains sensitive information (e.g. an API key as a query parameter), be
/// sure to remove it ([`without_url`](Error::without_url))
pub struct Error {
inner: Box<Inner>,
}
pub(crate) type BoxError = Box<dyn StdError + Send + Sync>;
struct Inner {
kind: Kind,
source: Option<BoxError>,
url: Option<Url>,
}
impl Error {
pub(crate) fn new<E>(kind: Kind, source: Option<E>) -> Error
where
E: Into<BoxError>,
{
Error {
inner: Box::new(Inner {
kind,
source: source.map(Into::into),
url: None,
}),
}
}
/// Returns a possible URL related to this error.
///
/// # Examples
///
/// ```
/// # async fn run() {
/// // displays last stop of a redirect loop
/// let response = reqwest::get("http://site.with.redirect.loop").await;
/// if let Err(e) = response {
/// if e.is_redirect() {
/// if let Some(final_stop) = e.url() {
/// println!("redirect loop at {final_stop}");
/// }
/// }
/// }
/// # }
/// ```
pub fn url(&self) -> Option<&Url> {
self.inner.url.as_ref()
}
/// Returns a mutable reference to the URL related to this error
///
/// This is useful if you need to remove sensitive information from the URL
/// (e.g. an API key in the query), but do not want to remove the URL
/// entirely.
pub fn url_mut(&mut self) -> Option<&mut Url> {
self.inner.url.as_mut()
}
/// Add a url related to this error (overwriting any existing)
pub fn with_url(mut self, url: Url) -> Self {
self.inner.url = Some(url);
self
}
pub(crate) fn if_no_url(mut self, f: impl FnOnce() -> Url) -> Self {
if self.inner.url.is_none() {
self.inner.url = Some(f());
}
self
}
/// Strip the related url from this error (if, for example, it contains
/// sensitive information)
pub fn without_url(mut self) -> Self {
self.inner.url = None;
self
}
/// Returns true if the error is from a type Builder.
pub fn is_builder(&self) -> bool {
matches!(self.inner.kind, Kind::Builder)
}
/// Returns true if the error is from a `RedirectPolicy`.
pub fn is_redirect(&self) -> bool {
matches!(self.inner.kind, Kind::Redirect)
}
/// Returns true if the error is from `Response::error_for_status`.
pub fn is_status(&self) -> bool {
#[cfg(not(target_arch = "wasm32"))]
{
matches!(self.inner.kind, Kind::Status(_, _))
}
#[cfg(target_arch = "wasm32")]
{
matches!(self.inner.kind, Kind::Status(_))
}
}
/// Returns true if the error is related to a timeout.
pub fn is_timeout(&self) -> bool {
let mut source = self.source();
while let Some(err) = source {
if err.is::<TimedOut>() {
return true;
}
#[cfg(not(target_arch = "wasm32"))]
if let Some(hyper_err) = err.downcast_ref::<hyper::Error>() {
if hyper_err.is_timeout() {
return true;
}
}
if let Some(io) = err.downcast_ref::<io::Error>() {
if io.kind() == io::ErrorKind::TimedOut {
return true;
}
}
source = err.source();
}
false
}
/// Returns true if the error is related to the request
pub fn is_request(&self) -> bool {
matches!(self.inner.kind, Kind::Request)
}
#[cfg(not(target_arch = "wasm32"))]
/// Returns true if the error is related to connect
pub fn is_connect(&self) -> bool {
let mut source = self.source();
while let Some(err) = source {
if let Some(hyper_err) = err.downcast_ref::<hyper_util::client::legacy::Error>() {
if hyper_err.is_connect() {
return true;
}
}
source = err.source();
}
false
}
/// Returns true if the error is related to the request or response body
pub fn is_body(&self) -> bool {
matches!(self.inner.kind, Kind::Body)
}
/// Returns true if the error is related to decoding the response's body
pub fn is_decode(&self) -> bool {
matches!(self.inner.kind, Kind::Decode)
}
/// Returns the status code, if the error was generated from a response.
pub fn status(&self) -> Option<StatusCode> {
match self.inner.kind {
#[cfg(target_arch = "wasm32")]
Kind::Status(code) => Some(code),
#[cfg(not(target_arch = "wasm32"))]
Kind::Status(code, _) => Some(code),
_ => None,
}
}
/// Returns true if the error is related to a protocol upgrade request
pub fn is_upgrade(&self) -> bool {
matches!(self.inner.kind, Kind::Upgrade)
}
// private
#[allow(unused)]
pub(crate) fn into_io(self) -> io::Error {
io::Error::new(io::ErrorKind::Other, self)
}
}
/// Converts from external types to reqwest's
/// internal equivalents.
///
/// Currently only is used for `tower::timeout::error::Elapsed`.
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn cast_to_internal_error(error: BoxError) -> BoxError {
if error.is::<tower::timeout::error::Elapsed>() {
Box::new(crate::error::TimedOut) as BoxError
} else {
error
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut builder = f.debug_struct("reqwest::Error");
builder.field("kind", &self.inner.kind);
if let Some(ref url) = self.inner.url {
builder.field("url", &url.as_str());
}
if let Some(ref source) = self.inner.source {
builder.field("source", source);
}
builder.finish()
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.inner.kind {
Kind::Builder => f.write_str("builder error")?,
Kind::Request => f.write_str("error sending request")?,
Kind::Body => f.write_str("request or response body error")?,
Kind::Decode => f.write_str("error decoding response body")?,
Kind::Redirect => f.write_str("error following redirect")?,
Kind::Upgrade => f.write_str("error upgrading connection")?,
#[cfg(target_arch = "wasm32")]
Kind::Status(ref code) => {
let prefix = if code.is_client_error() {
"HTTP status client error"
} else {
debug_assert!(code.is_server_error());
"HTTP status server error"
};
write!(f, "{prefix} ({code})")?;
}
#[cfg(not(target_arch = "wasm32"))]
Kind::Status(ref code, ref reason) => {
let prefix = if code.is_client_error() {
"HTTP status client error"
} else {
debug_assert!(code.is_server_error());
"HTTP status server error"
};
if let Some(reason) = reason {
write!(
f,
"{prefix} ({} {})",
code.as_str(),
Escape::new(reason.as_bytes())
)?;
} else {
write!(f, "{prefix} ({code})")?;
}
}
};
if let Some(url) = &self.inner.url {
write!(f, " for url ({url})")?;
}
Ok(())
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.inner.source.as_ref().map(|e| &**e as _)
}
}
#[cfg(target_arch = "wasm32")]
impl From<crate::error::Error> for wasm_bindgen::JsValue {
fn from(err: Error) -> wasm_bindgen::JsValue {
js_sys::Error::from(err).into()
}
}
#[cfg(target_arch = "wasm32")]
impl From<crate::error::Error> for js_sys::Error {
fn from(err: Error) -> js_sys::Error {
js_sys::Error::new(&format!("{err}"))
}
}
#[derive(Debug)]
pub(crate) enum Kind {
Builder,
Request,
Redirect,
#[cfg(not(target_arch = "wasm32"))]
Status(StatusCode, Option<hyper::ext::ReasonPhrase>),
#[cfg(target_arch = "wasm32")]
Status(StatusCode),
Body,
Decode,
Upgrade,
}
// constructors
pub(crate) fn builder<E: Into<BoxError>>(e: E) -> Error {
Error::new(Kind::Builder, Some(e))
}
pub(crate) fn body<E: Into<BoxError>>(e: E) -> Error {
Error::new(Kind::Body, Some(e))
}
pub(crate) fn decode<E: Into<BoxError>>(e: E) -> Error {
Error::new(Kind::Decode, Some(e))
}
pub(crate) fn request<E: Into<BoxError>>(e: E) -> Error {
Error::new(Kind::Request, Some(e))
}
pub(crate) fn redirect<E: Into<BoxError>>(e: E, url: Url) -> Error {
Error::new(Kind::Redirect, Some(e)).with_url(url)
}
pub(crate) fn status_code(
url: Url,
status: StatusCode,
#[cfg(not(target_arch = "wasm32"))] reason: Option<hyper::ext::ReasonPhrase>,
) -> Error {
Error::new(
Kind::Status(
status,
#[cfg(not(target_arch = "wasm32"))]
reason,
),
None::<Error>,
)
.with_url(url)
}
pub(crate) fn url_bad_scheme(url: Url) -> Error {
Error::new(Kind::Builder, Some(BadScheme)).with_url(url)
}
pub(crate) fn url_invalid_uri(url: Url) -> Error {
Error::new(Kind::Builder, Some("Parsed Url is not a valid Uri")).with_url(url)
}
if_wasm! {
pub(crate) fn wasm(js_val: wasm_bindgen::JsValue) -> BoxError {
format!("{js_val:?}").into()
}
}
pub(crate) fn upgrade<E: Into<BoxError>>(e: E) -> Error {
Error::new(Kind::Upgrade, Some(e))
}
// io::Error helpers
#[allow(unused)]
pub(crate) fn decode_io(e: io::Error) -> Error {
if e.get_ref().map(|r| r.is::<Error>()).unwrap_or(false) {
*e.into_inner()
.expect("io::Error::get_ref was Some(_)")
.downcast::<Error>()
.expect("StdError::is() was true")
} else {
decode(e)
}
}
// internal Error "sources"
#[derive(Debug)]
pub(crate) struct TimedOut;
impl fmt::Display for TimedOut {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("operation timed out")
}
}
impl StdError for TimedOut {}
#[derive(Debug)]
pub(crate) struct BadScheme;
impl fmt::Display for BadScheme {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("URL scheme is not allowed")
}
}
impl StdError for BadScheme {}
#[cfg(test)]
mod tests {
use super::*;
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
#[test]
fn test_source_chain() {
let root = Error::new(Kind::Request, None::<Error>);
assert!(root.source().is_none());
let link = super::body(root);
assert!(link.source().is_some());
assert_send::<Error>();
assert_sync::<Error>();
}
#[test]
fn mem_size_of() {
use std::mem::size_of;
assert_eq!(size_of::<Error>(), size_of::<usize>());
}
#[test]
fn roundtrip_io_error() {
let orig = super::request("orig");
// Convert reqwest::Error into an io::Error...
let io = orig.into_io();
// Convert that io::Error back into a reqwest::Error...
let err = super::decode_io(io);
// It should have pulled out the original, not nested it...
match err.inner.kind {
Kind::Request => (),
_ => panic!("{err:?}"),
}
}
#[test]
fn from_unknown_io_error() {
let orig = io::Error::new(io::ErrorKind::Other, "orly");
let err = super::decode_io(orig);
match err.inner.kind {
Kind::Decode => (),
_ => panic!("{err:?}"),
}
}
#[test]
fn is_timeout() {
let err = super::request(super::TimedOut);
assert!(err.is_timeout());
// todo: test `hyper::Error::is_timeout` when we can easily construct one
let io = io::Error::from(io::ErrorKind::TimedOut);
let nested = super::request(io);
assert!(nested.is_timeout());
}
}

117
vendor/reqwest/src/into_url.rs vendored Normal file
View File

@@ -0,0 +1,117 @@
use url::Url;
/// A trait to try to convert some type into a `Url`.
///
/// This trait is "sealed", such that only types within reqwest can
/// implement it.
pub trait IntoUrl: IntoUrlSealed {}
impl IntoUrl for Url {}
impl IntoUrl for String {}
impl<'a> IntoUrl for &'a str {}
impl<'a> IntoUrl for &'a String {}
pub trait IntoUrlSealed {
// Besides parsing as a valid `Url`, the `Url` must be a valid
// `http::Uri`, in that it makes sense to use in a network request.
fn into_url(self) -> crate::Result<Url>;
fn as_str(&self) -> &str;
}
impl IntoUrlSealed for Url {
fn into_url(self) -> crate::Result<Url> {
// With blob url the `self.has_host()` check is always false, so we
// remove the `blob:` scheme and check again if the url is valid.
#[cfg(target_arch = "wasm32")]
if self.scheme() == "blob"
&& self.path().starts_with("http") // Check if the path starts with http or https to avoid validating a `blob:blob:...` url.
&& self.as_str()[5..].into_url().is_ok()
{
return Ok(self);
}
if self.has_host() {
Ok(self)
} else {
Err(crate::error::url_bad_scheme(self))
}
}
fn as_str(&self) -> &str {
self.as_ref()
}
}
impl<'a> IntoUrlSealed for &'a str {
fn into_url(self) -> crate::Result<Url> {
Url::parse(self).map_err(crate::error::builder)?.into_url()
}
fn as_str(&self) -> &str {
self
}
}
impl<'a> IntoUrlSealed for &'a String {
fn into_url(self) -> crate::Result<Url> {
(&**self).into_url()
}
fn as_str(&self) -> &str {
self.as_ref()
}
}
impl IntoUrlSealed for String {
fn into_url(self) -> crate::Result<Url> {
(&*self).into_url()
}
fn as_str(&self) -> &str {
self.as_ref()
}
}
if_hyper! {
pub(crate) fn try_uri(url: &Url) -> crate::Result<http::Uri> {
url.as_str()
.parse()
.map_err(|_| crate::error::url_invalid_uri(url.clone()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
#[test]
fn into_url_file_scheme() {
let err = "file:///etc/hosts".into_url().unwrap_err();
assert_eq!(
err.source().unwrap().to_string(),
"URL scheme is not allowed"
);
}
#[test]
fn into_url_blob_scheme() {
let err = "blob:https://example.com".into_url().unwrap_err();
assert_eq!(
err.source().unwrap().to_string(),
"URL scheme is not allowed"
);
}
if_wasm! {
use wasm_bindgen_test::*;
#[wasm_bindgen_test]
fn into_url_blob_scheme_wasm() {
let url = "blob:http://example.com".into_url().unwrap();
assert_eq!(url.as_str(), "blob:http://example.com");
}
}
}

395
vendor/reqwest/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,395 @@
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(test, deny(warnings))]
//! # reqwest
//!
//! The `reqwest` crate provides a convenient, higher-level HTTP
//! [`Client`][client].
//!
//! It handles many of the things that most people just expect an HTTP client
//! to do for them.
//!
//! - Async and [blocking] Clients
//! - Plain bodies, [JSON](#json), [urlencoded](#forms), [multipart]
//! - Customizable [redirect policy](#redirect-policies)
//! - HTTP [Proxies](#proxies)
//! - Uses [TLS](#tls) by default
//! - Cookies
//!
//! The [`reqwest::Client`][client] is asynchronous (requiring Tokio). For
//! applications wishing to only make a few HTTP requests, the
//! [`reqwest::blocking`](blocking) API may be more convenient.
//!
//! Additional learning resources include:
//!
//! - [The Rust Cookbook](https://rust-lang-nursery.github.io/rust-cookbook/web/clients.html)
//! - [reqwest Repository Examples](https://github.com/seanmonstar/reqwest/tree/master/examples)
//!
//! ## Commercial Support
//!
//! For private advice, support, reviews, access to the maintainer, and the
//! like, reach out for [commercial support][sponsor].
//!
//! ## Making a GET request
//!
//! For a single request, you can use the [`get`][get] shortcut method.
//!
//! ```rust
//! # async fn run() -> Result<(), reqwest::Error> {
//! let body = reqwest::get("https://www.rust-lang.org")
//! .await?
//! .text()
//! .await?;
//!
//! println!("body = {body:?}");
//! # Ok(())
//! # }
//! ```
//!
//! **NOTE**: If you plan to perform multiple requests, it is best to create a
//! [`Client`][client] and reuse it, taking advantage of keep-alive connection
//! pooling.
//!
//! ## Making POST requests (or setting request bodies)
//!
//! There are several ways you can set the body of a request. The basic one is
//! by using the `body()` method of a [`RequestBuilder`][builder]. This lets you set the
//! exact raw bytes of what the body should be. It accepts various types,
//! including `String` and `Vec<u8>`. If you wish to pass a custom
//! type, you can use the `reqwest::Body` constructors.
//!
//! ```rust
//! # use reqwest::Error;
//! #
//! # async fn run() -> Result<(), Error> {
//! let client = reqwest::Client::new();
//! let res = client.post("http://httpbin.org/post")
//! .body("the exact body that is sent")
//! .send()
//! .await?;
//! # Ok(())
//! # }
//! ```
//!
//! ### Forms
//!
//! It's very common to want to send form data in a request body. This can be
//! done with any type that can be serialized into form data.
//!
//! This can be an array of tuples, or a `HashMap`, or a custom type that
//! implements [`Serialize`][serde].
//!
//! ```rust
//! # use reqwest::Error;
//! #
//! # async fn run() -> Result<(), Error> {
//! // This will POST a body of `foo=bar&baz=quux`
//! let params = [("foo", "bar"), ("baz", "quux")];
//! let client = reqwest::Client::new();
//! let res = client.post("http://httpbin.org/post")
//! .form(&params)
//! .send()
//! .await?;
//! # Ok(())
//! # }
//! ```
//!
//! ### JSON
//!
//! There is also a `json` method helper on the [`RequestBuilder`][builder] that works in
//! a similar fashion the `form` method. It can take any value that can be
//! serialized into JSON. The feature `json` is required.
//!
//! ```rust
//! # use reqwest::Error;
//! # use std::collections::HashMap;
//! #
//! # #[cfg(feature = "json")]
//! # async fn run() -> Result<(), Error> {
//! // This will POST a body of `{"lang":"rust","body":"json"}`
//! let mut map = HashMap::new();
//! map.insert("lang", "rust");
//! map.insert("body", "json");
//!
//! let client = reqwest::Client::new();
//! let res = client.post("http://httpbin.org/post")
//! .json(&map)
//! .send()
//! .await?;
//! # Ok(())
//! # }
//! ```
//!
//! ## Redirect Policies
//!
//! By default, a `Client` will automatically handle HTTP redirects, having a
//! maximum redirect chain of 10 hops. To customize this behavior, a
//! [`redirect::Policy`][redirect] can be used with a `ClientBuilder`.
//!
//! ## Cookies
//!
//! The automatic storing and sending of session cookies can be enabled with
//! the [`cookie_store`][ClientBuilder::cookie_store] method on `ClientBuilder`.
//!
//! ## Proxies
//!
//! **NOTE**: System proxies are enabled by default.
//!
//! System proxies look in environment variables to set HTTP or HTTPS proxies.
//!
//! `HTTP_PROXY` or `http_proxy` provide HTTP proxies for HTTP connections while
//! `HTTPS_PROXY` or `https_proxy` provide HTTPS proxies for HTTPS connections.
//! `ALL_PROXY` or `all_proxy` provide proxies for both HTTP and HTTPS connections.
//! If both the all proxy and HTTP or HTTPS proxy variables are set the more specific
//! HTTP or HTTPS proxies take precedence.
//!
//! These can be overwritten by adding a [`Proxy`] to `ClientBuilder`
//! i.e. `let proxy = reqwest::Proxy::http("https://secure.example")?;`
//! or disabled by calling `ClientBuilder::no_proxy()`.
//!
//! `socks` feature is required if you have configured socks proxy like this:
//!
//! ```bash
//! export https_proxy=socks5://127.0.0.1:1086
//! ```
//!
//! ## TLS
//!
//! A `Client` will use transport layer security (TLS) by default to connect to
//! HTTPS destinations.
//!
//! - Additional server certificates can be configured on a `ClientBuilder`
//! with the [`Certificate`] type.
//! - Client certificates can be added to a `ClientBuilder` with the
//! [`Identity`] type.
//! - Various parts of TLS can also be configured or even disabled on the
//! `ClientBuilder`.
//!
//! See more details in the [`tls`] module.
//!
//! ## WASM
//!
//! The Client implementation automatically switches to the WASM one when the target_arch is wasm32,
//! the usage is basically the same as the async api. Some of the features are disabled in wasm
//! : [`tls`], [`cookie`], [`blocking`], as well as various `ClientBuilder` methods such as `timeout()` and `connector_layer()`.
//!
//! TLS and cookies are provided through the browser environment, so reqwest can issue TLS requests with cookies,
//! but has limited configuration.
//!
//! ## Optional Features
//!
//! The following are a list of [Cargo features][cargo-features] that can be
//! enabled or disabled:
//!
//! - **http2** *(enabled by default)*: Enables HTTP/2 support.
//! - **default-tls** *(enabled by default)*: Provides TLS support to connect
//! over HTTPS.
//! - **native-tls**: Enables TLS functionality provided by `native-tls`.
//! - **native-tls-vendored**: Enables the `vendored` feature of `native-tls`.
//! - **native-tls-alpn**: Enables the `alpn` feature of `native-tls`.
//! - **rustls-tls**: Enables TLS functionality provided by `rustls`.
//! Equivalent to `rustls-tls-webpki-roots`.
//! - **rustls-tls-manual-roots**: Enables TLS functionality provided by `rustls`,
//! without setting any root certificates. Roots have to be specified manually.
//! - **rustls-tls-webpki-roots**: Enables TLS functionality provided by `rustls`,
//! while using root certificates from the `webpki-roots` crate.
//! - **rustls-tls-native-roots**: Enables TLS functionality provided by `rustls`,
//! while using root certificates from the `rustls-native-certs` crate.
//! - **blocking**: Provides the [blocking][] client API.
//! - **charset** *(enabled by default)*: Improved support for decoding text.
//! - **cookies**: Provides cookie session support.
//! - **gzip**: Provides response body gzip decompression.
//! - **brotli**: Provides response body brotli decompression.
//! - **zstd**: Provides response body zstd decompression.
//! - **deflate**: Provides response body deflate decompression.
//! - **json**: Provides serialization and deserialization for JSON bodies.
//! - **multipart**: Provides functionality for multipart forms.
//! - **stream**: Adds support for `futures::Stream`.
//! - **socks**: Provides SOCKS5 proxy support.
//! - **hickory-dns**: Enables a hickory-dns async resolver instead of default
//! threadpool using `getaddrinfo`.
//! - **system-proxy** *(enabled by default)*: Use Windows and macOS system
//! proxy settings automatically.
//!
//! ## Unstable Features
//!
//! Some feature flags require additional opt-in by the application, by setting
//! a `reqwest_unstable` flag.
//!
//! - **http3** *(unstable)*: Enables support for sending HTTP/3 requests.
//!
//! These features are unstable, and experimental. Details about them may be
//! changed in patch releases.
//!
//! You can pass such a flag to the compiler via `.cargo/config`, or
//! environment variables, such as:
//!
//! ```notrust
//! RUSTFLAGS="--cfg reqwest_unstable" cargo build
//! ```
//!
//! ## Sponsors
//!
//! Support this project by becoming a [sponsor][].
//!
//! [hyper]: https://hyper.rs
//! [blocking]: ./blocking/index.html
//! [client]: ./struct.Client.html
//! [response]: ./struct.Response.html
//! [get]: ./fn.get.html
//! [builder]: ./struct.RequestBuilder.html
//! [serde]: http://serde.rs
//! [redirect]: crate::redirect
//! [Proxy]: ./struct.Proxy.html
//! [cargo-features]: https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section
//! [sponsor]: https://seanmonstar.com/sponsor
#[cfg(all(feature = "http3", not(reqwest_unstable)))]
compile_error!(
"\
The `http3` feature is unstable, and requires the \
`RUSTFLAGS='--cfg reqwest_unstable'` environment variable to be set.\
"
);
// Ignore `unused_crate_dependencies` warnings.
// Used in many features that they're not worth making it optional.
use futures_core as _;
use sync_wrapper as _;
macro_rules! if_wasm {
($($item:item)*) => {$(
#[cfg(target_arch = "wasm32")]
$item
)*}
}
macro_rules! if_hyper {
($($item:item)*) => {$(
#[cfg(not(target_arch = "wasm32"))]
$item
)*}
}
pub use http::header;
pub use http::Method;
pub use http::{StatusCode, Version};
pub use url::Url;
// universal mods
#[macro_use]
mod error;
// TODO: remove `if_hyper` if wasm has been migrated to new config system.
if_hyper! {
mod config;
}
mod into_url;
mod response;
pub use self::error::{Error, Result};
pub use self::into_url::IntoUrl;
pub use self::response::ResponseBuilderExt;
/// Shortcut method to quickly make a `GET` request.
///
/// See also the methods on the [`reqwest::Response`](./struct.Response.html)
/// type.
///
/// **NOTE**: This function creates a new internal `Client` on each call,
/// and so should not be used if making many requests. Create a
/// [`Client`](./struct.Client.html) instead.
///
/// # Examples
///
/// ```rust
/// # async fn run() -> Result<(), reqwest::Error> {
/// let body = reqwest::get("https://www.rust-lang.org").await?
/// .text().await?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
///
/// This function fails if:
///
/// - native TLS backend cannot be initialized
/// - supplied `Url` cannot be parsed
/// - there was an error while sending request
/// - redirect limit was exhausted
pub async fn get<T: IntoUrl>(url: T) -> crate::Result<Response> {
Client::builder().build()?.get(url).send().await
}
fn _assert_impls() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
fn assert_clone<T: Clone>() {}
assert_send::<Client>();
assert_sync::<Client>();
assert_clone::<Client>();
assert_send::<Request>();
assert_send::<RequestBuilder>();
#[cfg(not(target_arch = "wasm32"))]
{
assert_send::<Response>();
}
assert_send::<Error>();
assert_sync::<Error>();
assert_send::<Body>();
assert_sync::<Body>();
}
if_hyper! {
#[cfg(test)]
#[macro_use]
extern crate doc_comment;
#[cfg(test)]
doctest!("../README.md");
pub use self::async_impl::{
Body, Client, ClientBuilder, Request, RequestBuilder, Response, Upgraded,
};
pub use self::proxy::{Proxy,NoProxy};
#[cfg(feature = "__tls")]
// Re-exports, to be removed in a future release
pub use tls::{Certificate, Identity};
#[cfg(feature = "multipart")]
pub use self::async_impl::multipart;
mod async_impl;
#[cfg(feature = "blocking")]
pub mod blocking;
mod connect;
#[cfg(feature = "cookies")]
pub mod cookie;
pub mod dns;
mod proxy;
pub mod redirect;
pub mod retry;
#[cfg(feature = "__tls")]
pub mod tls;
mod util;
#[cfg(docsrs)]
pub use connect::uds::UnixSocketProvider;
}
if_wasm! {
mod wasm;
mod util;
pub use self::wasm::{Body, Client, ClientBuilder, Request, RequestBuilder, Response};
#[cfg(feature = "multipart")]
pub use self::wasm::multipart;
}

952
vendor/reqwest/src/proxy.rs vendored Normal file
View File

@@ -0,0 +1,952 @@
use std::error::Error;
use std::fmt;
use std::sync::Arc;
use http::uri::Scheme;
use http::{header::HeaderValue, HeaderMap, Uri};
use hyper_util::client::proxy::matcher;
use crate::into_url::{IntoUrl, IntoUrlSealed};
use crate::Url;
// # Internals
//
// This module is a couple pieces:
//
// - The public builder API
// - The internal built types that our Connector knows how to use.
//
// The user creates a builder (`reqwest::Proxy`), and configures any extras.
// Once that type is passed to the `ClientBuilder`, we convert it into the
// built matcher types, making use of `hyper-util`'s matchers.
/// Configuration of a proxy that a `Client` should pass requests to.
///
/// A `Proxy` has a couple pieces to it:
///
/// - a URL of how to talk to the proxy
/// - rules on what `Client` requests should be directed to the proxy
///
/// For instance, let's look at `Proxy::http`:
///
/// ```rust
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let proxy = reqwest::Proxy::http("https://secure.example")?;
/// # Ok(())
/// # }
/// ```
///
/// This proxy will intercept all HTTP requests, and make use of the proxy
/// at `https://secure.example`. A request to `http://hyper.rs` will talk
/// to your proxy. A request to `https://hyper.rs` will not.
///
/// Multiple `Proxy` rules can be configured for a `Client`. The `Client` will
/// check each `Proxy` in the order it was added. This could mean that a
/// `Proxy` added first with eager intercept rules, such as `Proxy::all`,
/// would prevent a `Proxy` later in the list from ever working, so take care.
///
/// By enabling the `"socks"` feature it is possible to use a socks proxy:
/// ```rust
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let proxy = reqwest::Proxy::http("socks5://192.168.1.1:9000")?;
/// # Ok(())
/// # }
/// ```
#[derive(Clone)]
pub struct Proxy {
extra: Extra,
intercept: Intercept,
no_proxy: Option<NoProxy>,
}
/// A configuration for filtering out requests that shouldn't be proxied
#[derive(Clone, Debug, Default)]
pub struct NoProxy {
inner: String,
}
#[derive(Clone)]
struct Extra {
auth: Option<HeaderValue>,
misc: Option<HeaderMap>,
}
// ===== Internal =====
pub(crate) struct Matcher {
inner: Matcher_,
extra: Extra,
maybe_has_http_auth: bool,
maybe_has_http_custom_headers: bool,
}
enum Matcher_ {
Util(matcher::Matcher),
Custom(Custom),
}
/// Our own type, wrapping an `Intercept`, since we may have a few additional
/// pieces attached thanks to `reqwest`s extra proxy configuration.
pub(crate) struct Intercepted {
inner: matcher::Intercept,
/// This is because of `reqwest::Proxy`'s design which allows configuring
/// an explicit auth, besides what might have been in the URL (or Custom).
extra: Extra,
}
/*
impl ProxyScheme {
fn maybe_http_auth(&self) -> Option<&HeaderValue> {
match self {
ProxyScheme::Http { auth, .. } | ProxyScheme::Https { auth, .. } => auth.as_ref(),
#[cfg(feature = "socks")]
_ => None,
}
}
fn maybe_http_custom_headers(&self) -> Option<&HeaderMap> {
match self {
ProxyScheme::Http { misc, .. } | ProxyScheme::Https { misc, .. } => misc.as_ref(),
#[cfg(feature = "socks")]
_ => None,
}
}
}
*/
/// Trait used for converting into a proxy scheme. This trait supports
/// parsing from a URL-like type, whilst also supporting proxy schemes
/// built directly using the factory methods.
pub trait IntoProxy {
fn into_proxy(self) -> crate::Result<Url>;
}
impl<S: IntoUrl> IntoProxy for S {
fn into_proxy(self) -> crate::Result<Url> {
match self.as_str().into_url() {
Ok(mut url) => {
// If the scheme is a SOCKS protocol and no port is specified, set the default
if url.port().is_none()
&& matches!(url.scheme(), "socks4" | "socks4a" | "socks5" | "socks5h")
{
let _ = url.set_port(Some(1080));
}
Ok(url)
}
Err(e) => {
let mut presumed_to_have_scheme = true;
let mut source = e.source();
while let Some(err) = source {
if let Some(parse_error) = err.downcast_ref::<url::ParseError>() {
if *parse_error == url::ParseError::RelativeUrlWithoutBase {
presumed_to_have_scheme = false;
break;
}
} else if err.downcast_ref::<crate::error::BadScheme>().is_some() {
presumed_to_have_scheme = false;
break;
}
source = err.source();
}
if presumed_to_have_scheme {
return Err(crate::error::builder(e));
}
// the issue could have been caused by a missing scheme, so we try adding http://
let try_this = format!("http://{}", self.as_str());
try_this.into_url().map_err(|_| {
// return the original error
crate::error::builder(e)
})
}
}
}
}
// These bounds are accidentally leaked by the blanket impl of IntoProxy
// for all types that implement IntoUrl. So, this function exists to detect
// if we were to break those bounds for a user.
fn _implied_bounds() {
fn prox<T: IntoProxy>(_t: T) {}
fn url<T: IntoUrl>(t: T) {
prox(t);
}
}
impl Proxy {
/// Proxy all HTTP traffic to the passed URL.
///
/// # Example
///
/// ```
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let client = reqwest::Client::builder()
/// .proxy(reqwest::Proxy::http("https://my.prox")?)
/// .build()?;
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn http<U: IntoProxy>(proxy_scheme: U) -> crate::Result<Proxy> {
Ok(Proxy::new(Intercept::Http(proxy_scheme.into_proxy()?)))
}
/// Proxy all HTTPS traffic to the passed URL.
///
/// # Example
///
/// ```
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let client = reqwest::Client::builder()
/// .proxy(reqwest::Proxy::https("https://example.prox:4545")?)
/// .build()?;
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn https<U: IntoProxy>(proxy_scheme: U) -> crate::Result<Proxy> {
Ok(Proxy::new(Intercept::Https(proxy_scheme.into_proxy()?)))
}
/// Proxy **all** traffic to the passed URL.
///
/// "All" refers to `https` and `http` URLs. Other schemes are not
/// recognized by reqwest.
///
/// # Example
///
/// ```
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let client = reqwest::Client::builder()
/// .proxy(reqwest::Proxy::all("http://pro.xy")?)
/// .build()?;
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn all<U: IntoProxy>(proxy_scheme: U) -> crate::Result<Proxy> {
Ok(Proxy::new(Intercept::All(proxy_scheme.into_proxy()?)))
}
/// Provide a custom function to determine what traffic to proxy to where.
///
/// # Example
///
/// ```
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let target = reqwest::Url::parse("https://my.prox")?;
/// let client = reqwest::Client::builder()
/// .proxy(reqwest::Proxy::custom(move |url| {
/// if url.host_str() == Some("hyper.rs") {
/// Some(target.clone())
/// } else {
/// None
/// }
/// }))
/// .build()?;
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn custom<F, U: IntoProxy>(fun: F) -> Proxy
where
F: Fn(&Url) -> Option<U> + Send + Sync + 'static,
{
Proxy::new(Intercept::Custom(Custom {
func: Arc::new(move |url| fun(url).map(IntoProxy::into_proxy)),
no_proxy: None,
}))
}
fn new(intercept: Intercept) -> Proxy {
Proxy {
extra: Extra {
auth: None,
misc: None,
},
intercept,
no_proxy: None,
}
}
/// Set the `Proxy-Authorization` header using Basic auth.
///
/// # Example
///
/// ```
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let proxy = reqwest::Proxy::https("http://localhost:1234")?
/// .basic_auth("Aladdin", "open sesame");
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn basic_auth(mut self, username: &str, password: &str) -> Proxy {
match self.intercept {
Intercept::All(ref mut s)
| Intercept::Http(ref mut s)
| Intercept::Https(ref mut s) => url_auth(s, username, password),
Intercept::Custom(_) => {
let header = encode_basic_auth(username, password);
self.extra.auth = Some(header);
}
}
self
}
/// Set the `Proxy-Authorization` header to a specified value.
///
/// # Example
///
/// ```
/// # extern crate reqwest;
/// # use reqwest::header::*;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let proxy = reqwest::Proxy::https("http://localhost:1234")?
/// .custom_http_auth(HeaderValue::from_static("justletmeinalreadyplease"));
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn custom_http_auth(mut self, header_value: HeaderValue) -> Proxy {
self.extra.auth = Some(header_value);
self
}
/// Adds a Custom Headers to Proxy
/// Adds custom headers to this Proxy
///
/// # Example
/// ```
/// # extern crate reqwest;
/// # use reqwest::header::*;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let mut headers = HeaderMap::new();
/// headers.insert(USER_AGENT, "reqwest".parse().unwrap());
/// let proxy = reqwest::Proxy::https("http://localhost:1234")?
/// .headers(headers);
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn headers(mut self, headers: HeaderMap) -> Proxy {
match self.intercept {
Intercept::All(_) | Intercept::Http(_) | Intercept::Https(_) | Intercept::Custom(_) => {
self.extra.misc = Some(headers);
}
}
self
}
/// Adds a `No Proxy` exclusion list to this Proxy
///
/// # Example
///
/// ```
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let proxy = reqwest::Proxy::https("http://localhost:1234")?
/// .no_proxy(reqwest::NoProxy::from_string("direct.tld, sub.direct2.tld"));
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn no_proxy(mut self, no_proxy: Option<NoProxy>) -> Proxy {
self.no_proxy = no_proxy;
self
}
pub(crate) fn into_matcher(self) -> Matcher {
let Proxy {
intercept,
extra,
no_proxy,
} = self;
let maybe_has_http_auth;
let maybe_has_http_custom_headers;
let inner = match intercept {
Intercept::All(url) => {
maybe_has_http_auth = cache_maybe_has_http_auth(&url, &extra.auth);
maybe_has_http_custom_headers =
cache_maybe_has_http_custom_headers(&url, &extra.misc);
Matcher_::Util(
matcher::Matcher::builder()
.all(String::from(url))
.no(no_proxy.as_ref().map(|n| n.inner.as_ref()).unwrap_or(""))
.build(),
)
}
Intercept::Http(url) => {
maybe_has_http_auth = cache_maybe_has_http_auth(&url, &extra.auth);
maybe_has_http_custom_headers =
cache_maybe_has_http_custom_headers(&url, &extra.misc);
Matcher_::Util(
matcher::Matcher::builder()
.http(String::from(url))
.no(no_proxy.as_ref().map(|n| n.inner.as_ref()).unwrap_or(""))
.build(),
)
}
Intercept::Https(url) => {
maybe_has_http_auth = cache_maybe_has_http_auth(&url, &extra.auth);
maybe_has_http_custom_headers =
cache_maybe_has_http_custom_headers(&url, &extra.misc);
Matcher_::Util(
matcher::Matcher::builder()
.https(String::from(url))
.no(no_proxy.as_ref().map(|n| n.inner.as_ref()).unwrap_or(""))
.build(),
)
}
Intercept::Custom(mut custom) => {
maybe_has_http_auth = true; // never know
maybe_has_http_custom_headers = true;
custom.no_proxy = no_proxy;
Matcher_::Custom(custom)
}
};
Matcher {
inner,
extra,
maybe_has_http_auth,
maybe_has_http_custom_headers,
}
}
/*
pub(crate) fn maybe_has_http_auth(&self) -> bool {
match &self.intercept {
Intercept::All(p) | Intercept::Http(p) => p.maybe_http_auth().is_some(),
// Custom *may* match 'http', so assume so.
Intercept::Custom(_) => true,
Intercept::System(system) => system
.get("http")
.and_then(|s| s.maybe_http_auth())
.is_some(),
Intercept::Https(_) => false,
}
}
pub(crate) fn http_basic_auth<D: Dst>(&self, uri: &D) -> Option<HeaderValue> {
match &self.intercept {
Intercept::All(p) | Intercept::Http(p) => p.maybe_http_auth().cloned(),
Intercept::System(system) => system
.get("http")
.and_then(|s| s.maybe_http_auth().cloned()),
Intercept::Custom(custom) => {
custom.call(uri).and_then(|s| s.maybe_http_auth().cloned())
}
Intercept::Https(_) => None,
}
}
*/
}
fn cache_maybe_has_http_auth(url: &Url, extra: &Option<HeaderValue>) -> bool {
(url.scheme() == "http" || url.scheme() == "https")
&& (url.username().len() > 0 || url.password().is_some() || extra.is_some())
}
fn cache_maybe_has_http_custom_headers(url: &Url, extra: &Option<HeaderMap>) -> bool {
(url.scheme() == "http" || url.scheme() == "https") && extra.is_some()
}
impl fmt::Debug for Proxy {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Proxy")
.field(&self.intercept)
.field(&self.no_proxy)
.finish()
}
}
impl NoProxy {
/// Returns a new no-proxy configuration based on environment variables (or `None` if no variables are set)
/// see [self::NoProxy::from_string()] for the string format
pub fn from_env() -> Option<NoProxy> {
let raw = std::env::var("NO_PROXY")
.or_else(|_| std::env::var("no_proxy"))
.ok()?;
// Per the docs, this returns `None` if no environment variable is set. We can only reach
// here if an env var is set, so we return `Some(NoProxy::default)` if `from_string`
// returns None, which occurs with an empty string.
Some(Self::from_string(&raw).unwrap_or_default())
}
/// Returns a new no-proxy configuration based on a `no_proxy` string (or `None` if no variables
/// are set)
/// The rules are as follows:
/// * The environment variable `NO_PROXY` is checked, if it is not set, `no_proxy` is checked
/// * If neither environment variable is set, `None` is returned
/// * Entries are expected to be comma-separated (whitespace between entries is ignored)
/// * IP addresses (both IPv4 and IPv6) are allowed, as are optional subnet masks (by adding /size,
/// for example "`192.168.1.0/24`").
/// * An entry "`*`" matches all hostnames (this is the only wildcard allowed)
/// * Any other entry is considered a domain name (and may contain a leading dot, for example `google.com`
/// and `.google.com` are equivalent) and would match both that domain AND all subdomains.
///
/// For example, if `"NO_PROXY=google.com, 192.168.1.0/24"` was set, all the following would match
/// (and therefore would bypass the proxy):
/// * `http://google.com/`
/// * `http://www.google.com/`
/// * `http://192.168.1.42/`
///
/// The URL `http://notgoogle.com/` would not match.
pub fn from_string(no_proxy_list: &str) -> Option<Self> {
// lazy parsed, to not make the type public in hyper-util
Some(NoProxy {
inner: no_proxy_list.into(),
})
}
}
impl Matcher {
pub(crate) fn system() -> Self {
Self {
inner: Matcher_::Util(matcher::Matcher::from_system()),
extra: Extra {
auth: None,
misc: None,
},
// maybe env vars have auth!
maybe_has_http_auth: true,
maybe_has_http_custom_headers: true,
}
}
pub(crate) fn intercept(&self, dst: &Uri) -> Option<Intercepted> {
let inner = match self.inner {
Matcher_::Util(ref m) => m.intercept(dst),
Matcher_::Custom(ref c) => c.call(dst),
};
inner.map(|inner| Intercepted {
inner,
extra: self.extra.clone(),
})
}
/// Return whether this matcher might provide HTTP (not s) auth.
///
/// This is very specific. If this proxy needs auth to be part of a Forward
/// request (instead of a tunnel), this should return true.
///
/// If it's not sure, this should return true.
///
/// This is meant as a hint to allow skipping a more expensive check
/// (calling `intercept()`) if it will never need auth when Forwarding.
pub(crate) fn maybe_has_http_auth(&self) -> bool {
self.maybe_has_http_auth
}
pub(crate) fn http_non_tunnel_basic_auth(&self, dst: &Uri) -> Option<HeaderValue> {
if let Some(proxy) = self.intercept(dst) {
let scheme = proxy.uri().scheme();
if scheme == Some(&Scheme::HTTP) || scheme == Some(&Scheme::HTTPS) {
return proxy.basic_auth().cloned();
}
}
None
}
pub(crate) fn maybe_has_http_custom_headers(&self) -> bool {
self.maybe_has_http_custom_headers
}
pub(crate) fn http_non_tunnel_custom_headers(&self, dst: &Uri) -> Option<HeaderMap> {
if let Some(proxy) = self.intercept(dst) {
let scheme = proxy.uri().scheme();
if scheme == Some(&Scheme::HTTP) || scheme == Some(&Scheme::HTTPS) {
return proxy.custom_headers().cloned();
}
}
None
}
}
impl fmt::Debug for Matcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.inner {
Matcher_::Util(ref m) => m.fmt(f),
Matcher_::Custom(ref m) => m.fmt(f),
}
}
}
impl Intercepted {
pub(crate) fn uri(&self) -> &http::Uri {
self.inner.uri()
}
pub(crate) fn basic_auth(&self) -> Option<&HeaderValue> {
if let Some(ref val) = self.extra.auth {
return Some(val);
}
self.inner.basic_auth()
}
pub(crate) fn custom_headers(&self) -> Option<&HeaderMap> {
if let Some(ref val) = self.extra.misc {
return Some(val);
}
None
}
#[cfg(feature = "socks")]
pub(crate) fn raw_auth(&self) -> Option<(&str, &str)> {
self.inner.raw_auth()
}
}
impl fmt::Debug for Intercepted {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.uri().fmt(f)
}
}
/*
impl ProxyScheme {
/// Use a username and password when connecting to the proxy server
fn with_basic_auth<T: Into<String>, U: Into<String>>(
mut self,
username: T,
password: U,
) -> Self {
self.set_basic_auth(username, password);
self
}
fn set_basic_auth<T: Into<String>, U: Into<String>>(&mut self, username: T, password: U) {
match *self {
ProxyScheme::Http { ref mut auth, .. } => {
let header = encode_basic_auth(&username.into(), &password.into());
*auth = Some(header);
}
ProxyScheme::Https { ref mut auth, .. } => {
let header = encode_basic_auth(&username.into(), &password.into());
*auth = Some(header);
}
#[cfg(feature = "socks")]
ProxyScheme::Socks4 { .. } => {
panic!("Socks4 is not supported for this method")
}
#[cfg(feature = "socks")]
ProxyScheme::Socks5 { ref mut auth, .. } => {
*auth = Some((username.into(), password.into()));
}
}
}
fn set_custom_http_auth(&mut self, header_value: HeaderValue) {
match *self {
ProxyScheme::Http { ref mut auth, .. } => {
*auth = Some(header_value);
}
ProxyScheme::Https { ref mut auth, .. } => {
*auth = Some(header_value);
}
#[cfg(feature = "socks")]
ProxyScheme::Socks4 { .. } => {
panic!("Socks4 is not supported for this method")
}
#[cfg(feature = "socks")]
ProxyScheme::Socks5 { .. } => {
panic!("Socks5 is not supported for this method")
}
}
}
fn set_custom_headers(&mut self, headers: HeaderMap) {
match *self {
ProxyScheme::Http { ref mut misc, .. } => {
misc.get_or_insert_with(HeaderMap::new).extend(headers)
}
ProxyScheme::Https { ref mut misc, .. } => {
misc.get_or_insert_with(HeaderMap::new).extend(headers)
}
#[cfg(feature = "socks")]
ProxyScheme::Socks4 { .. } => {
panic!("Socks4 is not supported for this method")
}
#[cfg(feature = "socks")]
ProxyScheme::Socks5 { .. } => {
panic!("Socks5 is not supported for this method")
}
}
}
fn if_no_auth(mut self, update: &Option<HeaderValue>) -> Self {
match self {
ProxyScheme::Http { ref mut auth, .. } => {
if auth.is_none() {
*auth = update.clone();
}
}
ProxyScheme::Https { ref mut auth, .. } => {
if auth.is_none() {
*auth = update.clone();
}
}
#[cfg(feature = "socks")]
ProxyScheme::Socks4 { .. } => {}
#[cfg(feature = "socks")]
ProxyScheme::Socks5 { .. } => {}
}
self
}
/// Convert a URL into a proxy scheme
///
/// Supported schemes: HTTP, HTTPS, (SOCKS4, SOCKS5, SOCKS5H if `socks` feature is enabled).
// Private for now...
fn parse(url: Url) -> crate::Result<Self> {
use url::Position;
// Resolve URL to a host and port
#[cfg(feature = "socks")]
let to_addr = || {
let addrs = url
.socket_addrs(|| match url.scheme() {
"socks4" | "socks4a" | "socks5" | "socks5h" => Some(1080),
_ => None,
})
.map_err(crate::error::builder)?;
addrs
.into_iter()
.next()
.ok_or_else(|| crate::error::builder("unknown proxy scheme"))
};
let mut scheme = match url.scheme() {
"http" => Self::http(&url[Position::BeforeHost..Position::AfterPort])?,
"https" => Self::https(&url[Position::BeforeHost..Position::AfterPort])?,
#[cfg(feature = "socks")]
"socks4" => Self::socks4(to_addr()?)?,
#[cfg(feature = "socks")]
"socks4a" => Self::socks4a(to_addr()?)?,
#[cfg(feature = "socks")]
"socks5" => Self::socks5(to_addr()?)?,
#[cfg(feature = "socks")]
"socks5h" => Self::socks5h(to_addr()?)?,
_ => return Err(crate::error::builder("unknown proxy scheme")),
};
if let Some(pwd) = url.password() {
let decoded_username = percent_decode(url.username().as_bytes()).decode_utf8_lossy();
let decoded_password = percent_decode(pwd.as_bytes()).decode_utf8_lossy();
scheme = scheme.with_basic_auth(decoded_username, decoded_password);
}
Ok(scheme)
}
}
*/
#[derive(Clone, Debug)]
enum Intercept {
All(Url),
Http(Url),
Https(Url),
Custom(Custom),
}
fn url_auth(url: &mut Url, username: &str, password: &str) {
url.set_username(username).expect("is a base");
url.set_password(Some(password)).expect("is a base");
}
#[derive(Clone)]
struct Custom {
func: Arc<dyn Fn(&Url) -> Option<crate::Result<Url>> + Send + Sync + 'static>,
no_proxy: Option<NoProxy>,
}
impl Custom {
fn call(&self, uri: &http::Uri) -> Option<matcher::Intercept> {
let url = format!(
"{}://{}{}{}",
uri.scheme()?,
uri.host()?,
uri.port().map_or("", |_| ":"),
uri.port().map_or(String::new(), |p| p.to_string())
)
.parse()
.expect("should be valid Url");
(self.func)(&url)
.and_then(|result| result.ok())
.and_then(|target| {
let m = matcher::Matcher::builder()
.all(String::from(target))
.build();
m.intercept(uri)
})
//.map(|scheme| scheme.if_no_auth(&self.auth))
}
}
impl fmt::Debug for Custom {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("_")
}
}
pub(crate) fn encode_basic_auth(username: &str, password: &str) -> HeaderValue {
crate::util::basic_auth(username, Some(password))
}
#[cfg(test)]
mod tests {
use super::*;
fn url(s: &str) -> http::Uri {
s.parse().unwrap()
}
fn intercepted_uri(p: &Matcher, s: &str) -> Uri {
p.intercept(&s.parse().unwrap()).unwrap().uri().clone()
}
#[test]
fn test_http() {
let target = "http://example.domain/";
let p = Proxy::http(target).unwrap().into_matcher();
let http = "http://hyper.rs";
let other = "https://hyper.rs";
assert_eq!(intercepted_uri(&p, http), target);
assert!(p.intercept(&url(other)).is_none());
}
#[test]
fn test_https() {
let target = "http://example.domain/";
let p = Proxy::https(target).unwrap().into_matcher();
let http = "http://hyper.rs";
let other = "https://hyper.rs";
assert!(p.intercept(&url(http)).is_none());
assert_eq!(intercepted_uri(&p, other), target);
}
#[test]
fn test_all() {
let target = "http://example.domain/";
let p = Proxy::all(target).unwrap().into_matcher();
let http = "http://hyper.rs";
let https = "https://hyper.rs";
// no longer supported
//let other = "x-youve-never-heard-of-me-mr-proxy://hyper.rs";
assert_eq!(intercepted_uri(&p, http), target);
assert_eq!(intercepted_uri(&p, https), target);
//assert_eq!(intercepted_uri(&p, other), target);
}
#[test]
fn test_custom() {
let target1 = "http://example.domain/";
let target2 = "https://example.domain/";
let p = Proxy::custom(move |url| {
if url.host_str() == Some("hyper.rs") {
target1.parse().ok()
} else if url.scheme() == "http" {
target2.parse().ok()
} else {
None::<Url>
}
})
.into_matcher();
let http = "http://seanmonstar.com";
let https = "https://hyper.rs";
let other = "x-youve-never-heard-of-me-mr-proxy://seanmonstar.com";
assert_eq!(intercepted_uri(&p, http), target2);
assert_eq!(intercepted_uri(&p, https), target1);
assert!(p.intercept(&url(other)).is_none());
}
#[test]
fn test_standard_with_custom_auth_header() {
let target = "http://example.domain/";
let p = Proxy::all(target)
.unwrap()
.custom_http_auth(http::HeaderValue::from_static("testme"))
.into_matcher();
let got = p.intercept(&url("http://anywhere.local")).unwrap();
let auth = got.basic_auth().unwrap();
assert_eq!(auth, "testme");
}
#[test]
fn test_custom_with_custom_auth_header() {
let target = "http://example.domain/";
let p = Proxy::custom(move |_| target.parse::<Url>().ok())
.custom_http_auth(http::HeaderValue::from_static("testme"))
.into_matcher();
let got = p.intercept(&url("http://anywhere.local")).unwrap();
let auth = got.basic_auth().unwrap();
assert_eq!(auth, "testme");
}
#[test]
fn test_maybe_has_http_auth() {
let m = Proxy::all("https://letme:in@yo.local")
.unwrap()
.into_matcher();
assert!(m.maybe_has_http_auth(), "https forwards");
let m = Proxy::all("http://letme:in@yo.local")
.unwrap()
.into_matcher();
assert!(m.maybe_has_http_auth(), "http forwards");
let m = Proxy::all("http://:in@yo.local").unwrap().into_matcher();
assert!(m.maybe_has_http_auth(), "http forwards with empty username");
let m = Proxy::all("http://letme:@yo.local").unwrap().into_matcher();
assert!(m.maybe_has_http_auth(), "http forwards with empty password");
}
#[test]
fn test_socks_proxy_default_port() {
{
let m = Proxy::all("socks5://example.com").unwrap().into_matcher();
let http = "http://hyper.rs";
let https = "https://hyper.rs";
assert_eq!(intercepted_uri(&m, http).port_u16(), Some(1080));
assert_eq!(intercepted_uri(&m, https).port_u16(), Some(1080));
// custom port
let m = Proxy::all("socks5://example.com:1234")
.unwrap()
.into_matcher();
assert_eq!(intercepted_uri(&m, http).port_u16(), Some(1234));
assert_eq!(intercepted_uri(&m, https).port_u16(), Some(1234));
}
}
}

433
vendor/reqwest/src/redirect.rs vendored Normal file
View File

@@ -0,0 +1,433 @@
//! Redirect Handling
//!
//! By default, a `Client` will automatically handle HTTP redirects, having a
//! maximum redirect chain of 10 hops. To customize this behavior, a
//! `redirect::Policy` can be used with a `ClientBuilder`.
use std::fmt;
use std::{error::Error as StdError, sync::Arc};
use crate::header::{AUTHORIZATION, COOKIE, PROXY_AUTHORIZATION, REFERER, WWW_AUTHENTICATE};
use http::{HeaderMap, HeaderValue};
use hyper::StatusCode;
use crate::{async_impl, Url};
use tower_http::follow_redirect::policy::{
Action as TowerAction, Attempt as TowerAttempt, Policy as TowerPolicy,
};
/// A type that controls the policy on how to handle the following of redirects.
///
/// The default value will catch redirect loops, and has a maximum of 10
/// redirects it will follow in a chain before returning an error.
///
/// - `limited` can be used have the same as the default behavior, but adjust
/// the allowed maximum redirect hops in a chain.
/// - `none` can be used to disable all redirect behavior.
/// - `custom` can be used to create a customized policy.
pub struct Policy {
inner: PolicyKind,
}
/// A type that holds information on the next request and previous requests
/// in redirect chain.
#[derive(Debug)]
pub struct Attempt<'a> {
status: StatusCode,
next: &'a Url,
previous: &'a [Url],
}
/// An action to perform when a redirect status code is found.
#[derive(Debug)]
pub struct Action {
inner: ActionKind,
}
impl Policy {
/// Create a `Policy` with a maximum number of redirects.
///
/// An `Error` will be returned if the max is reached.
pub fn limited(max: usize) -> Self {
Self {
inner: PolicyKind::Limit(max),
}
}
/// Create a `Policy` that does not follow any redirect.
pub fn none() -> Self {
Self {
inner: PolicyKind::None,
}
}
/// Create a custom `Policy` using the passed function.
///
/// # Note
///
/// The default `Policy` handles a maximum loop
/// chain, but the custom variant does not do that for you automatically.
/// The custom policy should have some way of handling those.
///
/// Information on the next request and previous requests can be found
/// on the [`Attempt`] argument passed to the closure.
///
/// Actions can be conveniently created from methods on the
/// [`Attempt`].
///
/// # Example
///
/// ```rust
/// # use reqwest::{Error, redirect};
/// #
/// # fn run() -> Result<(), Error> {
/// let custom = redirect::Policy::custom(|attempt| {
/// if attempt.previous().len() > 5 {
/// attempt.error("too many redirects")
/// } else if attempt.url().host_str() == Some("example.domain") {
/// // prevent redirects to 'example.domain'
/// attempt.stop()
/// } else {
/// attempt.follow()
/// }
/// });
/// let client = reqwest::Client::builder()
/// .redirect(custom)
/// .build()?;
/// # Ok(())
/// # }
/// ```
///
/// [`Attempt`]: struct.Attempt.html
pub fn custom<T>(policy: T) -> Self
where
T: Fn(Attempt) -> Action + Send + Sync + 'static,
{
Self {
inner: PolicyKind::Custom(Box::new(policy)),
}
}
/// Apply this policy to a given [`Attempt`] to produce a [`Action`].
///
/// # Note
///
/// This method can be used together with `Policy::custom()`
/// to construct one `Policy` that wraps another.
///
/// # Example
///
/// ```rust
/// # use reqwest::{Error, redirect};
/// #
/// # fn run() -> Result<(), Error> {
/// let custom = redirect::Policy::custom(|attempt| {
/// eprintln!("{}, Location: {:?}", attempt.status(), attempt.url());
/// redirect::Policy::default().redirect(attempt)
/// });
/// # Ok(())
/// # }
/// ```
pub fn redirect(&self, attempt: Attempt) -> Action {
match self.inner {
PolicyKind::Custom(ref custom) => custom(attempt),
PolicyKind::Limit(max) => {
// The first URL in the previous is the initial URL and not a redirection. It needs to be excluded.
if attempt.previous.len() > max {
attempt.error(TooManyRedirects)
} else {
attempt.follow()
}
}
PolicyKind::None => attempt.stop(),
}
}
pub(crate) fn check(&self, status: StatusCode, next: &Url, previous: &[Url]) -> ActionKind {
self.redirect(Attempt {
status,
next,
previous,
})
.inner
}
pub(crate) fn is_default(&self) -> bool {
matches!(self.inner, PolicyKind::Limit(10))
}
}
impl Default for Policy {
fn default() -> Policy {
// Keep `is_default` in sync
Policy::limited(10)
}
}
impl<'a> Attempt<'a> {
/// Get the type of redirect.
pub fn status(&self) -> StatusCode {
self.status
}
/// Get the next URL to redirect to.
pub fn url(&self) -> &Url {
self.next
}
/// Get the list of previous URLs that have already been requested in this chain.
pub fn previous(&self) -> &[Url] {
self.previous
}
/// Returns an action meaning reqwest should follow the next URL.
pub fn follow(self) -> Action {
Action {
inner: ActionKind::Follow,
}
}
/// Returns an action meaning reqwest should not follow the next URL.
///
/// The 30x response will be returned as the `Ok` result.
pub fn stop(self) -> Action {
Action {
inner: ActionKind::Stop,
}
}
/// Returns an action failing the redirect with an error.
///
/// The `Error` will be returned for the result of the sent request.
pub fn error<E: Into<Box<dyn StdError + Send + Sync>>>(self, error: E) -> Action {
Action {
inner: ActionKind::Error(error.into()),
}
}
}
enum PolicyKind {
Custom(Box<dyn Fn(Attempt) -> Action + Send + Sync + 'static>),
Limit(usize),
None,
}
impl fmt::Debug for Policy {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Policy").field(&self.inner).finish()
}
}
impl fmt::Debug for PolicyKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
PolicyKind::Custom(..) => f.pad("Custom"),
PolicyKind::Limit(max) => f.debug_tuple("Limit").field(&max).finish(),
PolicyKind::None => f.pad("None"),
}
}
}
// pub(crate)
#[derive(Debug)]
pub(crate) enum ActionKind {
Follow,
Stop,
Error(Box<dyn StdError + Send + Sync>),
}
pub(crate) fn remove_sensitive_headers(headers: &mut HeaderMap, next: &Url, previous: &[Url]) {
if let Some(previous) = previous.last() {
let cross_host = next.host_str() != previous.host_str()
|| next.port_or_known_default() != previous.port_or_known_default();
if cross_host {
headers.remove(AUTHORIZATION);
headers.remove(COOKIE);
headers.remove("cookie2");
headers.remove(PROXY_AUTHORIZATION);
headers.remove(WWW_AUTHENTICATE);
}
}
}
#[derive(Debug)]
struct TooManyRedirects;
impl fmt::Display for TooManyRedirects {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("too many redirects")
}
}
impl StdError for TooManyRedirects {}
#[derive(Clone)]
pub(crate) struct TowerRedirectPolicy {
policy: Arc<Policy>,
referer: bool,
urls: Vec<Url>,
https_only: bool,
}
impl TowerRedirectPolicy {
pub(crate) fn new(policy: Policy) -> Self {
Self {
policy: Arc::new(policy),
referer: false,
urls: Vec::new(),
https_only: false,
}
}
pub(crate) fn with_referer(&mut self, referer: bool) -> &mut Self {
self.referer = referer;
self
}
pub(crate) fn with_https_only(&mut self, https_only: bool) -> &mut Self {
self.https_only = https_only;
self
}
}
fn make_referer(next: &Url, previous: &Url) -> Option<HeaderValue> {
if next.scheme() == "http" && previous.scheme() == "https" {
return None;
}
let mut referer = previous.clone();
let _ = referer.set_username("");
let _ = referer.set_password(None);
referer.set_fragment(None);
referer.as_str().parse().ok()
}
impl TowerPolicy<async_impl::body::Body, crate::Error> for TowerRedirectPolicy {
fn redirect(&mut self, attempt: &TowerAttempt<'_>) -> Result<TowerAction, crate::Error> {
let previous_url =
Url::parse(&attempt.previous().to_string()).expect("Previous URL must be valid");
let next_url = match Url::parse(&attempt.location().to_string()) {
Ok(url) => url,
Err(e) => return Err(crate::error::builder(e)),
};
self.urls.push(previous_url.clone());
match self.policy.check(attempt.status(), &next_url, &self.urls) {
ActionKind::Follow => {
if next_url.scheme() != "http" && next_url.scheme() != "https" {
return Err(crate::error::url_bad_scheme(next_url));
}
if self.https_only && next_url.scheme() != "https" {
return Err(crate::error::redirect(
crate::error::url_bad_scheme(next_url.clone()),
next_url,
));
}
Ok(TowerAction::Follow)
}
ActionKind::Stop => Ok(TowerAction::Stop),
ActionKind::Error(e) => Err(crate::error::redirect(e, previous_url)),
}
}
fn on_request(&mut self, req: &mut http::Request<async_impl::body::Body>) {
if let Ok(next_url) = Url::parse(&req.uri().to_string()) {
remove_sensitive_headers(req.headers_mut(), &next_url, &self.urls);
if self.referer {
if let Some(previous_url) = self.urls.last() {
if let Some(v) = make_referer(&next_url, previous_url) {
req.headers_mut().insert(REFERER, v);
}
}
}
};
}
// This must be implemented to make 307 and 308 redirects work
fn clone_body(&self, body: &async_impl::body::Body) -> Option<async_impl::body::Body> {
body.try_clone()
}
}
#[test]
fn test_redirect_policy_limit() {
let policy = Policy::default();
let next = Url::parse("http://x.y/z").unwrap();
let mut previous = (0..=9)
.map(|i| Url::parse(&format!("http://a.b/c/{i}")).unwrap())
.collect::<Vec<_>>();
match policy.check(StatusCode::FOUND, &next, &previous) {
ActionKind::Follow => (),
other => panic!("unexpected {other:?}"),
}
previous.push(Url::parse("http://a.b.d/e/33").unwrap());
match policy.check(StatusCode::FOUND, &next, &previous) {
ActionKind::Error(err) if err.is::<TooManyRedirects>() => (),
other => panic!("unexpected {other:?}"),
}
}
#[test]
fn test_redirect_policy_limit_to_0() {
let policy = Policy::limited(0);
let next = Url::parse("http://x.y/z").unwrap();
let previous = vec![Url::parse("http://a.b/c").unwrap()];
match policy.check(StatusCode::FOUND, &next, &previous) {
ActionKind::Error(err) if err.is::<TooManyRedirects>() => (),
other => panic!("unexpected {other:?}"),
}
}
#[test]
fn test_redirect_policy_custom() {
let policy = Policy::custom(|attempt| {
if attempt.url().host_str() == Some("foo") {
attempt.stop()
} else {
attempt.follow()
}
});
let next = Url::parse("http://bar/baz").unwrap();
match policy.check(StatusCode::FOUND, &next, &[]) {
ActionKind::Follow => (),
other => panic!("unexpected {other:?}"),
}
let next = Url::parse("http://foo/baz").unwrap();
match policy.check(StatusCode::FOUND, &next, &[]) {
ActionKind::Stop => (),
other => panic!("unexpected {other:?}"),
}
}
#[test]
fn test_remove_sensitive_headers() {
use hyper::header::{HeaderValue, ACCEPT, AUTHORIZATION, COOKIE};
let mut headers = HeaderMap::new();
headers.insert(ACCEPT, HeaderValue::from_static("*/*"));
headers.insert(AUTHORIZATION, HeaderValue::from_static("let me in"));
headers.insert(COOKIE, HeaderValue::from_static("foo=bar"));
let next = Url::parse("http://initial-domain.com/path").unwrap();
let mut prev = vec![Url::parse("http://initial-domain.com/new_path").unwrap()];
let mut filtered_headers = headers.clone();
remove_sensitive_headers(&mut headers, &next, &prev);
assert_eq!(headers, filtered_headers);
prev.push(Url::parse("http://new-domain.com/path").unwrap());
filtered_headers.remove(AUTHORIZATION);
filtered_headers.remove(COOKIE);
remove_sensitive_headers(&mut headers, &next, &prev);
assert_eq!(headers, filtered_headers);
}

41
vendor/reqwest/src/response.rs vendored Normal file
View File

@@ -0,0 +1,41 @@
use url::Url;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct ResponseUrl(pub Url);
/// Extension trait for http::response::Builder objects
///
/// Allows the user to add a `Url` to the http::Response
pub trait ResponseBuilderExt {
/// A builder method for the `http::response::Builder` type that allows the user to add a `Url`
/// to the `http::Response`
fn url(self, url: Url) -> Self;
}
impl ResponseBuilderExt for http::response::Builder {
fn url(self, url: Url) -> Self {
self.extension(ResponseUrl(url))
}
}
#[cfg(test)]
mod tests {
use super::{ResponseBuilderExt, ResponseUrl};
use http::response::Builder;
use url::Url;
#[test]
fn test_response_builder_ext() {
let url = Url::parse("http://example.com").unwrap();
let response = Builder::new()
.status(200)
.url(url.clone())
.body(())
.unwrap();
assert_eq!(
response.extensions().get::<ResponseUrl>(),
Some(&ResponseUrl(url))
);
}
}

477
vendor/reqwest/src/retry.rs vendored Normal file
View File

@@ -0,0 +1,477 @@
//! Retry requests
//!
//! A `Client` has the ability to retry requests, by sending additional copies
//! to the server if a response is considered retryable.
//!
//! The [`Builder`] makes it easier to configure what requests to retry, along
//! with including best practices by default, such as a retry budget.
//!
//! # Defaults
//!
//! The default retry behavior of a `Client` is to only retry requests where an
//! error or low-level protocol NACK is encountered that is known to be safe to
//! retry. Note however that providing a specific retry policy will override
//! the default, and you will need to explicitly include that behavior.
//!
//! All policies default to including a retry budget that permits 20% extra
//! requests to be sent.
//!
//! # Scoped
//!
//! A client's retry policy is scoped. That means that the policy doesn't
//! apply to all requests, but only those within a user-defined scope.
//!
//! Since all policies include a budget by default, it doesn't make sense to
//! apply it on _all_ requests. Rather, the retry history applied by a budget
//! should likely only be applied to the same host.
//!
//! # Classifiers
//!
//! A retry policy needs to be configured with a classifier that determines
//! if a request should be retried. Knowledge of the destination server's
//! behavior is required to make a safe classifier. **Requests should not be
//! retried** if the server cannot safely handle the same request twice, or if
//! it causes side effects.
//!
//! Some common properties to check include if the request method is
//! idempotent, or if the response status code indicates a transient error.
use std::sync::Arc;
use std::time::Duration;
use tower::retry::budget::{Budget as _, TpsBudget as Budget};
/// Builder to configure retries
///
/// Construct with [`for_host()`].
#[derive(Debug)]
pub struct Builder {
//backoff: Backoff,
budget: Option<f32>,
classifier: classify::Classifier,
max_retries_per_request: u32,
scope: scope::Scoped,
}
/// The internal type that we convert the builder into, that implements
/// tower::retry::Policy privately.
#[derive(Clone, Debug)]
pub(crate) struct Policy {
budget: Option<Arc<Budget>>,
classifier: classify::Classifier,
max_retries_per_request: u32,
retry_cnt: u32,
scope: scope::Scoped,
}
//#[derive(Debug)]
//struct Backoff;
/// Create a retry builder with a request scope.
///
/// To provide a scope that isn't a closure, use the more general
/// [`Builder::scoped()`].
pub fn for_host<S>(host: S) -> Builder
where
S: for<'a> PartialEq<&'a str> + Send + Sync + 'static,
{
scoped(move |req| host == req.uri().host().unwrap_or(""))
}
/// Create a retry policy that will never retry any request.
///
/// This is useful for disabling the `Client`s default behavior of retrying
/// protocol nacks.
pub fn never() -> Builder {
scoped(|_| false).no_budget()
}
fn scoped<F>(func: F) -> Builder
where
F: Fn(&Req) -> bool + Send + Sync + 'static,
{
Builder::scoped(scope::ScopeFn(func))
}
// ===== impl Builder =====
impl Builder {
/// Create a scoped retry policy.
///
/// For a more convenient constructor, see [`for_host()`].
pub fn scoped(scope: impl scope::Scope) -> Self {
Self {
budget: Some(0.2),
classifier: classify::Classifier::Never,
max_retries_per_request: 2, // on top of the original
scope: scope::Scoped::Dyn(Arc::new(scope)),
}
}
/// Set no retry budget.
///
/// Sets that no budget will be enforced. This could also be considered
/// to be an infinite budget.
///
/// This is NOT recommended. Disabling the budget can make your system more
/// susceptible to retry storms.
pub fn no_budget(mut self) -> Self {
self.budget = None;
self
}
/// Sets the max extra load the budget will allow.
///
/// Think of the amount of requests your client generates, and how much
/// load that puts on the server. This option configures as a percentage
/// how much extra load is allowed via retries.
///
/// For example, if you send 1,000 requests per second, setting a maximum
/// extra load value of `0.3` would allow 300 more requests per second
/// in retries. A value of `2.5` would allow 2,500 more requests.
///
/// # Panics
///
/// The `extra_percent` value must be within reasonable values for a
/// percentage. This method will panic if it is less than `0.0`, or greater
/// than `1000.0`.
pub fn max_extra_load(mut self, extra_percent: f32) -> Self {
assert!(extra_percent >= 0.0);
assert!(extra_percent <= 1000.0);
self.budget = Some(extra_percent);
self
}
// pub fn max_replay_body
/// Set the max retries allowed per request.
///
/// For each logical (initial) request, only retry up to `max` times.
///
/// This value is used in combination with a token budget that is applied
/// to all requests. Even if the budget would allow more requests, this
/// limit will prevent. Likewise, the budget may prevent retrying up to
/// `max` times. This setting prevents a single request from consuming
/// the entire budget.
///
/// Default is currently 2 retries.
pub fn max_retries_per_request(mut self, max: u32) -> Self {
self.max_retries_per_request = max;
self
}
/// Provide a classifier to determine if a request should be retried.
///
/// # Example
///
/// ```rust
/// # fn with_builder(builder: reqwest::retry::Builder) -> reqwest::retry::Builder {
/// builder.classify_fn(|req_rep| {
/// match (req_rep.method(), req_rep.status()) {
/// (&http::Method::GET, Some(http::StatusCode::SERVICE_UNAVAILABLE)) => {
/// req_rep.retryable()
/// },
/// _ => req_rep.success()
/// }
/// })
/// # }
/// ```
pub fn classify_fn<F>(self, func: F) -> Self
where
F: Fn(classify::ReqRep<'_>) -> classify::Action + Send + Sync + 'static,
{
self.classify(classify::ClassifyFn(func))
}
/// Provide a classifier to determine if a request should be retried.
pub fn classify(mut self, classifier: impl classify::Classify) -> Self {
self.classifier = classify::Classifier::Dyn(Arc::new(classifier));
self
}
pub(crate) fn default() -> Builder {
Self {
// unscoped protocols nacks doesn't need a budget
budget: None,
classifier: classify::Classifier::ProtocolNacks,
max_retries_per_request: 2, // on top of the original
scope: scope::Scoped::Unscoped,
}
}
pub(crate) fn into_policy(self) -> Policy {
let budget = self
.budget
.map(|p| Arc::new(Budget::new(Duration::from_secs(10), 10, p)));
Policy {
budget,
classifier: self.classifier,
max_retries_per_request: self.max_retries_per_request,
retry_cnt: 0,
scope: self.scope,
}
}
}
// ===== internal ======
type Req = http::Request<crate::async_impl::body::Body>;
impl<B> tower::retry::Policy<Req, http::Response<B>, crate::Error> for Policy {
// TODO? backoff futures...
type Future = std::future::Ready<()>;
fn retry(
&mut self,
req: &mut Req,
result: &mut crate::Result<http::Response<B>>,
) -> Option<Self::Future> {
match self.classifier.classify(req, result) {
classify::Action::Success => {
log::trace!("shouldn't retry!");
if let Some(ref budget) = self.budget {
budget.deposit();
}
None
}
classify::Action::Retryable => {
log::trace!("could retry!");
if self.budget.as_ref().map(|b| b.withdraw()).unwrap_or(true) {
self.retry_cnt += 1;
Some(std::future::ready(()))
} else {
log::debug!("retryable but could not withdraw from budget");
None
}
}
}
}
fn clone_request(&mut self, req: &Req) -> Option<Req> {
if self.retry_cnt > 0 && !self.scope.applies_to(req) {
return None;
}
if self.retry_cnt >= self.max_retries_per_request {
log::trace!("max_retries_per_request hit");
return None;
}
let body = req.body().try_clone()?;
let mut new = http::Request::new(body);
*new.method_mut() = req.method().clone();
*new.uri_mut() = req.uri().clone();
*new.version_mut() = req.version();
*new.headers_mut() = req.headers().clone();
*new.extensions_mut() = req.extensions().clone();
Some(new)
}
}
fn is_retryable_error(err: &crate::Error) -> bool {
use std::error::Error as _;
// pop the reqwest::Error
let err = if let Some(err) = err.source() {
err
} else {
return false;
};
// pop the legacy::Error
let err = if let Some(err) = err.source() {
err
} else {
return false;
};
#[cfg(not(any(feature = "http3", feature = "http2")))]
let _err = err;
#[cfg(feature = "http3")]
if let Some(cause) = err.source() {
if let Some(err) = cause.downcast_ref::<h3::error::ConnectionError>() {
log::trace!("determining if HTTP/3 error {err} can be retried");
// TODO: Does h3 provide an API for checking the error?
return err.to_string().as_str() == "timeout";
}
}
#[cfg(feature = "http2")]
if let Some(cause) = err.source() {
if let Some(err) = cause.downcast_ref::<h2::Error>() {
// They sent us a graceful shutdown, try with a new connection!
if err.is_go_away() && err.is_remote() && err.reason() == Some(h2::Reason::NO_ERROR) {
return true;
}
// REFUSED_STREAM was sent from the server, which is safe to retry.
// https://www.rfc-editor.org/rfc/rfc9113.html#section-8.7-3.2
if err.is_reset() && err.is_remote() && err.reason() == Some(h2::Reason::REFUSED_STREAM)
{
return true;
}
}
}
false
}
// sealed types and traits on purpose while exploring design space
mod scope {
pub trait Scope: Send + Sync + 'static {
fn applies_to(&self, req: &super::Req) -> bool;
}
// I think scopes likely make the most sense being to hosts.
// If that's the case, then it should probably be easiest to check for
// the host. Perhaps also considering the ability to add more things
// to scope off in the future...
// For Future Whoever: making a blanket impl for any closure sounds nice,
// but it causes inference issues at the call site. Every closure would
// need to include `: ReqRep` in the arguments.
//
// An alternative is to make things like `ScopeFn`. Slightly more annoying,
// but also more forwards-compatible. :shrug:
pub struct ScopeFn<F>(pub(super) F);
impl<F> Scope for ScopeFn<F>
where
F: Fn(&super::Req) -> bool + Send + Sync + 'static,
{
fn applies_to(&self, req: &super::Req) -> bool {
(self.0)(req)
}
}
#[derive(Clone)]
pub(super) enum Scoped {
Unscoped,
Dyn(std::sync::Arc<dyn Scope>),
}
impl Scoped {
pub(super) fn applies_to(&self, req: &super::Req) -> bool {
let ret = match self {
Self::Unscoped => true,
Self::Dyn(s) => s.applies_to(req),
};
log::trace!("retry in scope: {ret}");
ret
}
}
impl std::fmt::Debug for Scoped {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unscoped => f.write_str("Unscoped"),
Self::Dyn(_) => f.write_str("Scoped"),
}
}
}
}
// sealed types and traits on purpose while exploring design space
mod classify {
pub trait Classify: Send + Sync + 'static {
fn classify(&self, req_rep: ReqRep<'_>) -> Action;
}
// For Future Whoever: making a blanket impl for any closure sounds nice,
// but it causes inference issues at the call site. Every closure would
// need to include `: ReqRep` in the arguments.
//
// An alternative is to make things like `ClassifyFn`. Slightly more
// annoying, but also more forwards-compatible. :shrug:
pub struct ClassifyFn<F>(pub(super) F);
impl<F> Classify for ClassifyFn<F>
where
F: Fn(ReqRep<'_>) -> Action + Send + Sync + 'static,
{
fn classify(&self, req_rep: ReqRep<'_>) -> Action {
(self.0)(req_rep)
}
}
#[derive(Debug)]
pub struct ReqRep<'a>(&'a super::Req, Result<http::StatusCode, &'a crate::Error>);
impl ReqRep<'_> {
pub fn method(&self) -> &http::Method {
self.0.method()
}
pub fn uri(&self) -> &http::Uri {
self.0.uri()
}
pub fn status(&self) -> Option<http::StatusCode> {
self.1.ok()
}
pub fn error(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.1.as_ref().err().map(|e| &**e as _)
}
pub fn retryable(self) -> Action {
Action::Retryable
}
pub fn success(self) -> Action {
Action::Success
}
fn is_protocol_nack(&self) -> bool {
self.1
.as_ref()
.err()
.map(|&e| super::is_retryable_error(e))
.unwrap_or(false)
}
}
#[must_use]
#[derive(Debug)]
pub enum Action {
Success,
Retryable,
}
#[derive(Clone)]
pub(super) enum Classifier {
Never,
ProtocolNacks,
Dyn(std::sync::Arc<dyn Classify>),
}
impl Classifier {
pub(super) fn classify<B>(
&self,
req: &super::Req,
res: &Result<http::Response<B>, crate::Error>,
) -> Action {
let req_rep = ReqRep(req, res.as_ref().map(|r| r.status()));
match self {
Self::Never => Action::Success,
Self::ProtocolNacks => {
if req_rep.is_protocol_nack() {
Action::Retryable
} else {
Action::Success
}
}
Self::Dyn(c) => c.classify(req_rep),
}
}
}
impl std::fmt::Debug for Classifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Never => f.write_str("Never"),
Self::ProtocolNacks => f.write_str("ProtocolNacks"),
Self::Dyn(_) => f.write_str("Classifier"),
}
}
}
}

843
vendor/reqwest/src/tls.rs vendored Normal file
View File

@@ -0,0 +1,843 @@
//! TLS configuration and types
//!
//! A `Client` will use transport layer security (TLS) by default to connect to
//! HTTPS destinations.
//!
//! # Backends
//!
//! reqwest supports several TLS backends, enabled with Cargo features.
//!
//! ## default-tls
//!
//! reqwest will pick a TLS backend by default. This is true when the
//! `default-tls` feature is enabled.
//!
//! While it currently uses `native-tls`, the feature set is designed to only
//! enable configuration that is shared among available backends. This allows
//! reqwest to change the default to `rustls` (or another) at some point in the
//! future.
//!
//! <div class="warning">This feature is enabled by default, and takes
//! precedence if any other crate enables it. This is true even if you declare
//! `features = []`. You must set `default-features = false` instead.</div>
//!
//! Since Cargo features are additive, other crates in your dependency tree can
//! cause the default backend to be enabled. If you wish to ensure your
//! `Client` uses a specific backend, call the appropriate builder methods
//! (such as [`use_rustls_tls()`][]).
//!
//! [`use_rustls_tls()`]: crate::ClientBuilder::use_rustls_tls()
//!
//! ## native-tls
//!
//! This backend uses the [native-tls][] crate. That will try to use the system
//! TLS on Windows and Mac, and OpenSSL on Linux targets.
//!
//! Enabling the feature explicitly allows for `native-tls`-specific
//! configuration options.
//!
//! [native-tls]: https://crates.io/crates/native-tls
//!
//! ## rustls-tls
//!
//! This backend uses the [rustls][] crate, a TLS library written in Rust.
//!
//! [rustls]: https://crates.io/crates/rustls
#[cfg(feature = "__rustls")]
use rustls::{
client::danger::HandshakeSignatureValid, client::danger::ServerCertVerified,
client::danger::ServerCertVerifier, crypto::WebPkiSupportedAlgorithms,
server::ParsedCertificate, DigitallySignedStruct, Error as TLSError, RootCertStore,
SignatureScheme,
};
use rustls_pki_types::pem::PemObject;
#[cfg(feature = "__rustls")]
use rustls_pki_types::{ServerName, UnixTime};
use std::{
fmt,
io::{BufRead, BufReader},
};
/// Represents a X509 certificate revocation list.
#[cfg(feature = "__rustls")]
pub struct CertificateRevocationList {
#[cfg(feature = "__rustls")]
inner: rustls_pki_types::CertificateRevocationListDer<'static>,
}
/// Represents a server X509 certificate.
#[derive(Clone)]
pub struct Certificate {
#[cfg(feature = "default-tls")]
native: native_tls_crate::Certificate,
#[cfg(feature = "__rustls")]
original: Cert,
}
#[cfg(feature = "__rustls")]
#[derive(Clone)]
enum Cert {
Der(Vec<u8>),
Pem(Vec<u8>),
}
/// Represents a private key and X509 cert as a client certificate.
#[derive(Clone)]
pub struct Identity {
#[cfg_attr(not(any(feature = "native-tls", feature = "__rustls")), allow(unused))]
inner: ClientCert,
}
enum ClientCert {
#[cfg(feature = "native-tls")]
Pkcs12(native_tls_crate::Identity),
#[cfg(feature = "native-tls")]
Pkcs8(native_tls_crate::Identity),
#[cfg(feature = "__rustls")]
Pem {
key: rustls_pki_types::PrivateKeyDer<'static>,
certs: Vec<rustls_pki_types::CertificateDer<'static>>,
},
}
impl Clone for ClientCert {
fn clone(&self) -> Self {
match self {
#[cfg(feature = "native-tls")]
Self::Pkcs8(i) => Self::Pkcs8(i.clone()),
#[cfg(feature = "native-tls")]
Self::Pkcs12(i) => Self::Pkcs12(i.clone()),
#[cfg(feature = "__rustls")]
ClientCert::Pem { key, certs } => ClientCert::Pem {
key: key.clone_key(),
certs: certs.clone(),
},
#[cfg_attr(
any(feature = "native-tls", feature = "__rustls"),
allow(unreachable_patterns)
)]
_ => unreachable!(),
}
}
}
impl Certificate {
/// Create a `Certificate` from a binary DER encoded certificate
///
/// # Examples
///
/// ```
/// # use std::fs::File;
/// # use std::io::Read;
/// # fn cert() -> Result<(), Box<dyn std::error::Error>> {
/// let mut buf = Vec::new();
/// File::open("my_cert.der")?
/// .read_to_end(&mut buf)?;
/// let cert = reqwest::Certificate::from_der(&buf)?;
/// # drop(cert);
/// # Ok(())
/// # }
/// ```
pub fn from_der(der: &[u8]) -> crate::Result<Certificate> {
Ok(Certificate {
#[cfg(feature = "default-tls")]
native: native_tls_crate::Certificate::from_der(der).map_err(crate::error::builder)?,
#[cfg(feature = "__rustls")]
original: Cert::Der(der.to_owned()),
})
}
/// Create a `Certificate` from a PEM encoded certificate
///
/// # Examples
///
/// ```
/// # use std::fs::File;
/// # use std::io::Read;
/// # fn cert() -> Result<(), Box<dyn std::error::Error>> {
/// let mut buf = Vec::new();
/// File::open("my_cert.pem")?
/// .read_to_end(&mut buf)?;
/// let cert = reqwest::Certificate::from_pem(&buf)?;
/// # drop(cert);
/// # Ok(())
/// # }
/// ```
pub fn from_pem(pem: &[u8]) -> crate::Result<Certificate> {
Ok(Certificate {
#[cfg(feature = "default-tls")]
native: native_tls_crate::Certificate::from_pem(pem).map_err(crate::error::builder)?,
#[cfg(feature = "__rustls")]
original: Cert::Pem(pem.to_owned()),
})
}
/// Create a collection of `Certificate`s from a PEM encoded certificate bundle.
/// Example byte sources may be `.crt`, `.cer` or `.pem` files.
///
/// # Examples
///
/// ```
/// # use std::fs::File;
/// # use std::io::Read;
/// # fn cert() -> Result<(), Box<dyn std::error::Error>> {
/// let mut buf = Vec::new();
/// File::open("ca-bundle.crt")?
/// .read_to_end(&mut buf)?;
/// let certs = reqwest::Certificate::from_pem_bundle(&buf)?;
/// # drop(certs);
/// # Ok(())
/// # }
/// ```
pub fn from_pem_bundle(pem_bundle: &[u8]) -> crate::Result<Vec<Certificate>> {
let mut reader = BufReader::new(pem_bundle);
Self::read_pem_certs(&mut reader)?
.iter()
.map(|cert_vec| Certificate::from_der(cert_vec))
.collect::<crate::Result<Vec<Certificate>>>()
}
#[cfg(feature = "default-tls")]
pub(crate) fn add_to_native_tls(self, tls: &mut native_tls_crate::TlsConnectorBuilder) {
tls.add_root_certificate(self.native);
}
#[cfg(feature = "__rustls")]
pub(crate) fn add_to_rustls(
self,
root_cert_store: &mut rustls::RootCertStore,
) -> crate::Result<()> {
use std::io::Cursor;
match self.original {
Cert::Der(buf) => root_cert_store
.add(buf.into())
.map_err(crate::error::builder)?,
Cert::Pem(buf) => {
let mut reader = Cursor::new(buf);
let certs = Self::read_pem_certs(&mut reader)?;
for c in certs {
root_cert_store
.add(c.into())
.map_err(crate::error::builder)?;
}
}
}
Ok(())
}
fn read_pem_certs(reader: &mut impl BufRead) -> crate::Result<Vec<Vec<u8>>> {
rustls_pki_types::CertificateDer::pem_reader_iter(reader)
.map(|result| match result {
Ok(cert) => Ok(cert.as_ref().to_vec()),
Err(_) => Err(crate::error::builder("invalid certificate encoding")),
})
.collect()
}
}
impl Identity {
/// Parses a DER-formatted PKCS #12 archive, using the specified password to decrypt the key.
///
/// The archive should contain a leaf certificate and its private key, as well any intermediate
/// certificates that allow clients to build a chain to a trusted root.
/// The chain certificates should be in order from the leaf certificate towards the root.
///
/// PKCS #12 archives typically have the file extension `.p12` or `.pfx`, and can be created
/// with the OpenSSL `pkcs12` tool:
///
/// ```bash
/// openssl pkcs12 -export -out identity.pfx -inkey key.pem -in cert.pem -certfile chain_certs.pem
/// ```
///
/// # Examples
///
/// ```
/// # use std::fs::File;
/// # use std::io::Read;
/// # fn pkcs12() -> Result<(), Box<dyn std::error::Error>> {
/// let mut buf = Vec::new();
/// File::open("my-ident.pfx")?
/// .read_to_end(&mut buf)?;
/// let pkcs12 = reqwest::Identity::from_pkcs12_der(&buf, "my-privkey-password")?;
/// # drop(pkcs12);
/// # Ok(())
/// # }
/// ```
///
/// # Optional
///
/// This requires the `native-tls` Cargo feature enabled.
#[cfg(feature = "native-tls")]
pub fn from_pkcs12_der(der: &[u8], password: &str) -> crate::Result<Identity> {
Ok(Identity {
inner: ClientCert::Pkcs12(
native_tls_crate::Identity::from_pkcs12(der, password)
.map_err(crate::error::builder)?,
),
})
}
/// Parses a chain of PEM encoded X509 certificates, with the leaf certificate first.
/// `key` is a PEM encoded PKCS #8 formatted private key for the leaf certificate.
///
/// The certificate chain should contain any intermediate certificates that should be sent to
/// clients to allow them to build a chain to a trusted root.
///
/// A certificate chain here means a series of PEM encoded certificates concatenated together.
///
/// # Examples
///
/// ```
/// # use std::fs;
/// # fn pkcs8() -> Result<(), Box<dyn std::error::Error>> {
/// let cert = fs::read("client.pem")?;
/// let key = fs::read("key.pem")?;
/// let pkcs8 = reqwest::Identity::from_pkcs8_pem(&cert, &key)?;
/// # drop(pkcs8);
/// # Ok(())
/// # }
/// ```
///
/// # Optional
///
/// This requires the `native-tls` Cargo feature enabled.
#[cfg(feature = "native-tls")]
pub fn from_pkcs8_pem(pem: &[u8], key: &[u8]) -> crate::Result<Identity> {
Ok(Identity {
inner: ClientCert::Pkcs8(
native_tls_crate::Identity::from_pkcs8(pem, key).map_err(crate::error::builder)?,
),
})
}
/// Parses PEM encoded private key and certificate.
///
/// The input should contain a PEM encoded private key
/// and at least one PEM encoded certificate.
///
/// Note: The private key must be in RSA, SEC1 Elliptic Curve or PKCS#8 format.
///
/// # Examples
///
/// ```
/// # use std::fs::File;
/// # use std::io::Read;
/// # fn pem() -> Result<(), Box<dyn std::error::Error>> {
/// let mut buf = Vec::new();
/// File::open("my-ident.pem")?
/// .read_to_end(&mut buf)?;
/// let id = reqwest::Identity::from_pem(&buf)?;
/// # drop(id);
/// # Ok(())
/// # }
/// ```
///
/// # Optional
///
/// This requires the `rustls-tls(-...)` Cargo feature enabled.
#[cfg(feature = "__rustls")]
pub fn from_pem(buf: &[u8]) -> crate::Result<Identity> {
use rustls_pki_types::{pem::SectionKind, PrivateKeyDer};
use std::io::Cursor;
let (key, certs) = {
let mut pem = Cursor::new(buf);
let mut sk = Vec::<rustls_pki_types::PrivateKeyDer>::new();
let mut certs = Vec::<rustls_pki_types::CertificateDer>::new();
while let Some((kind, data)) =
rustls_pki_types::pem::from_buf(&mut pem).map_err(|_| {
crate::error::builder(TLSError::General(String::from(
"Invalid identity PEM file",
)))
})?
{
match kind {
SectionKind::Certificate => certs.push(data.into()),
SectionKind::PrivateKey => sk.push(PrivateKeyDer::Pkcs8(data.into())),
SectionKind::RsaPrivateKey => sk.push(PrivateKeyDer::Pkcs1(data.into())),
SectionKind::EcPrivateKey => sk.push(PrivateKeyDer::Sec1(data.into())),
_ => {
return Err(crate::error::builder(TLSError::General(String::from(
"No valid certificate was found",
))))
}
}
}
if let (Some(sk), false) = (sk.pop(), certs.is_empty()) {
(sk, certs)
} else {
return Err(crate::error::builder(TLSError::General(String::from(
"private key or certificate not found",
))));
}
};
Ok(Identity {
inner: ClientCert::Pem { key, certs },
})
}
#[cfg(feature = "native-tls")]
pub(crate) fn add_to_native_tls(
self,
tls: &mut native_tls_crate::TlsConnectorBuilder,
) -> crate::Result<()> {
match self.inner {
ClientCert::Pkcs12(id) | ClientCert::Pkcs8(id) => {
tls.identity(id);
Ok(())
}
#[cfg(feature = "__rustls")]
ClientCert::Pem { .. } => Err(crate::error::builder("incompatible TLS identity type")),
}
}
#[cfg(feature = "__rustls")]
pub(crate) fn add_to_rustls(
self,
config_builder: rustls::ConfigBuilder<
rustls::ClientConfig,
// Not sure here
rustls::client::WantsClientCert,
>,
) -> crate::Result<rustls::ClientConfig> {
match self.inner {
ClientCert::Pem { key, certs } => config_builder
.with_client_auth_cert(certs, key)
.map_err(crate::error::builder),
#[cfg(feature = "native-tls")]
ClientCert::Pkcs12(..) | ClientCert::Pkcs8(..) => {
Err(crate::error::builder("incompatible TLS identity type"))
}
}
}
}
#[cfg(feature = "__rustls")]
impl CertificateRevocationList {
/// Parses a PEM encoded CRL.
///
/// # Examples
///
/// ```
/// # use std::fs::File;
/// # use std::io::Read;
/// # fn crl() -> Result<(), Box<dyn std::error::Error>> {
/// let mut buf = Vec::new();
/// File::open("my_crl.pem")?
/// .read_to_end(&mut buf)?;
/// let crl = reqwest::tls::CertificateRevocationList::from_pem(&buf)?;
/// # drop(crl);
/// # Ok(())
/// # }
/// ```
///
/// # Optional
///
/// This requires the `rustls-tls(-...)` Cargo feature enabled.
#[cfg(feature = "__rustls")]
pub fn from_pem(pem: &[u8]) -> crate::Result<CertificateRevocationList> {
Ok(CertificateRevocationList {
#[cfg(feature = "__rustls")]
inner: rustls_pki_types::CertificateRevocationListDer::from(pem.to_vec()),
})
}
/// Creates a collection of `CertificateRevocationList`s from a PEM encoded CRL bundle.
/// Example byte sources may be `.crl` or `.pem` files.
///
/// # Examples
///
/// ```
/// # use std::fs::File;
/// # use std::io::Read;
/// # fn crls() -> Result<(), Box<dyn std::error::Error>> {
/// let mut buf = Vec::new();
/// File::open("crl-bundle.crl")?
/// .read_to_end(&mut buf)?;
/// let crls = reqwest::tls::CertificateRevocationList::from_pem_bundle(&buf)?;
/// # drop(crls);
/// # Ok(())
/// # }
/// ```
///
/// # Optional
///
/// This requires the `rustls-tls(-...)` Cargo feature enabled.
#[cfg(feature = "__rustls")]
pub fn from_pem_bundle(pem_bundle: &[u8]) -> crate::Result<Vec<CertificateRevocationList>> {
rustls_pki_types::CertificateRevocationListDer::pem_slice_iter(pem_bundle)
.map(|result| match result {
Ok(crl) => Ok(CertificateRevocationList { inner: crl }),
Err(_) => Err(crate::error::builder("invalid crl encoding")),
})
.collect::<crate::Result<Vec<CertificateRevocationList>>>()
}
#[cfg(feature = "__rustls")]
pub(crate) fn as_rustls_crl<'a>(&self) -> rustls_pki_types::CertificateRevocationListDer<'a> {
self.inner.clone()
}
}
impl fmt::Debug for Certificate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Certificate").finish()
}
}
impl fmt::Debug for Identity {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Identity").finish()
}
}
#[cfg(feature = "__rustls")]
impl fmt::Debug for CertificateRevocationList {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("CertificateRevocationList").finish()
}
}
/// A TLS protocol version.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Version(InnerVersion);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
enum InnerVersion {
Tls1_0,
Tls1_1,
Tls1_2,
Tls1_3,
}
// These could perhaps be From/TryFrom implementations, but those would be
// part of the public API so let's be careful
impl Version {
/// Version 1.0 of the TLS protocol.
pub const TLS_1_0: Version = Version(InnerVersion::Tls1_0);
/// Version 1.1 of the TLS protocol.
pub const TLS_1_1: Version = Version(InnerVersion::Tls1_1);
/// Version 1.2 of the TLS protocol.
pub const TLS_1_2: Version = Version(InnerVersion::Tls1_2);
/// Version 1.3 of the TLS protocol.
pub const TLS_1_3: Version = Version(InnerVersion::Tls1_3);
#[cfg(feature = "default-tls")]
pub(crate) fn to_native_tls(self) -> Option<native_tls_crate::Protocol> {
match self.0 {
InnerVersion::Tls1_0 => Some(native_tls_crate::Protocol::Tlsv10),
InnerVersion::Tls1_1 => Some(native_tls_crate::Protocol::Tlsv11),
InnerVersion::Tls1_2 => Some(native_tls_crate::Protocol::Tlsv12),
InnerVersion::Tls1_3 => None,
}
}
#[cfg(feature = "__rustls")]
pub(crate) fn from_rustls(version: rustls::ProtocolVersion) -> Option<Self> {
match version {
rustls::ProtocolVersion::SSLv2 => None,
rustls::ProtocolVersion::SSLv3 => None,
rustls::ProtocolVersion::TLSv1_0 => Some(Self(InnerVersion::Tls1_0)),
rustls::ProtocolVersion::TLSv1_1 => Some(Self(InnerVersion::Tls1_1)),
rustls::ProtocolVersion::TLSv1_2 => Some(Self(InnerVersion::Tls1_2)),
rustls::ProtocolVersion::TLSv1_3 => Some(Self(InnerVersion::Tls1_3)),
_ => None,
}
}
}
pub(crate) enum TlsBackend {
// This is the default and HTTP/3 feature does not use it so suppress it.
#[allow(dead_code)]
#[cfg(feature = "default-tls")]
Default,
#[cfg(feature = "native-tls")]
BuiltNativeTls(native_tls_crate::TlsConnector),
#[cfg(feature = "__rustls")]
Rustls,
#[cfg(feature = "__rustls")]
BuiltRustls(rustls::ClientConfig),
#[cfg(any(feature = "native-tls", feature = "__rustls",))]
UnknownPreconfigured,
}
impl fmt::Debug for TlsBackend {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
#[cfg(feature = "default-tls")]
TlsBackend::Default => write!(f, "Default"),
#[cfg(feature = "native-tls")]
TlsBackend::BuiltNativeTls(_) => write!(f, "BuiltNativeTls"),
#[cfg(feature = "__rustls")]
TlsBackend::Rustls => write!(f, "Rustls"),
#[cfg(feature = "__rustls")]
TlsBackend::BuiltRustls(_) => write!(f, "BuiltRustls"),
#[cfg(any(feature = "native-tls", feature = "__rustls",))]
TlsBackend::UnknownPreconfigured => write!(f, "UnknownPreconfigured"),
}
}
}
#[allow(clippy::derivable_impls)]
impl Default for TlsBackend {
fn default() -> TlsBackend {
#[cfg(all(feature = "default-tls", not(feature = "http3")))]
{
TlsBackend::Default
}
#[cfg(any(
all(feature = "__rustls", not(feature = "default-tls")),
feature = "http3"
))]
{
TlsBackend::Rustls
}
}
}
#[cfg(feature = "__rustls")]
#[derive(Debug)]
pub(crate) struct NoVerifier;
#[cfg(feature = "__rustls")]
impl ServerCertVerifier for NoVerifier {
fn verify_server_cert(
&self,
_end_entity: &rustls_pki_types::CertificateDer,
_intermediates: &[rustls_pki_types::CertificateDer],
_server_name: &ServerName,
_ocsp_response: &[u8],
_now: UnixTime,
) -> Result<ServerCertVerified, TLSError> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &rustls_pki_types::CertificateDer,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TLSError> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &rustls_pki_types::CertificateDer,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TLSError> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
vec![
SignatureScheme::RSA_PKCS1_SHA1,
SignatureScheme::ECDSA_SHA1_Legacy,
SignatureScheme::RSA_PKCS1_SHA256,
SignatureScheme::ECDSA_NISTP256_SHA256,
SignatureScheme::RSA_PKCS1_SHA384,
SignatureScheme::ECDSA_NISTP384_SHA384,
SignatureScheme::RSA_PKCS1_SHA512,
SignatureScheme::ECDSA_NISTP521_SHA512,
SignatureScheme::RSA_PSS_SHA256,
SignatureScheme::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA512,
SignatureScheme::ED25519,
SignatureScheme::ED448,
]
}
}
#[cfg(feature = "__rustls")]
#[derive(Debug)]
pub(crate) struct IgnoreHostname {
roots: RootCertStore,
signature_algorithms: WebPkiSupportedAlgorithms,
}
#[cfg(feature = "__rustls")]
impl IgnoreHostname {
pub(crate) fn new(
roots: RootCertStore,
signature_algorithms: WebPkiSupportedAlgorithms,
) -> Self {
Self {
roots,
signature_algorithms,
}
}
}
#[cfg(feature = "__rustls")]
impl ServerCertVerifier for IgnoreHostname {
fn verify_server_cert(
&self,
end_entity: &rustls_pki_types::CertificateDer<'_>,
intermediates: &[rustls_pki_types::CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp_response: &[u8],
now: UnixTime,
) -> Result<ServerCertVerified, TLSError> {
let cert = ParsedCertificate::try_from(end_entity)?;
rustls::client::verify_server_cert_signed_by_trust_anchor(
&cert,
&self.roots,
intermediates,
now,
self.signature_algorithms.all,
)?;
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &rustls_pki_types::CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TLSError> {
rustls::crypto::verify_tls12_signature(message, cert, dss, &self.signature_algorithms)
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &rustls_pki_types::CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TLSError> {
rustls::crypto::verify_tls13_signature(message, cert, dss, &self.signature_algorithms)
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
self.signature_algorithms.supported_schemes()
}
}
/// Hyper extension carrying extra TLS layer information.
/// Made available to clients on responses when `tls_info` is set.
#[derive(Clone)]
pub struct TlsInfo {
pub(crate) peer_certificate: Option<Vec<u8>>,
}
impl TlsInfo {
/// Get the DER encoded leaf certificate of the peer.
pub fn peer_certificate(&self) -> Option<&[u8]> {
self.peer_certificate.as_ref().map(|der| &der[..])
}
}
impl std::fmt::Debug for TlsInfo {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("TlsInfo").finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "default-tls")]
#[test]
fn certificate_from_der_invalid() {
Certificate::from_der(b"not der").unwrap_err();
}
#[cfg(feature = "default-tls")]
#[test]
fn certificate_from_pem_invalid() {
Certificate::from_pem(b"not pem").unwrap_err();
}
#[cfg(feature = "native-tls")]
#[test]
fn identity_from_pkcs12_der_invalid() {
Identity::from_pkcs12_der(b"not der", "nope").unwrap_err();
}
#[cfg(feature = "native-tls")]
#[test]
fn identity_from_pkcs8_pem_invalid() {
Identity::from_pkcs8_pem(b"not pem", b"not key").unwrap_err();
}
#[cfg(feature = "__rustls")]
#[test]
fn identity_from_pem_invalid() {
Identity::from_pem(b"not pem").unwrap_err();
}
#[cfg(feature = "__rustls")]
#[test]
fn identity_from_pem_pkcs1_key() {
let pem = b"-----BEGIN CERTIFICATE-----\n\
-----END CERTIFICATE-----\n\
-----BEGIN RSA PRIVATE KEY-----\n\
-----END RSA PRIVATE KEY-----\n";
Identity::from_pem(pem).unwrap();
}
#[test]
fn certificates_from_pem_bundle() {
const PEM_BUNDLE: &[u8] = b"
-----BEGIN CERTIFICATE-----
MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5
MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg
Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl
ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j
QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr
ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr
BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM
YyRIHN8wfdVoOw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5
MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg
Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi
9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk
M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB
/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB
MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw
CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW
1KyLa2tJElMzrdfkviT8tQp21KW8EA==
-----END CERTIFICATE-----
";
assert!(Certificate::from_pem_bundle(PEM_BUNDLE).is_ok())
}
#[cfg(feature = "__rustls")]
#[test]
fn crl_from_pem() {
let pem = b"-----BEGIN X509 CRL-----\n-----END X509 CRL-----\n";
CertificateRevocationList::from_pem(pem).unwrap();
}
#[cfg(feature = "__rustls")]
#[test]
fn crl_from_pem_bundle() {
let pem_bundle = std::fs::read("tests/support/crl.pem").unwrap();
let result = CertificateRevocationList::from_pem_bundle(&pem_bundle);
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.len(), 1);
}
}

130
vendor/reqwest/src/util.rs vendored Normal file
View File

@@ -0,0 +1,130 @@
use crate::header::{Entry, HeaderMap, HeaderValue, OccupiedEntry};
use std::fmt;
pub fn basic_auth<U, P>(username: U, password: Option<P>) -> HeaderValue
where
U: std::fmt::Display,
P: std::fmt::Display,
{
use base64::prelude::BASE64_STANDARD;
use base64::write::EncoderWriter;
use std::io::Write;
let mut buf = b"Basic ".to_vec();
{
let mut encoder = EncoderWriter::new(&mut buf, &BASE64_STANDARD);
let _ = write!(encoder, "{username}:");
if let Some(password) = password {
let _ = write!(encoder, "{password}");
}
}
let mut header = HeaderValue::from_maybe_shared(bytes::Bytes::from(buf))
.expect("base64 is always valid HeaderValue");
header.set_sensitive(true);
header
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn fast_random() -> u64 {
use std::cell::Cell;
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hasher};
thread_local! {
static KEY: RandomState = RandomState::new();
static COUNTER: Cell<u64> = Cell::new(0);
}
KEY.with(|key| {
COUNTER.with(|ctr| {
let n = ctr.get().wrapping_add(1);
ctr.set(n);
let mut h = key.build_hasher();
h.write_u64(n);
h.finish()
})
})
}
pub(crate) fn replace_headers(dst: &mut HeaderMap, src: HeaderMap) {
// IntoIter of HeaderMap yields (Option<HeaderName>, HeaderValue).
// The first time a name is yielded, it will be Some(name), and if
// there are more values with the same name, the next yield will be
// None.
let mut prev_entry: Option<OccupiedEntry<_>> = None;
for (key, value) in src {
match key {
Some(key) => match dst.entry(key) {
Entry::Occupied(mut e) => {
e.insert(value);
prev_entry = Some(e);
}
Entry::Vacant(e) => {
let e = e.insert_entry(value);
prev_entry = Some(e);
}
},
None => match prev_entry {
Some(ref mut entry) => {
entry.append(value);
}
None => unreachable!("HeaderMap::into_iter yielded None first"),
},
}
}
}
#[cfg(feature = "cookies")]
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn add_cookie_header(
headers: &mut HeaderMap,
cookie_store: &dyn crate::cookie::CookieStore,
url: &url::Url,
) {
if let Some(header) = cookie_store.cookies(url) {
headers.insert(crate::header::COOKIE, header);
}
}
pub(crate) struct Escape<'a>(&'a [u8]);
#[cfg(not(target_arch = "wasm32"))]
impl<'a> Escape<'a> {
pub(crate) fn new(bytes: &'a [u8]) -> Self {
Escape(bytes)
}
}
impl fmt::Debug for Escape<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "b\"{}\"", self)?;
Ok(())
}
}
impl fmt::Display for Escape<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for &c in self.0 {
// https://doc.rust-lang.org/reference.html#byte-escapes
if c == b'\n' {
write!(f, "\\n")?;
} else if c == b'\r' {
write!(f, "\\r")?;
} else if c == b'\t' {
write!(f, "\\t")?;
} else if c == b'\\' || c == b'"' {
write!(f, "\\{}", c as char)?;
} else if c == b'\0' {
write!(f, "\\0")?;
// ASCII printable
} else if c >= 0x20 && c < 0x7f {
write!(f, "{}", c as char)?;
} else {
write!(f, "\\x{c:02x}")?;
}
}
Ok(())
}
}

320
vendor/reqwest/src/wasm/body.rs vendored Normal file
View File

@@ -0,0 +1,320 @@
#[cfg(feature = "multipart")]
use super::multipart::Form;
/// dox
use bytes::Bytes;
use js_sys::Uint8Array;
use std::{borrow::Cow, fmt};
use wasm_bindgen::JsValue;
/// The body of a `Request`.
///
/// In most cases, this is not needed directly, as the
/// [`RequestBuilder.body`][builder] method uses `Into<Body>`, which allows
/// passing many things (like a string or vector of bytes).
///
/// [builder]: ./struct.RequestBuilder.html#method.body
pub struct Body {
inner: Inner,
}
enum Inner {
Single(Single),
/// MultipartForm holds a multipart/form-data body.
#[cfg(feature = "multipart")]
MultipartForm(Form),
}
#[derive(Clone)]
pub(crate) enum Single {
Bytes(Bytes),
Text(Cow<'static, str>),
}
impl Single {
fn as_bytes(&self) -> &[u8] {
match self {
Single::Bytes(bytes) => bytes.as_ref(),
Single::Text(text) => text.as_bytes(),
}
}
pub(crate) fn to_js_value(&self) -> JsValue {
match self {
Single::Bytes(bytes) => {
let body_bytes: &[u8] = bytes.as_ref();
let body_uint8_array: Uint8Array = body_bytes.into();
let js_value: &JsValue = body_uint8_array.as_ref();
js_value.to_owned()
}
Single::Text(text) => JsValue::from_str(text),
}
}
fn is_empty(&self) -> bool {
match self {
Single::Bytes(bytes) => bytes.is_empty(),
Single::Text(text) => text.is_empty(),
}
}
}
impl Body {
/// Returns a reference to the internal data of the `Body`.
///
/// `None` is returned, if the underlying data is a multipart form.
#[inline]
pub fn as_bytes(&self) -> Option<&[u8]> {
match &self.inner {
Inner::Single(single) => Some(single.as_bytes()),
#[cfg(feature = "multipart")]
Inner::MultipartForm(_) => None,
}
}
pub(crate) fn to_js_value(&self) -> crate::Result<JsValue> {
match &self.inner {
Inner::Single(single) => Ok(single.to_js_value()),
#[cfg(feature = "multipart")]
Inner::MultipartForm(form) => {
let form_data = form.to_form_data()?;
let js_value: &JsValue = form_data.as_ref();
Ok(js_value.to_owned())
}
}
}
#[cfg(feature = "multipart")]
pub(crate) fn as_single(&self) -> Option<&Single> {
match &self.inner {
Inner::Single(single) => Some(single),
Inner::MultipartForm(_) => None,
}
}
#[inline]
#[cfg(feature = "multipart")]
pub(crate) fn from_form(f: Form) -> Body {
Self {
inner: Inner::MultipartForm(f),
}
}
/// into_part turns a regular body into the body of a multipart/form-data part.
#[cfg(feature = "multipart")]
pub(crate) fn into_part(self) -> Body {
match self.inner {
Inner::Single(single) => Self {
inner: Inner::Single(single),
},
Inner::MultipartForm(form) => Self {
inner: Inner::MultipartForm(form),
},
}
}
pub(crate) fn is_empty(&self) -> bool {
match &self.inner {
Inner::Single(single) => single.is_empty(),
#[cfg(feature = "multipart")]
Inner::MultipartForm(form) => form.is_empty(),
}
}
pub(crate) fn try_clone(&self) -> Option<Body> {
match &self.inner {
Inner::Single(single) => Some(Self {
inner: Inner::Single(single.clone()),
}),
#[cfg(feature = "multipart")]
Inner::MultipartForm(_) => None,
}
}
}
impl From<Bytes> for Body {
#[inline]
fn from(bytes: Bytes) -> Body {
Body {
inner: Inner::Single(Single::Bytes(bytes)),
}
}
}
impl From<Vec<u8>> for Body {
#[inline]
fn from(vec: Vec<u8>) -> Body {
Body {
inner: Inner::Single(Single::Bytes(vec.into())),
}
}
}
impl From<&'static [u8]> for Body {
#[inline]
fn from(s: &'static [u8]) -> Body {
Body {
inner: Inner::Single(Single::Bytes(Bytes::from_static(s))),
}
}
}
impl From<String> for Body {
#[inline]
fn from(s: String) -> Body {
Body {
inner: Inner::Single(Single::Text(s.into())),
}
}
}
impl From<&'static str> for Body {
#[inline]
fn from(s: &'static str) -> Body {
Body {
inner: Inner::Single(Single::Text(s.into())),
}
}
}
impl fmt::Debug for Body {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Body").finish()
}
}
impl Default for Body {
fn default() -> Body {
Body {
inner: Inner::Single(Single::Bytes(Bytes::new())),
}
}
}
// Can use new methods in web-sys when requiring v0.2.93.
// > `init.method(m)` to `init.set_method(m)`
// For now, ignore their deprecation.
#[allow(deprecated)]
#[cfg(test)]
mod tests {
use crate::Body;
use js_sys::Uint8Array;
use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen]
extern "C" {
// Use `js_namespace` here to bind `console.log(..)` instead of just
// `log(..)`
#[wasm_bindgen(js_namespace = console)]
fn log(s: String);
}
#[wasm_bindgen_test]
async fn test_body() {
let body = Body::from("TEST");
assert_eq!([84, 69, 83, 84], body.as_bytes().unwrap());
}
#[wasm_bindgen_test]
async fn test_body_js_static_str() {
let body_value = "TEST";
let body = Body::from(body_value);
let mut init = web_sys::RequestInit::new();
init.method("POST");
init.body(Some(
body.to_js_value()
.expect("could not convert body to JsValue")
.as_ref(),
));
let js_req = web_sys::Request::new_with_str_and_init("", &init)
.expect("could not create JS request");
let text_promise = js_req.text().expect("could not get text promise");
let text = crate::wasm::promise::<JsValue>(text_promise)
.await
.expect("could not get request body as text");
assert_eq!(text.as_string().expect("text is not a string"), body_value);
}
#[wasm_bindgen_test]
async fn test_body_js_string() {
let body_value = "TEST".to_string();
let body = Body::from(body_value.clone());
let mut init = web_sys::RequestInit::new();
init.method("POST");
init.body(Some(
body.to_js_value()
.expect("could not convert body to JsValue")
.as_ref(),
));
let js_req = web_sys::Request::new_with_str_and_init("", &init)
.expect("could not create JS request");
let text_promise = js_req.text().expect("could not get text promise");
let text = crate::wasm::promise::<JsValue>(text_promise)
.await
.expect("could not get request body as text");
assert_eq!(text.as_string().expect("text is not a string"), body_value);
}
#[wasm_bindgen_test]
async fn test_body_js_static_u8_slice() {
let body_value: &'static [u8] = b"\x00\x42";
let body = Body::from(body_value);
let mut init = web_sys::RequestInit::new();
init.method("POST");
init.body(Some(
body.to_js_value()
.expect("could not convert body to JsValue")
.as_ref(),
));
let js_req = web_sys::Request::new_with_str_and_init("", &init)
.expect("could not create JS request");
let array_buffer_promise = js_req
.array_buffer()
.expect("could not get array_buffer promise");
let array_buffer = crate::wasm::promise::<JsValue>(array_buffer_promise)
.await
.expect("could not get request body as array buffer");
let v = Uint8Array::new(&array_buffer).to_vec();
assert_eq!(v, body_value);
}
#[wasm_bindgen_test]
async fn test_body_js_vec_u8() {
let body_value = vec![0u8, 42];
let body = Body::from(body_value.clone());
let mut init = web_sys::RequestInit::new();
init.method("POST");
init.body(Some(
body.to_js_value()
.expect("could not convert body to JsValue")
.as_ref(),
));
let js_req = web_sys::Request::new_with_str_and_init("", &init)
.expect("could not create JS request");
let array_buffer_promise = js_req
.array_buffer()
.expect("could not get array_buffer promise");
let array_buffer = crate::wasm::promise::<JsValue>(array_buffer_promise)
.await
.expect("could not get request body as array buffer");
let v = Uint8Array::new(&array_buffer).to_vec();
assert_eq!(v, body_value);
}
}

473
vendor/reqwest/src/wasm/client.rs vendored Normal file
View File

@@ -0,0 +1,473 @@
use http::header::USER_AGENT;
use http::{HeaderMap, HeaderValue, Method};
use js_sys::{Promise, JSON};
use std::convert::TryInto;
use std::{fmt, future::Future, sync::Arc};
use url::Url;
use wasm_bindgen::prelude::{wasm_bindgen, UnwrapThrowExt as _};
use super::{AbortGuard, Request, RequestBuilder, Response};
use crate::IntoUrl;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = fetch)]
fn fetch_with_request(input: &web_sys::Request) -> Promise;
}
fn js_fetch(req: &web_sys::Request) -> Promise {
use wasm_bindgen::{JsCast, JsValue};
let global = js_sys::global();
if let Ok(true) = js_sys::Reflect::has(&global, &JsValue::from_str("ServiceWorkerGlobalScope"))
{
global
.unchecked_into::<web_sys::ServiceWorkerGlobalScope>()
.fetch_with_request(req)
} else {
// browser
fetch_with_request(req)
}
}
/// An HTTP Client for WebAssembly.
///
/// Uses the browser's Fetch API to make requests. The `Client` holds
/// configuration that applies to all requests. To configure a `Client`,
/// use `Client::builder()`.
#[derive(Clone)]
pub struct Client {
config: Arc<Config>,
}
/// A `ClientBuilder` can be used to create a `Client` with custom configuration.
pub struct ClientBuilder {
config: Config,
}
impl Client {
/// Constructs a new `Client`.
pub fn new() -> Self {
Client::builder().build().unwrap_throw()
}
/// Creates a `ClientBuilder` to configure a `Client`.
///
/// This is the same as `ClientBuilder::new()`.
pub fn builder() -> ClientBuilder {
ClientBuilder::new()
}
/// Convenience method to make a `GET` request to a URL.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::GET, url)
}
/// Convenience method to make a `POST` request to a URL.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::POST, url)
}
/// Convenience method to make a `PUT` request to a URL.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::PUT, url)
}
/// Convenience method to make a `PATCH` request to a URL.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::PATCH, url)
}
/// Convenience method to make a `DELETE` request to a URL.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::DELETE, url)
}
/// Convenience method to make a `HEAD` request to a URL.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::HEAD, url)
}
/// Start building a `Request` with the `Method` and `Url`.
///
/// Returns a `RequestBuilder`, which will allow setting headers and
/// request body before sending.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
let req = url.into_url().map(move |url| Request::new(method, url));
RequestBuilder::new(self.clone(), req)
}
/// Executes a `Request`.
///
/// A `Request` can be built manually with `Request::new()` or obtained
/// from a RequestBuilder with `RequestBuilder::build()`.
///
/// You should prefer to use the `RequestBuilder` and
/// `RequestBuilder::send()`.
///
/// # Errors
///
/// This method fails if there was an error while sending request,
/// redirect loop was detected or redirect limit was exhausted.
pub fn execute(
&self,
request: Request,
) -> impl Future<Output = Result<Response, crate::Error>> {
self.execute_request(request)
}
// merge request headers with Client default_headers, prior to external http fetch
fn merge_headers(&self, req: &mut Request) {
use http::header::Entry;
let headers: &mut HeaderMap = req.headers_mut();
// insert default headers in the request headers
// without overwriting already appended headers.
for (key, value) in self.config.headers.iter() {
if let Entry::Vacant(entry) = headers.entry(key) {
entry.insert(value.clone());
}
}
}
pub(super) fn execute_request(
&self,
mut req: Request,
) -> impl Future<Output = crate::Result<Response>> {
self.merge_headers(&mut req);
fetch(req)
}
}
impl Default for Client {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for Client {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut builder = f.debug_struct("Client");
self.config.fmt_fields(&mut builder);
builder.finish()
}
}
impl fmt::Debug for ClientBuilder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut builder = f.debug_struct("ClientBuilder");
self.config.fmt_fields(&mut builder);
builder.finish()
}
}
// Can use new methods in web-sys when requiring v0.2.93.
// > `init.method(m)` to `init.set_method(m)`
// For now, ignore their deprecation.
#[allow(deprecated)]
async fn fetch(req: Request) -> crate::Result<Response> {
// Build the js Request
let mut init = web_sys::RequestInit::new();
init.method(req.method().as_str());
// convert HeaderMap to Headers
let js_headers = web_sys::Headers::new()
.map_err(crate::error::wasm)
.map_err(crate::error::builder)?;
for (name, value) in req.headers() {
js_headers
.append(
name.as_str(),
value.to_str().map_err(crate::error::builder)?,
)
.map_err(crate::error::wasm)
.map_err(crate::error::builder)?;
}
init.headers(&js_headers.into());
// When req.cors is true, do nothing because the default mode is 'cors'
if !req.cors {
init.mode(web_sys::RequestMode::NoCors);
}
if let Some(creds) = req.credentials {
init.credentials(creds);
}
if let Some(cache) = req.cache {
init.set_cache(cache);
}
if let Some(body) = req.body() {
if !body.is_empty() {
init.body(Some(body.to_js_value()?.as_ref()));
}
}
let mut abort = AbortGuard::new()?;
if let Some(timeout) = req.timeout() {
abort.timeout(*timeout);
}
init.signal(Some(&abort.signal()));
let js_req = web_sys::Request::new_with_str_and_init(req.url().as_str(), &init)
.map_err(crate::error::wasm)
.map_err(crate::error::builder)?;
// Await the fetch() promise
let p = js_fetch(&js_req);
let js_resp = super::promise::<web_sys::Response>(p)
.await
.map_err(|error| {
if error.to_string() == "JsValue(\"reqwest::errors::TimedOut\")" {
crate::error::TimedOut.into()
} else {
error
}
})
.map_err(crate::error::request)?;
// Convert from the js Response
let mut resp = http::Response::builder().status(js_resp.status());
let url = Url::parse(&js_resp.url()).expect_throw("url parse");
let js_headers = js_resp.headers();
let js_iter = js_sys::try_iter(&js_headers)
.expect_throw("headers try_iter")
.expect_throw("headers have an iterator");
for item in js_iter {
let item = item.expect_throw("headers iterator doesn't throw");
let serialized_headers: String = JSON::stringify(&item)
.expect_throw("serialized headers")
.into();
let [name, value]: [String; 2] = serde_json::from_str(&serialized_headers)
.expect_throw("deserializable serialized headers");
resp = resp.header(&name, &value);
}
resp.body(js_resp)
.map(|resp| Response::new(resp, url, abort))
.map_err(crate::error::request)
}
// ===== impl ClientBuilder =====
impl ClientBuilder {
/// Constructs a new `ClientBuilder`.
///
/// This is the same as `Client::builder()`.
pub fn new() -> Self {
ClientBuilder {
config: Config::default(),
}
}
/// Returns a 'Client' that uses this ClientBuilder configuration
pub fn build(mut self) -> Result<Client, crate::Error> {
if let Some(err) = self.config.error {
return Err(err);
}
let config = std::mem::take(&mut self.config);
Ok(Client {
config: Arc::new(config),
})
}
/// Sets the `User-Agent` header to be used by this client.
pub fn user_agent<V>(mut self, value: V) -> ClientBuilder
where
V: TryInto<HeaderValue>,
V::Error: Into<http::Error>,
{
match value.try_into() {
Ok(value) => {
self.config.headers.insert(USER_AGENT, value);
}
Err(e) => {
self.config.error = Some(crate::error::builder(e.into()));
}
}
self
}
/// Sets the default headers for every request
pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder {
for (key, value) in headers.iter() {
self.config.headers.insert(key, value.clone());
}
self
}
}
impl Default for ClientBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
struct Config {
headers: HeaderMap,
error: Option<crate::Error>,
}
impl Default for Config {
fn default() -> Config {
Config {
headers: HeaderMap::new(),
error: None,
}
}
}
impl Config {
fn fmt_fields(&self, f: &mut fmt::DebugStruct<'_, '_>) {
f.field("default_headers", &self.headers);
}
}
#[cfg(test)]
mod tests {
use wasm_bindgen_test::*;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
async fn default_headers() {
use crate::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
let mut headers = HeaderMap::new();
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
headers.insert("x-custom", HeaderValue::from_static("flibbertigibbet"));
let client = crate::Client::builder()
.default_headers(headers)
.build()
.expect("client");
let mut req = client
.get("https://www.example.com")
.build()
.expect("request");
// merge headers as if client were about to issue fetch
client.merge_headers(&mut req);
let test_headers = req.headers();
assert!(test_headers.get(CONTENT_TYPE).is_some(), "content-type");
assert!(test_headers.get("x-custom").is_some(), "custom header");
assert!(test_headers.get("accept").is_none(), "no accept header");
}
#[wasm_bindgen_test]
async fn default_headers_clone() {
use crate::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
let mut headers = HeaderMap::new();
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
headers.insert("x-custom", HeaderValue::from_static("flibbertigibbet"));
let client = crate::Client::builder()
.default_headers(headers)
.build()
.expect("client");
let mut req = client
.get("https://www.example.com")
.header(CONTENT_TYPE, "text/plain")
.build()
.expect("request");
client.merge_headers(&mut req);
let headers1 = req.headers();
// confirm that request headers override defaults
assert_eq!(
headers1.get(CONTENT_TYPE).unwrap(),
"text/plain",
"request headers override defaults"
);
// confirm that request headers don't change client defaults
let mut req2 = client
.get("https://www.example.com/x")
.build()
.expect("req 2");
client.merge_headers(&mut req2);
let headers2 = req2.headers();
assert_eq!(
headers2.get(CONTENT_TYPE).unwrap(),
"application/json",
"request headers don't change client defaults"
);
}
#[wasm_bindgen_test]
fn user_agent_header() {
use crate::header::USER_AGENT;
let client = crate::Client::builder()
.user_agent("FooBar/1.2.3")
.build()
.expect("client");
let mut req = client
.get("https://www.example.com")
.build()
.expect("request");
// Merge the client headers with the request's one.
client.merge_headers(&mut req);
let headers1 = req.headers();
// Confirm that we have the `User-Agent` header set
assert_eq!(
headers1.get(USER_AGENT).unwrap(),
"FooBar/1.2.3",
"The user-agent header was not set: {req:#?}"
);
// Now we try to overwrite the `User-Agent` value
let mut req2 = client
.get("https://www.example.com")
.header(USER_AGENT, "Another-User-Agent/42")
.build()
.expect("request 2");
client.merge_headers(&mut req2);
let headers2 = req2.headers();
assert_eq!(
headers2.get(USER_AGENT).expect("headers2 user agent"),
"Another-User-Agent/42",
"Was not able to overwrite the User-Agent value on the request-builder"
);
}
}

85
vendor/reqwest/src/wasm/mod.rs vendored Normal file
View File

@@ -0,0 +1,85 @@
use std::convert::TryInto;
use std::time::Duration;
use js_sys::Function;
use wasm_bindgen::prelude::{wasm_bindgen, Closure};
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{AbortController, AbortSignal};
mod body;
mod client;
/// TODO
#[cfg(feature = "multipart")]
pub mod multipart;
mod request;
mod response;
pub use self::body::Body;
pub use self::client::{Client, ClientBuilder};
pub use self::request::{Request, RequestBuilder};
pub use self::response::Response;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = "setTimeout")]
fn set_timeout(handler: &Function, timeout: i32) -> JsValue;
#[wasm_bindgen(js_name = "clearTimeout")]
fn clear_timeout(handle: JsValue) -> JsValue;
}
async fn promise<T>(promise: js_sys::Promise) -> Result<T, crate::error::BoxError>
where
T: JsCast,
{
use wasm_bindgen_futures::JsFuture;
let js_val = JsFuture::from(promise).await.map_err(crate::error::wasm)?;
js_val
.dyn_into::<T>()
.map_err(|_js_val| "promise resolved to unexpected type".into())
}
/// A guard that cancels a fetch request when dropped.
struct AbortGuard {
ctrl: AbortController,
timeout: Option<(JsValue, Closure<dyn FnMut()>)>,
}
impl AbortGuard {
fn new() -> crate::Result<Self> {
Ok(AbortGuard {
ctrl: AbortController::new()
.map_err(crate::error::wasm)
.map_err(crate::error::builder)?,
timeout: None,
})
}
fn signal(&self) -> AbortSignal {
self.ctrl.signal()
}
fn timeout(&mut self, timeout: Duration) {
let ctrl = self.ctrl.clone();
let abort =
Closure::once(move || ctrl.abort_with_reason(&"reqwest::errors::TimedOut".into()));
let timeout = set_timeout(
abort.as_ref().unchecked_ref::<js_sys::Function>(),
timeout.as_millis().try_into().expect("timeout"),
);
if let Some((id, _)) = self.timeout.replace((timeout, abort)) {
clear_timeout(id);
}
}
}
impl Drop for AbortGuard {
fn drop(&mut self) {
self.ctrl.abort();
if let Some((id, _)) = self.timeout.take() {
clear_timeout(id);
}
}
}

419
vendor/reqwest/src/wasm/multipart.rs vendored Normal file
View File

@@ -0,0 +1,419 @@
//! multipart/form-data
use std::borrow::Cow;
use std::fmt;
use http::HeaderMap;
use mime_guess::Mime;
use web_sys::FormData;
use super::Body;
/// An async multipart/form-data request.
pub struct Form {
inner: FormParts<Part>,
}
impl Form {
pub(crate) fn is_empty(&self) -> bool {
self.inner.fields.is_empty()
}
}
/// A field in a multipart form.
pub struct Part {
meta: PartMetadata,
value: Body,
}
pub(crate) struct FormParts<P> {
pub(crate) fields: Vec<(Cow<'static, str>, P)>,
}
pub(crate) struct PartMetadata {
mime: Option<Mime>,
file_name: Option<Cow<'static, str>>,
pub(crate) headers: HeaderMap,
}
pub(crate) trait PartProps {
fn metadata(&self) -> &PartMetadata;
}
// ===== impl Form =====
impl Default for Form {
fn default() -> Self {
Self::new()
}
}
impl Form {
/// Creates a new async Form without any content.
pub fn new() -> Form {
Form {
inner: FormParts::new(),
}
}
/// Add a data field with supplied name and value.
///
/// # Examples
///
/// ```
/// let form = reqwest::multipart::Form::new()
/// .text("username", "seanmonstar")
/// .text("password", "secret");
/// ```
pub fn text<T, U>(self, name: T, value: U) -> Form
where
T: Into<Cow<'static, str>>,
U: Into<Cow<'static, str>>,
{
self.part(name, Part::text(value))
}
/// Adds a customized Part.
pub fn part<T>(self, name: T, part: Part) -> Form
where
T: Into<Cow<'static, str>>,
{
self.with_inner(move |inner| inner.part(name, part))
}
fn with_inner<F>(self, func: F) -> Self
where
F: FnOnce(FormParts<Part>) -> FormParts<Part>,
{
Form {
inner: func(self.inner),
}
}
pub(crate) fn to_form_data(&self) -> crate::Result<FormData> {
let form = FormData::new()
.map_err(crate::error::wasm)
.map_err(crate::error::builder)?;
for (name, part) in self.inner.fields.iter() {
part.append_to_form(name, &form)
.map_err(crate::error::wasm)
.map_err(crate::error::builder)?;
}
Ok(form)
}
}
impl fmt::Debug for Form {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt_fields("Form", f)
}
}
// ===== impl Part =====
impl Part {
/// Makes a text parameter.
pub fn text<T>(value: T) -> Part
where
T: Into<Cow<'static, str>>,
{
let body = match value.into() {
Cow::Borrowed(slice) => Body::from(slice),
Cow::Owned(string) => Body::from(string),
};
Part::new(body)
}
/// Makes a new parameter from arbitrary bytes.
pub fn bytes<T>(value: T) -> Part
where
T: Into<Cow<'static, [u8]>>,
{
let body = match value.into() {
Cow::Borrowed(slice) => Body::from(slice),
Cow::Owned(vec) => Body::from(vec),
};
Part::new(body)
}
/// Makes a new parameter from an arbitrary stream.
pub fn stream<T: Into<Body>>(value: T) -> Part {
Part::new(value.into())
}
fn new(value: Body) -> Part {
Part {
meta: PartMetadata::new(),
value: value.into_part(),
}
}
/// Tries to set the mime of this part.
pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
}
// Re-export when mime 0.4 is available, with split MediaType/MediaRange.
fn mime(self, mime: Mime) -> Part {
self.with_inner(move |inner| inner.mime(mime))
}
/// Sets the filename, builder style.
pub fn file_name<T>(self, filename: T) -> Part
where
T: Into<Cow<'static, str>>,
{
self.with_inner(move |inner| inner.file_name(filename))
}
/// Sets custom headers for the part.
pub fn headers(self, headers: HeaderMap) -> Part {
self.with_inner(move |inner| inner.headers(headers))
}
fn with_inner<F>(self, func: F) -> Self
where
F: FnOnce(PartMetadata) -> PartMetadata,
{
Part {
meta: func(self.meta),
value: self.value,
}
}
fn append_to_form(
&self,
name: &str,
form: &web_sys::FormData,
) -> Result<(), wasm_bindgen::JsValue> {
let single = self
.value
.as_single()
.expect("A part's body can't be multipart itself");
let mut mime_type = self.metadata().mime.as_ref();
// The JS fetch API doesn't support file names and mime types for strings. So we do our best
// effort to use `append_with_str` and fallback to `append_with_blob_*` if that's not
// possible.
if let super::body::Single::Text(text) = single {
if mime_type.is_none() || mime_type == Some(&mime_guess::mime::TEXT_PLAIN) {
if self.metadata().file_name.is_none() {
return form.append_with_str(name, text);
}
} else {
mime_type = Some(&mime_guess::mime::TEXT_PLAIN);
}
}
let blob = self.blob(mime_type)?;
if let Some(file_name) = &self.metadata().file_name {
form.append_with_blob_and_filename(name, &blob, file_name)
} else {
form.append_with_blob(name, &blob)
}
}
fn blob(&self, mime_type: Option<&Mime>) -> crate::Result<web_sys::Blob> {
use web_sys::Blob;
use web_sys::BlobPropertyBag;
let mut properties = BlobPropertyBag::new();
if let Some(mime) = mime_type {
properties.type_(mime.as_ref());
}
let js_value = self
.value
.as_single()
.expect("A part's body can't be set to a multipart body")
.to_js_value();
let body_array = js_sys::Array::new();
body_array.push(&js_value);
Blob::new_with_u8_array_sequence_and_options(body_array.as_ref(), &properties)
.map_err(crate::error::wasm)
.map_err(crate::error::builder)
}
}
impl fmt::Debug for Part {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut dbg = f.debug_struct("Part");
dbg.field("value", &self.value);
self.meta.fmt_fields(&mut dbg);
dbg.finish()
}
}
impl PartProps for Part {
fn metadata(&self) -> &PartMetadata {
&self.meta
}
}
// ===== impl FormParts =====
impl<P: PartProps> FormParts<P> {
pub(crate) fn new() -> Self {
FormParts { fields: Vec::new() }
}
/// Adds a customized Part.
pub(crate) fn part<T>(mut self, name: T, part: P) -> Self
where
T: Into<Cow<'static, str>>,
{
self.fields.push((name.into(), part));
self
}
}
impl<P: fmt::Debug> FormParts<P> {
pub(crate) fn fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct(ty_name)
.field("parts", &self.fields)
.finish()
}
}
// ===== impl PartMetadata =====
impl PartMetadata {
pub(crate) fn new() -> Self {
PartMetadata {
mime: None,
file_name: None,
headers: HeaderMap::default(),
}
}
pub(crate) fn mime(mut self, mime: Mime) -> Self {
self.mime = Some(mime);
self
}
pub(crate) fn file_name<T>(mut self, filename: T) -> Self
where
T: Into<Cow<'static, str>>,
{
self.file_name = Some(filename.into());
self
}
pub(crate) fn headers<T>(mut self, headers: T) -> Self
where
T: Into<HeaderMap>,
{
self.headers = headers.into();
self
}
}
impl PartMetadata {
pub(crate) fn fmt_fields<'f, 'fa, 'fb>(
&self,
debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>,
) -> &'f mut fmt::DebugStruct<'fa, 'fb> {
debug_struct
.field("mime", &self.mime)
.field("file_name", &self.file_name)
.field("headers", &self.headers)
}
}
#[cfg(test)]
mod tests {
use wasm_bindgen_test::*;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
async fn test_multipart_js() {
use super::{Form, Part};
use js_sys::Uint8Array;
use wasm_bindgen::JsValue;
use web_sys::{File, FormData};
let text_file_name = "test.txt";
let text_file_type = "text/plain";
let text_content = "TEST";
let text_part = Part::text(text_content)
.file_name(text_file_name)
.mime_str(text_file_type)
.expect("invalid mime type");
let binary_file_name = "binary.bin";
let binary_file_type = "application/octet-stream";
let binary_content = vec![0u8, 42];
let binary_part = Part::bytes(binary_content.clone())
.file_name(binary_file_name)
.mime_str(binary_file_type)
.expect("invalid mime type");
let string_name = "string";
let string_content = "CONTENT";
let string_part = Part::text(string_content);
let text_name = "text part";
let binary_name = "binary part";
let form = Form::new()
.part(text_name, text_part)
.part(binary_name, binary_part)
.part(string_name, string_part);
let mut init = web_sys::RequestInit::new();
init.method("POST");
init.body(Some(
form.to_form_data()
.expect("could not convert to FormData")
.as_ref(),
));
let js_req = web_sys::Request::new_with_str_and_init("", &init)
.expect("could not create JS request");
let form_data_promise = js_req.form_data().expect("could not get form_data promise");
let form_data = crate::wasm::promise::<FormData>(form_data_promise)
.await
.expect("could not get body as form data");
// check text part
let text_file = File::from(form_data.get(text_name));
assert_eq!(text_file.name(), text_file_name);
assert_eq!(text_file.type_(), text_file_type);
let text_promise = text_file.text();
let text = crate::wasm::promise::<JsValue>(text_promise)
.await
.expect("could not get text body as text");
assert_eq!(
text.as_string().expect("text is not a string"),
text_content
);
// check binary part
let binary_file = File::from(form_data.get(binary_name));
assert_eq!(binary_file.name(), binary_file_name);
assert_eq!(binary_file.type_(), binary_file_type);
// check string part
let string = form_data
.get(string_name)
.as_string()
.expect("content is not a string");
assert_eq!(string, string_content);
let binary_array_buffer_promise = binary_file.array_buffer();
let array_buffer = crate::wasm::promise::<JsValue>(binary_array_buffer_promise)
.await
.expect("could not get request body as array buffer");
let binary = Uint8Array::new(&array_buffer).to_vec();
assert_eq!(binary, binary_content);
}
}

621
vendor/reqwest/src/wasm/request.rs vendored Normal file
View File

@@ -0,0 +1,621 @@
use std::convert::TryFrom;
use std::fmt;
use std::time::Duration;
use bytes::Bytes;
use http::{request::Parts, Method, Request as HttpRequest};
use serde::Serialize;
#[cfg(feature = "json")]
use serde_json;
use url::Url;
use web_sys::{RequestCache, RequestCredentials};
use super::{Body, Client, Response};
use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE};
/// A request which can be executed with `Client::execute()`.
pub struct Request {
method: Method,
url: Url,
headers: HeaderMap,
body: Option<Body>,
timeout: Option<Duration>,
pub(super) cors: bool,
pub(super) credentials: Option<RequestCredentials>,
pub(super) cache: Option<RequestCache>,
}
/// A builder to construct the properties of a `Request`.
pub struct RequestBuilder {
client: Client,
request: crate::Result<Request>,
}
impl Request {
/// Constructs a new request.
#[inline]
pub fn new(method: Method, url: Url) -> Self {
Request {
method,
url,
headers: HeaderMap::new(),
body: None,
timeout: None,
cors: true,
credentials: None,
cache: None,
}
}
/// Get the method.
#[inline]
pub fn method(&self) -> &Method {
&self.method
}
/// Get a mutable reference to the method.
#[inline]
pub fn method_mut(&mut self) -> &mut Method {
&mut self.method
}
/// Get the url.
#[inline]
pub fn url(&self) -> &Url {
&self.url
}
/// Get a mutable reference to the url.
#[inline]
pub fn url_mut(&mut self) -> &mut Url {
&mut self.url
}
/// Get the headers.
#[inline]
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
/// Get a mutable reference to the headers.
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}
/// Get the body.
#[inline]
pub fn body(&self) -> Option<&Body> {
self.body.as_ref()
}
/// Get a mutable reference to the body.
#[inline]
pub fn body_mut(&mut self) -> &mut Option<Body> {
&mut self.body
}
/// Get the timeout.
#[inline]
pub fn timeout(&self) -> Option<&Duration> {
self.timeout.as_ref()
}
/// Get a mutable reference to the timeout.
#[inline]
pub fn timeout_mut(&mut self) -> &mut Option<Duration> {
&mut self.timeout
}
/// Attempts to clone the `Request`.
///
/// None is returned if a body is which can not be cloned.
pub fn try_clone(&self) -> Option<Request> {
let body = match self.body.as_ref() {
Some(body) => Some(body.try_clone()?),
None => None,
};
Some(Self {
method: self.method.clone(),
url: self.url.clone(),
headers: self.headers.clone(),
body,
timeout: self.timeout,
cors: self.cors,
credentials: self.credentials,
cache: self.cache,
})
}
}
impl RequestBuilder {
pub(super) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
RequestBuilder { client, request }
}
/// Assemble a builder starting from an existing `Client` and a `Request`.
pub fn from_parts(client: crate::Client, request: crate::Request) -> crate::RequestBuilder {
crate::RequestBuilder {
client,
request: crate::Result::Ok(request),
}
}
/// Modify the query string of the URL.
///
/// Modifies the URL of this request, adding the parameters provided.
/// This method appends and does not overwrite. This means that it can
/// be called multiple times and that existing query parameters are not
/// overwritten if the same key is used. The key will simply show up
/// twice in the query string.
/// Calling `.query([("foo", "a"), ("foo", "b")])` gives `"foo=a&foo=b"`.
///
/// # Note
/// This method does not support serializing a single key-value
/// pair. Instead of using `.query(("key", "val"))`, use a sequence, such
/// as `.query(&[("key", "val")])`. It's also possible to serialize structs
/// and maps into a key-value pair.
///
/// # Errors
/// This method will fail if the object you provide cannot be serialized
/// into a query string.
pub fn query<T: Serialize + ?Sized>(mut self, query: &T) -> RequestBuilder {
let mut error = None;
if let Ok(ref mut req) = self.request {
let url = req.url_mut();
let mut pairs = url.query_pairs_mut();
let serializer = serde_urlencoded::Serializer::new(&mut pairs);
if let Err(err) = query.serialize(serializer) {
error = Some(crate::error::builder(err));
}
}
if let Ok(ref mut req) = self.request {
if let Some("") = req.url().query() {
req.url_mut().set_query(None);
}
}
if let Some(err) = error {
self.request = Err(err);
}
self
}
/// Send a form body.
///
/// Sets the body to the url encoded serialization of the passed value,
/// and also sets the `Content-Type: application/x-www-form-urlencoded`
/// header.
///
/// # Errors
///
/// This method fails if the passed value cannot be serialized into
/// url encoded format
pub fn form<T: Serialize + ?Sized>(mut self, form: &T) -> RequestBuilder {
let mut error = None;
if let Ok(ref mut req) = self.request {
match serde_urlencoded::to_string(form) {
Ok(body) => {
req.headers_mut().insert(
CONTENT_TYPE,
HeaderValue::from_static("application/x-www-form-urlencoded"),
);
*req.body_mut() = Some(body.into());
}
Err(err) => error = Some(crate::error::builder(err)),
}
}
if let Some(err) = error {
self.request = Err(err);
}
self
}
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
/// Set the request json
pub fn json<T: Serialize + ?Sized>(mut self, json: &T) -> RequestBuilder {
let mut error = None;
if let Ok(ref mut req) = self.request {
match serde_json::to_vec(json) {
Ok(body) => {
req.headers_mut()
.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
*req.body_mut() = Some(body.into());
}
Err(err) => error = Some(crate::error::builder(err)),
}
}
if let Some(err) = error {
self.request = Err(err);
}
self
}
/// Enable HTTP basic authentication.
pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> RequestBuilder
where
U: fmt::Display,
P: fmt::Display,
{
let header_value = crate::util::basic_auth(username, password);
self.header(crate::header::AUTHORIZATION, header_value)
}
/// Enable HTTP bearer authentication.
pub fn bearer_auth<T>(self, token: T) -> RequestBuilder
where
T: fmt::Display,
{
let header_value = format!("Bearer {token}");
self.header(crate::header::AUTHORIZATION, header_value)
}
/// Set the request body.
pub fn body<T: Into<Body>>(mut self, body: T) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.body = Some(body.into());
}
self
}
/// Enables a request timeout.
pub fn timeout(mut self, timeout: Duration) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
*req.timeout_mut() = Some(timeout);
}
self
}
/// TODO
#[cfg(feature = "multipart")]
#[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
pub fn multipart(mut self, multipart: super::multipart::Form) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
*req.body_mut() = Some(Body::from_form(multipart))
}
self
}
/// Add a `Header` to this Request.
pub fn header<K, V>(mut self, key: K, value: V) -> RequestBuilder
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
{
let mut error = None;
if let Ok(ref mut req) = self.request {
match <HeaderName as TryFrom<K>>::try_from(key) {
Ok(key) => match <HeaderValue as TryFrom<V>>::try_from(value) {
Ok(value) => {
req.headers_mut().append(key, value);
}
Err(e) => error = Some(crate::error::builder(e.into())),
},
Err(e) => error = Some(crate::error::builder(e.into())),
};
}
if let Some(err) = error {
self.request = Err(err);
}
self
}
/// Add a set of Headers to the existing ones on this Request.
///
/// The headers will be merged in to any already set.
pub fn headers(mut self, headers: crate::header::HeaderMap) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
crate::util::replace_headers(req.headers_mut(), headers);
}
self
}
/// Disable CORS on fetching the request.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request mode][mdn] will be set to 'no-cors'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/mode
pub fn fetch_mode_no_cors(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cors = false;
}
self
}
/// Set fetch credentials to 'same-origin'
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request credentials][mdn] will be set to 'same-origin'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
pub fn fetch_credentials_same_origin(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.credentials = Some(RequestCredentials::SameOrigin);
}
self
}
/// Set fetch credentials to 'include'
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request credentials][mdn] will be set to 'include'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
pub fn fetch_credentials_include(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.credentials = Some(RequestCredentials::Include);
}
self
}
/// Set fetch credentials to 'omit'
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request credentials][mdn] will be set to 'omit'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
pub fn fetch_credentials_omit(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.credentials = Some(RequestCredentials::Omit);
}
self
}
/// Set fetch cache mode to 'default'.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request cache][mdn] will be set to 'default'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
pub fn fetch_cache_default(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cache = Some(RequestCache::Default);
}
self
}
/// Set fetch cache mode to 'no-store'.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request cache][mdn] will be set to 'no-store'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
pub fn fetch_cache_no_store(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cache = Some(RequestCache::NoStore);
}
self
}
/// Set fetch cache mode to 'reload'.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request cache][mdn] will be set to 'reload'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
pub fn fetch_cache_reload(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cache = Some(RequestCache::Reload);
}
self
}
/// Set fetch cache mode to 'no-cache'.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request cache][mdn] will be set to 'no-cache'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
pub fn fetch_cache_no_cache(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cache = Some(RequestCache::NoCache);
}
self
}
/// Set fetch cache mode to 'force-cache'.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request cache][mdn] will be set to 'force-cache'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
pub fn fetch_cache_force_cache(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cache = Some(RequestCache::ForceCache);
}
self
}
/// Set fetch cache mode to 'only-if-cached'.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request cache][mdn] will be set to 'only-if-cached'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
pub fn fetch_cache_only_if_cached(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cache = Some(RequestCache::OnlyIfCached);
}
self
}
/// Build a `Request`, which can be inspected, modified and executed with
/// `Client::execute()`.
pub fn build(self) -> crate::Result<Request> {
self.request
}
/// Build a `Request`, which can be inspected, modified and executed with
/// `Client::execute()`.
///
/// This is similar to [`RequestBuilder::build()`], but also returns the
/// embedded `Client`.
pub fn build_split(self) -> (Client, crate::Result<Request>) {
(self.client, self.request)
}
/// Constructs the Request and sends it to the target URL, returning a
/// future Response.
///
/// # Errors
///
/// This method fails if there was an error while sending request.
///
/// # Example
///
/// ```no_run
/// # use reqwest::Error;
/// #
/// # async fn run() -> Result<(), Error> {
/// let response = reqwest::Client::new()
/// .get("https://hyper.rs")
/// .send()
/// .await?;
/// # Ok(())
/// # }
/// ```
pub async fn send(self) -> crate::Result<Response> {
let req = self.request?;
self.client.execute_request(req).await
}
/// Attempt to clone the RequestBuilder.
///
/// `None` is returned if the RequestBuilder can not be cloned.
///
/// # Examples
///
/// ```no_run
/// # use reqwest::Error;
/// #
/// # fn run() -> Result<(), Error> {
/// let client = reqwest::Client::new();
/// let builder = client.post("http://httpbin.org/post")
/// .body("from a &str!");
/// let clone = builder.try_clone();
/// assert!(clone.is_some());
/// # Ok(())
/// # }
/// ```
pub fn try_clone(&self) -> Option<RequestBuilder> {
self.request
.as_ref()
.ok()
.and_then(|req| req.try_clone())
.map(|req| RequestBuilder {
client: self.client.clone(),
request: Ok(req),
})
}
}
impl fmt::Debug for Request {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt_request_fields(&mut f.debug_struct("Request"), self).finish()
}
}
impl fmt::Debug for RequestBuilder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut builder = f.debug_struct("RequestBuilder");
match self.request {
Ok(ref req) => fmt_request_fields(&mut builder, req).finish(),
Err(ref err) => builder.field("error", err).finish(),
}
}
}
fn fmt_request_fields<'a, 'b>(
f: &'a mut fmt::DebugStruct<'a, 'b>,
req: &Request,
) -> &'a mut fmt::DebugStruct<'a, 'b> {
f.field("method", &req.method)
.field("url", &req.url)
.field("headers", &req.headers)
}
impl<T> TryFrom<HttpRequest<T>> for Request
where
T: Into<Body>,
{
type Error = crate::Error;
fn try_from(req: HttpRequest<T>) -> crate::Result<Self> {
let (parts, body) = req.into_parts();
let Parts {
method,
uri,
headers,
..
} = parts;
let url = Url::parse(&uri.to_string()).map_err(crate::error::builder)?;
Ok(Request {
method,
url,
headers,
body: Some(body.into()),
timeout: None,
cors: true,
credentials: None,
cache: None,
})
}
}
impl TryFrom<Request> for HttpRequest<Body> {
type Error = crate::Error;
fn try_from(req: Request) -> crate::Result<Self> {
let Request {
method,
url,
headers,
body,
..
} = req;
let mut req = HttpRequest::builder()
.method(method)
.uri(url.as_str())
.body(body.unwrap_or_else(|| Body::from(Bytes::default())))
.map_err(crate::error::builder)?;
*req.headers_mut() = headers;
Ok(req)
}
}

195
vendor/reqwest/src/wasm/response.rs vendored Normal file
View File

@@ -0,0 +1,195 @@
use std::fmt;
use bytes::Bytes;
use http::{HeaderMap, StatusCode};
use js_sys::Uint8Array;
use url::Url;
use crate::wasm::AbortGuard;
#[cfg(feature = "stream")]
use wasm_bindgen::JsCast;
#[cfg(feature = "stream")]
use futures_util::stream::{self, StreamExt};
#[cfg(feature = "json")]
use serde::de::DeserializeOwned;
/// A Response to a submitted `Request`.
pub struct Response {
http: http::Response<web_sys::Response>,
_abort: AbortGuard,
// Boxed to save space (11 words to 1 word), and it's not accessed
// frequently internally.
url: Box<Url>,
}
impl Response {
pub(super) fn new(
res: http::Response<web_sys::Response>,
url: Url,
abort: AbortGuard,
) -> Response {
Response {
http: res,
url: Box::new(url),
_abort: abort,
}
}
/// Get the `StatusCode` of this `Response`.
#[inline]
pub fn status(&self) -> StatusCode {
self.http.status()
}
/// Get the `Headers` of this `Response`.
#[inline]
pub fn headers(&self) -> &HeaderMap {
self.http.headers()
}
/// Get a mutable reference to the `Headers` of this `Response`.
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
self.http.headers_mut()
}
/// Get the content-length of this response, if known.
///
/// Reasons it may not be known:
///
/// - The server didn't send a `content-length` header.
/// - The response is compressed and automatically decoded (thus changing
/// the actual decoded length).
pub fn content_length(&self) -> Option<u64> {
self.headers()
.get(http::header::CONTENT_LENGTH)?
.to_str()
.ok()?
.parse()
.ok()
}
/// Get the final `Url` of this `Response`.
#[inline]
pub fn url(&self) -> &Url {
&self.url
}
/* It might not be possible to detect this in JS?
/// Get the HTTP `Version` of this `Response`.
#[inline]
pub fn version(&self) -> Version {
self.http.version()
}
*/
/// Try to deserialize the response body as JSON.
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
pub async fn json<T: DeserializeOwned>(self) -> crate::Result<T> {
let full = self.bytes().await?;
serde_json::from_slice(&full).map_err(crate::error::decode)
}
/// Get the response text.
pub async fn text(self) -> crate::Result<String> {
let p = self
.http
.body()
.text()
.map_err(crate::error::wasm)
.map_err(crate::error::decode)?;
let js_val = super::promise::<wasm_bindgen::JsValue>(p)
.await
.map_err(crate::error::decode)?;
if let Some(s) = js_val.as_string() {
Ok(s)
} else {
Err(crate::error::decode("response.text isn't string"))
}
}
/// Get the response as bytes
pub async fn bytes(self) -> crate::Result<Bytes> {
let p = self
.http
.body()
.array_buffer()
.map_err(crate::error::wasm)
.map_err(crate::error::decode)?;
let buf_js = super::promise::<wasm_bindgen::JsValue>(p)
.await
.map_err(crate::error::decode)?;
let buffer = Uint8Array::new(&buf_js);
let mut bytes = vec![0; buffer.length() as usize];
buffer.copy_to(&mut bytes);
Ok(bytes.into())
}
/// Convert the response into a `Stream` of `Bytes` from the body.
#[cfg(feature = "stream")]
pub fn bytes_stream(self) -> impl futures_core::Stream<Item = crate::Result<Bytes>> {
use futures_core::Stream;
use std::pin::Pin;
let web_response = self.http.into_body();
let abort = self._abort;
if let Some(body) = web_response.body() {
let body = wasm_streams::ReadableStream::from_raw(body.unchecked_into());
Box::pin(body.into_stream().map(move |buf_js| {
// Keep the abort guard alive as long as this stream is.
let _abort = &abort;
let buffer = Uint8Array::new(
&buf_js
.map_err(crate::error::wasm)
.map_err(crate::error::decode)?,
);
let mut bytes = vec![0; buffer.length() as usize];
buffer.copy_to(&mut bytes);
Ok(bytes.into())
})) as Pin<Box<dyn Stream<Item = crate::Result<Bytes>>>>
} else {
// If there's no body, return an empty stream.
Box::pin(stream::empty()) as Pin<Box<dyn Stream<Item = crate::Result<Bytes>>>>
}
}
// util methods
/// Turn a response into an error if the server returned an error.
pub fn error_for_status(self) -> crate::Result<Self> {
let status = self.status();
if status.is_client_error() || status.is_server_error() {
Err(crate::error::status_code(*self.url, status))
} else {
Ok(self)
}
}
/// Turn a reference to a response into an error if the server returned an error.
pub fn error_for_status_ref(&self) -> crate::Result<&Self> {
let status = self.status();
if status.is_client_error() || status.is_server_error() {
Err(crate::error::status_code(*self.url.clone(), status))
} else {
Ok(self)
}
}
}
impl fmt::Debug for Response {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Response")
//.field("url", self.url())
.field("status", &self.status())
.field("headers", self.headers())
.finish()
}
}