wreq-rb 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Cargo.lock +2688 -0
- data/Cargo.toml +6 -0
- data/README.md +179 -0
- data/ext/wreq_rb/Cargo.toml +39 -0
- data/ext/wreq_rb/extconf.rb +22 -0
- data/ext/wreq_rb/src/client.rs +565 -0
- data/ext/wreq_rb/src/error.rs +25 -0
- data/ext/wreq_rb/src/lib.rs +20 -0
- data/ext/wreq_rb/src/response.rs +132 -0
- data/lib/wreq-rb/version.rb +5 -0
- data/lib/wreq-rb.rb +17 -0
- data/patches/0001-add-transfer-size-tracking.patch +292 -0
- data/vendor/wreq/Cargo.toml +306 -0
- data/vendor/wreq/LICENSE +202 -0
- data/vendor/wreq/README.md +122 -0
- data/vendor/wreq/examples/cert_store.rs +77 -0
- data/vendor/wreq/examples/connect_via_lower_priority_tokio_runtime.rs +258 -0
- data/vendor/wreq/examples/emulation.rs +118 -0
- data/vendor/wreq/examples/form.rs +14 -0
- data/vendor/wreq/examples/http1_websocket.rs +37 -0
- data/vendor/wreq/examples/http2_websocket.rs +45 -0
- data/vendor/wreq/examples/json_dynamic.rs +41 -0
- data/vendor/wreq/examples/json_typed.rs +47 -0
- data/vendor/wreq/examples/keylog.rs +16 -0
- data/vendor/wreq/examples/request_with_emulation.rs +115 -0
- data/vendor/wreq/examples/request_with_interface.rs +37 -0
- data/vendor/wreq/examples/request_with_local_address.rs +16 -0
- data/vendor/wreq/examples/request_with_proxy.rs +13 -0
- data/vendor/wreq/examples/request_with_redirect.rs +22 -0
- data/vendor/wreq/examples/request_with_version.rs +15 -0
- data/vendor/wreq/examples/tor_socks.rs +24 -0
- data/vendor/wreq/examples/unix_socket.rs +33 -0
- data/vendor/wreq/src/client/body.rs +304 -0
- data/vendor/wreq/src/client/conn/conn.rs +231 -0
- data/vendor/wreq/src/client/conn/connector.rs +549 -0
- data/vendor/wreq/src/client/conn/http.rs +1023 -0
- data/vendor/wreq/src/client/conn/proxy/socks.rs +233 -0
- data/vendor/wreq/src/client/conn/proxy/tunnel.rs +260 -0
- data/vendor/wreq/src/client/conn/proxy.rs +39 -0
- data/vendor/wreq/src/client/conn/tls_info.rs +98 -0
- data/vendor/wreq/src/client/conn/uds.rs +44 -0
- data/vendor/wreq/src/client/conn/verbose.rs +149 -0
- data/vendor/wreq/src/client/conn.rs +323 -0
- data/vendor/wreq/src/client/core/body/incoming.rs +485 -0
- data/vendor/wreq/src/client/core/body/length.rs +118 -0
- data/vendor/wreq/src/client/core/body.rs +34 -0
- data/vendor/wreq/src/client/core/common/buf.rs +149 -0
- data/vendor/wreq/src/client/core/common/rewind.rs +141 -0
- data/vendor/wreq/src/client/core/common/watch.rs +76 -0
- data/vendor/wreq/src/client/core/common.rs +3 -0
- data/vendor/wreq/src/client/core/conn/http1.rs +342 -0
- data/vendor/wreq/src/client/core/conn/http2.rs +307 -0
- data/vendor/wreq/src/client/core/conn.rs +11 -0
- data/vendor/wreq/src/client/core/dispatch.rs +299 -0
- data/vendor/wreq/src/client/core/error.rs +435 -0
- data/vendor/wreq/src/client/core/ext.rs +201 -0
- data/vendor/wreq/src/client/core/http1.rs +178 -0
- data/vendor/wreq/src/client/core/http2.rs +483 -0
- data/vendor/wreq/src/client/core/proto/h1/conn.rs +988 -0
- data/vendor/wreq/src/client/core/proto/h1/decode.rs +1170 -0
- data/vendor/wreq/src/client/core/proto/h1/dispatch.rs +684 -0
- data/vendor/wreq/src/client/core/proto/h1/encode.rs +580 -0
- data/vendor/wreq/src/client/core/proto/h1/io.rs +879 -0
- data/vendor/wreq/src/client/core/proto/h1/role.rs +694 -0
- data/vendor/wreq/src/client/core/proto/h1.rs +104 -0
- data/vendor/wreq/src/client/core/proto/h2/client.rs +650 -0
- data/vendor/wreq/src/client/core/proto/h2/ping.rs +539 -0
- data/vendor/wreq/src/client/core/proto/h2.rs +379 -0
- data/vendor/wreq/src/client/core/proto/headers.rs +138 -0
- data/vendor/wreq/src/client/core/proto.rs +58 -0
- data/vendor/wreq/src/client/core/rt/bounds.rs +57 -0
- data/vendor/wreq/src/client/core/rt/timer.rs +150 -0
- data/vendor/wreq/src/client/core/rt/tokio.rs +99 -0
- data/vendor/wreq/src/client/core/rt.rs +25 -0
- data/vendor/wreq/src/client/core/upgrade.rs +267 -0
- data/vendor/wreq/src/client/core.rs +16 -0
- data/vendor/wreq/src/client/emulation.rs +161 -0
- data/vendor/wreq/src/client/http/client/error.rs +142 -0
- data/vendor/wreq/src/client/http/client/exec.rs +29 -0
- data/vendor/wreq/src/client/http/client/extra.rs +77 -0
- data/vendor/wreq/src/client/http/client/lazy.rs +79 -0
- data/vendor/wreq/src/client/http/client/pool.rs +1105 -0
- data/vendor/wreq/src/client/http/client/util.rs +104 -0
- data/vendor/wreq/src/client/http/client.rs +1003 -0
- data/vendor/wreq/src/client/http/future.rs +99 -0
- data/vendor/wreq/src/client/http.rs +1629 -0
- data/vendor/wreq/src/client/layer/config/options.rs +156 -0
- data/vendor/wreq/src/client/layer/config.rs +116 -0
- data/vendor/wreq/src/client/layer/cookie.rs +161 -0
- data/vendor/wreq/src/client/layer/decoder.rs +139 -0
- data/vendor/wreq/src/client/layer/redirect/future.rs +270 -0
- data/vendor/wreq/src/client/layer/redirect/policy.rs +63 -0
- data/vendor/wreq/src/client/layer/redirect.rs +145 -0
- data/vendor/wreq/src/client/layer/retry/classify.rs +105 -0
- data/vendor/wreq/src/client/layer/retry/scope.rs +51 -0
- data/vendor/wreq/src/client/layer/retry.rs +151 -0
- data/vendor/wreq/src/client/layer/timeout/body.rs +233 -0
- data/vendor/wreq/src/client/layer/timeout/future.rs +90 -0
- data/vendor/wreq/src/client/layer/timeout.rs +177 -0
- data/vendor/wreq/src/client/layer.rs +15 -0
- data/vendor/wreq/src/client/multipart.rs +717 -0
- data/vendor/wreq/src/client/request.rs +818 -0
- data/vendor/wreq/src/client/response.rs +534 -0
- data/vendor/wreq/src/client/ws/json.rs +99 -0
- data/vendor/wreq/src/client/ws/message.rs +453 -0
- data/vendor/wreq/src/client/ws.rs +714 -0
- data/vendor/wreq/src/client.rs +27 -0
- data/vendor/wreq/src/config.rs +140 -0
- data/vendor/wreq/src/cookie.rs +579 -0
- data/vendor/wreq/src/dns/gai.rs +249 -0
- data/vendor/wreq/src/dns/hickory.rs +78 -0
- data/vendor/wreq/src/dns/resolve.rs +180 -0
- data/vendor/wreq/src/dns.rs +69 -0
- data/vendor/wreq/src/error.rs +502 -0
- data/vendor/wreq/src/ext.rs +398 -0
- data/vendor/wreq/src/hash.rs +143 -0
- data/vendor/wreq/src/header.rs +506 -0
- data/vendor/wreq/src/into_uri.rs +187 -0
- data/vendor/wreq/src/lib.rs +586 -0
- data/vendor/wreq/src/proxy/mac.rs +82 -0
- data/vendor/wreq/src/proxy/matcher.rs +806 -0
- data/vendor/wreq/src/proxy/uds.rs +66 -0
- data/vendor/wreq/src/proxy/win.rs +31 -0
- data/vendor/wreq/src/proxy.rs +569 -0
- data/vendor/wreq/src/redirect.rs +575 -0
- data/vendor/wreq/src/retry.rs +198 -0
- data/vendor/wreq/src/sync.rs +129 -0
- data/vendor/wreq/src/tls/conn/cache.rs +123 -0
- data/vendor/wreq/src/tls/conn/cert_compression.rs +125 -0
- data/vendor/wreq/src/tls/conn/ext.rs +82 -0
- data/vendor/wreq/src/tls/conn/macros.rs +34 -0
- data/vendor/wreq/src/tls/conn/service.rs +138 -0
- data/vendor/wreq/src/tls/conn.rs +681 -0
- data/vendor/wreq/src/tls/keylog/handle.rs +64 -0
- data/vendor/wreq/src/tls/keylog.rs +99 -0
- data/vendor/wreq/src/tls/options.rs +464 -0
- data/vendor/wreq/src/tls/x509/identity.rs +122 -0
- data/vendor/wreq/src/tls/x509/parser.rs +71 -0
- data/vendor/wreq/src/tls/x509/store.rs +228 -0
- data/vendor/wreq/src/tls/x509.rs +68 -0
- data/vendor/wreq/src/tls.rs +154 -0
- data/vendor/wreq/src/trace.rs +55 -0
- data/vendor/wreq/src/util.rs +122 -0
- data/vendor/wreq/tests/badssl.rs +228 -0
- data/vendor/wreq/tests/brotli.rs +350 -0
- data/vendor/wreq/tests/client.rs +1098 -0
- data/vendor/wreq/tests/connector_layers.rs +227 -0
- data/vendor/wreq/tests/cookie.rs +306 -0
- data/vendor/wreq/tests/deflate.rs +347 -0
- data/vendor/wreq/tests/emulation.rs +260 -0
- data/vendor/wreq/tests/gzip.rs +347 -0
- data/vendor/wreq/tests/layers.rs +261 -0
- data/vendor/wreq/tests/multipart.rs +165 -0
- data/vendor/wreq/tests/proxy.rs +438 -0
- data/vendor/wreq/tests/redirect.rs +629 -0
- data/vendor/wreq/tests/retry.rs +135 -0
- data/vendor/wreq/tests/support/delay_server.rs +117 -0
- data/vendor/wreq/tests/support/error.rs +16 -0
- data/vendor/wreq/tests/support/layer.rs +183 -0
- data/vendor/wreq/tests/support/mod.rs +9 -0
- data/vendor/wreq/tests/support/server.rs +232 -0
- data/vendor/wreq/tests/timeouts.rs +281 -0
- data/vendor/wreq/tests/unix_socket.rs +135 -0
- data/vendor/wreq/tests/upgrade.rs +98 -0
- data/vendor/wreq/tests/zstd.rs +559 -0
- metadata +225 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
use std::{
|
|
2
|
+
borrow::Cow,
|
|
3
|
+
task::{Context, Poll},
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
use bytes::Bytes;
|
|
7
|
+
use http::Uri;
|
|
8
|
+
use tokio::io::{AsyncRead, AsyncWrite};
|
|
9
|
+
use tokio_socks::{
|
|
10
|
+
TargetAddr,
|
|
11
|
+
tcp::{Socks4Stream, Socks5Stream},
|
|
12
|
+
};
|
|
13
|
+
use tower::Service;
|
|
14
|
+
|
|
15
|
+
use super::Tunneling;
|
|
16
|
+
use crate::{
|
|
17
|
+
dns::{GaiResolver, InternalResolve, Name},
|
|
18
|
+
error::BoxError,
|
|
19
|
+
ext::UriExt,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
#[derive(Debug)]
|
|
23
|
+
pub enum SocksError {
|
|
24
|
+
ConnectFailed(BoxError),
|
|
25
|
+
DnsResolveFailure(BoxError),
|
|
26
|
+
Socks(tokio_socks::Error),
|
|
27
|
+
Io(std::io::Error),
|
|
28
|
+
Utf8(std::str::Utf8Error),
|
|
29
|
+
DnsFailure,
|
|
30
|
+
MissingHost,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
impl std::fmt::Display for SocksError {
|
|
34
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
35
|
+
f.write_str("SOCKS error: ")?;
|
|
36
|
+
|
|
37
|
+
match self {
|
|
38
|
+
Self::ConnectFailed(e) => {
|
|
39
|
+
f.write_fmt(format_args!("failed to create underlying connection: {e}"))
|
|
40
|
+
}
|
|
41
|
+
Self::Socks(e) => f.write_fmt(format_args!("error during SOCKS handshake: {e}")),
|
|
42
|
+
Self::Io(e) => f.write_fmt(format_args!("io error during SOCKS handshake: {e}")),
|
|
43
|
+
Self::Utf8(e) => f.write_fmt(format_args!(
|
|
44
|
+
"invalid UTF-8 during SOCKS authentication: {e}"
|
|
45
|
+
)),
|
|
46
|
+
Self::DnsResolveFailure(e) => {
|
|
47
|
+
f.write_fmt(format_args!("failed to resolve DNS for SOCKS target: {e}"))
|
|
48
|
+
}
|
|
49
|
+
Self::DnsFailure => f.write_str("could not resolve to acceptable address type"),
|
|
50
|
+
Self::MissingHost => f.write_str("missing destination host"),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
impl std::error::Error for SocksError {}
|
|
56
|
+
|
|
57
|
+
impl From<std::io::Error> for SocksError {
|
|
58
|
+
fn from(err: std::io::Error) -> Self {
|
|
59
|
+
Self::Io(err)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
impl From<std::str::Utf8Error> for SocksError {
|
|
64
|
+
fn from(err: std::str::Utf8Error) -> Self {
|
|
65
|
+
Self::Utf8(err)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
impl From<tokio_socks::Error> for SocksError {
|
|
70
|
+
fn from(err: tokio_socks::Error) -> Self {
|
|
71
|
+
Self::Socks(err)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Represents the SOCKS protocol version.
|
|
76
|
+
#[derive(Clone, Copy)]
|
|
77
|
+
#[repr(u8)]
|
|
78
|
+
pub enum Version {
|
|
79
|
+
V4,
|
|
80
|
+
V5,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// Represents the DNS resolution strategy for SOCKS connections.
|
|
84
|
+
#[derive(Clone, Copy)]
|
|
85
|
+
#[repr(u8)]
|
|
86
|
+
pub enum DnsResolve {
|
|
87
|
+
Local,
|
|
88
|
+
Remote,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// A connector that establishes connections through a SOCKS proxy.
|
|
92
|
+
pub struct SocksConnector<C, R = GaiResolver> {
|
|
93
|
+
inner: C,
|
|
94
|
+
resolver: R,
|
|
95
|
+
proxy_dst: Uri,
|
|
96
|
+
auth: Option<(Bytes, Bytes)>,
|
|
97
|
+
version: Version,
|
|
98
|
+
dns_resolve: DnsResolve,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
impl<C, R> SocksConnector<C, R>
|
|
102
|
+
where
|
|
103
|
+
R: InternalResolve + Clone,
|
|
104
|
+
{
|
|
105
|
+
/// Create a new SOCKS connector with the given inner service.
|
|
106
|
+
///
|
|
107
|
+
/// This wraps an underlying connector, and stores the address of a
|
|
108
|
+
/// SOCKS proxy server.
|
|
109
|
+
///
|
|
110
|
+
/// A `SocksConnector` can then be called with any destination. The `proxy_dst` passed to
|
|
111
|
+
/// `call` will not be used to create the underlying connection, but will
|
|
112
|
+
/// be used in a SOCKS handshake sent to the proxy destination.
|
|
113
|
+
pub fn new_with_resolver(proxy_dst: Uri, inner: C, resolver: R) -> Self {
|
|
114
|
+
SocksConnector {
|
|
115
|
+
inner,
|
|
116
|
+
resolver,
|
|
117
|
+
proxy_dst,
|
|
118
|
+
version: Version::V5,
|
|
119
|
+
dns_resolve: DnsResolve::Local,
|
|
120
|
+
auth: None,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// Sets the authentication credentials for the SOCKS proxy connection.
|
|
125
|
+
#[inline]
|
|
126
|
+
pub fn set_auth(&mut self, auth: Option<(Bytes, Bytes)>) {
|
|
127
|
+
self.auth = auth;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// Sets whether to use the SOCKS5 protocol for the proxy connection.
|
|
131
|
+
#[inline]
|
|
132
|
+
pub fn set_version(&mut self, version: Version) {
|
|
133
|
+
self.version = version;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// Sets whether to resolve DNS locally or let the proxy handle DNS resolution.
|
|
137
|
+
#[inline]
|
|
138
|
+
pub fn set_dns_mode(&mut self, dns_resolve: DnsResolve) {
|
|
139
|
+
self.dns_resolve = dns_resolve;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
impl<C, R> Service<Uri> for SocksConnector<C, R>
|
|
144
|
+
where
|
|
145
|
+
C: Service<Uri>,
|
|
146
|
+
C::Future: Send + 'static,
|
|
147
|
+
C::Response: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
|
148
|
+
C::Error: Into<BoxError>,
|
|
149
|
+
R: InternalResolve + Clone + Send + 'static,
|
|
150
|
+
<R as InternalResolve>::Future: Send + 'static,
|
|
151
|
+
{
|
|
152
|
+
type Response = C::Response;
|
|
153
|
+
type Error = SocksError;
|
|
154
|
+
type Future = Tunneling<C::Future, C::Response, Self::Error>;
|
|
155
|
+
|
|
156
|
+
#[inline]
|
|
157
|
+
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
158
|
+
self.inner
|
|
159
|
+
.poll_ready(cx)
|
|
160
|
+
.map_err(Into::into)
|
|
161
|
+
.map_err(SocksError::ConnectFailed)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fn call(&mut self, dst: Uri) -> Self::Future {
|
|
165
|
+
let connecting = self.inner.call(self.proxy_dst.clone());
|
|
166
|
+
|
|
167
|
+
let version = self.version;
|
|
168
|
+
let dns_resolve = self.dns_resolve;
|
|
169
|
+
let auth = self.auth.clone();
|
|
170
|
+
let mut resolver = self.resolver.clone();
|
|
171
|
+
|
|
172
|
+
let fut = async move {
|
|
173
|
+
let host = dst.host().ok_or(SocksError::MissingHost)?;
|
|
174
|
+
let port = dst.port_or_default();
|
|
175
|
+
|
|
176
|
+
// Attempt to tcp connect to the proxy server.
|
|
177
|
+
// This will return a `tokio::net::TcpStream` if successful.
|
|
178
|
+
let socket = connecting
|
|
179
|
+
.await
|
|
180
|
+
.map_err(Into::into)
|
|
181
|
+
.map_err(SocksError::ConnectFailed)?;
|
|
182
|
+
|
|
183
|
+
// Resolve the target address using the provided resolver.
|
|
184
|
+
let target_addr = match dns_resolve {
|
|
185
|
+
DnsResolve::Local => {
|
|
186
|
+
let mut socket_addr = resolver
|
|
187
|
+
.resolve(Name::new(host.into()))
|
|
188
|
+
.await
|
|
189
|
+
.map(|mut s| s.next())
|
|
190
|
+
.transpose()
|
|
191
|
+
.ok_or(SocksError::DnsFailure)?
|
|
192
|
+
.map_err(Into::into)
|
|
193
|
+
.map_err(SocksError::DnsResolveFailure)?;
|
|
194
|
+
socket_addr.set_port(port);
|
|
195
|
+
TargetAddr::Ip(socket_addr)
|
|
196
|
+
}
|
|
197
|
+
DnsResolve::Remote => TargetAddr::Domain(Cow::Borrowed(host), port),
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
match version {
|
|
201
|
+
Version::V4 => {
|
|
202
|
+
// For SOCKS4, we connect directly to the target address.
|
|
203
|
+
let stream = Socks4Stream::connect_with_socket(socket, target_addr).await?;
|
|
204
|
+
Ok(stream.into_inner())
|
|
205
|
+
}
|
|
206
|
+
Version::V5 => {
|
|
207
|
+
// For SOCKS5, we need to handle authentication if provided.
|
|
208
|
+
// The `auth` is an optional tuple of (username, password).
|
|
209
|
+
let stream = match auth {
|
|
210
|
+
Some((username, password)) => {
|
|
211
|
+
let username = std::str::from_utf8(&username)?;
|
|
212
|
+
let password = std::str::from_utf8(&password)?;
|
|
213
|
+
Socks5Stream::connect_with_password_and_socket(
|
|
214
|
+
socket,
|
|
215
|
+
target_addr,
|
|
216
|
+
username,
|
|
217
|
+
password,
|
|
218
|
+
)
|
|
219
|
+
.await?
|
|
220
|
+
}
|
|
221
|
+
None => Socks5Stream::connect_with_socket(socket, target_addr).await?,
|
|
222
|
+
};
|
|
223
|
+
Ok(stream.into_inner())
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
Tunneling {
|
|
229
|
+
fut: Box::pin(fut),
|
|
230
|
+
_marker: Default::default(),
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
use std::{
|
|
2
|
+
io,
|
|
3
|
+
marker::{PhantomData, Unpin},
|
|
4
|
+
pin::Pin,
|
|
5
|
+
task::{self, Poll, ready},
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
use http::{HeaderMap, HeaderValue, Uri};
|
|
9
|
+
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
|
10
|
+
use tower::Service;
|
|
11
|
+
|
|
12
|
+
use super::Tunneling;
|
|
13
|
+
use crate::{error::BoxError, ext::UriExt};
|
|
14
|
+
|
|
15
|
+
/// Tunnel Proxy via HTTP CONNECT
|
|
16
|
+
///
|
|
17
|
+
/// This is a connector that can be used by the `Client`. It wraps
|
|
18
|
+
/// another connector, and after getting an underlying connection, it creates
|
|
19
|
+
/// an HTTP CONNECT tunnel over it.
|
|
20
|
+
#[derive(Debug)]
|
|
21
|
+
pub struct TunnelConnector<C> {
|
|
22
|
+
headers: Headers,
|
|
23
|
+
inner: C,
|
|
24
|
+
proxy_dst: Uri,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#[derive(Clone, Debug)]
|
|
28
|
+
enum Headers {
|
|
29
|
+
Empty,
|
|
30
|
+
Auth(HeaderValue),
|
|
31
|
+
Extra(HeaderMap),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#[derive(Debug)]
|
|
35
|
+
pub enum TunnelError {
|
|
36
|
+
ConnectFailed(BoxError),
|
|
37
|
+
Io(std::io::Error),
|
|
38
|
+
MissingHost,
|
|
39
|
+
ProxyAuthRequired,
|
|
40
|
+
ProxyHeadersTooLong,
|
|
41
|
+
TunnelUnexpectedEof,
|
|
42
|
+
TunnelUnsuccessful,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
impl<C> TunnelConnector<C> {
|
|
46
|
+
/// Create a new tunnel connector.
|
|
47
|
+
///
|
|
48
|
+
/// This wraps an underlying connector, and stores the address of a
|
|
49
|
+
/// tunneling proxy server.
|
|
50
|
+
///
|
|
51
|
+
/// A `TunnelConnector` can then be called with any destination. The `proxy_dst` passed to
|
|
52
|
+
/// `call` will not be used to create the underlying connection, but will
|
|
53
|
+
/// be used in an HTTP CONNECT request sent to the proxy destination.
|
|
54
|
+
pub fn new(proxy_dst: Uri, connector: C) -> Self {
|
|
55
|
+
Self {
|
|
56
|
+
headers: Headers::Empty,
|
|
57
|
+
inner: connector,
|
|
58
|
+
proxy_dst,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Add `proxy-authorization` header value to the CONNECT request.
|
|
63
|
+
pub fn with_auth(mut self, mut auth: HeaderValue) -> Self {
|
|
64
|
+
// just in case the user forgot
|
|
65
|
+
auth.set_sensitive(true);
|
|
66
|
+
match self.headers {
|
|
67
|
+
Headers::Empty => {
|
|
68
|
+
self.headers = Headers::Auth(auth);
|
|
69
|
+
}
|
|
70
|
+
Headers::Auth(ref mut existing) => {
|
|
71
|
+
*existing = auth;
|
|
72
|
+
}
|
|
73
|
+
Headers::Extra(ref mut extra) => {
|
|
74
|
+
extra.insert(http::header::PROXY_AUTHORIZATION, auth);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
self
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Add extra headers to be sent with the CONNECT request.
|
|
82
|
+
///
|
|
83
|
+
/// If existing headers have been set, these will be merged.
|
|
84
|
+
pub fn with_headers(mut self, mut headers: HeaderMap) -> Self {
|
|
85
|
+
match self.headers {
|
|
86
|
+
Headers::Empty => {
|
|
87
|
+
self.headers = Headers::Extra(headers);
|
|
88
|
+
}
|
|
89
|
+
Headers::Auth(auth) => {
|
|
90
|
+
headers
|
|
91
|
+
.entry(http::header::PROXY_AUTHORIZATION)
|
|
92
|
+
.or_insert(auth);
|
|
93
|
+
self.headers = Headers::Extra(headers);
|
|
94
|
+
}
|
|
95
|
+
Headers::Extra(ref mut extra) => {
|
|
96
|
+
extra.extend(headers);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
self
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
impl<C> Service<Uri> for TunnelConnector<C>
|
|
105
|
+
where
|
|
106
|
+
C: Service<Uri>,
|
|
107
|
+
C::Future: Send + 'static,
|
|
108
|
+
C::Response: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
|
109
|
+
C::Error: Into<BoxError>,
|
|
110
|
+
{
|
|
111
|
+
type Response = C::Response;
|
|
112
|
+
type Error = TunnelError;
|
|
113
|
+
type Future = Tunneling<C::Future, C::Response, Self::Error>;
|
|
114
|
+
|
|
115
|
+
#[inline]
|
|
116
|
+
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
117
|
+
self.inner
|
|
118
|
+
.poll_ready(cx)
|
|
119
|
+
.map_err(Into::into)
|
|
120
|
+
.map_err(TunnelError::ConnectFailed)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
fn call(&mut self, dst: Uri) -> Self::Future {
|
|
124
|
+
let connecting = self.inner.call(self.proxy_dst.clone());
|
|
125
|
+
let headers = self.headers.clone();
|
|
126
|
+
|
|
127
|
+
Tunneling {
|
|
128
|
+
fut: Box::pin(async move {
|
|
129
|
+
let conn = connecting
|
|
130
|
+
.await
|
|
131
|
+
.map_err(Into::into)
|
|
132
|
+
.map_err(TunnelError::ConnectFailed)?;
|
|
133
|
+
tunnel(
|
|
134
|
+
conn,
|
|
135
|
+
dst.host().ok_or(TunnelError::MissingHost)?,
|
|
136
|
+
dst.port_or_default(),
|
|
137
|
+
&headers,
|
|
138
|
+
)
|
|
139
|
+
.await
|
|
140
|
+
}),
|
|
141
|
+
_marker: PhantomData,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async fn tunnel<T>(mut conn: T, host: &str, port: u16, headers: &Headers) -> Result<T, TunnelError>
|
|
147
|
+
where
|
|
148
|
+
T: AsyncRead + AsyncWrite + Unpin,
|
|
149
|
+
{
|
|
150
|
+
let mut buf = format!(
|
|
151
|
+
"\
|
|
152
|
+
CONNECT {host}:{port} HTTP/1.1\r\n\
|
|
153
|
+
Host: {host}:{port}\r\n\
|
|
154
|
+
"
|
|
155
|
+
)
|
|
156
|
+
.into_bytes();
|
|
157
|
+
|
|
158
|
+
match headers {
|
|
159
|
+
Headers::Auth(auth) => {
|
|
160
|
+
buf.extend_from_slice(b"Proxy-Authorization: ");
|
|
161
|
+
buf.extend_from_slice(auth.as_bytes());
|
|
162
|
+
buf.extend_from_slice(b"\r\n");
|
|
163
|
+
}
|
|
164
|
+
Headers::Extra(extra) => {
|
|
165
|
+
for (name, value) in extra {
|
|
166
|
+
buf.extend_from_slice(name.as_str().as_bytes());
|
|
167
|
+
buf.extend_from_slice(b": ");
|
|
168
|
+
buf.extend_from_slice(value.as_bytes());
|
|
169
|
+
buf.extend_from_slice(b"\r\n");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
Headers::Empty => (),
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// headers end
|
|
176
|
+
buf.extend_from_slice(b"\r\n");
|
|
177
|
+
|
|
178
|
+
write_all(&mut conn, &buf).await.map_err(TunnelError::Io)?;
|
|
179
|
+
|
|
180
|
+
let mut buf = [0; 8192];
|
|
181
|
+
let mut pos = 0;
|
|
182
|
+
|
|
183
|
+
loop {
|
|
184
|
+
let n = read(&mut conn, &mut buf[pos..])
|
|
185
|
+
.await
|
|
186
|
+
.map_err(TunnelError::Io)?;
|
|
187
|
+
|
|
188
|
+
if n == 0 {
|
|
189
|
+
return Err(TunnelError::TunnelUnexpectedEof);
|
|
190
|
+
}
|
|
191
|
+
pos += n;
|
|
192
|
+
|
|
193
|
+
let recvd = &buf[..pos];
|
|
194
|
+
if recvd.starts_with(b"HTTP/1.1 200") || recvd.starts_with(b"HTTP/1.0 200") {
|
|
195
|
+
if recvd.ends_with(b"\r\n\r\n") {
|
|
196
|
+
return Ok(conn);
|
|
197
|
+
}
|
|
198
|
+
if pos == buf.len() {
|
|
199
|
+
return Err(TunnelError::ProxyHeadersTooLong);
|
|
200
|
+
}
|
|
201
|
+
// else read more
|
|
202
|
+
} else if recvd.starts_with(b"HTTP/1.1 407") {
|
|
203
|
+
return Err(TunnelError::ProxyAuthRequired);
|
|
204
|
+
} else {
|
|
205
|
+
return Err(TunnelError::TunnelUnsuccessful);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async fn read<T>(io: &mut T, buf: &mut [u8]) -> io::Result<usize>
|
|
211
|
+
where
|
|
212
|
+
T: AsyncRead + Unpin,
|
|
213
|
+
{
|
|
214
|
+
std::future::poll_fn(move |cx| {
|
|
215
|
+
let mut buf = ReadBuf::new(buf);
|
|
216
|
+
ready!(Pin::new(&mut *io).poll_read(cx, &mut buf))?;
|
|
217
|
+
Poll::Ready(Ok(buf.filled().len()))
|
|
218
|
+
})
|
|
219
|
+
.await
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async fn write_all<T>(io: &mut T, buf: &[u8]) -> io::Result<()>
|
|
223
|
+
where
|
|
224
|
+
T: AsyncWrite + Unpin,
|
|
225
|
+
{
|
|
226
|
+
let mut n = 0;
|
|
227
|
+
std::future::poll_fn(move |cx| {
|
|
228
|
+
while n < buf.len() {
|
|
229
|
+
n += ready!(Pin::new(&mut *io).poll_write(cx, &buf[n..])?);
|
|
230
|
+
}
|
|
231
|
+
Poll::Ready(Ok(()))
|
|
232
|
+
})
|
|
233
|
+
.await
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
impl std::fmt::Display for TunnelError {
|
|
237
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
238
|
+
f.write_str("tunnel error: ")?;
|
|
239
|
+
|
|
240
|
+
f.write_str(match self {
|
|
241
|
+
TunnelError::MissingHost => "missing destination host",
|
|
242
|
+
TunnelError::ProxyAuthRequired => "proxy authorization required",
|
|
243
|
+
TunnelError::ProxyHeadersTooLong => "proxy response headers too long",
|
|
244
|
+
TunnelError::TunnelUnexpectedEof => "unexpected end of file",
|
|
245
|
+
TunnelError::TunnelUnsuccessful => "unsuccessful",
|
|
246
|
+
TunnelError::ConnectFailed(_) => "failed to create underlying connection",
|
|
247
|
+
TunnelError::Io(_) => "io error establishing tunnel",
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
impl std::error::Error for TunnelError {
|
|
253
|
+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
|
254
|
+
match self {
|
|
255
|
+
TunnelError::Io(e) => Some(e),
|
|
256
|
+
TunnelError::ConnectFailed(e) => Some(&**e),
|
|
257
|
+
_ => None,
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//! Proxy helpers
|
|
2
|
+
|
|
3
|
+
#[cfg(feature = "socks")]
|
|
4
|
+
pub mod socks;
|
|
5
|
+
pub mod tunnel;
|
|
6
|
+
|
|
7
|
+
use std::{
|
|
8
|
+
marker::PhantomData,
|
|
9
|
+
pin::Pin,
|
|
10
|
+
task::{Context, Poll},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
use pin_project_lite::pin_project;
|
|
14
|
+
|
|
15
|
+
pin_project! {
|
|
16
|
+
// Not publicly exported (so missing_docs doesn't trigger).
|
|
17
|
+
//
|
|
18
|
+
// We return this `Future` instead of the `Pin<Box<dyn Future>>` directly
|
|
19
|
+
// so that users don't rely on it fitting in a `Pin<Box<dyn Future>>` slot
|
|
20
|
+
// (and thus we can change the type in the future).
|
|
21
|
+
#[must_use = "futures do nothing unless polled"]
|
|
22
|
+
pub struct Tunneling<Fut, T, E> {
|
|
23
|
+
#[pin]
|
|
24
|
+
fut: Pin<Box<dyn Future<Output = Result<T, E>> + Send>>,
|
|
25
|
+
_marker: PhantomData<Fut>,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
impl<F, T, E1, E2> Future for Tunneling<F, T, E2>
|
|
30
|
+
where
|
|
31
|
+
F: Future<Output = Result<T, E1>>,
|
|
32
|
+
{
|
|
33
|
+
type Output = Result<T, E2>;
|
|
34
|
+
|
|
35
|
+
#[inline]
|
|
36
|
+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
37
|
+
self.project().fut.poll(cx)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
use bytes::Bytes;
|
|
2
|
+
use tokio::net::TcpStream;
|
|
3
|
+
#[cfg(unix)]
|
|
4
|
+
use tokio::net::UnixStream;
|
|
5
|
+
use tokio_boring2::SslStream;
|
|
6
|
+
|
|
7
|
+
use crate::tls::{TlsInfo, conn::MaybeHttpsStream};
|
|
8
|
+
|
|
9
|
+
/// A trait for extracting TLS information from a connection.
|
|
10
|
+
///
|
|
11
|
+
/// Implementors can provide access to peer certificate data or other TLS-related metadata.
|
|
12
|
+
/// For non-TLS connections, this typically returns `None`.
|
|
13
|
+
pub trait TlsInfoFactory {
|
|
14
|
+
fn tls_info(&self) -> Option<TlsInfo>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fn extract_tls_info<S>(ssl_stream: &SslStream<S>) -> TlsInfo {
|
|
18
|
+
let ssl = ssl_stream.ssl();
|
|
19
|
+
TlsInfo {
|
|
20
|
+
peer_certificate: ssl
|
|
21
|
+
.peer_certificate()
|
|
22
|
+
.and_then(|cert| cert.to_der().ok())
|
|
23
|
+
.map(Bytes::from),
|
|
24
|
+
peer_certificate_chain: ssl.peer_cert_chain().map(|chain| {
|
|
25
|
+
chain
|
|
26
|
+
.iter()
|
|
27
|
+
.filter_map(|cert| cert.to_der().ok())
|
|
28
|
+
.map(Bytes::from)
|
|
29
|
+
.collect()
|
|
30
|
+
}),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ===== impl TcpStream =====
|
|
35
|
+
|
|
36
|
+
impl TlsInfoFactory for TcpStream {
|
|
37
|
+
fn tls_info(&self) -> Option<TlsInfo> {
|
|
38
|
+
None
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
impl TlsInfoFactory for SslStream<TcpStream> {
|
|
43
|
+
#[inline]
|
|
44
|
+
fn tls_info(&self) -> Option<TlsInfo> {
|
|
45
|
+
Some(extract_tls_info(self))
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
impl TlsInfoFactory for MaybeHttpsStream<TcpStream> {
|
|
50
|
+
fn tls_info(&self) -> Option<TlsInfo> {
|
|
51
|
+
match self {
|
|
52
|
+
MaybeHttpsStream::Https(tls) => tls.tls_info(),
|
|
53
|
+
MaybeHttpsStream::Http(_) => None,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
impl TlsInfoFactory for SslStream<MaybeHttpsStream<TcpStream>> {
|
|
59
|
+
#[inline]
|
|
60
|
+
fn tls_info(&self) -> Option<TlsInfo> {
|
|
61
|
+
Some(extract_tls_info(self))
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ===== impl UnixStream =====
|
|
66
|
+
|
|
67
|
+
#[cfg(unix)]
|
|
68
|
+
impl TlsInfoFactory for UnixStream {
|
|
69
|
+
fn tls_info(&self) -> Option<TlsInfo> {
|
|
70
|
+
None
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#[cfg(unix)]
|
|
75
|
+
impl TlsInfoFactory for SslStream<UnixStream> {
|
|
76
|
+
#[inline]
|
|
77
|
+
fn tls_info(&self) -> Option<TlsInfo> {
|
|
78
|
+
Some(extract_tls_info(self))
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#[cfg(unix)]
|
|
83
|
+
impl TlsInfoFactory for MaybeHttpsStream<UnixStream> {
|
|
84
|
+
fn tls_info(&self) -> Option<TlsInfo> {
|
|
85
|
+
match self {
|
|
86
|
+
MaybeHttpsStream::Https(tls) => tls.tls_info(),
|
|
87
|
+
MaybeHttpsStream::Http(_) => None,
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#[cfg(unix)]
|
|
93
|
+
impl TlsInfoFactory for SslStream<MaybeHttpsStream<UnixStream>> {
|
|
94
|
+
#[inline]
|
|
95
|
+
fn tls_info(&self) -> Option<TlsInfo> {
|
|
96
|
+
Some(extract_tls_info(self))
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
use std::{
|
|
2
|
+
io,
|
|
3
|
+
path::Path,
|
|
4
|
+
pin::Pin,
|
|
5
|
+
sync::Arc,
|
|
6
|
+
task::{Context, Poll},
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
use http::Uri;
|
|
10
|
+
use tokio::net::UnixStream;
|
|
11
|
+
|
|
12
|
+
use super::{Connected, Connection};
|
|
13
|
+
|
|
14
|
+
type ConnectResult = io::Result<UnixStream>;
|
|
15
|
+
type BoxConnecting = Pin<Box<dyn Future<Output = ConnectResult> + Send>>;
|
|
16
|
+
|
|
17
|
+
#[derive(Clone)]
|
|
18
|
+
pub struct UnixConnector(pub(crate) Arc<Path>);
|
|
19
|
+
|
|
20
|
+
impl tower::Service<Uri> for UnixConnector {
|
|
21
|
+
type Response = UnixStream;
|
|
22
|
+
type Error = io::Error;
|
|
23
|
+
type Future = BoxConnecting;
|
|
24
|
+
|
|
25
|
+
#[inline]
|
|
26
|
+
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
27
|
+
Poll::Ready(Ok(()))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fn call(&mut self, _: Uri) -> Self::Future {
|
|
31
|
+
let fut = UnixStream::connect(self.0.clone());
|
|
32
|
+
Box::pin(async move {
|
|
33
|
+
let io = fut.await?;
|
|
34
|
+
Ok::<_, io::Error>(io)
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
impl Connection for UnixStream {
|
|
40
|
+
#[inline]
|
|
41
|
+
fn connected(&self) -> Connected {
|
|
42
|
+
Connected::new()
|
|
43
|
+
}
|
|
44
|
+
}
|