프로그래밍/Let's Share it

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

초록생선 2011. 4. 4. 18:17

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;
}