mirror of
https://github.com/openai/codex.git
synced 2026-09-05 15:18:41 +00:00
Handle Windows proxy configuration edge cases
This commit is contained in:
@@ -593,22 +593,53 @@ enum ParsedProxyListDecision {
|
||||
fn parse_proxy_list(input: &str, target_scheme: &str) -> ParsedProxyListDecision {
|
||||
let mut saw_unsupported = false;
|
||||
let mut http_fallback = None;
|
||||
for token in input
|
||||
.split(';')
|
||||
.map(str::trim)
|
||||
.filter(|token| !token.is_empty())
|
||||
|
||||
{
|
||||
if target_scheme == "https"
|
||||
&& http_fallback.is_none()
|
||||
&& let Some(ParsedProxyListDecision::Proxy(url)) = parse_proxy_key_token(token, "http")
|
||||
let mut process_token = |token: &str| {
|
||||
if target_scheme == "https"
|
||||
&& http_fallback.is_none()
|
||||
&& let Some(ParsedProxyListDecision::Proxy(url)) =
|
||||
parse_proxy_key_token(token, "http")
|
||||
{
|
||||
http_fallback = Some(url);
|
||||
}
|
||||
match parse_proxy_token(token, target_scheme) {
|
||||
ParsedProxyListDecision::Direct => Some(ParsedProxyListDecision::Direct),
|
||||
ParsedProxyListDecision::Proxy(url) => Some(ParsedProxyListDecision::Proxy(url)),
|
||||
ParsedProxyListDecision::UnsupportedScheme => {
|
||||
saw_unsupported = true;
|
||||
None
|
||||
}
|
||||
ParsedProxyListDecision::Unavailable => None,
|
||||
}
|
||||
};
|
||||
|
||||
for segment in input
|
||||
.split(';')
|
||||
.map(str::trim)
|
||||
.filter(|segment| !segment.is_empty())
|
||||
{
|
||||
http_fallback = Some(url);
|
||||
}
|
||||
match parse_proxy_token(token, target_scheme) {
|
||||
ParsedProxyListDecision::Direct => return ParsedProxyListDecision::Direct,
|
||||
ParsedProxyListDecision::Proxy(url) => return ParsedProxyListDecision::Proxy(url),
|
||||
ParsedProxyListDecision::UnsupportedScheme => saw_unsupported = true,
|
||||
ParsedProxyListDecision::Unavailable => {}
|
||||
let mut parts = segment.split_whitespace();
|
||||
let directive = parts.next();
|
||||
let hostport = parts.next();
|
||||
let extra = parts.next();
|
||||
let is_proxy_directive = matches!(
|
||||
directive.map(str::to_ascii_lowercase).as_deref(),
|
||||
Some("proxy" | "http" | "https" | "socks" | "socks4" | "socks5")
|
||||
) && hostport.is_some()
|
||||
&& extra.is_none();
|
||||
|
||||
if is_proxy_directive {
|
||||
if let Some(decision) = process_token(segment) {
|
||||
return decision;
|
||||
}
|
||||
} else {
|
||||
for token in segment.split_whitespace() {
|
||||
if let Some(decision) = process_token(token) {
|
||||
return decision;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -634,10 +665,11 @@ fn parse_proxy_token(token: &str, target_scheme: &str) -> ParsedProxyListDecisio
|
||||
return ParsedProxyListDecision::Unavailable;
|
||||
}
|
||||
|
||||
if let Some((scheme, hostport)) = token.split_once(' ') {
|
||||
let scheme = scheme.trim().to_ascii_lowercase();
|
||||
let hostport = hostport.trim();
|
||||
return match scheme.as_str() {
|
||||
let mut parts = token.split_whitespace();
|
||||
let directive = parts.next();
|
||||
let hostport = parts.next();
|
||||
if let (Some(directive), Some(hostport), None) = (directive, hostport, parts.next()) {
|
||||
return match directive.to_ascii_lowercase().as_str() {
|
||||
"proxy" | "http" => proxy_url_from_hostport("http", hostport),
|
||||
"https" => proxy_url_from_hostport("https", hostport),
|
||||
"socks" | "socks4" | "socks5" => ParsedProxyListDecision::UnsupportedScheme,
|
||||
@@ -692,6 +724,10 @@ mod tests {
|
||||
parse_proxy_list("http=web-proxy:8080;https=secure-proxy:8443", "https"),
|
||||
ParsedProxyListDecision::Proxy("http://secure-proxy:8443".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
parse_proxy_list("http=web-proxy:8080 https=secure-proxy:8443", "https"),
|
||||
ParsedProxyListDecision::Proxy("http://secure-proxy:8443".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
parse_proxy_list("proxy.internal:8080", "https"),
|
||||
ParsedProxyListDecision::Proxy("http://proxy.internal:8080".to_string())
|
||||
|
||||
@@ -7,6 +7,7 @@ use super::RouteFailureClass;
|
||||
use super::SystemProxyDecision;
|
||||
use super::no_proxy_matches_origin;
|
||||
use super::parse_proxy_list;
|
||||
use windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND;
|
||||
use windows_sys::Win32::Foundation::FALSE;
|
||||
use windows_sys::Win32::Foundation::GetLastError;
|
||||
use windows_sys::Win32::Foundation::GlobalFree;
|
||||
@@ -192,7 +193,11 @@ fn current_user_ie_proxy_config() -> Result<IeProxyConfig, RouteFailureClass> {
|
||||
};
|
||||
let ok = unsafe { WinHttpGetIEProxyConfigForCurrentUser(&mut raw) };
|
||||
if ok == FALSE {
|
||||
return Err(classify_winhttp_error(last_error()));
|
||||
let error = last_error();
|
||||
if error == ERROR_FILE_NOT_FOUND {
|
||||
return Ok(IeProxyConfig::default());
|
||||
}
|
||||
return Err(classify_winhttp_error(error));
|
||||
}
|
||||
|
||||
let auto_config_url = GlobalWideString::from_raw(raw.lpszAutoConfigUrl).into_string();
|
||||
@@ -207,7 +212,7 @@ fn current_user_ie_proxy_config() -> Result<IeProxyConfig, RouteFailureClass> {
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
struct IeProxyConfig {
|
||||
auto_detect: bool,
|
||||
auto_config_url: Option<String>,
|
||||
@@ -294,15 +299,23 @@ impl Drop for WinHttpSession {
|
||||
}
|
||||
|
||||
fn proxy_bypass_matches_origin(proxy_bypass: &str, origin: &RequestOrigin) -> bool {
|
||||
proxy_bypass.split([';', ',']).map(str::trim).any(|entry| {
|
||||
if entry.eq_ignore_ascii_case("<local>") {
|
||||
!origin.host.contains('.')
|
||||
} else {
|
||||
no_proxy_matches_origin(entry, origin)
|
||||
}
|
||||
})
|
||||
proxy_bypass
|
||||
.split(|ch: char| ch == ';' || ch == ',' || ch.is_whitespace())
|
||||
.map(str::trim)
|
||||
.filter(|entry| !entry.is_empty())
|
||||
.any(|entry| {
|
||||
if entry.eq_ignore_ascii_case("<local>") {
|
||||
!origin.host.contains('.')
|
||||
} else {
|
||||
no_proxy_matches_origin(entry, origin)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "windows_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
fn wide_null(value: &str) -> Vec<u16> {
|
||||
value.encode_utf16().chain(std::iter::once(0)).collect()
|
||||
}
|
||||
|
||||
18
codex-rs/codex-client/src/outbound_proxy/windows_tests.rs
Normal file
18
codex-rs/codex-client/src/outbound_proxy/windows_tests.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn proxy_bypass_matches_whitespace_separated_winhttp_entries() {
|
||||
let local_origin = RequestOrigin {
|
||||
scheme: "https".to_string(),
|
||||
host: "intranet".to_string(),
|
||||
port: 443,
|
||||
};
|
||||
assert!(proxy_bypass_matches_origin("<local> *.corp", &local_origin));
|
||||
|
||||
let corp_origin = RequestOrigin {
|
||||
scheme: "https".to_string(),
|
||||
host: "service.corp".to_string(),
|
||||
port: 443,
|
||||
};
|
||||
assert!(proxy_bypass_matches_origin("<local> *.corp", &corp_origin));
|
||||
}
|
||||
Reference in New Issue
Block a user