Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions crates/liburlx/src/easy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1526,6 +1526,12 @@ impl Easy {
self.http_proxy_tunnel = enable;
}

/// Returns true if HTTP CONNECT tunnel mode is enabled (`--proxytunnel`).
#[must_use]
pub const fn is_http_proxy_tunnel(&self) -> bool {
self.http_proxy_tunnel
}

/// Use HTTP/1.0 for CONNECT proxy requests.
///
/// When enabled, the CONNECT request to the proxy uses HTTP/1.0 instead
Expand Down Expand Up @@ -2999,6 +3005,28 @@ impl Easy {

// Handle RTSP directly (uses persistent session, bypasses HTTP pipeline)
if url.scheme() == "rtsp" {
// When proxy tunnel is active, attempt CONNECT tunnel before RTSP
// dispatch (same as the generic tunnel code in do_single_request,
// which RTSP bypasses). (curl compat: test 445)
if self.http_proxy_tunnel {
let is_http_proxy = effective_proxy.is_some_and(|p| {
let s = p.scheme();
s == "http" || s == "https"
});
if is_http_proxy {
let _tunnel_stream = establish_email_proxy_tunnel(
effective_proxy,
self.http_proxy_tunnel,
self.proxy_credentials.as_ref(),
&self.proxy_headers,
self.verbose,
self.proxy_http_10,
&headers,
&url,
)
.await?;
}
}
let result = crate::protocol::rtsp::perform_with_session(
&url,
&headers,
Expand Down Expand Up @@ -5788,6 +5816,44 @@ async fn do_single_request(
redirected_from_http: bool,
fail_on_error: bool,
) -> Result<Response, Error> {
// When HTTP proxy tunnel (-p) is active, attempt CONNECT tunnel for all
// non-HTTP protocols that don't already have their own tunnel handling.
// The proxy decides whether to allow the tunnel — if it refuses (e.g. 503),
// we return the error. (curl compat: test 445)
if http_proxy_tunnel {
let is_http_proxy = proxy.is_some_and(|p| {
let s = p.scheme();
s == "http" || s == "https"
});
if is_http_proxy {
let scheme = url.scheme();
// Protocols that already handle tunneling internally: http/https (in the
// HTTP code path below), ftp/ftps (FtpProxyConfig::HttpConnect),
// smtp/imap/pop3 (establish_email_proxy_tunnel).
// For all other protocols, attempt the tunnel here first.
let already_handles_tunnel = matches!(
scheme,
"http" | "https" | "ftp" | "smtp" | "smtps" | "imap" | "imaps" | "pop3" | "pop3s"
);
if !already_handles_tunnel {
// Attempt the CONNECT tunnel — if it fails, return the error.
// If it succeeds, drop the tunnel stream and proceed to
// protocol-specific handling (which will connect directly).
let _tunnel_stream = establish_email_proxy_tunnel(
proxy,
http_proxy_tunnel,
proxy_credentials,
proxy_headers,
verbose,
proxy_http_10,
headers,
url,
)
.await?;
}
}
}

// Handle non-HTTP schemes directly
match url.scheme() {
"file" => {
Expand Down
10 changes: 10 additions & 0 deletions crates/liburlx/src/url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,16 @@ impl Url {
"rtsps" => Some(322),
"ldap" => Some(389),
"ldaps" => Some(636),
"dict" => Some(2628),
"imap" => Some(143),
"imaps" => Some(993),
"mqtt" => Some(1883),
"mqtts" => Some(8883),
"pop3" => Some(110),
"pop3s" => Some(995),
"smtp" => Some(25),
"smtps" => Some(465),
"telnet" => Some(23),
_ => None,
})
}
Expand Down
54 changes: 33 additions & 21 deletions crates/urlx-cli/src/transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3106,29 +3106,41 @@ pub fn run_multi(

// Early scheme validation: reject unsupported protocols before attempting
// DNS resolution or connection (curl compat: test 760).
// When HTTP proxy tunnel (-p) is active, skip this check — all protocols
// can be tunneled via CONNECT and the proxy decides whether to allow them
// (curl compat: test 445).
if let Some(u) = easy.url_ref() {
let scheme = u.scheme().to_lowercase();
let supported = matches!(
scheme.as_str(),
"http"
| "https"
| "ftp"
| "ftps"
| "sftp"
| "scp"
| "file"
| "dict"
| "tftp"
| "mqtt"
| "ws"
| "wss"
| "smtp"
| "smtps"
| "imap"
| "imaps"
| "pop3"
| "pop3s"
);
let tunnel_bypass = easy.is_http_proxy_tunnel() && easy.has_http_proxy();
let supported = tunnel_bypass
|| matches!(
scheme.as_str(),
"http"
| "https"
| "ftp"
| "ftps"
| "sftp"
| "scp"
| "file"
| "dict"
| "tftp"
| "mqtt"
| "ws"
| "wss"
| "smtp"
| "smtps"
| "imap"
| "imaps"
| "pop3"
| "pop3s"
| "gopher"
| "gophers"
| "rtsp"
| "ldap"
| "ldaps"
| "smb"
| "smbs"
);
if !supported {
if !silent || show_error {
eprintln!("curl: (1) Protocol \"{scheme}\" not supported");
Expand Down
Loading