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

View File

@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"22f6790ad16a617c5c9527bcc0ea3f72d30dd614f0291f1bd43a964daa47a40a",".github/workflows/checks.yaml":"a9655bcb79df8e6a2a749f9836f6f290106d3b7b5506ee3f0d594064bda3965b",".github/workflows/release.yaml":"10731e851bc1b755baa61154ae2e0043fb7c249d0a03a724ad4c76cdd8bda8e1","Cargo.lock":"76a905f085fb3b6bb6a646f0df961c925c46ad1d16d2c9c08789efd1b6bc6410","Cargo.toml":"66c9a9515c58fd2e1f5f0caed98635f26f913b033dce1aa94b8b4a44a05a6785","Cargo.toml.orig":"0d53b8294d907f96a5b9713aba157cfa5446f31905d3cad20d06f4ae0a678d32","LICENSE-MIT.md":"adae5de3bd67736229d636f4e18d89bd7652cc8a0445e4af2f95c467bf2a5140","README.md":"eca8a55fada9b5a53c811fbb4f282f97e8430d3e11799d47f2a126c36a0ab785","examples/basic.rs":"cb0a1f3d044de7157dc0c890f0e48a68f990e20814225fdf3f323185f6756c93","src/lib.rs":"d56588787ed3c3f90e37fd4fba7e62190300e80d01db2323b99b19f7600266a3","src/rt.rs":"8bdf2ed83d18154604f6bceae22ef46ef0c5bafe6f6d940e295b181782a79a11","src/stream.rs":"003866e2254cc5b001d584ab95d8c8d4085ace39a542538c6e24eec209015356","src/tunnel.rs":"b7c134a2f8ba6229bbd75a74680d9c65feb1f1fed8e74d00e821a7a2a34b32d7"},"package":"7ad4b0a1e37510028bc4ba81d0e38d239c39671b0f0ce9e02dfa93a8133f7c08"}

View File

@@ -0,0 +1,6 @@
{
"git": {
"sha1": "c7b29b1655c8e59761a22c868cecba283e4dd3b7"
},
"path_in_vcs": ""
}

View File

@@ -0,0 +1,38 @@
name: Checks
on:
push:
branches:
- main
pull_request: ~
jobs:
test:
name: Test
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install latest stable
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
components: rustfmt,clippy
- name: Run rustfmt
run: cargo fmt --all --check
- name: Run clippy
uses: giraffate/clippy-action@v1
with:
reporter: 'github-pr-check'
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Run tests default
run: cargo test
- name: Run tests rustls-tls-webpki-roots
run: cargo test --no-default-features --features rustls-tls-webpki-roots
- name: Run tests native-tls-vendored
run: cargo test --no-default-features --features native-tls-vendored
- name: Run tests native-tls
run: cargo test --no-default-features --features native-tls

View File

@@ -0,0 +1,20 @@
name: Release
on:
push:
tags:
- "*.*.*"
workflow_dispatch:
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- uses: katyo/publish-crates@v2
with:
registry-token: ${{ secrets.CRATES_IO_SECRET }}

1158
vendor/hyper-http-proxy/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

171
vendor/hyper-http-proxy/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,171 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.70.0"
name = "hyper-http-proxy"
version = "1.1.0"
authors = ["MetalBear Tech LTD <hi@metalbear.co>"]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "A proxy connector for Hyper-based applications"
documentation = "https://docs.rs/hyper-http-proxy"
readme = "README.md"
keywords = [
"hyper",
"proxy",
"tokio",
"ssl",
]
categories = [
"web-programming::http-client",
"asynchronous",
"authentication",
]
license = "MIT"
repository = "https://github.com/metalbear-co/hyper-http-proxy"
[features]
__rustls = [
"dep:hyper-rustls",
"dep:tokio-rustls",
"__tls",
]
__tls = []
default = ["default-tls"]
default-tls = ["rustls-tls-native-roots"]
native-tls = [
"dep:native-tls",
"tokio-native-tls",
"hyper-tls",
"__tls",
]
native-tls-vendored = [
"native-tls",
"tokio-native-tls?/vendored",
]
rustls-tls-native-roots = [
"dep:rustls-native-certs",
"__rustls",
"hyper-rustls/rustls-native-certs",
]
rustls-tls-webpki-roots = [
"dep:webpki-roots",
"__rustls",
"hyper-rustls/webpki-roots",
]
[lib]
name = "hyper_http_proxy"
path = "src/lib.rs"
[[example]]
name = "basic"
path = "examples/basic.rs"
[dependencies.bytes]
version = "1.5"
[dependencies.futures-util]
version = "0.3"
default-features = false
[dependencies.headers]
version = "0.4"
[dependencies.http]
version = "1"
[dependencies.hyper]
version = "1"
features = ["client"]
[dependencies.hyper-rustls]
version = "0.27"
optional = true
default-features = false
[dependencies.hyper-tls]
version = "0.6"
optional = true
[dependencies.hyper-util]
version = "0.1"
features = [
"client",
"client-legacy",
"tokio",
]
[dependencies.native-tls]
version = "0.2"
optional = true
[dependencies.pin-project-lite]
version = "0.2"
[dependencies.rustls-native-certs]
version = "0.7"
optional = true
[dependencies.tokio]
version = "1.35"
features = [
"io-std",
"io-util",
]
[dependencies.tokio-native-tls]
version = "0.3"
optional = true
[dependencies.tokio-rustls]
version = "0.26"
optional = true
default-features = false
[dependencies.tower-service]
version = "0.3"
[dependencies.webpki-roots]
version = "0.26"
optional = true
[dev-dependencies.futures]
version = "0.3"
[dev-dependencies.http-body-util]
version = "0.1"
[dev-dependencies.hyper]
version = "1.0"
features = [
"client",
"http1",
]
[dev-dependencies.hyper-util]
version = "0.1"
features = [
"client",
"client-legacy",
"http1",
"tokio",
]
[dev-dependencies.tokio]
version = "1.35"
features = ["full"]

