2011. 4. 4. 18:17

Proxy 서버 구하기 (자동 구성 스크립트, pac)

proxy 환경에서,
MFC 함수 (CHttpFile)등 이외 curl library 같은 것을 사용하면, 시스템의 proxy 설정을 직접 해야 합니다.
즉, MFC 함수등에서는 따로 proxy등을 구하지 않아도 되지만,
그 위외의 경우에서는 직접 proxy를 구해 세팅을 해줘야 합니다.

그래서, 아래와 같은 함수를 공유합니다.

DWORD
GetProxyAddressFromSystem
IN LPCTSTR lpszUrl
IN LPCTSTR lpszAgentNameForQuery
OUT CStringArray& strArrayProxyAddr

lpszUrl : 구하려고 하는 Url. 예) http://www.naver.com (반드시 protocol이 필요함)
lpszAgentNameForQuery : Internet Session을 열때 필요한 Agent 이름. 예) MyApp.ver1
strArrayProxyAddr : 구해진 proxy 정보 list. 예) "111.222.111.222:1212", "222.111.222.111:2323", ...
리턴값 : ERROR_SUCCESS : 해당 url에 대해 proxy 서버가 필요함. strArrayProxyAddr 참고하기 바람.
이외는 오류값.

공유한 함수는 MFC 함수(CString)등을 사용하고 있는데, 좀더 참고하면 Plain C/C++ 등으로 수정 가능하리라 봅니다.

