697 lines
16 KiB
C++
697 lines
16 KiB
C++
//
|
|
// 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 <windows.h>
|
|
#include <stdio.h>
|
|
#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;
|
|
}
|
|
|