// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/proxy_resolution/proxy_resolver_winhttp.h" #include #include #include "base/macros.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "net/base/net_errors.h" #include "net/proxy_resolution/proxy_info.h" #include "net/proxy_resolution/proxy_resolver.h" #include "url/gurl.h" using base::TimeDelta; using base::TimeTicks; namespace net { namespace { static void FreeInfo(WINHTTP_PROXY_INFO* info) { if (info->lpszProxy) GlobalFree(info->lpszProxy); if (info->lpszProxyBypass) GlobalFree(info->lpszProxyBypass); } static Error WinHttpErrorToNetError(DWORD win_http_error) { switch (win_http_error) { case ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR: case ERROR_WINHTTP_INTERNAL_ERROR: case ERROR_WINHTTP_INCORRECT_HANDLE_TYPE: return ERR_FAILED; case ERROR_WINHTTP_LOGIN_FAILURE: return ERR_PROXY_AUTH_UNSUPPORTED; case ERROR_WINHTTP_BAD_AUTO_PROXY_SCRIPT: return ERR_PAC_SCRIPT_FAILED; case ERROR_WINHTTP_INVALID_URL: case ERROR_WINHTTP_OPERATION_CANCELLED: case ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT: case ERROR_WINHTTP_UNRECOGNIZED_SCHEME: return ERR_PAC_STATUS_NOT_OK; case ERROR_NOT_ENOUGH_MEMORY: return ERR_INSUFFICIENT_RESOURCES; default: return ERR_FAILED; } } class ProxyResolverWinHttp : public ProxyResolver { public: ProxyResolverWinHttp(const scoped_refptr& script_data); ~ProxyResolverWinHttp() override; // ProxyResolver implementation: int GetProxyForURL(const GURL& url, ProxyInfo* results, CompletionOnceCallback /*callback*/, std::unique_ptr* /*request*/, const NetLogWithSource& /*net_log*/) override; private: bool OpenWinHttpSession(); void CloseWinHttpSession(); // Proxy configuration is cached on the session handle. HINTERNET session_handle_; const GURL pac_url_; DISALLOW_COPY_AND_ASSIGN(ProxyResolverWinHttp); }; ProxyResolverWinHttp::ProxyResolverWinHttp( const scoped_refptr& script_data) : session_handle_(NULL), pac_url_(script_data->type() == PacFileData::TYPE_AUTO_DETECT ? GURL("http://wpad/wpad.dat") : script_data->url()) {} ProxyResolverWinHttp::~ProxyResolverWinHttp() { CloseWinHttpSession(); } int ProxyResolverWinHttp::GetProxyForURL(const GURL& query_url, ProxyInfo* results, CompletionOnceCallback /*callback*/, std::unique_ptr* /*request*/, const NetLogWithSource& /*net_log*/) { // If we don't have a WinHTTP session, then create a new one. if (!session_handle_ && !OpenWinHttpSession()) return ERR_FAILED; // Windows' system resolver does not support WebSocket URLs in proxy.pac. This // was tested in version 10.0.16299, and is also implied by the description of // the ERROR_WINHTTP_UNRECOGNIZED_SCHEME error code in the Microsoft // documentation at // https://docs.microsoft.com/en-us/windows/desktop/api/winhttp/nf-winhttp-winhttpgetproxyforurl. // See https://crbug.com/862121. GURL mutable_query_url = query_url; if (query_url.SchemeIsWSOrWSS()) { GURL::Replacements replacements; replacements.SetSchemeStr(query_url.SchemeIsCryptographic() ? "https" : "http"); mutable_query_url = query_url.ReplaceComponents(replacements); } // If we have been given an empty PAC url, then use auto-detection. // // NOTE: We just use DNS-based auto-detection here like Firefox. We do this // to avoid WinHTTP's auto-detection code, which while more featureful (it // supports DHCP based auto-detection) also appears to have issues. // WINHTTP_AUTOPROXY_OPTIONS options = {0}; options.fAutoLogonIfChallenged = FALSE; options.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL; base::string16 pac_url16 = base::ASCIIToUTF16(pac_url_.spec()); options.lpszAutoConfigUrl = pac_url16.c_str(); WINHTTP_PROXY_INFO info = {0}; DCHECK(session_handle_); // Per http://msdn.microsoft.com/en-us/library/aa383153(VS.85).aspx, it is // necessary to first try resolving with fAutoLogonIfChallenged set to false. // Otherwise, we fail over to trying it with a value of true. This way we // get good performance in the case where WinHTTP uses an out-of-process // resolver. This is important for Vista and Win2k3. BOOL ok = WinHttpGetProxyForUrl( session_handle_, base::ASCIIToUTF16(mutable_query_url.spec()).c_str(), &options, &info); if (!ok) { if (ERROR_WINHTTP_LOGIN_FAILURE == GetLastError()) { options.fAutoLogonIfChallenged = TRUE; ok = WinHttpGetProxyForUrl( session_handle_, base::ASCIIToUTF16(mutable_query_url.spec()).c_str(), &options, &info); } if (!ok) { DWORD error = GetLastError(); // If we got here because of RPC timeout during out of process PAC // resolution, no further requests on this session are going to work. if (ERROR_WINHTTP_TIMEOUT == error || ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR == error) { CloseWinHttpSession(); } return WinHttpErrorToNetError(error); } } int rv = OK; switch (info.dwAccessType) { case WINHTTP_ACCESS_TYPE_NO_PROXY: results->UseDirect(); break; case WINHTTP_ACCESS_TYPE_NAMED_PROXY: // According to MSDN: // // The proxy server list contains one or more of the following strings // separated by semicolons or whitespace. // // ([=]["://"][":"]) // // Based on this description, ProxyInfo::UseNamedProxy() isn't // going to handle all the variations (in particular =). // // However in practice, it seems that WinHTTP is simply returning // things like "foopy1:80;foopy2:80". It strips out the non-HTTP // proxy types, and stops the list when PAC encounters a "DIRECT". // So UseNamedProxy() should work OK. results->UseNamedProxy(base::UTF16ToASCII(info.lpszProxy)); break; default: NOTREACHED(); rv = ERR_FAILED; } FreeInfo(&info); return rv; } bool ProxyResolverWinHttp::OpenWinHttpSession() { DCHECK(!session_handle_); session_handle_ = WinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!session_handle_) return false; // Since this session handle will never be used for WinHTTP connections, // these timeouts don't really mean much individually. However, WinHTTP's // out of process PAC resolution will use a combined (sum of all timeouts) // value to wait for an RPC reply. BOOL rv = WinHttpSetTimeouts(session_handle_, 10000, 10000, 5000, 5000); DCHECK(rv); return true; } void ProxyResolverWinHttp::CloseWinHttpSession() { if (session_handle_) { WinHttpCloseHandle(session_handle_); session_handle_ = NULL; } } } // namespace ProxyResolverFactoryWinHttp::ProxyResolverFactoryWinHttp() : ProxyResolverFactory(false /*expects_pac_bytes*/) { } int ProxyResolverFactoryWinHttp::CreateProxyResolver( const scoped_refptr& pac_script, std::unique_ptr* resolver, CompletionOnceCallback callback, std::unique_ptr* request) { resolver->reset(new ProxyResolverWinHttp(pac_script)); return OK; } } // namespace net