use base64::Engine;
use base64::prelude::BASE64_STANDARD;
use std::borrow::Cow;
use std::fmt;
use std::io::Write;
use ureq_proto::parser::try_parse_response;
use http::StatusCode;
use crate::Error;
use crate::config::DEFAULT_USER_AGENT;
use crate::http;
use crate::transport::{Buffers, ConnectionDetails, Connector, Either};
use crate::transport::{NextTimeout, Transport, TransportAdapter};
use crate::util::{SchemeExt, UriExt};
#[derive(Default)]
pub struct ConnectProxyConnector(());
impl<In: Transport> Connector<In> for ConnectProxyConnector {
type Out = Either<In, Box<dyn Transport>>;
fn connect(
&self,
details: &ConnectionDetails,
chained: Option<In>,
) -> Result<Option<Self::Out>, Error> {
if let Some(transport) = chained {
return Ok(Some(Either::A(transport)));
}
let maybe_connect_uri = details.config.connect_proxy_uri();
let Some(connect_uri) = maybe_connect_uri else {
return Ok(None);
};
let target = details.uri;
let target_addrs = &details.addrs;
let is_no_proxy = details
.config
.proxy()
.map(|p| p.is_no_proxy(target))
.unwrap_or(false);
if is_no_proxy {
return Ok(None);
}
let proxy_addrs = details
.resolver
.resolve(connect_uri, details.config, details.timeout)?;
let proxy_config = details.config.clone_without_proxy();
let proxy_details = ConnectionDetails {
uri: connect_uri,
addrs: proxy_addrs,
config: &proxy_config,
request_level: details.request_level,
resolver: details.resolver,
now: details.now,
timeout: details.timeout,
current_time: details.current_time.clone(),
run_connector: details.run_connector.clone(),
};
let transport = (details.run_connector)(&proxy_details)?;
let proxy = details.config.proxy().unwrap();
let mut w = TransportAdapter::new(transport);
target.ensure_valid_url()?;
let mut target_host = Cow::Borrowed(target.host().unwrap());
let target_port = target
.port_u16()
.unwrap_or(target.scheme().unwrap().default_port().unwrap());
if proxy.resolve_target() {
let resolved = target_addrs.first().expect("at least one resolved address");
target_host = Cow::Owned(resolved.to_string());
}
write!(w, "CONNECT {}:{} HTTP/1.1\r\n", target_host, target_port)?;
write!(w, "Host: {}:{}\r\n", target_host, target_port)?;
if let Some(v) = details.config.user_agent().as_str(DEFAULT_USER_AGENT) {
write!(w, "User-Agent: {}\r\n", v)?;
}
write!(w, "Proxy-Connection: Keep-Alive\r\n")?;
let use_creds = proxy.username().is_some() || proxy.password().is_some();
if use_creds {
let user = proxy.username().unwrap_or_default();
let pass = proxy.password().unwrap_or_default();
let creds = BASE64_STANDARD.encode(format!("{}:{}", user, pass));
write!(w, "Proxy-Authorization: Basic {}\r\n", creds)?;
}
write!(w, "\r\n")?;
w.flush()?;
let mut transport = w.into_inner();
let response = loop {
let made_progress = transport.maybe_await_input(details.timeout)?;
let buffers = transport.buffers();
let input = buffers.input();
let Some((used_input, response)) = try_parse_response::<20>(input)? else {
if !made_progress {
let reason = "proxy server did not respond".to_string();
return Err(Error::ConnectProxyFailed(reason));
}
continue;
};
buffers.input_consume(used_input);
break response;
};
match response.status() {
StatusCode::OK => {
trace!("CONNECT proxy connected");
}
x => {
let reason = format!("proxy server responded {}/{}", x.as_u16(), x.as_str());
return Err(Error::ConnectProxyFailed(reason));
}
}
Ok(Some(Either::B(TunnelTransport(transport).boxed())))
}
}
#[derive(Debug)]
struct TunnelTransport(Box<dyn Transport>);
impl Transport for TunnelTransport {
fn buffers(&mut self) -> &mut dyn Buffers {
self.0.buffers()
}
fn transmit_output(&mut self, amount: usize, timeout: NextTimeout) -> Result<(), Error> {
self.0.transmit_output(amount, timeout)
}
fn await_input(&mut self, timeout: NextTimeout) -> Result<bool, Error> {
self.0.await_input(timeout)
}
fn is_open(&mut self) -> bool {
self.0.is_open()
}
fn is_tls(&self) -> bool {
false
}
}
impl fmt::Debug for ConnectProxyConnector {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ProxyConnector").finish()
}
}