Skip to content

Commit

Permalink
Use "IE Proxy Settings" correctly on Windows in vcpkg. (#49)
Browse files Browse the repository at this point in the history
* Unify design, abandon WPAD, manually detects IE Proxy at "tls12-download.c", "vcpkg/base/downloads.cpp". Add an helper code in "build.cpp:409" to make setting proxy easier.

* Meet @BillyONeal's review

* Formatting

* Python's proxy detection

* Use vcpkg::Optional

* Allow mismatches protocol and address prefix
  • Loading branch information
reitowo committed Apr 19, 2021
1 parent 8b2aefa commit 235d4b7
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 35 deletions.
24 changes: 24 additions & 0 deletions include/vcpkg/base/system.proxy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#pragma once

#include <vcpkg/base/optional.h>

#include <string>

/*
* This is a helper class. It reads IE Proxy settings.
* For coherence of vcpkg design "using HTTP(S)_PROXY not WPAD", it is a user-friendly design to manually
* read the IE Proxy settings on Windows, and set the environment variables HTTP(S)_PROXY to the same
* when starting an external program (like CMake, which file(DOWNLOAD) only accept proxy settings
* by HTTP(S)_PROXY variables).
*/

namespace vcpkg::System
{
struct IEProxySetting
{
std::wstring server;
std::wstring bypass;
};

vcpkg::Optional<IEProxySetting> get_windows_ie_proxy_server();
}
28 changes: 26 additions & 2 deletions src/tls12-download.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <Windows.h>
#include <process.h>
#include <winhttp.h>

/*
* This program must be as small as possible, because it is committed in binary form to the
* vcpkg github repo to enable downloading the main vcpkg program on Windows 7, where TLS 1.2 is
Expand Down Expand Up @@ -208,8 +209,6 @@ int __stdcall entry()
abort_api_failure(std_out, L"GetEnvironmentVariableW");
}

write_message(std_out, L"\r\n");

const HANDLE out_file = CreateFileW(out_file_path, FILE_WRITE_DATA, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (out_file == INVALID_HANDLE_VALUE)
{
Expand All @@ -223,6 +222,31 @@ int __stdcall entry()
abort_api_failure(std_out, L"WinHttpOpen");
}

// If HTTPS_PROXY not found, try use IE Proxy. This works on Windows 10 20H2, so there's no need to determine
// the OS version >= 8.1.
if (access_type == WINHTTP_ACCESS_TYPE_NO_PROXY)
{
WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieProxy;
if (WinHttpGetIEProxyConfigForCurrentUser(&ieProxy) && ieProxy.lpszProxy != NULL)
{
WINHTTP_PROXY_INFO proxy;
proxy.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
proxy.lpszProxy = ieProxy.lpszProxy;
proxy.lpszProxyBypass = ieProxy.lpszProxyBypass;
WinHttpSetOption(session, WINHTTP_OPTION_PROXY, &proxy, sizeof(proxy));

write_message(std_out, L" (using IE proxy: ");
write_message(std_out, proxy.lpszProxy);
write_message(std_out, L")");

GlobalFree(ieProxy.lpszProxy);
GlobalFree(ieProxy.lpszProxyBypass);
GlobalFree(ieProxy.lpszAutoConfigUrl);
}
}

write_message(std_out, L"\r\n");

unsigned long secure_protocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;
if (!WinHttpSetOption(session, WINHTTP_OPTION_SECURE_PROTOCOLS, &secure_protocols, sizeof(DWORD)))
{
Expand Down
47 changes: 14 additions & 33 deletions src/vcpkg/base/downloads.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@
#include <vcpkg/base/system.h>
#include <vcpkg/base/system.print.h>
#include <vcpkg/base/system.process.h>
#include <vcpkg/base/system.proxy.h>
#include <vcpkg/base/util.h>

#if defined(_WIN32)
#include <VersionHelpers.h>
#endif

namespace vcpkg::Downloads
{
#if defined(_WIN32)
Expand Down Expand Up @@ -92,12 +89,8 @@ namespace vcpkg::Downloads
{
static ExpectedS<WinHttpSession> make()
{
auto h = WinHttpOpen(L"vcpkg/1.0",
IsWindows8Point1OrGreater() ? WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY
: WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS,
0);
auto h = WinHttpOpen(
L"vcpkg/1.0", WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (!h) return Strings::concat("WinHttpOpen() failed: ", GetLastError());
WinHttpSession ret;
ret.m_hSession.reset(h);
Expand All @@ -116,31 +109,19 @@ namespace vcpkg::Downloads

WinHttpSetOption(ret.m_hSession.get(), WINHTTP_OPTION_PROXY, &proxy, sizeof(proxy));
}
// Win7 IE Proxy fallback
else if (IsWindows7OrGreater() && !IsWindows8Point1OrGreater())
// IE Proxy fallback, this works on Windows 10
else
{
// First check if any proxy has been found automatically
WINHTTP_PROXY_INFO proxyInfo;
DWORD proxyInfoSize = sizeof(WINHTTP_PROXY_INFO);
auto noProxyFound =
!WinHttpQueryOption(ret.m_hSession.get(), WINHTTP_OPTION_PROXY, &proxyInfo, &proxyInfoSize) ||
proxyInfo.dwAccessType == WINHTTP_ACCESS_TYPE_NO_PROXY;

// If no proxy was found automatically, use IE's proxy settings, if any
if (noProxyFound)
// We do not use WPAD anymore
// Directly read IE Proxy setting
auto ieProxy = System::get_windows_ie_proxy_server();
if (ieProxy.has_value())
{
WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieProxy;
if (WinHttpGetIEProxyConfigForCurrentUser(&ieProxy) && ieProxy.lpszProxy != nullptr)
{
WINHTTP_PROXY_INFO proxy;
proxy.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
proxy.lpszProxy = ieProxy.lpszProxy;
proxy.lpszProxyBypass = ieProxy.lpszProxyBypass;
WinHttpSetOption(ret.m_hSession.get(), WINHTTP_OPTION_PROXY, &proxy, sizeof(proxy));
GlobalFree(ieProxy.lpszProxy);
GlobalFree(ieProxy.lpszProxyBypass);
GlobalFree(ieProxy.lpszAutoConfigUrl);
}
WINHTTP_PROXY_INFO proxy;
proxy.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
proxy.lpszProxy = ieProxy.get()->server.data();
proxy.lpszProxyBypass = ieProxy.get()->bypass.data();
WinHttpSetOption(ret.m_hSession.get(), WINHTTP_OPTION_PROXY, &proxy, sizeof(proxy));
}
}

Expand Down
25 changes: 25 additions & 0 deletions src/vcpkg/base/system.proxy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include <vcpkg/base/system.proxy.h>

vcpkg::Optional<vcpkg::System::IEProxySetting> vcpkg::System::get_windows_ie_proxy_server()
{
#if defined(_WIN32)
WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieProxy;
if (WinHttpGetIEProxyConfigForCurrentUser(&ieProxy) && ieProxy.lpszProxy != nullptr)
{
vcpkg::System::IEProxySetting ieProxySetting;

ieProxySetting.server = ieProxy.lpszProxy;

if (ieProxy.lpszProxyBypass != nullptr) ieProxySetting.bypass = ieProxy.lpszProxyBypass;

GlobalFree(ieProxy.lpszProxy);
GlobalFree(ieProxy.lpszProxyBypass);
GlobalFree(ieProxy.lpszAutoConfigUrl);

return ieProxySetting;
}
return vcpkg::Optional<vcpkg::System::IEProxySetting>();
#else
return vcpkg::Optional<vcpkg::System::IEProxySetting>();
#endif
}
64 changes: 64 additions & 0 deletions src/vcpkg/build.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <vcpkg/base/system.debug.h>
#include <vcpkg/base/system.print.h>
#include <vcpkg/base/system.process.h>
#include <vcpkg/base/system.proxy.h>
#include <vcpkg/base/util.h>

#include <vcpkg/binarycaching.h>
Expand Down Expand Up @@ -396,6 +397,69 @@ namespace vcpkg::Build
if (auto p_val = val.get()) env.emplace(var, *p_val);
}

/*
* On Windows 10 (>= 8.1) it is a user-friendly way to automatically set HTTP_PROXY and HTTPS_PROXY
* environment variables by reading proxy settings via WinHttpGetIEProxyConfigForCurrentUser, preventing
* users set and unset these variables manually (which is not a decent way). It is common in China or any
* other regions that needs an proxy software (v2ray, shadowsocks, etc.), which sets the IE Proxy Settings,
* but not setting environment variables. This will make vcpkg easier to use, specially when use vcpkg in
* Visual Studio, we even cannot set HTTP(S)_PROXY in CLI, if we want to open or close Proxy we need to
* restart VS.
*/
auto ieProxy = System::get_windows_ie_proxy_server();
if (ieProxy.has_value())
{
std::string server = Strings::to_utf8(ieProxy.get()->server);

// Separate settings in IE Proxy Settings, which is rare?
// Python implementation:
// https://github.com/python/cpython/blob/7215d1ae25525c92b026166f9d5cac85fb1defe1/Lib/urllib/request.py#L2655
if (Strings::contains(server, "="))
{
auto proxy_settings = Strings::split(server, ';');
for (auto& s : proxy_settings)
{
auto kvp = Strings::split(s, '=');
if (kvp.size() == 2)
{
auto protocol = kvp[0];
auto address = kvp[1];
if (!Strings::contains(address, "://"))
{
address = Strings::concat(protocol, "://", address);
}
protocol = Strings::concat(Strings::ascii_to_uppercase(protocol.c_str()), "_PROXY");
env.emplace(protocol, address);
System::print2("-- Setting ", protocol, " environment variables to ", address, "\n");
}
}
}
// Specified http:// prefix
else if (Strings::starts_with(server, "http://"))
{
System::print2("-- Setting HTTP_PROXY environment variables to ", server, "\n");
env.emplace("HTTP_PROXY", server);
}
// Specified https:// prefix
else if (Strings::starts_with(server, "https://"))
{
System::print2("-- Setting HTTPS_PROXY environment variables to ", server, "\n");
env.emplace("HTTPS_PROXY", server);
}
// Most common case: "ip:port" style, apply to HTTP and HTTPS proxies.
// An HTTP(S)_PROXY means https requests go through that, it can be:
// http:// prefixed: the request go through an HTTP proxy without end-to-end security.
// https:// prefixed: the request go through an HTTPS proxy with end-to-end security.
// Nothing prefixed: don't know the default behaviour, seems considering HTTP proxy as default.
// We simply set "ip:port" to HTTP(S)_PROXY variables because it works on most common cases.
else
{
System::print2("-- Automatically setting HTTP(S)_PROXY environment variables to ", server, "\n");

env.emplace("HTTP_PROXY", server.c_str());
env.emplace("HTTPS_PROXY", server.c_str());
}
}
return {env};
});

Expand Down

0 comments on commit 235d4b7

Please sign in to comment.