/*

Miranda NG: the free IM client for Microsoft* Windows*

Copyright (�) 2012-15 Miranda NG project (http://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include "..\..\core\commonheaders.h"
#include "netlib.h"

typedef enum
{
	reqHelloGet,
	reqOldGet,
	reqOldPost,
	reqNewPost,
}
RequestType;

static int HttpGatewayReadSetResult(NetlibConnection *nlc, char *buf, int num, int peek)
{
	if (nlc->dataBufferLen == 0) return 0;

	int bytes = min(num, nlc->dataBufferLen);
	int rbytes = nlc->dataBufferLen - bytes;

	memcpy(buf, nlc->dataBuffer, bytes);
	if (!peek)
	{
		memmove(nlc->dataBuffer, nlc->dataBuffer + bytes, rbytes);
		nlc->dataBufferLen = rbytes;
	}

	return bytes;
}

void HttpGatewayRemovePacket(NetlibConnection *nlc, int pck)
{
	mir_cslock lck(nlc->csHttpSequenceNums);
	while (pck-- && nlc->pHttpProxyPacketQueue != NULL) {
		NetlibHTTPProxyPacketQueue *p = nlc->pHttpProxyPacketQueue;
		nlc->pHttpProxyPacketQueue = nlc->pHttpProxyPacketQueue->next;

		mir_free(p->dataBuffer);
		mir_free(p);
	}
}

static bool NetlibHttpGatewaySend(NetlibConnection *nlc, RequestType reqType, const char *buf, int len)
{
	NETLIBHTTPREQUEST nlhrSend = {0};
	char szUrl[512];

	nlhrSend.cbSize = sizeof(nlhrSend);
	nlhrSend.nlc = nlc;

	nlhrSend.pData = (char*)buf;
	nlhrSend.dataLength = len;

	nlhrSend.flags = NLHRF_GENERATEHOST | NLHRF_DUMPPROXY | NLHRF_SMARTAUTHHEADER | NLHRF_NOPROXY | NLHRF_REDIRECT;
	if (nlc->nlhpi.flags & NLHPIF_HTTP11) nlhrSend.flags |= NLHRF_HTTP11;

	switch (reqType)
	{
	case reqHelloGet:
		nlhrSend.requestType = REQUEST_GET;
		nlhrSend.szUrl = nlc->nlu->user.szHttpGatewayHello;
		break;

	case reqOldGet:
		nlhrSend.requestType = REQUEST_GET;
		nlhrSend.timeout = -1;
		if ((nlc->nlhpi.flags & NLHPIF_USEGETSEQUENCE) && (nlc->nlhpi.szHttpGetUrl != NULL)) {
			mir_cslock lck(nlc->csHttpSequenceNums);
			mir_snprintf(szUrl, SIZEOF(szUrl), "%s%u", nlc->nlhpi.szHttpGetUrl, nlc->nlhpi.firstGetSequence++);
			if (nlc->nlhpi.flags & NLHPIF_GETPOSTSAMESEQUENCE)
				nlc->nlhpi.firstPostSequence++;
			nlhrSend.szUrl = szUrl;
		}
		else nlhrSend.szUrl = nlc->nlhpi.szHttpGetUrl;
		break;

	case reqOldPost:
		nlhrSend.requestType = REQUEST_POST;
		if ((nlc->nlhpi.flags & NLHPIF_USEPOSTSEQUENCE) && (nlc->nlhpi.szHttpPostUrl != NULL))
		{
			mir_snprintf(szUrl, SIZEOF(szUrl), "%s%u", nlc->nlhpi.szHttpPostUrl, nlc->nlhpi.firstPostSequence);
			nlhrSend.szUrl = szUrl;
		}
		else
			nlhrSend.szUrl = nlc->nlhpi.szHttpPostUrl;
		break;

	case reqNewPost:
		nlhrSend.requestType = REQUEST_POST;
		nlhrSend.szUrl = nlc->nlhpi.szHttpPostUrl;
		break;
	}

	if (nlc->usingDirectHttpGateway)
	{
		NETLIBOPENCONNECTION nloc;
		NetlibConnFromUrl(nlhrSend.szUrl, false, nloc);

		bool sameHost = mir_strcmp(nlc->nloc.szHost, nloc.szHost) == 0 && nlc->nloc.wPort == nloc.wPort;

		if (!sameHost)
		{
			NetlibDoClose(nlc);

			mir_free((char*)nlc->nloc.szHost);
			nlc->nloc = nloc;
			if (!NetlibDoConnect(nlc))
				return false;
		}
		else
			mir_free((char*)nloc.szHost);
	}

	nlhrSend.headersCount = 3;
	nlhrSend.headers = (NETLIBHTTPHEADER*)alloca(sizeof(NETLIBHTTPHEADER) * nlhrSend.headersCount);
	nlhrSend.headers[0].szName = "User-Agent";
	nlhrSend.headers[0].szValue = nlc->nlu->user.szHttpGatewayUserAgent;
	nlhrSend.headers[1].szName = "Cache-Control";
	nlhrSend.headers[1].szValue = "no-cache, no-store ";
	nlhrSend.headers[2].szName = "Pragma";
	nlhrSend.headers[2].szValue = "no-cache";
//	nlhrSend.headers[3].szName = "Accept-Encoding";
//	nlhrSend.headers[3].szValue = "deflate, gzip";

	return NetlibHttpSendRequest((WPARAM)nlc, (LPARAM)&nlhrSend) != SOCKET_ERROR;
}

static bool NetlibHttpGatewayStdPost(NetlibConnection *nlc, int& numPackets)
{
	int np = 0, len = 0;
	char *buf;
	{
		mir_cslock lck(nlc->csHttpSequenceNums);

		NetlibHTTPProxyPacketQueue *p = nlc->pHttpProxyPacketQueue;
		while (p != NULL && np < nlc->nlhpi.combinePackets) { ++np; len += p->dataBufferLen;  p = p->next;}

		buf = (char*)alloca(len);

		numPackets = np;
		int dlen = 0;

		p = nlc->pHttpProxyPacketQueue;
		while (np--) {
			memcpy(buf + dlen, p->dataBuffer, p->dataBufferLen);
			dlen += p->dataBufferLen;
			p = p->next;
		}
	}

	return NetlibHttpGatewaySend(nlc, reqNewPost, buf, len);
}

static bool NetlibHttpGatewayOscarPost(NetlibConnection *nlc, const char *buf, int len, int flags)
{
	NETLIBHTTPREQUEST *nlhrReply = NULL;
	NetlibConnection nlcSend = {0};

	nlcSend.handleType = NLH_CONNECTION;
	nlcSend.nlu = nlc->nlu;
	nlcSend.nlhpi = nlc->nlhpi;
	nlcSend.s = nlc->s2;
	nlcSend.usingHttpGateway = nlc->usingHttpGateway;
	nlcSend.szProxyServer = nlc->szProxyServer;
	nlcSend.wProxyPort = nlc->wProxyPort;
	nlcSend.proxyType = nlc->proxyType;

	if (!NetlibReconnect(&nlcSend)) return false;
	nlc->s2 = nlcSend.s;

	nlcSend.hOkToCloseEvent	 = CreateEvent(NULL, TRUE, TRUE, NULL);
	NetlibInitializeNestedCS(&nlcSend.ncsRecv);
	NetlibInitializeNestedCS(&nlcSend.ncsSend);

	bool res = NetlibHttpGatewaySend(&nlcSend, reqOldPost, buf, len);
	if (res) {
		NETLIBHTTPREQUEST *nlhrReply = NetlibHttpRecv(&nlcSend, flags | MSG_RAW | MSG_DUMPPROXY, MSG_RAW | MSG_DUMPPROXY);
		if (nlhrReply != NULL) {
			if (nlhrReply->resultCode != 200) {
				NetlibHttpSetLastErrorUsingHttpResult(nlhrReply->resultCode);
				res = false;
			}
			NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply);
		}
		else res = false;
	}

	NetlibDeleteNestedCS(&nlcSend.ncsSend);
	NetlibDeleteNestedCS(&nlcSend.ncsRecv);
	CloseHandle(nlcSend.hOkToCloseEvent);

	nlc->s2 = nlcSend.s;
	mir_free((char*)nlcSend.nloc.szHost);

	mir_cslock lck(nlc->csHttpSequenceNums);
	nlc->nlhpi.firstPostSequence++;
	if (nlc->nlhpi.flags & NLHPIF_GETPOSTSAMESEQUENCE)
		nlc->nlhpi.firstGetSequence++;

	return res;
}

 int NetlibHttpGatewayPost(NetlibConnection *nlc, const char *buf, int len, int flags)
{
	if (nlc->nlhpi.szHttpGetUrl != NULL)
		return NetlibHttpGatewayOscarPost(nlc, buf, len, flags) ? len : SOCKET_ERROR;

	/*
	 * Gena01 - many changes here, do compare against the other version.
	 *
	 * Change #1: simplify to use similar code to GET
	 * Change #2: we need to allow to parse POST reply if szHttpGetUrl is NULL
	 * Change #3: Keep connection open if we need to.
	 *
	 * Impact: NONE! Since currently miranda doesn't allow szHttpGetUrl to be NULL, it will not connect
	 *         with the new plugins that use this code.
	 */

	NetlibHTTPProxyPacketQueue *p = (NetlibHTTPProxyPacketQueue*)mir_alloc(sizeof(struct NetlibHTTPProxyPacketQueue));
	p->dataBuffer = (PBYTE)mir_alloc(len);
	memcpy(p->dataBuffer, buf, len);
	p->dataBufferLen = len;
	p->next = NULL;

	/*
	 * Now check to see where to insert this in our queue
	 */

	mir_cslock lck(nlc->csHttpSequenceNums);
	if (nlc->pHttpProxyPacketQueue == NULL)
		nlc->pHttpProxyPacketQueue = p;
	else {
		NetlibHTTPProxyPacketQueue *t = nlc->pHttpProxyPacketQueue;
		while (t->next != NULL)
			t = t->next;
		t->next = p;
	}

	/*
	 * Gena01 - fake a Send!! tell 'em all is ok. We catch errors in Recv.
	 */
	return len;
}