25
vendor/hyper-http-proxy/LICENSE-MIT.md vendored Normal file
View File

@@ -0,0 +1,25 @@
The MIT License (MIT)
Copyright (c) 2017 Johann Tuffe
Copyright (c) 2024 Natsuki Ikeguchi
Copyright (c) 2024 MetalBear Tech LTD
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

100
vendor/hyper-http-proxy/README.md vendored Normal file
View File

@@ -0,0 +1,100 @@
# hyper-http-proxy
[![Checks](https://github.com/metalbear-co/hyper-http-proxy/actions/workflows/checks.yaml/badge.svg)](https://github.com/metalbear-co/hyper-http-proxy/actions/workflows/checks.yaml)
[![MIT licensed](https://img.shields.io/github/license/metalbear-co/hyper-http-proxy)](./LICENSE-MIT.md)
[![crates.io](https://img.shields.io/crates/v/hyper-http-proxy)](https://crates.io/crates/hyper-http-proxy)
A proxy connector for [hyper][1] based applications.
[Documentation][3]
## Example
```rust
use std::error::Error;
use bytes::Bytes;
use headers::Authorization;
use http_body_util::{BodyExt, Empty};
use hyper::{Request, Uri};
use hyper_http_proxy::{Proxy, ProxyConnector, Intercept};
use hyper_util::client::legacy::Client;
use hyper_util::client::legacy::connect::HttpConnector;
use hyper_util::rt::TokioExecutor;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let proxy = {
let proxy_uri = "http://my-proxy:8080".parse().unwrap();
let mut proxy = Proxy::new(Intercept::All, proxy_uri);
proxy.set_authorization(Authorization::basic("John Doe", "Agent1234"));
let connector = HttpConnector::new();
let proxy_connector = ProxyConnector::from_proxy(connector, proxy).unwrap();
proxy_connector
};
// Connecting to http will trigger regular GETs and POSTs.
// We need to manually append the relevant headers to the request
let uri: Uri = "http://my-remote-website.com".parse().unwrap();
let mut req = Request::get(uri.clone()).body(Empty::<Bytes>::new()).unwrap();
if let Some(headers) = proxy.http_headers(&uri) {
req.headers_mut().extend(headers.clone().into_iter());
}
let client = Client::builder(TokioExecutor::new()).build(proxy);
let fut_http = async {
let res = client.request(req).await?;
let body = res.into_body().collect().await?.to_bytes();
Ok::<_, Box<dyn Error>>(String::from_utf8(body.to_vec()).unwrap())
};
// Connecting to an https uri is straightforward (uses 'CONNECT' method underneath)
let uri = "https://my-remote-websitei-secured.com".parse().unwrap();
let fut_https = async {
let res = client.get(uri).await?;
let body = res.into_body().collect().await?.to_bytes();
Ok::<_, Box<dyn Error>>(String::from_utf8(body.to_vec()).unwrap())
};
let (http_res, https_res) = futures::future::join(fut_http, fut_https).await;
let (_, _) = (http_res?, https_res?);
Ok(())
}
```
## Features
`hyper-http-proxy` exposes Cargo features, to configure which TLS implementation it uses to
connect to a proxy. It can also be configured without TLS support, by compiling without default
features entirely. The supported list of configurations is:
native-tls = ["dep:native-tls", "tokio-native-tls", "hyper-tls", "__tls"]
native-tls-vendored = ["native-tls", "tokio-native-tls?/vendored"]
rustls-tls-manual-roots = ["__rustls"]
rustls-tls-webpki-roots = ["dep:webpki-roots", "__rustls"]
rustls-tls-native-roots = ["dep:rustls-native-certs", "__rustls", "hyper-rustls/rustls-native-certs"]
1. No TLS support (`default-features = false`)
2. TLS support via `native-tls` to link against the operating system's native TLS implementation (`default-features = false, features = ["native-tls"]`)
3. TLS support via `rustls` using native certificates (default).
4. TLS support via `rustls`, using a statically-compiled set of CA certificates to bypass the
operating system's default store (`default-features = false, features = ["rustls-tls-webpki-roots"]`)
## Credits
This was forked from https://github.com/siketyan/hyper-http-proxy that originally forked from https://github.com/tafia/hyper-proxy
Large part of the code comes from [reqwest][2].
The core part as just been extracted and slightly enhanced.
Main changes are:
- support for authentication
- add non secured tunneling
- add the possibility to add additional headers when connecting to the proxy
[1]: https://crates.io/crates/hyper
[2]: https://github.com/seanmonstar/reqwest
[3]: https://docs.rs/hyper-http-proxy

View File

@@ -0,0 +1,61 @@
use std::error::Error;
use bytes::Bytes;
use headers::Authorization;
use http_body_util::{BodyExt, Empty};
use hyper::{Request, Uri};
use hyper_http_proxy::{Intercept, Proxy, ProxyConnector};
use hyper_util::client::legacy::connect::HttpConnector;
use hyper_util::client::legacy::Client;
use hyper_util::rt::TokioExecutor;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let proxy = {
let proxy_uri = "http://my-proxy:8080".parse().unwrap();
let mut proxy = Proxy::new(Intercept::All, proxy_uri);
proxy.set_authorization(Authorization::basic("John Doe", "Agent1234"));
let connector = HttpConnector::new();
#[cfg(not(any(feature = "tls", feature = "rustls-base", feature = "openssl-tls")))]
let proxy_connector = ProxyConnector::from_proxy_unsecured(connector, proxy);
#[cfg(any(feature = "tls", feature = "rustls-base", feature = "openssl"))]
let proxy_connector = ProxyConnector::from_proxy(connector, proxy).unwrap();
proxy_connector
};
// Connecting to http will trigger regular GETs and POSTs.
// We need to manually append the relevant headers to the request
let uri: Uri = "http://my-remote-website.com".parse().unwrap();
let mut req = Request::get(uri.clone())
.body(Empty::<Bytes>::new())
.unwrap();
if let Some(headers) = proxy.http_headers(&uri) {
req.headers_mut().extend(headers.clone().into_iter());
}
let client = Client::builder(TokioExecutor::new()).build(proxy);
let fut_http = async {
let res = client.request(req).await?;
let body = res.into_body().collect().await?.to_bytes();
Ok::<_, Box<dyn Error>>(String::from_utf8(body.to_vec()).unwrap())
};
// Connecting to an https uri is straightforward (uses 'CONNECT' method underneath)
let uri = "https://my-remote-websitei-secured.com".parse().unwrap();
let fut_https = async {
let res = client.get(uri).await?;
let body = res.into_body().collect().await?.to_bytes();
Ok::<_, Box<dyn Error>>(String::from_utf8(body.to_vec()).unwrap())
};
let (http_res, https_res) = futures::future::join(fut_http, fut_https).await;
let (_, _) = (http_res?, https_res?);
Ok(())
}

567
vendor/hyper-http-proxy/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,567 @@
//! A Proxy Connector crate for Hyper based applications
//!
//! # Example
//! ```rust,no_run
//! use hyper::{Request, Uri, body::Body};
//! use hyper_util::client::legacy::Client;
//! use hyper_util::client::legacy::connect::HttpConnector;
//! use hyper_util::rt::TokioExecutor;
//! use bytes::Bytes;
//! use futures_util::{TryFutureExt, TryStreamExt};
//! use http_body_util::{BodyExt, Empty};
//! use hyper_http_proxy::{Proxy, ProxyConnector, Intercept};
//! use headers::Authorization;
//! use std::error::Error;
//! use tokio::io::{stdout, AsyncWriteExt as _};
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn Error>> {
//! let proxy = {
//! let proxy_uri = "http://my-proxy:8080".parse().unwrap();
//! let mut proxy = Proxy::new(Intercept::All, proxy_uri);
//! proxy.set_authorization(Authorization::basic("John Doe", "Agent1234"));
//! let connector = HttpConnector::new();
//! # #[cfg(not(any(feature = "tls", feature = "rustls-base", feature = "openssl-tls")))]
//! # let proxy_connector = ProxyConnector::from_proxy_unsecured(connector, proxy);
//! # #[cfg(any(feature = "tls", feature = "rustls-base", feature = "openssl"))]
//! let proxy_connector = ProxyConnector::from_proxy(connector, proxy).unwrap();
//! proxy_connector
//! };
//!
//! // Connecting to http will trigger regular GETs and POSTs.
//! // We need to manually append the relevant headers to the request
//! let uri: Uri = "http://my-remote-website.com".parse().unwrap();
//! let mut req = Request::get(uri.clone()).body(Empty::<Bytes>::new()).unwrap();
//!
//! if let Some(headers) = proxy.http_headers(&uri) {
//! req.headers_mut().extend(headers.clone().into_iter());
//! }
//!
//! let client = Client::builder(TokioExecutor::new()).build(proxy);
//! let mut resp = client.request(req).await?;
//! println!("Response: {}", resp.status());
//! while let Some(chunk) = resp.body_mut().collect().await.ok().map(|c| c.to_bytes()) {
//! stdout().write_all(&chunk).await?;
//! }
//!
//! // Connecting to an https uri is straightforward (uses 'CONNECT' method underneath)
//! let uri = "https://my-remote-websitei-secured.com".parse().unwrap();
//! let mut resp = client.get(uri).await?;
//! println!("Response: {}", resp.status());
//! while let Some(chunk) = resp.body_mut().collect().await.ok().map(|c| c.to_bytes()) {
//! stdout().write_all(&chunk).await?;
//! }
//!
//! Ok(())
//! }
//! ```
#![allow(missing_docs)]
mod rt;
mod stream;
mod tunnel;
use std::{fmt, io, sync::Arc};
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
use futures_util::future::TryFutureExt;
use headers::{authorization::Credentials, Authorization, HeaderMapExt, ProxyAuthorization};
use http::header::{HeaderMap, HeaderName, HeaderValue};
use hyper::rt::{Read, Write};
use hyper::Uri;
use tower_service::Service;
pub use stream::ProxyStream;
#[cfg(all(not(feature = "__rustls"), feature = "native-tls"))]
use native_tls::TlsConnector as NativeTlsConnector;
#[cfg(all(not(feature = "__rustls"), feature = "native-tls"))]
use tokio_native_tls::TlsConnector;
#[cfg(feature = "__rustls")]
use hyper_rustls::ConfigBuilderExt;
#[cfg(feature = "__rustls")]
use tokio_rustls::TlsConnector;
#[cfg(feature = "__rustls")]
use tokio_rustls::rustls::pki_types::ServerName;
type BoxError = Box<dyn std::error::Error + Send + Sync>;
/// The Intercept enum to filter connections
#[derive(Debug, Clone)]
pub enum Intercept {
/// All incoming connection will go through proxy
All,
/// Only http connections will go through proxy
Http,
/// Only https connections will go through proxy
Https,
/// No connection will go through this proxy
None,
/// A custom intercept
Custom(Custom),
}
/// A trait for matching between Destination and Uri
pub trait Dst {
/// Returns the connection scheme, e.g. "http" or "https"
fn scheme(&self) -> Option<&str>;
/// Returns the host of the connection
fn host(&self) -> Option<&str>;
/// Returns the port for the connection
fn port(&self) -> Option<u16>;
}
impl Dst for Uri {
fn scheme(&self) -> Option<&str> {
self.scheme_str()
}
fn host(&self) -> Option<&str> {
self.host()
}
fn port(&self) -> Option<u16> {
self.port_u16()
}
}
#[inline]
pub(crate) fn io_err<E: Into<Box<dyn std::error::Error + Send + Sync>>>(e: E) -> io::Error {
io::Error::new(io::ErrorKind::Other, e)
}
pub type CustomProxyCallback =
dyn Fn(Option<&str>, Option<&str>, Option<u16>) -> bool + Send + Sync;
/// A Custom struct to proxy custom uris
#[derive(Clone)]
pub struct Custom(Arc<CustomProxyCallback>);
impl fmt::Debug for Custom {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "_")
}
}
impl<F: Fn(Option<&str>, Option<&str>, Option<u16>) -> bool + Send + Sync + 'static> From<F>
for Custom
{
fn from(f: F) -> Custom {
Custom(Arc::new(f))
}
}
impl Intercept {
/// A function to check if given `Uri` is proxied
pub fn matches<D: Dst>(&self, uri: &D) -> bool {
match (self, uri.scheme()) {
(&Intercept::All, _)
| (&Intercept::Http, Some("http"))
| (&Intercept::Https, Some("https")) => true,
(&Intercept::Custom(Custom(ref f)), _) => f(uri.scheme(), uri.host(), uri.port()),
_ => false,
}
}
}
impl<F: Fn(Option<&str>, Option<&str>, Option<u16>) -> bool + Send + Sync + 'static> From<F>
for Intercept
{
fn from(f: F) -> Intercept {
Intercept::Custom(f.into())
}
}
/// A Proxy struct
#[derive(Clone, Debug)]
pub struct Proxy {
intercept: Intercept,
force_connect: bool,
headers: HeaderMap,
uri: Uri,
}
impl Proxy {
/// Create a new `Proxy`
pub fn new<I: Into<Intercept>>(intercept: I, uri: Uri) -> Proxy {
let mut proxy = Proxy {
intercept: intercept.into(),
uri: uri.clone(),
headers: HeaderMap::new(),
force_connect: false,
};
if let Some((user, pass)) = extract_user_pass(&uri) {
proxy.set_authorization(Authorization::basic(user, pass));
}
proxy
}
/// Set `Proxy` authorization
pub fn set_authorization<C: Credentials + Clone>(&mut self, credentials: Authorization<C>) {
match self.intercept {
Intercept::Http => {
self.headers.typed_insert(Authorization(credentials.0));
}
Intercept::Https => {
self.headers.typed_insert(ProxyAuthorization(credentials.0));
}
_ => {
self.headers
.typed_insert(Authorization(credentials.0.clone()));
self.headers.typed_insert(ProxyAuthorization(credentials.0));
}
}
}
/// Forces the use of the CONNECT method.
pub fn force_connect(&mut self) {
self.force_connect = true;
}
/// Set a custom header
pub fn set_header(&mut self, name: HeaderName, value: HeaderValue) {
self.headers.insert(name, value);
}
/// Get current intercept
pub fn intercept(&self) -> &Intercept {
&self.intercept
}
/// Get current `Headers` which must be sent to proxy
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
/// Get proxy uri
pub fn uri(&self) -> &Uri {
&self.uri
}
}
/// A wrapper around `Proxy`s with a connector.
#[derive(Clone)]
pub struct ProxyConnector<C> {
proxies: Vec<Proxy>,
connector: C,
#[cfg(all(not(feature = "__rustls"), feature = "native-tls"))]
tls: Option<NativeTlsConnector>,
#[cfg(feature = "__rustls")]
tls: Option<TlsConnector>,
#[cfg(not(feature = "__tls"))]
tls: Option<()>,
}
impl<C: fmt::Debug> fmt::Debug for ProxyConnector<C> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(
f,
"ProxyConnector {}{{ proxies: {:?}, connector: {:?} }}",
if self.tls.is_some() {
""
} else {
"(unsecured)"
},
self.proxies,
self.connector
)
}
}
impl<C> ProxyConnector<C> {
/// Create a new secured Proxies
#[cfg(all(not(feature = "__rustls"), feature = "native-tls"))]
pub fn new(connector: C) -> Result<Self, io::Error> {
let tls = NativeTlsConnector::builder()
.build()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
Ok(ProxyConnector {
proxies: Vec::new(),
connector: connector,
tls: Some(tls),
})
}
/// Create a new secured Proxies
#[cfg(feature = "__rustls")]
pub fn new(connector: C) -> Result<Self, io::Error> {
let config = tokio_rustls::rustls::ClientConfig::builder();
#[cfg(feature = "rustls-tls-native-roots")]
let config = config.with_native_roots()?;
#[cfg(feature = "rustls-tls-webpki-roots")]
let config = config.with_webpki_roots();
let cfg = Arc::new(config.with_no_client_auth());
let tls = TlsConnector::from(cfg);
Ok(ProxyConnector {
proxies: Vec::new(),
connector,
tls: Some(tls),
})
}
/// Create a new unsecured Proxy
pub fn unsecured(connector: C) -> Self {
ProxyConnector {
proxies: Vec::new(),
connector,
tls: None,
}
}
/// Create a proxy connector and attach a particular proxy
#[cfg(feature = "__tls")]
pub fn from_proxy(connector: C, proxy: Proxy) -> Result<Self, io::Error> {
let mut c = ProxyConnector::new(connector)?;
c.proxies.push(proxy);
Ok(c)
}
/// Create a proxy connector and attach a particular proxy
pub fn from_proxy_unsecured(connector: C, proxy: Proxy) -> Self {
let mut c = ProxyConnector::unsecured(connector);
c.proxies.push(proxy);
c
}
/// Change proxy connector
pub fn with_connector<CC>(self, connector: CC) -> ProxyConnector<CC> {
ProxyConnector {
connector,
proxies: self.proxies,
tls: self.tls,
}
}
/// Set or unset tls when tunneling
#[cfg(all(not(feature = "__rustls"), feature = "native-tls"))]
pub fn set_tls(&mut self, tls: Option<NativeTlsConnector>) {
self.tls = tls;
}
/// Set or unset tls when tunneling
#[cfg(feature = "__rustls")]
pub fn set_tls(&mut self, tls: Option<TlsConnector>) {
self.tls = tls;
}
/// Get the current proxies
pub fn proxies(&self) -> &[Proxy] {
&self.proxies
}
/// Add a new additional proxy
pub fn add_proxy(&mut self, proxy: Proxy) {
self.proxies.push(proxy);
}
/// Extend the list of proxies
pub fn extend_proxies<I: IntoIterator<Item = Proxy>>(&mut self, proxies: I) {
self.proxies.extend(proxies)
}
/// Get http headers for a matching uri
///
/// These headers must be appended to the hyper Request for the proxy to work properly.
/// This is needed only for http requests.
pub fn http_headers(&self, uri: &Uri) -> Option<&HeaderMap> {
if uri.scheme_str().map_or(true, |s| s != "http") {
return None;
}
self.match_proxy(uri).map(|p| &p.headers)
}
fn match_proxy<D: Dst>(&self, uri: &D) -> Option<&Proxy> {
self.proxies.iter().find(|p| p.intercept.matches(uri))
}
}
macro_rules! mtry {
($e:expr) => {
match $e {
Ok(v) => v,
Err(e) => break Err(e.into()),
}
};
}
impl<C> Service<Uri> for ProxyConnector<C>
where
C: Service<Uri>,
C::Response: Read + Write + Send + Unpin + 'static,
C::Future: Send + 'static,
C::Error: Into<BoxError>,
{
type Response = ProxyStream<C::Response>;
type Error = io::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
match self.connector.poll_ready(cx) {
Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
Poll::Ready(Err(e)) => Poll::Ready(Err(io_err(e.into()))),
Poll::Pending => Poll::Pending,
}
}
fn call(&mut self, uri: Uri) -> Self::Future {
if let (Some(p), Some(host)) = (self.match_proxy(&uri), uri.host()) {
if uri.scheme() == Some(&http::uri::Scheme::HTTPS) || p.force_connect {
let host = host.to_owned();
let port =
uri.port_u16()
.unwrap_or(if uri.scheme() == Some(&http::uri::Scheme::HTTP) {
80
} else {
443
});
let tunnel = tunnel::new(&host, port, &p.headers);
let connection =
proxy_dst(&uri, &p.uri).map(|proxy_url| self.connector.call(proxy_url));
let tls = if uri.scheme() == Some(&http::uri::Scheme::HTTPS) {
self.tls.clone()
} else {
None
};
Box::pin(async move {
// this hack will gone once `try_blocks` will eventually stabilized
#[allow(clippy::never_loop)]
loop {
let proxy_stream = mtry!(mtry!(connection).await.map_err(io_err));
let tunnel_stream = mtry!(tunnel.with_stream(proxy_stream).await);
break match tls {
#[cfg(all(not(feature = "__rustls"), feature = "native-tls"))]
Some(tls) => {
use hyper_util::rt::TokioIo;
let tls = TlsConnector::from(tls);
let secure_stream = mtry!(tls
.connect(&host, TokioIo::new(tunnel_stream))
.await
.map_err(io_err));
Ok(ProxyStream::Secured(Box::new(TokioIo::new(secure_stream))))
}
#[cfg(feature = "__rustls")]
Some(tls) => {
use hyper_util::rt::TokioIo;
let server_name =
mtry!(ServerName::try_from(host.to_string()).map_err(io_err));
let secure_stream = mtry!(tls
.connect(server_name, TokioIo::new(tunnel_stream))
.await
.map_err(io_err));
Ok(ProxyStream::Secured(Box::new(TokioIo::new(secure_stream))))
}
#[cfg(not(feature = "__tls",))]
Some(_) => panic!("hyper-proxy was not built with TLS support"),
None => Ok(ProxyStream::Regular(tunnel_stream)),
};
}
})
} else {
match proxy_dst(&uri, &p.uri) {
Ok(proxy_uri) => Box::pin(
self.connector
.call(proxy_uri)
.map_ok(ProxyStream::Regular)
.map_err(|err| io_err(err.into())),
),
Err(err) => Box::pin(futures_util::future::err(io_err(err))),
}
}
} else {
Box::pin(
self.connector
.call(uri)
.map_ok(ProxyStream::NoProxy)
.map_err(|err| io_err(err.into())),
)
}
}
}
fn proxy_dst(dst: &Uri, proxy: &Uri) -> io::Result<Uri> {
Uri::builder()
.scheme(
proxy
.scheme_str()
.ok_or_else(|| io_err(format!("proxy uri missing scheme: {}", proxy)))?,
)
.authority(
proxy
.authority()
.ok_or_else(|| io_err(format!("proxy uri missing host: {}", proxy)))?
.clone(),
)
.path_and_query(dst.path_and_query().unwrap().clone())
.build()
.map_err(|err| io_err(format!("other error: {}", err)))
}
/// Extracts the username and password from the URI
fn extract_user_pass(uri: &Uri) -> Option<(&str, &str)> {
let authority = uri.authority()?.as_str();
let (userinfo, _) = authority.rsplit_once('@')?;
let mut parts = userinfo.splitn(2, ':');
let username = parts.next()?;
let password = parts.next()?;
Some((username, password))
}
#[cfg(test)]
mod tests {
use http::Uri;
use crate::{Intercept, Proxy};
#[test]
fn test_new_proxy_with_authorization() {
let proxy = Proxy::new(
Intercept::All,
Uri::from_static("https://bob:secret@my-proxy:8080"),
);
assert_eq!(
proxy
.headers()
.get("authorization")
.unwrap()
.to_str()
.unwrap(),
"Basic Ym9iOnNlY3JldA=="
);
}
#[test]
fn test_new_proxy_without_authorization() {
let proxy = Proxy::new(Intercept::All, Uri::from_static("https://my-proxy:8080"));
assert_eq!(proxy.headers().get("authorization"), None);
}
}

140
vendor/hyper-http-proxy/src/rt.rs vendored Normal file
View File

@@ -0,0 +1,140 @@
use std::future::Future;
use std::marker::PhantomPinned;
use std::pin::Pin;
use std::task::{Context, Poll};
use bytes::{Buf, BufMut};
use futures_util::ready;
use hyper::rt;
use pin_project_lite::pin_project;
pin_project! {
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct ReadBuf<'a, R: ?Sized, B: ?Sized> {
reader: &'a mut R,
buf: &'a mut B,
#[pin]
_pin: PhantomPinned,
}
}
impl<R, B> Future for ReadBuf<'_, R, B>
where
R: rt::Read + Unpin + ?Sized,
B: BufMut + ?Sized,
{
type Output = std::io::Result<usize>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
use hyper::rt::{Read as _, ReadBuf};
use std::mem::MaybeUninit;
let me = self.project();
if !me.buf.has_remaining_mut() {
return Poll::Ready(Ok(0));
}
let n = {
let dst = me.buf.chunk_mut();
let dst = unsafe { &mut *(dst as *mut _ as *mut [MaybeUninit<u8>]) };
let mut buf = ReadBuf::uninit(dst);
let ptr = buf.filled().as_ptr();
ready!(Pin::new(me.reader).poll_read(cx, buf.unfilled())?);
// Ensure the pointer does not change from under us
assert_eq!(ptr, buf.filled().as_ptr());
buf.filled().len()
};
// Safety: This is guaranteed to be the number of initialized (and read)
// bytes due to the invariants provided by `ReadBuf::filled`.
unsafe {
me.buf.advance_mut(n);
}
Poll::Ready(Ok(n))
}
}
pub(crate) fn read_buf<'a, R, B>(reader: &'a mut R, buf: &'a mut B) -> ReadBuf<'a, R, B>
where
R: rt::Read + Unpin + ?Sized,
B: BufMut + ?Sized,
{
ReadBuf {
reader,
buf,
_pin: PhantomPinned,
}
}
pin_project! {
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct WriteBuf<'a, W, B> {
writer: &'a mut W,
buf: &'a mut B,
#[pin]
_pin: PhantomPinned,
}
}
impl<W, B> Future for WriteBuf<'_, W, B>
where
W: rt::Write + Unpin,
B: Buf,
{
type Output = std::io::Result<usize>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
use rt::Write as _;
let me = self.project();
if !me.buf.has_remaining() {
return Poll::Ready(Ok(0));
}
let n = ready!(Pin::new(me.writer).poll_write(cx, me.buf.chunk()))?;
me.buf.advance(n);
Poll::Ready(Ok(n))
}
}
pub(crate) fn write_buf<'a, W, B>(writer: &'a mut W, buf: &'a mut B) -> WriteBuf<'a, W, B>
where
W: rt::Write + Unpin,
B: Buf,
{
WriteBuf {
writer,
buf,
_pin: PhantomPinned,
}
}
pub(crate) trait ReadExt: rt::Read {
fn read_buf<'a, B>(&'a mut self, buf: &'a mut B) -> ReadBuf<'a, Self, B>
where
Self: Unpin,
B: BufMut + ?Sized,
{
read_buf(self, buf)
}
}
impl<T> ReadExt for T where T: rt::Read {}
pub(crate) trait WriteExt: rt::Write {
fn write_buf<'a, B>(&'a mut self, src: &'a mut B) -> WriteBuf<'a, Self, B>
where
Self: Sized + Unpin,
B: Buf,
{
write_buf(self, src)
}
}
impl<T> WriteExt for T where T: rt::Write {}

