// // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved // // Sample WinHttp application for determining the proxy for a particular URL. // This sample demonstrates the core functionality for querying the proxy // settings. Additional features may be added by the application/module basing // their proxy code from this sample, including but not limited to: // 1) Per URL proxy cache. // 2) Network Change awareness. // 3) Bad Proxy Filter. // #include #include #include "GetProxy.h" ProxyResolver::ProxyResolver() { m_fInit = FALSE; m_fReturnedFirstProxy = FALSE; m_fReturnedLastProxy = FALSE; m_fProxyFailOverValid = FALSE; m_pwszProxyCursor = NULL; ZeroMemory(&m_wpiProxyInfo, sizeof(WINHTTP_PROXY_INFO)); } ProxyResolver::~ProxyResolver() { if (m_wpiProxyInfo.lpszProxy != NULL) { GlobalFree(m_wpiProxyInfo.lpszProxy); m_wpiProxyInfo.lpszProxy = NULL; } if (m_wpiProxyInfo.lpszProxyBypass != NULL) { GlobalFree(m_wpiProxyInfo.lpszProxyBypass); m_wpiProxyInfo.lpszProxyBypass = NULL; } m_wpiProxyInfo.dwAccessType = WINHTTP_ACCESS_TYPE_DEFAULT_PROXY; m_fInit = FALSE; m_fReturnedFirstProxy = FALSE; m_fReturnedLastProxy = FALSE; m_fProxyFailOverValid = FALSE; m_pwszProxyCursor = NULL; } BOOL ProxyResolver::IsWhitespace(WCHAR wcChar) /*++ Routine Description: Determines if a wide character is a whitespace character. Arguments: wcChar - The character to check for whitespace. Return Value: TRUE if the character is whitespace. FALSE otherwise. --*/ { BOOL fResults = FALSE; // // Check for ' '. // if (wcChar == 32) { fResults = TRUE; goto quit; } // // Check for \t\n\v\f\r. // if (wcChar >= 9 && wcChar <= 13) { fResults = TRUE; goto quit; } quit: return fResults; } BOOL ProxyResolver::IsRecoverableAutoProxyError( __in DWORD dwError ) /*++ Routine Description: Determines whether the result of WinHttpGetProxyForUrl is recoverable, allowing the caller to fall back to other proxy mechanisms. Arguments: dwError - The Win32 error code returned by GetLastError on WinHttpGetProxyForUrl failure. Return Value: TRUE - The caller can continue execution safely. FALSE - The caller should immediately fail with dwError. --*/ { BOOL fRecoverable = FALSE; switch (dwError) { case ERROR_SUCCESS: case ERROR_INVALID_PARAMETER: case ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR: case ERROR_WINHTTP_AUTODETECTION_FAILED: case ERROR_WINHTTP_BAD_AUTO_PROXY_SCRIPT: case ERROR_WINHTTP_LOGIN_FAILURE: case ERROR_WINHTTP_OPERATION_CANCELLED: case ERROR_WINHTTP_TIMEOUT: case ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT: case ERROR_WINHTTP_UNRECOGNIZED_SCHEME: fRecoverable = TRUE; break; default: break; } return fRecoverable; } BOOL ProxyResolver::IsErrorValidForProxyFailover( __in DWORD dwError ) /*++ Routine Description: Determines whether the result of WinHttpSendRequest (Sync) or the error from WINHTTP_CALLBACK_STATUS_REQUEST_ERROR (Async) can assume a possible proxy error and fallback to the next proxy. Arguments: dwError - The Win32 error code from WinHttpSendRequest (Sync) or from WINHTTP_CALLBACK_STATUS_REQUEST_ERROR (Async) Return Value: TRUE - The caller should set the next proxy and resend the request. FALSE - The caller should assume the request has failed. --*/ { BOOL fValid = FALSE; switch(dwError) { case ERROR_WINHTTP_NAME_NOT_RESOLVED: case ERROR_WINHTTP_CANNOT_CONNECT: case ERROR_WINHTTP_CONNECTION_ERROR: case ERROR_WINHTTP_TIMEOUT: fValid = TRUE; break; default: break; } return fValid; } VOID ProxyResolver::ResetProxyCursor() /*++ Routine Description: Resets the proxy cursor for reuse starting at the beginning of the list. Arguments: Return Value: None. --*/ { m_fReturnedFirstProxy = FALSE; m_fReturnedLastProxy = FALSE; m_pwszProxyCursor = NULL; } VOID ProxyResolver::PrintProxyData() /*++ Routine Description: Prints the proxy information in for the resolver including proxy, bypass list, and if failover is allowed. Arguments: Return Value: None. --*/ { wprintf(L" Proxy: %s\n", m_wpiProxyInfo.lpszProxy); wprintf(L" Bypass: %s\n", m_wpiProxyInfo.lpszProxyBypass); wprintf(L" Failover Valid: %d\n\n", m_fProxyFailOverValid); } DWORD ProxyResolver::SetNextProxySetting( __in HINTERNET hInternet, __in DWORD dwRequestError ) /*++ Routine Description: Finds the next proxy in a list of proxies separated by whitespace and/or semicolons if proxy failover is supported. It is not safe to use this function concurrently, implement a concurrency mechanism for proxy lists if needed, such as making a copy or a separate iterator. Each sequential request to the same URL should use ResetProxyCursor before the first call for proxy settings during a single request. Arguments: hInternet - The Session or Request handle to set the proxy info on. dwRequestError - The Win32 error code from WinHttpSendRequest (Sync) or from WINHTTP_CALLBACK_STATUS_REQUEST_ERROR (Async) or ERROR_SUCCESS if this is the first usage. Return Value: ERROR_SUCCESS - Found the next proxy and it has been set on the HINTERNET. ERROR_NO_MORE_ITEMS - Reached the end of the list or failover not valid. ERROR_INVALID_OPERATION - The class is not initialized. Call ResolveProxy first. Other Win32 Errors returned from WinHttpSetOption. --*/ { DWORD dwError = ERROR_SUCCESS; PWSTR pwszCursor = NULL; WINHTTP_PROXY_INFO NextProxyInfo = {}; if (!m_fInit) { dwError = ERROR_INVALID_OPERATION; goto quit; } if (!m_fReturnedFirstProxy) { // // We have yet to set the first proxy type, the first one is always // valid. // pwszCursor = m_wpiProxyInfo.lpszProxy; m_fReturnedFirstProxy = TRUE; goto commit; } // // Find the next proxy in the list if it is valid to do so. // if (m_fReturnedLastProxy || !m_fProxyFailOverValid || m_wpiProxyInfo.lpszProxy == NULL) { // // Already reached end, failover not valid, or type is not proxy. // dwError = ERROR_NO_MORE_ITEMS; goto quit; } if (!IsErrorValidForProxyFailover(dwRequestError)) { dwError = ERROR_NO_MORE_ITEMS; goto quit; } pwszCursor = m_pwszProxyCursor; // // Skip the current entry. // while(*pwszCursor != L'\0' && *pwszCursor != L';' && !IsWhitespace(*pwszCursor)) { pwszCursor++; } // // Skip any additional separators. // while(*pwszCursor == L';' || IsWhitespace(*pwszCursor)) { pwszCursor++; } if (*pwszCursor == L'\0') { // // Hit the end of the list. // m_fReturnedLastProxy = TRUE; dwError = ERROR_NO_MORE_ITEMS; goto quit; } commit: NextProxyInfo.dwAccessType = m_wpiProxyInfo.dwAccessType; NextProxyInfo.lpszProxy = pwszCursor; NextProxyInfo.lpszProxyBypass = m_wpiProxyInfo.lpszProxyBypass; wprintf(L"Setting winhttp proxy to\n Proxy: %s\n Bypass: %s\n\n", NextProxyInfo.lpszProxy, NextProxyInfo.lpszProxyBypass); if (!WinHttpSetOption(hInternet, WINHTTP_OPTION_PROXY, &NextProxyInfo, sizeof(NextProxyInfo))) { dwError = GetLastError(); goto quit; } m_pwszProxyCursor = pwszCursor; quit: return dwError; } DWORD ProxyResolver::GetProxyForAutoSettings( __in HINTERNET hSession, __in_z PCWSTR pwszUrl, __in_z_opt PCWSTR pwszAutoConfigUrl, __out PWSTR *ppwszProxy, __out PWSTR *ppwszProxyBypass ) /*++ Routine Description: Uses Auto detection or AutoConfigURL to run WinHttpGetProxyForUrl. Additionally provides autologon by calling once without autologon, which is most performant, and then with autologon if logon fails. Arguments: hSession - The WinHttp session to use for the proxy resolution. pwszUrl - The URL to get the proxy for. pwszAutoConfig - The autoconfig URL or NULL for Autodetection. ppwszProxy - Upon success, the proxy string found for pwszUrl or NULL if no proxy should be used for this URL. Use GlobalFree to free. ppwszProxyBypass - Upon success, the proxy bypass string found for pwszUrl or NULL if there is no proxy bypass for the configuration type. Use GlobalFree to free. Return Value: WIN32 Error codes. The caller should use IsRecoverableAutoProxyError to decide whether execution can continue. --*/ { DWORD dwError = ERROR_SUCCESS; WINHTTP_AUTOPROXY_OPTIONS waoOptions = {}; WINHTTP_PROXY_INFO wpiProxyInfo = {}; *ppwszProxy = NULL; *ppwszProxyBypass = NULL; if (pwszAutoConfigUrl) { waoOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL; waoOptions.lpszAutoConfigUrl = pwszAutoConfigUrl; } else { waoOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT; waoOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A; } // // First call with no autologon. Autologon prevents the // session (in proc) or autoproxy service (out of proc) from caching // the proxy script. This causes repetitive network traffic, so it is // best not to do autologon unless it is required according to the // result of WinHttpGetProxyForUrl. // if (!WinHttpGetProxyForUrl(hSession, pwszUrl, &waoOptions, &wpiProxyInfo)) { dwError = GetLastError(); if (dwError != ERROR_WINHTTP_LOGIN_FAILURE) { goto quit; } // // Enable autologon if challenged. // dwError = ERROR_SUCCESS; waoOptions.fAutoLogonIfChallenged = TRUE; if (!WinHttpGetProxyForUrl(hSession, pwszUrl, &waoOptions, &wpiProxyInfo)) { dwError = GetLastError(); goto quit; } } *ppwszProxy = wpiProxyInfo.lpszProxy; wpiProxyInfo.lpszProxy = NULL; *ppwszProxyBypass = wpiProxyInfo.lpszProxyBypass; wpiProxyInfo.lpszProxyBypass = NULL; quit: if (wpiProxyInfo.lpszProxy) { GlobalFree(wpiProxyInfo.lpszProxy); wpiProxyInfo.lpszProxy = NULL; } if (wpiProxyInfo.lpszProxyBypass) { GlobalFree(wpiProxyInfo.lpszProxyBypass); wpiProxyInfo.lpszProxyBypass = NULL; } return dwError; } DWORD ProxyResolver::ResolveProxy( __in HINTERNET hSession, __in_z PCWSTR pwszUrl ) /*++ Routine Description: Uses the users IE settings to get the proxy for the URL. Arguments: pwszUrl - The URL to get the proxy for. hSession - The session to use for the proxy resolution. Return Value: WIN32 Error codes. --*/ { DWORD dwError = ERROR_SUCCESS; WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ProxyConfig = {}; PWSTR pwszProxy = NULL; PWSTR pwszProxyBypass = NULL; BOOL fFailOverValid = FALSE; if (m_fInit) { dwError = ERROR_INVALID_OPERATION; goto quit; } if (!WinHttpGetIEProxyConfigForCurrentUser(&ProxyConfig)) { dwError = GetLastError(); if (dwError != ERROR_FILE_NOT_FOUND) { goto quit; } // // No IE proxy settings found, just do autodetect. // ProxyConfig.fAutoDetect = TRUE; dwError = ERROR_SUCCESS; } // // Begin processing the proxy settings in the following order: // 1) Auto-Detect if configured. // 2) Auto-Config URL if configured. // 3) Static Proxy Settings if configured. // // Once any of these methods succeed in finding a proxy we are finished. // In the event one mechanism fails with an expected error code it is // required to fall back to the next mechanism. If the request fails // after exhausting all detected proxies, there should be no attempt // to discover additional proxies. // if (ProxyConfig.fAutoDetect) { fFailOverValid = TRUE; // // Detect Proxy Settings. // dwError = GetProxyForAutoSettings(hSession, pwszUrl, NULL, &pwszProxy, &pwszProxyBypass); if (dwError == ERROR_SUCCESS) { goto commit; } if (!IsRecoverableAutoProxyError(dwError)) { goto quit; } // // Fall back to Autoconfig URL or Static settings. Application can // optionally take some action such as logging, or creating a mechanism // to expose multiple error codes in the class. // dwError = ERROR_SUCCESS; } if (ProxyConfig.lpszAutoConfigUrl) { fFailOverValid = TRUE; // // Run autoproxy with AutoConfig URL. // dwError = GetProxyForAutoSettings(hSession, pwszUrl, ProxyConfig.lpszAutoConfigUrl, &pwszProxy, &pwszProxyBypass); if (dwError == ERROR_SUCCESS) { goto commit; } if (!IsRecoverableAutoProxyError(dwError)) { goto quit; } // // Fall back to Static Settings. Application can optionally take some // action such as logging, or creating a mechanism to to expose multiple // error codes in the class. // dwError = ERROR_SUCCESS; } fFailOverValid = FALSE; // // Static Proxy Config. Failover is not valid for static proxy since // it is always either a single proxy or a list containing protocol // specific proxies such as "proxy" or http=httpproxy;https=sslproxy // pwszProxy = ProxyConfig.lpszProxy; ProxyConfig.lpszProxy = NULL; pwszProxyBypass = ProxyConfig.lpszProxyBypass; ProxyConfig.lpszProxyBypass = NULL; commit: m_fProxyFailOverValid = fFailOverValid; if (pwszProxy == NULL) { m_wpiProxyInfo.dwAccessType = WINHTTP_ACCESS_TYPE_NO_PROXY; } else { m_wpiProxyInfo.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY; } m_wpiProxyInfo.lpszProxy = pwszProxy; pwszProxy = NULL; m_wpiProxyInfo.lpszProxyBypass = pwszProxyBypass; pwszProxyBypass = NULL; m_fInit = TRUE; quit: if (pwszProxy != NULL) { GlobalFree(pwszProxy); pwszProxy = NULL; } if (pwszProxyBypass != NULL) { GlobalFree(pwszProxyBypass); pwszProxyBypass = NULL; } if (ProxyConfig.lpszAutoConfigUrl != NULL) { GlobalFree(ProxyConfig.lpszAutoConfigUrl); ProxyConfig.lpszAutoConfigUrl = NULL; } if (ProxyConfig.lpszProxy != NULL) { GlobalFree(ProxyConfig.lpszProxy); ProxyConfig.lpszProxy = NULL; } if (ProxyConfig.lpszProxyBypass != NULL) { GlobalFree(ProxyConfig.lpszProxyBypass); ProxyConfig.lpszProxyBypass = NULL; } return dwError; }