Proxy: Only use HTTP CONNECT for HTTPS requests
Previously, `ureq` used `HTTP CONNECT` for *all* requests, including plain-text HTTP requests. However, some proxy servers like Squid only allow tunneling via the `HTTP CONNECT` method on port 443 - since it is usually only used to proxy HTTPS requests. As a result, it was not possible to use `ureq` with a Squid server in its default configuration. With the changes from this commit, ureq will interact with HTTP proxies in a more standard-conforming way, where `CONNECT` is only used for HTTPS requests. HTTP request paths are transformed in such a way that they comply with RFC 7230 [1]. Tested against Squid 4.13 in standard configuration, with and without basic authentication, for HTTP and HTTPS requests. [1] https://www.rfc-editor.org/rfc/rfc7230#section-5.3.2
This commit is contained in:
committed by
Martin Algesten
parent
bdcee72c53
commit
20a9ae7977
@@ -199,14 +199,14 @@ to encode the request body using that.
|
|||||||
|
|
||||||
## Proxying
|
## Proxying
|
||||||
|
|
||||||
ureq supports two kinds of proxies, HTTP [`CONNECT`], [`SOCKS4`] and [`SOCKS5`], the former is
|
ureq supports two kinds of proxies, [`HTTP`], [`SOCKS4`] and [`SOCKS5`], the former is
|
||||||
always available while the latter must be enabled using the feature
|
always available while the latter must be enabled using the feature
|
||||||
`ureq = { version = "*", features = ["socks-proxy"] }`.
|
`ureq = { version = "*", features = ["socks-proxy"] }`.
|
||||||
|
|
||||||
Proxies settings are configured on an [Agent] (using [AgentBuilder]). All request sent
|
Proxies settings are configured on an [Agent] (using [AgentBuilder]). All request sent
|
||||||
through the agent will be proxied.
|
through the agent will be proxied.
|
||||||
|
|
||||||
### Example using HTTP CONNECT
|
### Example using HTTP
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn proxy_example_1() -> std::result::Result<(), ureq::Error> {
|
fn proxy_example_1() -> std::result::Result<(), ureq::Error> {
|
||||||
|
|||||||
@@ -225,14 +225,14 @@
|
|||||||
//!
|
//!
|
||||||
//! # Proxying
|
//! # Proxying
|
||||||
//!
|
//!
|
||||||
//! ureq supports two kinds of proxies, HTTP [`CONNECT`], [`SOCKS4`] and [`SOCKS5`], the former is
|
//! ureq supports two kinds of proxies, [`HTTP`], [`SOCKS4`] and [`SOCKS5`], the former is
|
||||||
//! always available while the latter must be enabled using the feature
|
//! always available while the latter must be enabled using the feature
|
||||||
//! `ureq = { version = "*", features = ["socks-proxy"] }`.
|
//! `ureq = { version = "*", features = ["socks-proxy"] }`.
|
||||||
//!
|
//!
|
||||||
//! Proxies settings are configured on an [Agent] (using [AgentBuilder]). All request sent
|
//! Proxies settings are configured on an [Agent] (using [AgentBuilder]). All request sent
|
||||||
//! through the agent will be proxied.
|
//! through the agent will be proxied.
|
||||||
//!
|
//!
|
||||||
//! ## Example using HTTP CONNECT
|
//! ## Example using HTTP
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! fn proxy_example_1() -> std::result::Result<(), ureq::Error> {
|
//! fn proxy_example_1() -> std::result::Result<(), ureq::Error> {
|
||||||
|
|||||||
14
src/proxy.rs
14
src/proxy.rs
@@ -5,7 +5,7 @@ use crate::error::{Error, ErrorKind};
|
|||||||
/// Proxy protocol
|
/// Proxy protocol
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
pub enum Proto {
|
pub enum Proto {
|
||||||
HTTPConnect,
|
HTTP,
|
||||||
SOCKS4,
|
SOCKS4,
|
||||||
SOCKS4A,
|
SOCKS4A,
|
||||||
SOCKS5,
|
SOCKS5,
|
||||||
@@ -71,7 +71,7 @@ impl Proxy {
|
|||||||
/// # Arguments:
|
/// # Arguments:
|
||||||
/// * `proxy` - a str of format `<protocol>://<user>:<password>@<host>:port` . All parts except host are optional.
|
/// * `proxy` - a str of format `<protocol>://<user>:<password>@<host>:port` . All parts except host are optional.
|
||||||
/// # Protocols
|
/// # Protocols
|
||||||
/// * `http`: HTTP Connect
|
/// * `http`: HTTP
|
||||||
/// * `socks4`: SOCKS4 (requires socks feature)
|
/// * `socks4`: SOCKS4 (requires socks feature)
|
||||||
/// * `socks4a`: SOCKS4A (requires socks feature)
|
/// * `socks4a`: SOCKS4A (requires socks feature)
|
||||||
/// * `socks5` and `socks`: SOCKS5 (requires socks feature)
|
/// * `socks5` and `socks`: SOCKS5 (requires socks feature)
|
||||||
@@ -91,7 +91,7 @@ impl Proxy {
|
|||||||
|
|
||||||
let proto = if proxy_parts.len() == 2 {
|
let proto = if proxy_parts.len() == 2 {
|
||||||
match proxy_parts.next() {
|
match proxy_parts.next() {
|
||||||
Some("http") => Proto::HTTPConnect,
|
Some("http") => Proto::HTTP,
|
||||||
Some("socks4") => Proto::SOCKS4,
|
Some("socks4") => Proto::SOCKS4,
|
||||||
Some("socks4a") => Proto::SOCKS4A,
|
Some("socks4a") => Proto::SOCKS4A,
|
||||||
Some("socks") => Proto::SOCKS5,
|
Some("socks") => Proto::SOCKS5,
|
||||||
@@ -99,7 +99,7 @@ impl Proxy {
|
|||||||
_ => return Err(ErrorKind::InvalidProxyUrl.new()),
|
_ => return Err(ErrorKind::InvalidProxyUrl.new()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Proto::HTTPConnect
|
Proto::HTTP
|
||||||
};
|
};
|
||||||
|
|
||||||
let remaining_parts = proxy_parts.next();
|
let remaining_parts = proxy_parts.next();
|
||||||
@@ -140,7 +140,7 @@ impl Proxy {
|
|||||||
));
|
));
|
||||||
|
|
||||||
match self.proto {
|
match self.proto {
|
||||||
Proto::HTTPConnect => format!("Proxy-Authorization: basic {}\r\n", creds),
|
Proto::HTTP => format!("Proxy-Authorization: basic {}\r\n", creds),
|
||||||
_ => String::new(),
|
_ => String::new(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -198,7 +198,7 @@ mod tests {
|
|||||||
assert_eq!(proxy.password, Some(String::from("p@ssw0rd")));
|
assert_eq!(proxy.password, Some(String::from("p@ssw0rd")));
|
||||||
assert_eq!(proxy.server, String::from("localhost"));
|
assert_eq!(proxy.server, String::from("localhost"));
|
||||||
assert_eq!(proxy.port, 9999);
|
assert_eq!(proxy.port, 9999);
|
||||||
assert_eq!(proxy.proto, Proto::HTTPConnect);
|
assert_eq!(proxy.proto, Proto::HTTP);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -208,7 +208,7 @@ mod tests {
|
|||||||
assert_eq!(proxy.password, Some(String::from("p@ssw0rd")));
|
assert_eq!(proxy.password, Some(String::from("p@ssw0rd")));
|
||||||
assert_eq!(proxy.server, String::from("localhost"));
|
assert_eq!(proxy.server, String::from("localhost"));
|
||||||
assert_eq!(proxy.port, 9999);
|
assert_eq!(proxy.port, 9999);
|
||||||
assert_eq!(proxy.proto, Proto::HTTPConnect);
|
assert_eq!(proxy.proto, Proto::HTTP);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "socks-proxy")]
|
#[cfg(feature = "socks-proxy")]
|
||||||
|
|||||||
@@ -377,7 +377,7 @@ pub(crate) fn connect_host(
|
|||||||
|
|
||||||
// connect with a configured timeout.
|
// connect with a configured timeout.
|
||||||
#[allow(clippy::unnecessary_unwrap)]
|
#[allow(clippy::unnecessary_unwrap)]
|
||||||
let stream = if proto.is_some() && Some(Proto::HTTPConnect) != proto {
|
let stream = if proto.is_some() && Some(Proto::HTTP) != proto {
|
||||||
connect_socks(
|
connect_socks(
|
||||||
unit,
|
unit,
|
||||||
proxy.clone().unwrap(),
|
proxy.clone().unwrap(),
|
||||||
@@ -423,7 +423,7 @@ pub(crate) fn connect_host(
|
|||||||
stream.set_write_timeout(unit.agent.config.timeout_write)?;
|
stream.set_write_timeout(unit.agent.config.timeout_write)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if proto == Some(Proto::HTTPConnect) {
|
if proto == Some(Proto::HTTP) && unit.url.scheme() == "https" {
|
||||||
if let Some(ref proxy) = proxy {
|
if let Some(ref proxy) = proxy {
|
||||||
write!(
|
write!(
|
||||||
stream,
|
stream,
|
||||||
|
|||||||
23
src/unit.rs
23
src/unit.rs
@@ -15,6 +15,7 @@ use crate::body::{self, BodySize, Payload, SizedReader};
|
|||||||
use crate::error::{Error, ErrorKind};
|
use crate::error::{Error, ErrorKind};
|
||||||
use crate::header;
|
use crate::header;
|
||||||
use crate::header::{get_header, Header};
|
use crate::header::{get_header, Header};
|
||||||
|
use crate::proxy::Proto;
|
||||||
use crate::resolve::ArcResolver;
|
use crate::resolve::ArcResolver;
|
||||||
use crate::response::Response;
|
use crate::response::Response;
|
||||||
use crate::stream::{self, connect_test, Stream};
|
use crate::stream::{self, connect_test, Stream};
|
||||||
@@ -404,12 +405,24 @@ fn send_prelude(unit: &Unit, stream: &mut Stream) -> io::Result<()> {
|
|||||||
// build into a buffer and send in one go.
|
// build into a buffer and send in one go.
|
||||||
let mut prelude = PreludeBuilder::new();
|
let mut prelude = PreludeBuilder::new();
|
||||||
|
|
||||||
|
let path = if let Some(proxy) = &unit.agent.config.proxy {
|
||||||
|
// HTTP proxies require the path to be in absolute URI form
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc7230#section-5.3.2
|
||||||
|
match proxy.proto {
|
||||||
|
Proto::HTTP => format!(
|
||||||
|
"{}://{}{}",
|
||||||
|
unit.url.scheme(),
|
||||||
|
unit.url.host().unwrap(),
|
||||||
|
unit.url.path()
|
||||||
|
),
|
||||||
|
_ => unit.url.path().into(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unit.url.path().into()
|
||||||
|
};
|
||||||
|
|
||||||
// request line
|
// request line
|
||||||
prelude.write_request_line(
|
prelude.write_request_line(&unit.method, &path, unit.url.query().unwrap_or_default())?;
|
||||||
&unit.method,
|
|
||||||
unit.url.path(),
|
|
||||||
unit.url.query().unwrap_or_default(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// host header if not set by user.
|
// host header if not set by user.
|
||||||
if !header::has_header(&unit.headers, "host") {
|
if !header::has_header(&unit.headers, "host") {
|
||||||
|
|||||||
Reference in New Issue
Block a user