116
vendor/hyper-http-proxy/src/stream.rs vendored Normal file
View File

@@ -0,0 +1,116 @@
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use hyper::rt::{Read, ReadBufCursor, Write};
use hyper_util::client::legacy::connect::{Connected, Connection};
#[cfg(feature = "__tls")]
use hyper_util::rt::TokioIo;
#[cfg(feature = "__rustls")]
use tokio_rustls::client::TlsStream as RustlsStream;
#[cfg(all(not(feature = "__rustls"), feature = "native-tls"))]
use tokio_native_tls::TlsStream as TokioNativeTlsStream;
#[cfg(feature = "__rustls")]
pub type TlsStream<R> = TokioIo<RustlsStream<TokioIo<R>>>;
#[cfg(all(not(feature = "__rustls"), feature = "native-tls"))]
pub type TlsStream<R> = TokioIo<TokioNativeTlsStream<TokioIo<R>>>;
/// A Proxy Stream wrapper
pub enum ProxyStream<R> {
NoProxy(R),
Regular(R),
#[cfg(feature = "__tls")]
Secured(Box<TlsStream<R>>),
}
macro_rules! match_fn_pinned {
($self:expr, $fn:ident, $ctx:expr, $buf:expr) => {
match $self.get_mut() {
ProxyStream::NoProxy(s) => Pin::new(s).$fn($ctx, $buf),
ProxyStream::Regular(s) => Pin::new(s).$fn($ctx, $buf),
#[cfg(feature = "__tls")]
ProxyStream::Secured(s) => Pin::new(s).$fn($ctx, $buf),
}
};
($self:expr, $fn:ident, $ctx:expr) => {
match $self.get_mut() {
ProxyStream::NoProxy(s) => Pin::new(s).$fn($ctx),
ProxyStream::Regular(s) => Pin::new(s).$fn($ctx),
#[cfg(feature = "__tls")]
ProxyStream::Secured(s) => Pin::new(s).$fn($ctx),
}
};
}
impl<R: Read + Write + Unpin> Read for ProxyStream<R> {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: ReadBufCursor<'_>,
) -> Poll<io::Result<()>> {
match_fn_pinned!(self, poll_read, cx, buf)
}
}
impl<R: Read + Write + Unpin> Write for ProxyStream<R> {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
match_fn_pinned!(self, poll_write, cx, buf)
}
fn poll_write_vectored(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[io::IoSlice<'_>],
) -> Poll<Result<usize, io::Error>> {
match_fn_pinned!(self, poll_write_vectored, cx, bufs)
}
fn is_write_vectored(&self) -> bool {
match self {
ProxyStream::NoProxy(s) => s.is_write_vectored(),
ProxyStream::Regular(s) => s.is_write_vectored(),
#[cfg(feature = "__tls")]
ProxyStream::Secured(s) => s.is_write_vectored(),
}
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
match_fn_pinned!(self, poll_flush, cx)
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
match_fn_pinned!(self, poll_shutdown, cx)
}
}
impl<R: Read + Write + Connection + Unpin> Connection for ProxyStream<R> {
fn connected(&self) -> Connected {
match self {
ProxyStream::NoProxy(s) => s.connected(),
ProxyStream::Regular(s) => s.connected().proxy(true),
#[cfg(all(not(feature = "__rustls"), feature = "native-tls"))]
ProxyStream::Secured(s) => s
.inner()
.get_ref()
.get_ref()
.get_ref()
.inner()
.connected()
.proxy(true),
#[cfg(feature = "__rustls")]
ProxyStream::Secured(s) => s.inner().get_ref().0.inner().connected().proxy(true),
}
}
}

