ureq/unversioned/transport/
connect.rs1use base64::Engine;
2use base64::prelude::BASE64_STANDARD;
3use std::borrow::Cow;
4use std::fmt;
5use std::io::Write;
6use ureq_proto::parser::try_parse_response;
7
8use http::StatusCode;
9
10use crate::Error;
11use crate::config::DEFAULT_USER_AGENT;
12use crate::http;
13use crate::transport::{Buffers, ConnectionDetails, Connector, Either};
14use crate::transport::{NextTimeout, Transport, TransportAdapter};
15use crate::util::{SchemeExt, UriExt};
16
17#[derive(Default)]
22pub struct ConnectProxyConnector(());
23
24impl<In: Transport> Connector<In> for ConnectProxyConnector {
25 type Out = Either<In, Box<dyn Transport>>;
26
27 fn connect(
28 &self,
29 details: &ConnectionDetails,
30 chained: Option<In>,
31 ) -> Result<Option<Self::Out>, Error> {
32 if let Some(transport) = chained {
34 return Ok(Some(Either::A(transport)));
35 }
36
37 let maybe_connect_uri = details.config.connect_proxy_uri();
39
40 let Some(connect_uri) = maybe_connect_uri else {
41 return Ok(None);
43 };
44
45 let target = details.uri;
46 let target_addrs = &details.addrs;
47
48 let is_no_proxy = details
50 .config
51 .proxy()
52 .map(|p| p.is_no_proxy(target))
53 .unwrap_or(false);
54
55 if is_no_proxy {
56 return Ok(None);
57 }
58
59 let proxy_addrs = details
63 .resolver
64 .resolve(connect_uri, details.config, details.timeout)?;
65
66 let proxy_config = details.config.clone_without_proxy();
67
68 let proxy_details = ConnectionDetails {
71 uri: connect_uri,
72 addrs: proxy_addrs,
73 config: &proxy_config,
74 request_level: details.request_level,
75 resolver: details.resolver,
76 now: details.now,
77 timeout: details.timeout,
78 current_time: details.current_time.clone(),
79 run_connector: details.run_connector.clone(),
80 };
81
82 let transport = (details.run_connector)(&proxy_details)?;
83
84 let proxy = details.config.proxy().unwrap();
86
87 let mut w = TransportAdapter::new(transport);
88
89 target.ensure_valid_url()?;
90
91 let mut target_host = Cow::Borrowed(target.host().unwrap());
93 let target_port = target
94 .port_u16()
95 .unwrap_or(target.scheme().unwrap().default_port().unwrap());
96
97 if proxy.resolve_target() {
98 let resolved = target_addrs.first().expect("at least one resolved address");
103
104 target_host = Cow::Owned(resolved.to_string());
105 }
106
107 write!(w, "CONNECT {}:{} HTTP/1.1\r\n", target_host, target_port)?;
108 write!(w, "Host: {}:{}\r\n", target_host, target_port)?;
109 if let Some(v) = details.config.user_agent().as_str(DEFAULT_USER_AGENT) {
110 write!(w, "User-Agent: {}\r\n", v)?;
111 }
112 write!(w, "Proxy-Connection: Keep-Alive\r\n")?;
113
114 let use_creds = proxy.username().is_some() || proxy.password().is_some();
115
116 if use_creds {
117 let user = proxy.username().unwrap_or_default();
118 let pass = proxy.password().unwrap_or_default();
119 let creds = BASE64_STANDARD.encode(format!("{}:{}", user, pass));
120 write!(w, "Proxy-Authorization: Basic {}\r\n", creds)?;
123 }
124
125 write!(w, "\r\n")?;
126 w.flush()?;
127
128 let mut transport = w.into_inner();
129
130 let response = loop {
131 let made_progress = transport.maybe_await_input(details.timeout)?;
132 let buffers = transport.buffers();
133 let input = buffers.input();
134 let Some((used_input, response)) = try_parse_response::<20>(input)? else {
135 if !made_progress {
136 let reason = "proxy server did not respond".to_string();
137 return Err(Error::ConnectProxyFailed(reason));
138 }
139 continue;
140 };
141 buffers.input_consume(used_input);
142 break response;
143 };
144
145 match response.status() {
146 StatusCode::OK => {
147 trace!("CONNECT proxy connected");
148 }
149 x => {
150 let reason = format!("proxy server responded {}/{}", x.as_u16(), x.as_str());
151 return Err(Error::ConnectProxyFailed(reason));
152 }
153 }
154
155 Ok(Some(Either::B(TunnelTransport(transport).boxed())))
158 }
159}
160
161#[derive(Debug)]
162struct TunnelTransport(Box<dyn Transport>);
163
164impl Transport for TunnelTransport {
165 fn buffers(&mut self) -> &mut dyn Buffers {
166 self.0.buffers()
167 }
168
169 fn transmit_output(&mut self, amount: usize, timeout: NextTimeout) -> Result<(), Error> {
170 self.0.transmit_output(amount, timeout)
171 }
172
173 fn await_input(&mut self, timeout: NextTimeout) -> Result<bool, Error> {
174 self.0.await_input(timeout)
175 }
176
177 fn is_open(&mut self) -> bool {
178 self.0.is_open()
179 }
180
181 fn is_tls(&self) -> bool {
182 false
183 }
184}
185
186impl fmt::Debug for ConnectProxyConnector {
187 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188 f.debug_struct("ProxyConnector").finish()
189 }
190}