#define NETLIBHTTP_RETRYCOUNT   3
#define NETLIBHTTP_RETRYTIMEOUT 2000

int NetlibHttpGatewayRecv(NetlibConnection *nlc, char *buf, int len, int flags)
{
	bool peek = (flags & MSG_PEEK) != 0;

	if (nlc->dataBufferLen != 0 && (!peek || nlc->dataBufferLen >= len))
	{
		return HttpGatewayReadSetResult(nlc, buf, len, peek);
	}

	for (int retryCount = 0; retryCount < NETLIBHTTP_RETRYCOUNT;)
	{
		if (nlc->nlhpi.szHttpGetUrl == NULL && retryCount == 0)
		{
			if (nlc->pollingTimeout == 0) nlc->pollingTimeout = 30;

			/* We Need to sleep/wait for the data to send before we do receive */
			for (int pollCount = nlc->pollingTimeout; pollCount--;)
			{
				if (nlc->pHttpProxyPacketQueue != NULL && GetTickCount() - nlc->lastPost > 1000)
					break;

				if (nlc->termRequested || (SleepEx(1000, TRUE) && Miranda_Terminated()))
					return SOCKET_ERROR;
			}

			nlc->lastPost = GetTickCount();
			if (nlc->pHttpProxyPacketQueue == NULL && nlc->nlu->user.pfnHttpGatewayWrapSend != NULL)
			{
				if (nlc->nlu->user.pfnHttpGatewayWrapSend(nlc, (PBYTE)"", 0, MSG_NOHTTPGATEWAYWRAP, NetlibSend) == SOCKET_ERROR)
					return SOCKET_ERROR;
			}
		}

		int numPackets = 0;
		if (nlc->nlhpi.szHttpGetUrl)
		{
			if (!NetlibHttpGatewaySend(nlc, reqOldGet, NULL, 0))
			{
				if (GetLastError() == ERROR_ACCESS_DENIED || nlc->termRequested)
					break;

				++retryCount;
				continue;
			}
		}
		else
		{
			if (!NetlibHttpGatewayStdPost(nlc, numPackets))
			{
				if (GetLastError() == ERROR_ACCESS_DENIED || nlc->termRequested)
					break;

				++retryCount;
				continue;
			}
		}
		NETLIBHTTPREQUEST *nlhrReply = NetlibHttpRecv(nlc, flags | MSG_RAW | MSG_DUMPPROXY, MSG_RAW | MSG_DUMPPROXY);
		if (nlhrReply == NULL) return SOCKET_ERROR;

		if (nlc->nlu->user.pfnHttpGatewayUnwrapRecv && !(flags & MSG_NOHTTPGATEWAYWRAP))
		{
			nlhrReply->pData = (char*)nlc->nlu->user.pfnHttpGatewayUnwrapRecv(nlhrReply,
				(PBYTE)nlhrReply->pData, nlhrReply->dataLength, &nlhrReply->dataLength, mir_realloc);
		}

		if (nlhrReply->resultCode >= 300)
		{
			int resultCode = nlhrReply->resultCode;
			NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply);

			if (nlc->nlhpi.szHttpGetUrl && resultCode != 404)
			{
				NetlibLogf(nlc->nlu, "Error received from proxy, retrying");
				continue;
			}
			else
			{
				NetlibLogf(nlc->nlu, "Error received from proxy, retry attempts exceeded (%u)", retryCount);
				SetLastError(ERROR_GEN_FAILURE);
				return SOCKET_ERROR;
			}
		}
		else
		{
			retryCount = 0;
			HttpGatewayRemovePacket(nlc, numPackets);
		}

		if (nlhrReply->dataLength)
		{
			if (peek)
			{
				int rbytes = nlc->dataBufferLen + nlhrReply->dataLength;

				nlc->dataBuffer = (PBYTE)mir_realloc(nlc->dataBuffer, rbytes);
				memcpy(nlc->dataBuffer + nlc->dataBufferLen, nlhrReply->pData, nlhrReply->dataLength);
				nlc->dataBufferLen = rbytes;

				NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply);

				return HttpGatewayReadSetResult(nlc, buf, len, peek);
			}
			else
			{
				int bytes = min(len, nlhrReply->dataLength);
				int rbytes = nlhrReply->dataLength - bytes;

				memcpy(buf, nlhrReply->pData, bytes);

				nlc->dataBuffer = (PBYTE)mir_realloc(nlc->dataBuffer, rbytes);
				if (rbytes) memcpy(nlc->dataBuffer, nlhrReply->pData + bytes, rbytes);
				nlc->dataBufferLen = rbytes;

				NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply);
				return bytes;
			}
		}
		else
		{
			if ((peek && nlc->dataBufferLen != 0) || nlhrReply->pData)
			{
				NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply);
				return HttpGatewayReadSetResult(nlc, buf, len, peek);
			}
		}
		NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply);
	}

	SetLastError(ERROR_GEN_FAILURE);
	return SOCKET_ERROR;
}

