Skip to content

Instantly share code, notes, and snippets.

@phongphan
Created August 5, 2016 09:57
Show Gist options
  • Save phongphan/c55781933d60262cdefbfbf56e7e86d9 to your computer and use it in GitHub Desktop.
Save phongphan/c55781933d60262cdefbfbf56e7e86d9 to your computer and use it in GitHub Desktop.
Windows automatic proxy settings resolver
/* Windows automatic proxy settings resolver
* Jiri Hruska <jirka@fud.cz>, 2012-2013
* Released under the WTFPL license. */
#define UNICODE
#define _UNICODE
#include <string>
#include <Windows.h>
#include <winhttp.h>
#pragma comment(lib, "winhttp.lib")
namespace detail {
struct WinHttpHandle {
HINTERNET m_hInternet;
WinHttpHandle(HANDLE hInternet = NULL)
: m_hInternet(hInternet)
{
}
WinHttpHandle& operator=(HINTERNET hInternet)
{
if (hInternet != m_hInternet) {
if (m_hInternet)
WinHttpCloseHandle(m_hInternet);
m_hInternet = hInternet;
}
return *this;
}
operator HINTERNET()
{
return m_hInternet;
}
~WinHttpHandle()
{
if (m_hInternet)
WinHttpCloseHandle(m_hInternet);
}
};
struct WinHttpCurrentUserIeProxyConfig : public WINHTTP_CURRENT_USER_IE_PROXY_CONFIG {
WinHttpCurrentUserIeProxyConfig()
{
memset(this, 0, sizeof(*this));
}
~WinHttpCurrentUserIeProxyConfig()
{
if (lpszAutoConfigUrl)
GlobalFree(lpszAutoConfigUrl);
if (lpszProxy)
GlobalFree(lpszProxy);
if (lpszProxyBypass)
GlobalFree(lpszProxyBypass);
}
};
struct WinHttpProxyInfo : public WINHTTP_PROXY_INFO {
WinHttpProxyInfo()
{
memset(this, 0, sizeof(*this));
}
~WinHttpProxyInfo()
{
if (lpszProxy)
GlobalFree(lpszProxy);
if (lpszProxyBypass)
GlobalFree(lpszProxyBypass);
}
};
class CrackedUrl {
public:
CrackedUrl()
{
m_scheme[0] = 0;
m_hostname[0] = 0;
}
bool crack(LPCWSTR url)
{
// Loosely based on InternetCrackUrl(), which needs another .dll dependency and
// including both <WinHttp.h> and <WinInet.h> is broken in Windows SDK (...).
LPCWSTR ptr = wcschr(url, L':');
if (!ptr || ptr - url >= _countof(m_scheme))
return false;
wcsncpy_s(m_scheme, url, ptr - url);
CharLower(m_scheme);
if (wcsncmp(ptr, L"://", 3) == 0)
url = ptr + 3;
else
url = ptr + 1;
ptr = wcschr(url, L'/');
LPCWSTR ptr2 = wcschr(url, L'@');
if (ptr2 && ptr2 < ptr)
url = ptr2 + 1;
ptr2 = wcschr(url, L':');
if (ptr2 && ptr2 < ptr)
ptr = ptr2;
wcsncpy_s(m_hostname, url, ptr - url);
CharLower(m_hostname);
return true;
}
LPCWSTR scheme() const
{
return m_scheme;
}
LPCWSTR hostname() const
{
return m_hostname;
}
protected:
WCHAR m_scheme[32];
WCHAR m_hostname[256];
};
class ProxyChecker
{
protected:
struct StaticContext {
CRITICAL_SECTION cs;
WinHttpHandle hInternet;
bool fullAutodetectionFailed;
StaticContext()
: fullAutodetectionFailed(false)
{
InitializeCriticalSection(&cs);
}
~StaticContext()
{
DeleteCriticalSection(&cs);
}
};
protected:
static StaticContext s_context;
LPCWSTR m_url;
CrackedUrl m_urlInfo;
std::wstring m_out;
WINHTTP_AUTOPROXY_OPTIONS m_apo;
protected:
bool checkProxyBypassList(LPWSTR list)
{
// The proxy bypass list contains one or more server names separated by semicolons
// or whitespace. The proxy bypass list can also contain the string "<local>" to
// indicate that all local intranet sites are bypassed. Local intranet sites are
// considered to be all servers that do not contain a period in their name.
static const wchar_t separators[] = L"\t\r\n ;";
wchar_t* context;
for (wchar_t* tok = wcstok_s(list, separators, &context); tok; tok = wcstok_s(NULL, separators, &context)) {
CharLower(tok);
if (wcscmp(tok, L"<local>") == 0) {
if (wcschr(m_urlInfo.hostname(), L'.') == NULL ||
wcscmp(m_urlInfo.hostname(), L"127.0.0.1") == 0)
return true;
continue;
}
const wchar_t* mask = tok;
const wchar_t* hostname = m_urlInfo.hostname();
const wchar_t* backtrack = NULL;
while (*mask && *hostname) {
if (*mask == *hostname) {
mask++;
hostname++;
} else if (*mask == L'*') {
while (mask[1] == L'*')
mask++;
backtrack = mask++;
while (*hostname && *mask != *hostname)
hostname++;
} else if (backtrack) {
mask = backtrack;
} else {
break;
}
if (!*mask && *hostname && backtrack)
mask = backtrack;
}
while(*mask == L'*')
mask++;
if (!*mask && !*hostname)
return true;
}
return false;
}
bool getProxyFromList(LPWSTR list)
{
// The proxy server list contains one or more of the following strings
// separated by semicolons or whitespace:
// ([<scheme>=][<scheme>"://"]<server>[":"<port>])
static const wchar_t separators[] = L"\t\r\n ;";
wchar_t* context;
for (wchar_t* tok = wcstok_s(list, separators, &context); tok; tok = wcstok_s(NULL, separators, &context)) {
// If there are multiple entries for different protocols, accept only the requested one.
wchar_t* pos = wcschr(tok, L'=');
if (pos) {
*pos = L'\0';
if (wcscmp(tok, m_urlInfo.scheme()) != 0)
continue;
tok = pos + 1;
}
// If there is protocol included in the proxy address, accept only "http://".
pos = wcsstr(tok, L"://");
if (pos) {
if (wcsncmp(tok, L"http:", pos - tok + 1) != 0)
continue;
tok = pos + 3;
}
m_out = tok;
return true;
}
return false;
}
bool checkBypassAndGetProxy(LPWSTR proxyBypassList, LPWSTR proxyList)
{
if (proxyBypassList && checkProxyBypassList(proxyBypassList))
return true;
if (proxyList && getProxyFromList(proxyList))
return true;
return false;
}
bool tryGetProxyForUrl()
{
EnterCriticalSection(&s_context.cs);
if ((m_apo.dwFlags & WINHTTP_AUTOPROXY_AUTO_DETECT) && s_context.fullAutodetectionFailed) {
LeaveCriticalSection(&s_context.cs);
return false;
}
if (!s_context.hInternet)
s_context.hInternet = WinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC);
if (!s_context.hInternet) {
LeaveCriticalSection(&s_context.cs);
return false;
}
m_apo.lpvReserved = NULL;
m_apo.dwReserved = 0;
m_apo.fAutoLogonIfChallenged = FALSE;
WinHttpProxyInfo pi;
BOOL bResult = WinHttpGetProxyForUrl(s_context.hInternet, m_url, &m_apo, &pi);
if (!bResult) {
if (GetLastError() == ERROR_WINHTTP_AUTODETECTION_FAILED) {
s_context.fullAutodetectionFailed = true;
} else if (GetLastError() == ERROR_WINHTTP_LOGIN_FAILURE) {
m_apo.fAutoLogonIfChallenged = TRUE;
bResult = WinHttpGetProxyForUrl(s_context.hInternet, m_url, &m_apo, &pi);
}
}
LeaveCriticalSection(&s_context.cs);
if (!bResult)
return false;
if (pi.dwAccessType == WINHTTP_ACCESS_TYPE_NO_PROXY)
return true;
if (pi.dwAccessType == WINHTTP_ACCESS_TYPE_NAMED_PROXY) {
if (checkBypassAndGetProxy(pi.lpszProxyBypass, pi.lpszProxy))
return true;
}
return false;
}
public:
bool process(LPCWSTR url)
{
m_out.clear();
m_url = url;
if (!m_urlInfo.crack(url))
return false;
WinHttpCurrentUserIeProxyConfig iecfg;
if (!WinHttpGetIEProxyConfigForCurrentUser(&iecfg))
return false;
bool triedSomething = false;
// If auto-config script is provided, try to use it.
if (iecfg.lpszAutoConfigUrl) {
triedSomething = true;
m_apo.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
m_apo.dwAutoDetectFlags = 0;
m_apo.lpszAutoConfigUrl = iecfg.lpszAutoConfigUrl;
if (tryGetProxyForUrl())
return true;
}
// If automatic detection is enabled, try to use it.
if (iecfg.fAutoDetect) {
triedSomething = true;
m_apo.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT;
m_apo.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
m_apo.lpszAutoConfigUrl = NULL;
WinHttpProxyInfo pi;
if (tryGetProxyForUrl())
return true;
}
// If custom proxy servers are configured, try to use them.
if (iecfg.lpszProxy) {
triedSomething = true;
if (checkBypassAndGetProxy(iecfg.lpszProxyBypass, iecfg.lpszProxy))
return true;
}
// If nothing was enabled, direct mode should be used.
if (!triedSomething)
return true;
// Something was tried but failed. Perhaps use direct mode, but only as fallback.
return false;
}
LPCWSTR proxyAddr() const
{
return (m_out.empty() ? NULL : m_out.c_str());
}
};
ProxyChecker::StaticContext ProxyChecker::s_context;
}
/**
* Pass in an URL, get HTTP proxy "hostname:port" back, or false return value to not use any proxy.
*/
bool getProxyForUrl(LPCWSTR url, std::wstring& out)
{
detail::ProxyChecker pc;
if (!pc.process(url) || !pc.proxyAddr())
return false;
out = pc.proxyAddr();
return true;
}
//=============================================================================
#include <vector>
#include <CommCtrl.h>
#pragma comment(lib, "comctl32.lib")
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
enum {
IDC_CONFIG_REFRESH = 100,
IDC_CONFIG_AUTO,
IDC_CONFIG_SCRIPT,
IDC_CONFIG_SCRIPT_ADDR,
IDC_CONFIG_CUSTOM,
IDC_CONFIG_CUSTOM_ADDR,
IDC_CONFIG_CUSTOM_BYPASS,
IDC_INPUT,
IDC_RESOLVE,
IDC_OUTPUT,
IDC_OUTPUT_CLEAR
};
INT_PTR WINAPI dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg) {
case WM_INITDIALOG:
SetFocus(GetDlgItem(hwndDlg, IDC_INPUT));
SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_CONFIG_REFRESH, BN_CLICKED), 0);
return FALSE;
case WM_DESTROY:
PostQuitMessage(0);
return TRUE;
case WM_CLOSE:
DestroyWindow(hwndDlg);
return TRUE;
case WM_COMMAND:
switch(LOWORD(wParam)) {
case IDC_CONFIG_REFRESH: {
detail::WinHttpCurrentUserIeProxyConfig iecfg;
if (!WinHttpGetIEProxyConfigForCurrentUser(&iecfg)) {
MessageBox(hwndDlg, L"WinHttpGetIEProxyConfigForCurrentUser() failed.", L"Error", MB_ICONERROR | MB_OK);
} else {
CheckDlgButton(hwndDlg, IDC_CONFIG_AUTO, iecfg.fAutoDetect ? BST_CHECKED : BST_UNCHECKED);
CheckDlgButton(hwndDlg, IDC_CONFIG_SCRIPT, iecfg.lpszAutoConfigUrl ? BST_CHECKED : BST_UNCHECKED);
SetDlgItemText(hwndDlg, IDC_CONFIG_SCRIPT_ADDR, iecfg.lpszAutoConfigUrl ? iecfg.lpszAutoConfigUrl : L"(null)");
CheckDlgButton(hwndDlg, IDC_CONFIG_CUSTOM, iecfg.lpszProxy ? BST_CHECKED : BST_UNCHECKED);
SetDlgItemText(hwndDlg, IDC_CONFIG_CUSTOM_ADDR, iecfg.lpszProxy ? iecfg.lpszProxy : L"(null)");
SetDlgItemText(hwndDlg, IDC_CONFIG_CUSTOM_BYPASS, iecfg.lpszProxyBypass ? iecfg.lpszProxyBypass : L"(null)");
}
break;
}
case IDC_RESOLVE: {
SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_CONFIG_REFRESH, BN_CLICKED), 0);
WCHAR url[1024];
GetDlgItemText(hwndDlg, IDC_INPUT, url, _countof(url));
wchar_t* context;
for (wchar_t* tok = wcstok_s(url, L" \t\r\n", &context); tok; tok = wcstok_s(NULL, L" \t\r\n", &context)) {
std::wstring proxy;
bool success;
{
detail::ProxyChecker pc;
success = pc.process(tok);
if (success && pc.proxyAddr())
proxy = pc.proxyAddr();
}
WCHAR tmp[1024];
swprintf_s(tmp, L"%s -> %s\n", tok, success ? (proxy.empty() ? L"<direct>" : proxy.c_str()) : L"<failed>");
HWND hwndOutput = GetDlgItem(hwndDlg, IDC_OUTPUT);
int pos = GetWindowTextLength(hwndOutput);
SendMessage(hwndOutput, EM_SETSEL, pos, pos);
SendMessage(hwndOutput, EM_REPLACESEL, FALSE, reinterpret_cast<LPARAM>(tmp));
}
break;
}
case IDC_OUTPUT_CLEAR:
SetDlgItemText(hwndDlg, IDC_OUTPUT, L"");
break;
}
return TRUE;
}
return FALSE;
}
class DialogTemplate {
public:
enum Classes {
BUTTON = 0x0080,
EDIT = 0x0081,
STATIC = 0x0082,
LISTBOX = 0x0083,
SCROLLBAR = 0x0084,
COMBOBOX = 0x0085
};
public:
DialogTemplate(DWORD dwStyle, DWORD dwExtendedStyle, short x, short y, short cx, short cy, LPCWSTR lpszTitle)
{
size_t len = wcslen(lpszTitle) + 1;
m_buffer.resize(sizeof(DLGTEMPLATE) + 2 + 2 + 2 * len);
DLGTEMPLATE* dlg = reinterpret_cast<DLGTEMPLATE*>(&m_buffer[0]);
dlg->style = dwStyle & ~DS_SETFONT;
dlg->dwExtendedStyle = dwExtendedStyle;
dlg->cdit = 0;
dlg->x = x;
dlg->y = y;
dlg->cx = cx;
dlg->cy = cy;
WORD* meta = reinterpret_cast<WORD*>(&m_buffer[sizeof(DLGTEMPLATE)]);
meta[0] = 0x0000;
meta[1] = 0x0000;
memcpy(meta + 2, lpszTitle, 2 * len);
}
void setFont(WORD wFontSize, LPCWSTR lpszFontFace)
{
size_t ofs = sizeof(DLGTEMPLATE) + 2 + 2 + 2 * (wcslen(reinterpret_cast<WCHAR*>(&m_buffer[sizeof(DLGTEMPLATE) + 2 + 2])) + 1);
size_t len = wcslen(lpszFontFace) + 1;
m_buffer.resize(ofs + 2 + 2 * len);
DLGTEMPLATE* dlg = reinterpret_cast<DLGTEMPLATE*>(&m_buffer[0]);
dlg->style |= DS_SETFONT;
dlg->cdit = 0;
WORD* meta = reinterpret_cast<WORD*>(&m_buffer[ofs]);
meta[0] = wFontSize;
memcpy(meta + 1, lpszFontFace, 2 * len);
}
void addControl(WORD wId, WORD wClassId, short x, short y, short cx, short cy, DWORD dwStyle, DWORD dwExtendedStyle, LPCWSTR lpszTitle)
{
size_t pad = (m_buffer.size() & 3) ? 2 : 0;
size_t ofs = m_buffer.size() + pad;
size_t len = wcslen(lpszTitle) + 1;
m_buffer.resize(ofs + sizeof(DLGITEMTEMPLATE) + 2 + 2 + 2 * len + 2);
DLGTEMPLATE* dlg = reinterpret_cast<DLGTEMPLATE*>(&m_buffer[0]);
dlg->cdit++;
DLGITEMTEMPLATE* item = reinterpret_cast<DLGITEMTEMPLATE*>(&m_buffer[ofs]);
item->style = dwStyle;
item->dwExtendedStyle = dwExtendedStyle;
item->x = x;
item->y = y;
item->cx = cx;
item->cy = cy;
item->id = wId;
WORD* meta = reinterpret_cast<WORD*>(&m_buffer[ofs + sizeof(DLGITEMTEMPLATE)]);
meta[0] = 0xFFFF;
meta[1] = wClassId;
memcpy(meta + 2, lpszTitle, 2 * len);
}
void addStatic(WORD wId, short x, short y, short cx, short cy, DWORD dwStyle, LPCWSTR lpszTitle)
{
addControl(wId, STATIC, x, y, cx, cy, dwStyle | WS_GROUP | WS_VISIBLE, 0, lpszTitle);
}
void addButton(WORD wId, short x, short y, short cx, short cy, DWORD dwStyle, LPCWSTR lpszTitle)
{
addControl(wId, BUTTON, x, y, cx, cy, dwStyle | WS_TABSTOP | WS_VISIBLE, 0, lpszTitle);
}
void addEdit(WORD wId, short x, short y, short cx, short cy, DWORD dwStyle, LPCWSTR lpszTitle)
{
addControl(wId, EDIT, x, y, cx, cy, dwStyle | WS_BORDER | WS_TABSTOP | WS_VISIBLE, 0, lpszTitle);
}
operator DLGTEMPLATE*()
{
return reinterpret_cast<DLGTEMPLATE*>(&m_buffer[0]);
}
protected:
std::vector<BYTE> m_buffer;
};
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
INITCOMMONCONTROLSEX icc;
icc.dwSize = sizeof(icc);
icc.dwICC = ICC_STANDARD_CLASSES;
if (!InitCommonControlsEx(&icc))
return -1;
HWND hMainWnd;
{
DialogTemplate dlg(DS_CENTER | DS_SHELLFONT | WS_CAPTION | WS_SYSMENU, WS_EX_WINDOWEDGE, 0, 0, 304, 319, L"Proxy Test");
dlg.setFont(8, L"MS Shell Dlg");
dlg.addControl(-1, DialogTemplate::BUTTON, 7, 7, 290, 115, BS_GROUPBOX | WS_VISIBLE, 0, L"Proxy settings for the current user");
dlg.addButton (IDC_CONFIG_REFRESH, 238, 4, 50, 14, BS_PUSHBUTTON, L"Refresh");
dlg.addButton (IDC_CONFIG_AUTO, 17, 21, 270, 8, BS_CHECKBOX, L"Automatically detect settings");
dlg.addButton (IDC_CONFIG_SCRIPT, 17, 35, 270, 8, BS_CHECKBOX, L"Use automatic configuration script:");
dlg.addEdit (IDC_CONFIG_SCRIPT_ADDR, 27, 45, 260, 13, ES_AUTOHSCROLL | ES_READONLY, L"");
dlg.addButton (IDC_CONFIG_CUSTOM, 17, 65, 270, 8, BS_CHECKBOX, L"Use custom proxy server(s):");
dlg.addEdit (IDC_CONFIG_CUSTOM_ADDR, 27, 75, 260, 13, ES_AUTOHSCROLL | ES_READONLY, L"");
dlg.addStatic (-1, 27, 90, 260, 8, SS_LEFT, L"Exceptions:");
dlg.addEdit (IDC_CONFIG_CUSTOM_BYPASS, 27, 100, 260, 13, ES_AUTOHSCROLL | ES_READONLY, L"");
dlg.addStatic (-1, 7, 132, 280, 8, SS_LEFT, L"URLs to test (separated by whitespace):");
dlg.addEdit (IDC_INPUT, 7, 142, 225, 70, WS_VSCROLL | ES_MULTILINE | ES_WANTRETURN,
L"http://localhost/\r\n"
L"http://127.0.0.1/\r\n"
L"http://fud.cz/\r\n"
L"http://pluto/something.mp3\r\n"
L"http://foo.bar:secret.pass@long.domain.fud.cz/blah/blah/blah?meh=wtf@url&from:hell\r\n");
dlg.addButton (IDC_RESOLVE, 237, 142, 50, 14, BS_DEFPUSHBUTTON, L"Resolve");
dlg.addStatic (-1, 7, 216, 225, 8, SS_LEFT, L"Output:");
dlg.addEdit (IDC_OUTPUT, 7, 227, 225, 85, WS_VSCROLL | ES_MULTILINE | ES_READONLY, L"");
dlg.addButton (IDC_OUTPUT_CLEAR, 238, 227, 50, 14, BS_PUSHBUTTON, L"Clear");
hMainWnd = CreateDialogIndirect(GetModuleHandle(NULL), dlg, NULL, &dialogProc);
}
if (!hMainWnd)
return -1;
ShowWindow(hMainWnd, nCmdShow);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0) {
if (IsDialogMessage(hMainWnd, &msg))
continue;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment