diff --git a/include/vcpkg/base/system.proxy.h b/include/vcpkg/base/system.proxy.h new file mode 100644 index 0000000000..cbac576ead --- /dev/null +++ b/include/vcpkg/base/system.proxy.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include + +/* + * 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 get_windows_ie_proxy_server(); +} \ No newline at end of file diff --git a/src/tls12-download.c b/src/tls12-download.c index 0a3a03fb4c..6f34bbfbd6 100644 --- a/src/tls12-download.c +++ b/src/tls12-download.c @@ -1,6 +1,7 @@ #include #include #include + /* * 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 @@ -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) { @@ -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))) { diff --git a/src/vcpkg/base/downloads.cpp b/src/vcpkg/base/downloads.cpp index 9fd3351d0e..5cf9fefd58 100644 --- a/src/vcpkg/base/downloads.cpp +++ b/src/vcpkg/base/downloads.cpp @@ -6,12 +6,9 @@ #include #include #include +#include #include -#if defined(_WIN32) -#include -#endif - namespace vcpkg::Downloads { #if defined(_WIN32) @@ -92,12 +89,8 @@ namespace vcpkg::Downloads { static ExpectedS 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); @@ -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)); } } diff --git a/src/vcpkg/base/system.proxy.cpp b/src/vcpkg/base/system.proxy.cpp new file mode 100644 index 0000000000..f7cd1e7a04 --- /dev/null +++ b/src/vcpkg/base/system.proxy.cpp @@ -0,0 +1,25 @@ +#include + +vcpkg::Optional 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(); +#else + return vcpkg::Optional(); +#endif +} \ No newline at end of file diff --git a/src/vcpkg/build.cpp b/src/vcpkg/build.cpp index 0a22425f3e..7692cac354 100644 --- a/src/vcpkg/build.cpp +++ b/src/vcpkg/build.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -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}; });