int NetlibInitHttpConnection(NetlibConnection *nlc, NetlibUser *nlu, NETLIBOPENCONNECTION *nloc)
{
	NETLIBHTTPREQUEST *nlhrReply = NULL;

	nlc->nlhpi.firstGetSequence = 1;
	nlc->nlhpi.firstPostSequence = 1;

	if (nlu->user.szHttpGatewayHello != NULL)
	{
		nlc->usingHttpGateway = true;
		if (NetlibHttpGatewaySend(nlc, reqHelloGet, NULL, 0))
			nlhrReply = NetlibHttpRecv(nlc, MSG_DUMPPROXY | MSG_RAW, MSG_DUMPPROXY | MSG_RAW);
		nlc->usingHttpGateway = false;
		if (nlhrReply == NULL) return 0;

		if (nlhrReply->resultCode != 200)
		{
			NetlibHttpSetLastErrorUsingHttpResult(nlhrReply->resultCode);
			NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply);
			return 0;
		}
	}
	if (!nlu->user.pfnHttpGatewayInit(nlc, nloc, nlhrReply))
	{
		NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply);
		return 0;
	}
	NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply);

	/*
	 * Gena01 - Ok, we should be able to use just POST. Needed for Yahoo, NO GET requests
	 */
	if (nlc->nlhpi.szHttpPostUrl == NULL)
	{
		SetLastError(ERROR_BAD_FORMAT);
		return 0;
	}

	nlc->usingHttpGateway = true;

	//now properly connected
	if (nlu->user.pfnHttpGatewayBegin && !nlu->user.pfnHttpGatewayBegin(nlc, nloc))
		return 0;

	return 1;
}