그리고, httpfile 함수군을 쓰니 (즉, #include <httpfile.h>)하니 오류가 생겨...
httpfile.dll을 동적으로 Load하는 방식을 사용했으니 참고 바랍니다.
(view plain 하시면 좀더 편하게 확인할 수 있습니다.)

(참고: WinHttpGetProxyForUrl를 사용하는데, 몇몇 옛날 OS(XPSP0, Win2K)에서는 지원여부가 명확하지 않습니다.)

#include 
DWORD GetProxyAddressFromSystem(IN LPCTSTR lpszUrl, IN LPCTSTR lpszAgentNameForQuery, OUT CStringArray& strArrayProxyAddr)
{
	#ifndef WINHTTP_AUTOPROXY_OPTIONS
		typedef struct
		{
			DWORD   dwFlags;
			DWORD   dwAutoDetectFlags;
			LPCWSTR lpszAutoConfigUrl;
			LPVOID  lpvReserved;
			DWORD   dwReserved;
			BOOL    fAutoLogonIfChallenged;
		}
		WINHTTP_AUTOPROXY_OPTIONS;
	#endif

	#ifndef WINHTTP_PROXY_INFO
		typedef struct
		{
			DWORD  dwAccessType;      // see WINHTTP_ACCESS_* types below
			LPWSTR lpszProxy;         // proxy server list
			LPWSTR lpszProxyBypass;   // proxy bypass list
		}
		WINHTTP_PROXY_INFO, * LPWINHTTP_PROXY_INFO;
	#endif

	typedef BOOL		(WINAPI *LPFN_WinHttpGetProxyForUrl)(HINTERNET hSession, LPCWSTR lpcwszUrl, WINHTTP_AUTOPROXY_OPTIONS* pAutoProxyOptions, WINHTTP_PROXY_INFO* pProxyInfo);
	typedef HINTERNET	(WINAPI *LPFN_WinHttpOpen)			(LPCWSTR pwszUserAgent, DWORD dwAccessType, LPCWSTR pwszProxyName, LPCWSTR pwszProxyBypass, DWORD dwFlags);
	typedef BOOL		(WINAPI *LPFN_WinHttpCloseHandle)	(HINTERNET hInternet);

	DWORD							dwRtnValue					= ERROR_SUCCESS;
	INT								nFind						= 0;
	INT								nFind2						= 0;
	INTERNET_PER_CONN_OPTION_LIST	List						= {0,};
	INTERNET_PER_CONN_OPTION		Option[5]					= {0,};
	unsigned long					nSize						= sizeof(INTERNET_PER_CONN_OPTION_LIST);
	HMODULE							hWinHttp					= NULL;
	LPFN_WinHttpGetProxyForUrl		pfnWinHttpGetProxyForUrl	= NULL;
	LPFN_WinHttpOpen				pfnWinHttpOpen				= NULL;
	LPFN_WinHttpCloseHandle			pfnWinHttpCloseHandle		= NULL;
	TCHAR							szTmp[MAX_PATH]				= {0,};
	HINTERNET						hInternet					= NULL;
	WINHTTP_AUTOPROXY_OPTIONS		stProxyOption				= {0,};
	WINHTTP_PROXY_INFO				stProxyInfo					= {0,};
	CString							strTmp;
	CString							strDllPath;

	strArrayProxyAddr.RemoveAll();

	if ((NULL == lpszUrl) || (NULL == lpszAgentNameForQuery))
	{
		dwRtnValue = ERROR_INVALID_PARAMETER;
		ASSERT(FALSE);
		goto FINAL;
	}

	// 시스템 Proxy 설정부터 구하자.
	Option[0].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL;
	Option[1].dwOption = INTERNET_PER_CONN_AUTODISCOVERY_FLAGS;
	Option[2].dwOption = INTERNET_PER_CONN_FLAGS;
	Option[3].dwOption = INTERNET_PER_CONN_PROXY_BYPASS;
	Option[4].dwOption = INTERNET_PER_CONN_PROXY_SERVER;

	List.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);
	List.pszConnection = NULL;
	List.dwOptionCount = 5;
	List.dwOptionError = 0;
	List.pOptions = Option;

	if(!InternetQueryOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &nSize))
	{
		// getlasterror
		dwRtnValue = ::GetLastError();
		if (ERROR_SUCCESS == dwRtnValue)
		{
			dwRtnValue = ERROR_INTERNAL_ERROR;
		}
		ASSERT(FALSE);
		goto FINAL;
	}

	if (((Option[2].Value.dwValue & PROXY_TYPE_AUTO_PROXY_URL) == PROXY_TYPE_AUTO_PROXY_URL) &&
		(NULL != Option[0].Value.pszValue))
	{
		// 자동 구성 스크립스 사용
		::GetSystemDirectory(szTmp, MAX_PATH);
		if (TEXT('\0') == szTmp[0])
		{
			dwRtnValue = ERROR_INTERNAL_ERROR;
			ASSERT(FALSE);
			goto FINAL;
		}
		strDllPath = szTmp;
		strDllPath.TrimRight(TEXT("\\"));
		strDllPath += TEXT("\\winhttp.dll");

		hWinHttp = ::LoadLibrary(strDllPath);
		if (NULL == hWinHttp)
		{
			dwRtnValue = ::GetLastError();
			ASSERT(FALSE);
			goto FINAL;
		}

		pfnWinHttpGetProxyForUrl = (LPFN_WinHttpGetProxyForUrl)	::GetProcAddress(hWinHttp, "WinHttpGetProxyForUrl");
		pfnWinHttpOpen			 = (LPFN_WinHttpOpen)			::GetProcAddress(hWinHttp, "WinHttpOpen");
		pfnWinHttpCloseHandle	 = (LPFN_WinHttpCloseHandle)	::GetProcAddress(hWinHttp, "WinHttpCloseHandle");
		if ((NULL == pfnWinHttpGetProxyForUrl)	||
			(NULL == pfnWinHttpOpen)			||
			(NULL == pfnWinHttpCloseHandle))
		{
			dwRtnValue = ERROR_MOD_NOT_FOUND;
			ASSERT(FALSE);
			goto FINAL;
		}

		hInternet = (*pfnWinHttpOpen)(lpszAgentNameForQuery, 
									  0,	// WINHTTP_ACCESS_TYPE_DEFAULT_PROXY
									  NULL,	// WINHTTP_NO_PROXY_NAME
									  NULL,	// WINHTTP_NO_PROXY_BYPASS
									  0);
		if (NULL == hInternet)
		{
			dwRtnValue = ::GetLastError();
			if (ERROR_SUCCESS == dwRtnValue)
			{
				dwRtnValue = ERROR_INTERNAL_ERROR;
			}
			goto FINAL;
		}

		// 전달할 Option을 구한다.
		stProxyOption.dwFlags			= 0x00000002; // WINHTTP_AUTOPROXY_CONFIG_URL
		stProxyOption.lpszAutoConfigUrl	= Option[0].Value.pszValue;

		// 프록시 설정을 구한다.
		if (FALSE == (*pfnWinHttpGetProxyForUrl)(hInternet, 
												 lpszUrl, 
												 &stProxyOption, 
												 &stProxyInfo))
		{
			dwRtnValue = ::GetLastError();
			if (ERROR_SUCCESS == dwRtnValue)
			{
				dwRtnValue = ERROR_INTERNAL_ERROR;
			}
			goto FINAL;
		}

		if (NULL != stProxyInfo.lpszProxy)
		{
			// 여기까지 왔다면 성공
			strTmp = stProxyInfo.lpszProxy;
			strTmp.TrimLeft(TEXT(" ;"));
			strTmp.TrimRight(TEXT(" ;"));
			for (;;)
			{
				nFind = strTmp.Find(TEXT(";"), nFind2);
				if (-1 == nFind)
				{
					strArrayProxyAddr.Add(strTmp.Mid(nFind2));
					break;
				}

				strArrayProxyAddr.Add(strTmp.Mid(nFind2, nFind - nFind2));
				nFind2 = nFind+1;
			}
		}
		else
		{
			dwRtnValue = ERROR_NOT_FOUND;
			goto FINAL;
		}
	}
	else if (((Option[2].Value.dwValue & PROXY_TYPE_PROXY) == PROXY_TYPE_PROXY) &&
			  (NULL != Option[4].Value.pszValue))
	{
		// 사용자 LAN에 프록시 서버 사용
		strArrayProxyAddr.Add(Option[4].Value.pszValue);
	}

	// 여기까지 왔다면 성공~!!!
	dwRtnValue = ERROR_SUCCESS;

	if (0 == strArrayProxyAddr.GetCount())
	{
		// 발견되지 못했음
		dwRtnValue = ERROR_NOT_FOUND;
	}