221
vendor/hyper-http-proxy/src/tunnel.rs vendored Normal file
View File

@@ -0,0 +1,221 @@
use std::fmt::{self, Display, Formatter};
use std::future::Future;
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use bytes::{buf::Buf, BytesMut};
use http::HeaderMap;
use hyper::rt::{Read, Write};
use crate::io_err;
use crate::rt::{ReadExt as _, WriteExt as _};
macro_rules! try_ready {
($x:expr) => {
match $x {
core::task::Poll::Ready(Ok(x)) => x,
core::task::Poll::Ready(Err(e)) => return core::task::Poll::Ready(Err(e.into())),
core::task::Poll::Pending => return core::task::Poll::Pending,
}
};
}
pub(crate) struct TunnelConnect {
buf: BytesMut,
}
impl TunnelConnect {
/// Change stream
pub fn with_stream<S>(self, stream: S) -> Tunnel<S> {
Tunnel {
buf: self.buf,
stream: Some(stream),
state: TunnelState::Writing,
}
}
}
pub(crate) struct Tunnel<S> {
buf: BytesMut,
stream: Option<S>,
state: TunnelState,
}
#[derive(Debug)]
enum TunnelState {
Writing,
Reading,
}
struct HeadersDisplay<'a>(&'a HeaderMap);
impl<'a> Display for HeadersDisplay<'a> {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
for (key, value) in self.0 {
let value_str = value.to_str().map_err(|_| fmt::Error)?;
write!(f, "{}: {}\r\n", key.as_str(), value_str)?;
}
Ok(())
}
}
/// Creates a new tunnel through proxy
pub(crate) fn new(host: &str, port: u16, headers: &HeaderMap) -> TunnelConnect {
let buf = format!(
"CONNECT {0}:{1} HTTP/1.1\r\n\
Host: {0}:{1}\r\n\
{2}\
\r\n",
host,
port,
HeadersDisplay(headers)
)
.into_bytes();
TunnelConnect {
buf: buf.as_slice().into(),
}
}
impl<S: Read + Write + Unpin> Future for Tunnel<S> {
type Output = Result<S, io::Error>;
fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
if self.stream.is_none() {
panic!("must not poll after future is complete")
}
let this = self.get_mut();
loop {
if let TunnelState::Writing = &this.state {
let fut = this.stream.as_mut().unwrap().write_buf(&mut this.buf);
futures_util::pin_mut!(fut);
let n = try_ready!(fut.poll(ctx));
if !this.buf.has_remaining() {
this.state = TunnelState::Reading;
this.buf.truncate(0);
} else if n == 0 {
return Poll::Ready(Err(io_err("unexpected EOF while tunnel writing")));
}
} else {
let fut = this.stream.as_mut().unwrap().read_buf(&mut this.buf);
futures_util::pin_mut!(fut);
let n = try_ready!(fut.poll(ctx));
if n == 0 {
return Poll::Ready(Err(io_err("unexpected EOF while tunnel reading")));
} else {
let read = &this.buf[..];
if read.len() > 12 {
if read.starts_with(b"HTTP/1.1 200") || read.starts_with(b"HTTP/1.0 200") {
if read.ends_with(b"\r\n\r\n") {
return Poll::Ready(Ok(this.stream.take().unwrap()));
}
// else read more
} else {
let len = read.len().min(16);
return Poll::Ready(Err(io_err(format!(
"unsuccessful tunnel ({})",
String::from_utf8_lossy(&read[0..len])
))));
}
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::{HeaderMap, Tunnel};
use futures_util::future::TryFutureExt;
use hyper_util::rt::TokioIo;
use std::io::{Read, Write};
use std::net::TcpListener;
use std::thread;
use tokio::net::TcpStream;
use tokio::runtime::Runtime;
fn tunnel<S>(conn: S, host: String, port: u16) -> Tunnel<S> {
super::new(&host, port, &HeaderMap::new()).with_stream(conn)
}
#[rustfmt::skip]
macro_rules! mock_tunnel {
() => {{
mock_tunnel!(
b"\
HTTP/1.1 200 OK\r\n\
\r\n\
"
)
}};
($write:expr) => {{
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let connect_expected = format!(
"\
CONNECT {0}:{1} HTTP/1.1\r\n\
Host: {0}:{1}\r\n\
\r\n\
",
addr.ip(),
addr.port()
).into_bytes();
thread::spawn(move || {
let (mut sock, _) = listener.accept().unwrap();
let mut buf = [0u8; 4096];
let n = sock.read(&mut buf).unwrap();
assert_eq!(&buf[..n], &connect_expected[..]);
sock.write_all($write).unwrap();
});
addr
}};
}
#[test]
fn test_tunnel() {
let addr = mock_tunnel!();
let core = Runtime::new().unwrap();
let work = TcpStream::connect(&addr);
let host = addr.ip().to_string();
let port = addr.port();
let work = work.and_then(|tcp| tunnel(TokioIo::new(tcp), host, port));
core.block_on(work).unwrap();
}
#[test]
fn test_tunnel_eof() {
let addr = mock_tunnel!(b"HTTP/1.1 200 OK");
let core = Runtime::new().unwrap();
let work = TcpStream::connect(&addr);
let host = addr.ip().to_string();
let port = addr.port();
let work = work.and_then(|tcp| tunnel(TokioIo::new(tcp), host, port));
core.block_on(work).unwrap_err();
}
#[test]
fn test_tunnel_bad_response() {
let addr = mock_tunnel!(b"foo bar baz hallo");
let core = Runtime::new().unwrap();
let work = TcpStream::connect(&addr);
let host = addr.ip().to_string();
let port = addr.port();
let work = work.and_then(|tcp| tunnel(TokioIo::new(tcp), host, port));
core.block_on(work).unwrap_err();
}
}

Binary file not shown.

File diff suppressed because one or more lines are too long