Skip to content

Commit a5f41ef

Browse files
author
=
committed
fix(client): parse Windows per-scheme proxy settings
Closes hyperium/hyper#4067.
1 parent 911d0f2 commit a5f41ef

1 file changed

Lines changed: 154 additions & 4 deletions

File tree

src/client/proxy/matcher.rs

Lines changed: 154 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,66 @@ mod mac {
662662
#[cfg(feature = "client-proxy-system")]
663663
#[cfg(windows)]
664664
mod win {
665+
666+
struct WindowsProxy {
667+
http: Option<String>,
668+
https: Option<String>,
669+
all: Option<String>,
670+
}
671+
672+
const HTTP_SCHEME: &str = "http";
673+
const HTTPS_SCHEME: &str = "https";
674+
const SOCKS_SCHEME: &str = "socks";
675+
676+
const SOCKS_WINDOWS_MAP_TO: &str = "socks5";
677+
678+
impl WindowsProxy {
679+
fn uri_from_schema_and_authority(scheme: &str, authority: &str) -> String {
680+
let safe_authority = authority
681+
.split_once("://")
682+
.map(|(_scheme, authority)| authority)
683+
.unwrap_or(authority);
684+
format!("{scheme}://{safe_authority}")
685+
}
686+
687+
fn from_proxy_server_setting(proxy_server_setting: &str) -> Self {
688+
let mut http = None;
689+
let mut https = None;
690+
let mut all = None;
691+
for proxy in proxy_server_setting
692+
.split(';')
693+
.map(str::trim)
694+
.filter(|p| !p.is_empty())
695+
{
696+
let schema_authority = proxy
697+
.split_once('=')
698+
.map(|(a, b)| (Some(a), Some(b)))
699+
.unwrap_or((None, None));
700+
match schema_authority {
701+
(Some(HTTP_SCHEME), Some(authority)) => http = Some(authority.to_owned()),
702+
(Some(HTTPS_SCHEME), Some(authority)) => https = Some(authority.to_owned()),
703+
(Some(SOCKS_SCHEME), Some(authority)) => {
704+
all = Some(Self::uri_from_schema_and_authority(
705+
SOCKS_WINDOWS_MAP_TO,
706+
authority,
707+
))
708+
}
709+
// What to do if a non-schemed proxy is there? Set all, or http and https
710+
// Lets set all so http and https can override later on Matcher's build
711+
(None, None) => all = Some(proxy.into()),
712+
_ => {}
713+
}
714+
}
715+
Self { http, https, all }
716+
}
717+
}
718+
719+
impl From<String> for WindowsProxy {
720+
fn from(value: String) -> Self {
721+
Self::from_proxy_server_setting(value.as_str())
722+
}
723+
}
724+
665725
pub(super) fn with_system(builder: &mut super::Builder) {
666726
let settings = if let Ok(settings) = windows_registry::CURRENT_USER
667727
.open("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")
@@ -676,11 +736,21 @@ mod win {
676736
}
677737

678738
if let Ok(val) = settings.get_string("ProxyServer") {
679-
if builder.http.is_empty() {
680-
builder.http = val.clone();
739+
let windows_proxy: WindowsProxy = val.into();
740+
if let Some(val) = windows_proxy.http {
741+
if builder.http.is_empty() {
742+
builder.http = val;
743+
}
681744
}
682-
if builder.https.is_empty() {
683-
builder.https = val;
745+
if let Some(val) = windows_proxy.https {
746+
if builder.https.is_empty() {
747+
builder.https = val;
748+
}
749+
}
750+
if let Some(val) = windows_proxy.all {
751+
if builder.all.is_empty() {
752+
builder.all = val;
753+
}
684754
}
685755
}
686756

@@ -695,6 +765,86 @@ mod win {
695765
}
696766
}
697767
}
768+
769+
#[cfg(test)]
770+
mod tests {
771+
use super::WindowsProxy;
772+
773+
#[test]
774+
fn per_scheme_values_go_to_their_own_slots() {
775+
let proxy =
776+
WindowsProxy::from_proxy_server_setting("http=127.0.0.1:8080;https=127.0.0.1:8443");
777+
assert_eq!(proxy.http.as_deref(), Some("127.0.0.1:8080"));
778+
assert_eq!(proxy.https.as_deref(), Some("127.0.0.1:8443"));
779+
assert_eq!(proxy.all, None);
780+
}
781+
782+
#[test]
783+
fn socks_is_normalized_to_socks5_and_stored_as_all() {
784+
let proxy = WindowsProxy::from_proxy_server_setting("socks=127.0.0.1:1080");
785+
assert_eq!(proxy.http, None);
786+
assert_eq!(proxy.https, None);
787+
assert_eq!(proxy.all.as_deref(), Some("socks5://127.0.0.1:1080"));
788+
}
789+
790+
#[test]
791+
fn socks_with_existing_scheme_is_rewritten_to_socks5() {
792+
let proxy = WindowsProxy::from_proxy_server_setting("socks=socks://127.0.0.1:1080");
793+
assert_eq!(proxy.all.as_deref(), Some("socks5://127.0.0.1:1080"));
794+
}
795+
796+
#[test]
797+
fn all_schemes_together() {
798+
let proxy = WindowsProxy::from_proxy_server_setting(
799+
"http=10.0.0.1:8080;https=10.0.0.1:8443;socks=10.0.0.1:1080",
800+
);
801+
assert_eq!(proxy.http.as_deref(), Some("10.0.0.1:8080"));
802+
assert_eq!(proxy.https.as_deref(), Some("10.0.0.1:8443"));
803+
assert_eq!(proxy.all.as_deref(), Some("socks5://10.0.0.1:1080"));
804+
}
805+
806+
#[test]
807+
fn single_proxy_without_scheme_goes_to_all() {
808+
let proxy = WindowsProxy::from_proxy_server_setting("127.0.0.1:8080");
809+
assert_eq!(proxy.http, None);
810+
assert_eq!(proxy.https, None);
811+
assert_eq!(proxy.all.as_deref(), Some("127.0.0.1:8080"));
812+
}
813+
814+
#[test]
815+
fn unknown_schemes_are_ignored() {
816+
let proxy = WindowsProxy::from_proxy_server_setting("ftp=127.0.0.1:8021");
817+
assert_eq!(proxy.http, None);
818+
assert_eq!(proxy.https, None);
819+
assert_eq!(proxy.all, None);
820+
}
821+
822+
#[test]
823+
fn whitespace_and_empty_segments_are_ignored() {
824+
let proxy = WindowsProxy::from_proxy_server_setting(" http=127.0.0.1:8080 ; ; ");
825+
assert_eq!(proxy.http.as_deref(), Some("127.0.0.1:8080"));
826+
assert_eq!(proxy.https, None);
827+
assert_eq!(proxy.all, None);
828+
}
829+
830+
#[test]
831+
fn trailing_separator_does_not_clobber_socks() {
832+
let proxy = WindowsProxy::from_proxy_server_setting("socks=127.0.0.1:1080;");
833+
assert_eq!(proxy.all.as_deref(), Some("socks5://127.0.0.1:1080"));
834+
}
835+
836+
#[test]
837+
fn uri_from_schema_and_authority_strips_existing_scheme() {
838+
assert_eq!(
839+
WindowsProxy::uri_from_schema_and_authority("socks5", "127.0.0.1:1080"),
840+
"socks5://127.0.0.1:1080"
841+
);
842+
assert_eq!(
843+
WindowsProxy::uri_from_schema_and_authority("socks5", "socks://127.0.0.1:1080"),
844+
"socks5://127.0.0.1:1080"
845+
);
846+
}
847+
}
698848
}
699849

700850
#[cfg(test)]

0 commit comments

Comments
 (0)