FINAL:

	if(Option[0].Value.pszValue != NULL)
	{
		GlobalFree(Option[0].Value.pszValue);
		Option[0].Value.pszValue = NULL;
	}

	if(Option[3].Value.pszValue != NULL)
	{
		GlobalFree(Option[3].Value.pszValue);
		Option[3].Value.pszValue = NULL;
	}

	if(Option[4].Value.pszValue != NULL)
	{
		GlobalFree(Option[4].Value.pszValue);
		Option[4].Value.pszValue = NULL;
	}

	if ((NULL != hInternet) &&
		(NULL != pfnWinHttpCloseHandle))
	{
		(*pfnWinHttpCloseHandle)(hInternet);
		hInternet = NULL;
	}

	if (NULL != hWinHttp)
	{
		::FreeLibrary(hWinHttp);
		hWinHttp				 = NULL;
		pfnWinHttpGetProxyForUrl = NULL;
		pfnWinHttpOpen			 = NULL;
		pfnWinHttpCloseHandle	 = NULL;
	}

	if ((NULL != stProxyInfo.lpszProxy))
	{
		GlobalFree(stProxyInfo.lpszProxy);
		stProxyInfo.lpszProxy = NULL;
	}

	if ((NULL != stProxyInfo.lpszProxyBypass))
	{
		GlobalFree(stProxyInfo.lpszProxyBypass);
		stProxyInfo.lpszProxyBypass = NULL;
	}

	return dwRtnValue;
}