Skip to content

Respect system proxies in the Windows installer#2075

Closed
zanieb wants to merge 1 commit intoaxodotdev:mainfrom
zanieb:zb/proxy-ps1
Closed

Respect system proxies in the Windows installer#2075
zanieb wants to merge 1 commit intoaxodotdev:mainfrom
zanieb:zb/proxy-ps1

Conversation

@zanieb
Copy link
Contributor

@zanieb zanieb commented Sep 5, 2025

Closes astral-sh/uv#10709

I did not test this, but it appears to be the appropriate fix for the installer not respecting HTTPS_PROXY?

See https://learn.microsoft.com/en-us/dotnet/api/system.net.webrequest.getsystemwebproxy?view=net-9.0

@mistydemeo
Copy link
Contributor

Are you planning to test, or should I try to get this tested?

@zanieb
Copy link
Contributor Author

zanieb commented Sep 6, 2025

I asked @Gankra to test for me (<3) and she said it doesn't work, so... I'll need to dig deeper (or, if someone else with Windows experience has a suggestion, I'd greatly appreciate it!)

@zsol
Copy link
Contributor

zsol commented Sep 6, 2025

AFAICT this PR is a noop, because WebClient.Proxy returns the default proxy if it's never been set anyway (which delegates to HttpClient.DefaultProxy).

But that means that the code as-is is supposed to be proxy-aware anyway even before this PR, so what's going on? The clue is in the above links, where these static properties are actually lazily initialized, which means the first time they're invoked they'll look at the proxy configuration and any changes to HTTPS_PROXY et al. env vars will be ignored.
Indeed, in a fresh powershell:

❯ $env:HTTPS_PROXY = "http://foo.barasdasdad"
❯ [System.Net.Http.HttpClient]::DefaultProxy.GetProxy('https://google.com')
...
OriginalString : http://foo.bar:80/
...
❯ $wc = New-Object Net.Webclient
❯ $wc.DownloadData('https://py.wtf')
MethodInvocationException: Exception calling "DownloadData" with "1" argument(s): "No such host is known. (foo.bar:80)"
❯ $env:HTTPS_PROXY = $null
❯ $wc.Proxy.getProxy('https://py.wtf')
...
OriginalString : http://foo.bar:80/
...
❯ $wc.DownloadData('https://py.wtf')
MethodInvocationException: Exception calling "DownloadData" with "1" argument(s): "No such host is known. (foo.bar:80)"

Arguably this is a misfeature of the dotnet runtime, and the workaround will be painful as the implementation of SystemProxyInfo.ConstructSystemProxy is an internal (i.e. non-public) class which of course has platform-dependant implementations for Windows, Linux, and MacOS - although for cargo-dist's purposes, we could probably get away with a Windows-only implementation.

@zanieb
Copy link
Contributor Author

zanieb commented Sep 6, 2025

Thanks for the details!

So the claim here is that the client has been initialized before the installer runs? Doesn't the fresh powershell session created to run the installer get a fresh static client?

@zsol
Copy link
Contributor

zsol commented Sep 6, 2025

Doesn't the fresh powershell session created to run the installer get a fresh static client?

Hm I didn't realize we spawn a fresh session just for the install script, but you're right, we do, and that makes the lazy inits a non-issue (I've confirmed this: after the above shell snippet, running powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" lets the installer succeed).

In any case, this diff as it stands doesn't change the functionality. But now I'm not sure why it's not working, there's something fundamental I don't get:

❯ echo $env:ALL_PROXY
http://foo.bar
❯ irm https://py.wtf
Invoke-RestMethod: No such host is known.
❯ powershell -executionPolicy ByPass -c 'echo $env:ALL_PROXY; irm https://py.wtf'
http://foo.bar
<!DOCTYPE html>...

Am I looking at a different implementation of these classes?

Edit: looks like yes, because this fails as expected:

pwsh -executionPolicy ByPass -c 'echo $env:ALL_PROXY; irm https://py.wtf'
http://foo.bar
Invoke-RestMethod: No such host is known.

So Windows Powershell must be using a different implementation of the http client/proxies compared to Powershell Core (which is what I linked to above)

@zanieb
Copy link
Contributor Author

zanieb commented Sep 6, 2025

That's pretty tragic...

@samypr100
Copy link
Contributor

iirc they're different as one is written in dotnet framework and the other in dotnet core

@zsol
Copy link
Contributor

zsol commented Sep 6, 2025

I've got a fix but I'm not super proud of it.

function New-WebProxyFromUrl {
    param([string]$ProxyUrl)

    if ([string]::IsNullOrWhiteSpace($ProxyUrl)) {
        return $null
    }

    try {
        # Parse the proxy URL
        $uri = [System.Uri]$ProxyUrl

        # Create WebProxy instance
        $webProxy = New-Object System.Net.WebProxy($uri)

        # Set credentials if provided in URL
        if (-not [string]::IsNullOrEmpty($uri.UserInfo)) {
            $userInfo = $uri.UserInfo.Split(':')
            if ($userInfo.Length -eq 2) {
                $username = [System.Uri]::UnescapeDataString($userInfo[0])
                $password = [System.Uri]::UnescapeDataString($userInfo[1])
                $webProxy.Credentials = New-Object System.Net.NetworkCredential($username, $password)
            }
        }

        return $webProxy
    }
    catch {
        Write-Verbose("Failed to parse proxy URL '$ProxyUrl': $($_.Exception.Message)")
        return $null
    }
}

function New-WebProxyFromEnvironment {
    $httpsProxy = [System.Environment]::GetEnvironmentVariable("HTTPS_PROXY")
    $allProxy = [System.Environment]::GetEnvironmentVariable("ALL_PROXY")
    $proxyUrl = if (-not [string]::IsNullOrWhiteSpace($httpsProxy)) { $httpsProxy } else { $allProxy }
    $webProxy = New-WebProxyFromUrl -ProxyUrl $proxyUrl
    return $webProxy
}

And then instead of the current $wc.Proxy assignment:

  $proxy = New-WebProxyFromEnvironment
  if ($null -ne $proxy) {
    $wc.Proxy = $proxy
  }

I've tested it with cargo-dist itself (I'm dogsciencing this, tell me if I'm doing it wrong):

cargo build --release
.\target\release\dist build -a global -i powershell

And then, without proxy env vars:

❯ powershell -ExecutionPolicy ByPass .\target\distrib\dist-installer.ps1
Downloading dist 1.0.0-rc.1 (x86_64-pc-windows-msvc)
Installing to C:\Users\zsolz\.cargo\bin
  dist.exe
everything's installed!

With proxy env vars:

❯ $env:HTTPS_PROXY = "http://user:pass@foo.barasdasdad"
❯ powershell -ExecutionPolicy ByPass .\target\distrib\dist-installer.ps1
Downloading dist 1.0.0-rc.1 (x86_64-pc-windows-msvc)
Exception calling "DownloadFile" with "2" argument(s): "The remote name could not be resolved: 'foo.barasdasdad'"

Note

This doesn't support HTTP_PROXY, or NO_PROXY env vars, and I haven't tested whether proxy auth actually works.

@mistydemeo
Copy link
Contributor

Well, it's verbose, but if it works it's a big upgrade over it not working! That certainly looks legit to me.

@mistydemeo
Copy link
Contributor

I'm prepping a release 0.30.0 (#2076); would you like to get this in before that goes out?

@zsol
Copy link
Contributor

zsol commented Sep 6, 2025

I just checked, auth also works! I'm making #2078 with the above changes in a moment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The uv installer does not respect HTTPS_PROXY on Windows

4 participants

Comments