INT_PTR NetlibHttpGatewaySetInfo(WPARAM wParam, LPARAM lParam)
{
	NETLIBHTTPPROXYINFO *nlhpi = (NETLIBHTTPPROXYINFO*)lParam;
	NetlibConnection *nlc = (struct NetlibConnection*)wParam;

	if (GetNetlibHandleType(nlc) != NLH_CONNECTION || nlhpi == NULL  ||
		nlhpi->cbSize < (sizeof(NETLIBHTTPPROXYINFO) - sizeof(int)) ||
		nlhpi->szHttpPostUrl == NULL)
	{
		SetLastError(ERROR_INVALID_PARAMETER);
		return 0;
	}

	mir_free(nlc->nlhpi.szHttpGetUrl);
	mir_free(nlc->nlhpi.szHttpPostUrl);

	nlc->nlhpi.combinePackets = 1;
	memcpy(&nlc->nlhpi, nlhpi, min(nlhpi->cbSize, sizeof(*nlhpi)));
	if (nlc->nlhpi.combinePackets == 0) nlc->nlhpi.combinePackets = 1;

	nlc->nlhpi.szHttpGetUrl = mir_strdup(nlc->nlhpi.szHttpGetUrl);
	nlc->nlhpi.szHttpPostUrl = mir_strdup(nlc->nlhpi.szHttpPostUrl);

	return 1;
}

INT_PTR NetlibHttpSetSticky(WPARAM wParam, LPARAM lParam)
{
	NetlibUser * nu = (NetlibUser*)wParam;
	if (GetNetlibHandleType(nu) != NLH_USER) return ERROR_INVALID_PARAMETER;
	mir_free(nu->szStickyHeaders);
	nu->szStickyHeaders = mir_strdup((char*)lParam); // pointer is ours
	return 0;
}

INT_PTR NetlibHttpSetPollingTimeout(WPARAM wParam, LPARAM lParam)
{
	int oldTimeout;
	NetlibConnection *nlc = (struct NetlibConnection*)wParam;
	if (GetNetlibHandleType(nlc) != NLH_CONNECTION) return -1;
	oldTimeout = nlc->pollingTimeout;
	nlc->pollingTimeout = lParam;
	return oldTimeout;
}