//
// From MSDN.  License as per MSDN docs.
//
// NOTE: When building, link explicitly with the following libraries:
//                               wininet.lib
//                               ws2_32.lib
//                               urlmon.lib

//
// Note: This requires an up to date SDK - the one that ships with VC.NET is a little too old since
// these APIs were only made public recently even though they existed in Win95.
//
// FIXME: Could probably work around this with a cut/paste of the relevant bits

#define WIN32_LEAN_AND_MEAN	1
#include <windows.h>
#include "wininet.h"
#include <urlmon.h>
#include <stdio.h>
#include <winsock2.h>
#include <ws2tcpip.h>

#include "../cvs_string.h"

/* ==================================================================
                            HELPER FUNCTIONS
   ================================================================== */

/////////////////////////////////////////////////////////////////////
//  ResolveHostName                               (a helper function)
/////////////////////////////////////////////////////////////////////
DWORD __stdcall ResolveHostName( LPSTR   lpszHostName,
                                 LPSTR   lpszIPAddress,
                                 LPDWORD lpdwIPAddressSize )
{
  DWORD dwIPAddressSize;
  addrinfo Hints;
  LPADDRINFO lpAddrInfo;
  LPADDRINFO IPv4Only;
  DWORD error;

  // Figure out first whether to resolve a name or an address literal.
  // If getaddrinfo( ) with the AI_NUMERICHOST flag succeeds, then
  // lpszHostName points to a string representation of an IPv4 or IPv6 
  // address. Otherwise, getaddrinfo( ) should return EAI_NONAME.
  ZeroMemory( &Hints, sizeof(addrinfo) );
  Hints.ai_flags    = AI_NUMERICHOST;  // Only check for address literals.
  Hints.ai_family   = PF_UNSPEC;       // Accept any protocol family.
  Hints.ai_socktype = SOCK_STREAM;     // Constrain results to stream socket.
  Hints.ai_protocol = IPPROTO_TCP;     // Constrain results to TCP.

  error = getaddrinfo( lpszHostName, NULL, &Hints, &lpAddrInfo );
  if( error != EAI_NONAME )
  {
    if( error != 0 )
    {
      error = ( error == EAI_MEMORY ) ?
              ERROR_NOT_ENOUGH_MEMORY : ERROR_INTERNET_NAME_NOT_RESOLVED;
      goto quit;
    }
    freeaddrinfo( lpAddrInfo );

    // An IP address (either v4 or v6) was passed in, so if there is 
    // room in the lpszIPAddress buffer, copy it back out and return.
    dwIPAddressSize = lstrlenA( lpszHostName );

    if( ( *lpdwIPAddressSize < dwIPAddressSize ) ||
        ( lpszIPAddress == NULL ) )
    {
      *lpdwIPAddressSize = dwIPAddressSize + 1;
      error = ERROR_INSUFFICIENT_BUFFER;
      goto quit;
    }
    lstrcpyA( lpszIPAddress, lpszHostName );
    goto quit;
  }

  // Call getaddrinfo( ) again, this time with no flag set.
  Hints.ai_flags = 0;
  error = getaddrinfo( lpszHostName, NULL, &Hints, &lpAddrInfo );
  if( error != 0 )
  {
    error = ( error == EAI_MEMORY ) ?
            ERROR_NOT_ENOUGH_MEMORY : ERROR_INTERNET_NAME_NOT_RESOLVED;
    goto quit;
  }

  // Convert the IP address in addrinfo into a string.
  // (the following code only handles IPv4 addresses)
  IPv4Only = lpAddrInfo;
  while( IPv4Only->ai_family != AF_INET )
  {
    IPv4Only = IPv4Only->ai_next;
    if( IPv4Only == NULL )
    {
      error = ERROR_INTERNET_NAME_NOT_RESOLVED;
      goto quit;
    }
  }
  error = getnameinfo( IPv4Only->ai_addr, (socklen_t)IPv4Only->ai_addrlen, lpszIPAddress,
                       *lpdwIPAddressSize, NULL, 0, NI_NUMERICHOST );
  if( error != 0 )
    error = ERROR_INTERNET_NAME_NOT_RESOLVED;

quit:
  return( error );
}


/////////////////////////////////////////////////////////////////////
//  IsResolvable                                  (a helper function)
/////////////////////////////////////////////////////////////////////
BOOL __stdcall IsResolvable( LPSTR lpszHost )
{
  char szDummy[255];
  DWORD dwDummySize = sizeof(szDummy) - 1;

  if( ResolveHostName( lpszHost, szDummy, &dwDummySize ) )
    return( FALSE );
  return TRUE;
}


/////////////////////////////////////////////////////////////////////
//  GetIPAddress                                  (a helper function)
/////////////////////////////////////////////////////////////////////
DWORD __stdcall GetIPAddress( LPSTR   lpszIPAddress,
                              LPDWORD lpdwIPAddressSize )
{
  char szHostBuffer[255];

  if( gethostname( szHostBuffer, sizeof(szHostBuffer) - 1 ) != ERROR_SUCCESS )
    return( ERROR_INTERNET_INTERNAL_ERROR );
  return( ResolveHostName( szHostBuffer, lpszIPAddress, lpdwIPAddressSize ) );
}


/////////////////////////////////////////////////////////////////////
//  IsInNet                                       (a helper function)
/////////////////////////////////////////////////////////////////////
BOOL __stdcall IsInNet( LPSTR lpszIPAddress, LPSTR lpszDest, LPSTR lpszMask )
{
  DWORD dwDest;
  DWORD dwIpAddr;
  DWORD dwMask;

  dwIpAddr = inet_addr( lpszIPAddress );
  dwDest   = inet_addr( lpszDest );
  dwMask   = inet_addr( lpszMask );

  if( ( dwDest == INADDR_NONE ) ||
      ( dwIpAddr == INADDR_NONE ) ||
      ( ( dwIpAddr & dwMask ) != dwDest ) )
    return( FALSE );

  return( TRUE );
}

bool GetProxyForHost(const char *url, const char *hostname, cvs::string& proxy, cvs::string& proxy_port)
{
	static bool bInitialised = false, bWorking = false;

	char WPADLocation[1024]= "";
	char TempPath[MAX_PATH];
	char TempFile[MAX_PATH];
	DWORD dwProxyHostNameLength;
	DWORD returnVal;
	HMODULE hModJS;

	// Declare and populate an AutoProxyHelperVtbl structure, and then 
	// place a pointer to it in a containing AutoProxyHelperFunctions 
	// structure, which will be passed to InternetInitializeAutoProxyDll:

	// Failure to compile this line means you have an out of date platfrom SDK.
	static const AutoProxyHelperVtbl Vtbl =
	{
		IsResolvable,
		GetIPAddress,
		ResolveHostName,
		IsInNet
	};
	static const AutoProxyHelperFunctions HelperFunctions = { &Vtbl };

	// Declare function pointers for the three autoproxy functions
	static pfnInternetInitializeAutoProxyDll    pInternetInitializeAutoProxyDll;
	static pfnInternetDeInitializeAutoProxyDll  pInternetDeInitializeAutoProxyDll;
	static pfnInternetGetProxyInfo              pInternetGetProxyInfo;

	static cvs::string static_proxy,static_port;


	if(!bInitialised)
	{
		bInitialised=true;
		bWorking=false;

		if( !( hModJS = LoadLibraryA( "jsproxy.dll" ) ) )
			return false;

		if( !( pInternetInitializeAutoProxyDll = (pfnInternetInitializeAutoProxyDll)
					GetProcAddress( hModJS, "InternetInitializeAutoProxyDll" ) ) ||
			!( pInternetDeInitializeAutoProxyDll = (pfnInternetDeInitializeAutoProxyDll)
					GetProcAddress( hModJS, "InternetDeInitializeAutoProxyDll" ) ) ||
			!( pInternetGetProxyInfo = (pfnInternetGetProxyInfo)
					GetProcAddress( hModJS, "InternetGetProxyInfo" ) ) )
			return false;

		INTERNET_PER_CONN_OPTIONA pco[] =
		{
			{ INTERNET_PER_CONN_FLAGS },
			{ INTERNET_PER_CONN_AUTOCONFIG_URL },
			{ INTERNET_PER_CONN_PROXY_SERVER },
			{ INTERNET_PER_CONN_PROXY_BYPASS },
			{ INTERNET_PER_CONN_AUTOCONFIG_SECONDARY_URL }
		};
		INTERNET_PER_CONN_OPTION_LISTA pcol = { sizeof(pco), NULL, sizeof(pco)/sizeof(pco[0]), 0, pco }; 

		DWORD dwLen=sizeof(pcol);
		if(!InternetQueryOptionA(NULL,INTERNET_OPTION_PER_CONNECTION_OPTION,&pcol,&dwLen))
			return false;

		DWORD pt = pco[0].Value.dwValue;
		if(pt&PROXY_TYPE_AUTO_DETECT)
		{
			// Autodetect proxy file.  We only want to do this once becuase it's slow
			// We do DNS first... DHCP can take up to 10 seconds to complete.
			if(!DetectAutoProxyUrl( WPADLocation, sizeof(WPADLocation),PROXY_AUTO_DETECT_TYPE_DNS_A) &&
			   !DetectAutoProxyUrl( WPADLocation, sizeof(WPADLocation), PROXY_AUTO_DETECT_TYPE_DHCP ))
			return false;
		}
		else if(pt&PROXY_TYPE_AUTO_PROXY_URL)
		{
			// Download proxy.pac from url
			strncpy(WPADLocation,pco[1].Value.pszValue,sizeof(WPADLocation));
			LocalFree((HLOCAL)pco[1].Value.pszValue);
		}
		else if(pt&PROXY_TYPE_PROXY)
		{
			// Explicitly set proxy server
			LPSTR szValue = pco[2].Value.pszValue;
			static_port="";
			char *p=strrchr(szValue,':');
			if(p)
			{
				*p='\0';
				static_port=p+1;
			}
			static_proxy=szValue;
			LocalFree((HLOCAL)szValue);
		}
		else 
		{
			return false; // Fail the detect, forcing us to go direct
		}

		if(!static_proxy.length())
		{
			GetTempPathA( sizeof(TempPath)/sizeof(TempPath[0]), TempPath );
			GetTempFileNameA( TempPath, NULL, 0, TempFile );
			URLDownloadToFileA( NULL, WPADLocation, TempFile, NULL, NULL );

			if( !( returnVal = pInternetInitializeAutoProxyDll( 0, TempFile, NULL, 
																(AutoProxyHelperFunctions*)&HelperFunctions, NULL ) ) )
				return false;

			// Delete the temporary file
			DeleteFileA( TempFile );
		}

		bWorking = true;
	}

	if(!bWorking)
		return false;

	if(static_proxy.length())
	{
		proxy = static_proxy;
		proxy_port = static_port;
	}
	else
	{
		dwProxyHostNameLength=0;
		LPSTR szProxy = NULL;
		if( !pInternetGetProxyInfo( (LPSTR) url, (DWORD)strlen(url), 
									(LPSTR) hostname, (DWORD)strlen(hostname),
									&szProxy, &dwProxyHostNameLength ) )
			return false;

		if(!szProxy || !strcmp(szProxy,"DIRECT"))
			return false; // Direct connection

		if(strncmp(szProxy,"PROXY ",6))
			return false; // This should be the only possible string (maybe "SOCKS" but that's little used)

		proxy_port="";
		char *p = strrchr(szProxy,':');
		if(p)
		{
			proxy_port=p+1;
			*p='\0';
		}
		proxy=szProxy+6;
		// Not documented - how to free this?
		LocalFree((HLOCAL)szProxy);
	}
	return true;
}


syntax highlighted by Code2HTML, v. 0.9.1