// DIMERequestSock.cpp: implementation of the WMDIMERequestSock class.
//
//////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2002 Robert Cunnings. All rights reserved.
//
// You may copy, modify, distribute or publish this code free of charge.
//
// NO WARRANTY: This software is provided AS IS, without warranty of any kind.
//
//////////////////////////////////////////////////////////////////////
//
// DIMERequestSock class - a class for HTTP protocol support. 
// Uses DIME packaging, in accordance with Internet-Draft "WS-Attachments"
// (draft-nielsen-dime-soap-01 June 17, 2002).
// 
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "DIMERequestSock.h"
#include "DIMEParser.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

WMDIMERequestSock::WMDIMERequestSock(int nPort, DWORD dwTimeOut, DIMEParser *pParser) :
												WMHTTPRequestSock(nPort, dwTimeOut)
{
	m_pDIMEParser = pParser;
	m_bIsDIMERequest = false;
	m_bIsDIMEResponse = false;
}

WMDIMERequestSock::~WMDIMERequestSock()
{

}

bool WMDIMERequestSock::OnSockRead()
{
	int nNumBytesRcvd;
	if (Recv(m_szRcvBuffer, BUFFER_SIZE, &nNumBytesRcvd) == false || nNumBytesRcvd == 0)
	{
		return false;
	}
	// still reading HTTP header?
	if (m_bHeaderRead == false)
	{
		// allocate or increase buffer
		if (m_pszBuffer == NULL && nNumBytesRcvd > 0)
		{
			m_pszBuffer = (char *)malloc(nNumBytesRcvd + 1);
		}
		else
		{
			m_pszBuffer = (char *)realloc(m_pszBuffer, m_dwIndex + nNumBytesRcvd + 1);
		}
		memcpy(&m_pszBuffer[m_dwIndex], m_szRcvBuffer, nNumBytesRcvd);
		// update index
		m_dwIndex += nNumBytesRcvd;
		m_pszBuffer[m_dwIndex] = '\0';
		m_bHeaderRead = ReadHeader();
		if (m_bHeaderRead == true && m_nHTTPMajorVersion > 0)
		{
			ParseHeader();
			// content-type application/dime?
			if (m_strContentType.find("application/dime") == string::npos)
			{
				// done for now
				return true;
			}
			// this is a DIME response
			m_bIsDIMEResponse = true;
			// now intialize DIME parser for deserialization of entity body
			try
			{
				m_pDIMEParser->DeserializeInit();
			}
			catch (DIMEParserException *e)
			{
				delete e;
				return false;
			}
			// any body content to process?
			if (m_dwIndex > 0 && m_pszBuffer != NULL)
			{
				// feed data to dime parser
				try
				{
					m_pDIMEParser->Deserialize(m_pszBuffer, m_dwIndex);
				}
				catch (DIMEParserException *e)
				{
					delete e;
					return false;
				}
				free(m_pszBuffer);
				m_pszBuffer = NULL;
				m_dwIndex = 0;
			}
		}
	}
	else
	{
		// no, reading body
		if (m_bIsDIMEResponse == false)
		{
			// allocate or increase buffer
			if (m_pszBuffer == NULL && nNumBytesRcvd > 0)
				m_pszBuffer = (char *)malloc(nNumBytesRcvd + 1);
			else
				m_pszBuffer = (char *)realloc(m_pszBuffer, m_dwIndex + nNumBytesRcvd + 1);

			memcpy(&m_pszBuffer[m_dwIndex], m_szRcvBuffer, nNumBytesRcvd);

			// update index
			m_dwIndex += nNumBytesRcvd;
			m_pszBuffer[m_dwIndex] = '\0';
			return true;
		}
		// this is a DIME response
		try
		{
			// feed data to dime parser
			m_pDIMEParser->Deserialize(m_szRcvBuffer, nNumBytesRcvd);
		}
		catch (DIMEParserException *e)
		{
			delete e;
			return false;
		}
	}
	return true;
}

bool WMDIMERequestSock::OnSockWrite()
{
	// HTTP header
	if (m_bHeaderSent == false)
	{
		string strTemp = m_strMethod;
		strTemp += " ";
		strTemp += m_strURI;
		strTemp += " HTTP/";
		char szVersion[16];
		_snprintf(szVersion, 15, "%d.%d\r\n", m_nHTTPMajorVersion, m_nHTTPMinorVersion);
        szVersion[15] = 0;
		strTemp += szVersion;
		strTemp += m_strHeaders;
		strTemp += "\r\n";
		int nLength = strTemp.length();
		char *pData = (char *)malloc(nLength);
		if (pData != NULL)
		{
			memcpy(pData, strTemp.c_str(), nLength);
            bool bResult = Send(pData, nLength);
			free(pData);
			if (bResult == false)
			{
				// trouble
				return false;
			}
			m_bHeaderSent = true;
		}
	}
	if (m_bIsDIMERequest == true)
	{
		// write DIME package into HTTP entity body
		char szBuffer[BUFFER_SIZE];
		unsigned long ulBytes;

		try
		{
			for (bool bContinue = true; bContinue == true; )
			{
				// one DIME record at a time
				bContinue = m_pDIMEParser->Serialize(szBuffer, BUFFER_SIZE, &ulBytes);
				if (Send(szBuffer, ulBytes) == false)
				{
					// trouble
					return false;
				}
			}
		}
		catch (DIMEParserException *e)
		{
			delete e;
			return false;
		}
	}
	else
	{
		// not DIME request, append to buffer
		int nLength = m_strBody.length();
		char *pData = (char *)malloc(nLength);
		if (pData != NULL)
		{
			memcpy(pData, m_strBody.c_str(), nLength);
			Send(pData, nLength);
			free(pData);
		}
	}
	return true;
}

void WMDIMERequestSock::OnSockClose()
{
	// DIME response?
	if (m_bIsDIMEResponse == true)
	{
		// keep reading from socket until deserialization complete 
		while (m_pDIMEParser->DeserializationFinished() == false)
		{
			if (OnSockRead() == false)
			{
				// no more, stop reading
				break;
			}
		}
	}
	else
	{
		while (true)
		{
			if (OnSockRead() == false)
			{
				// no more, stop reading
				break;
			}
		}
	}
}

bool WMDIMERequestSock::SendHTTPRequest(int nPort,
									   const string& strMethod,
									   const string& strHost,
									   const string& strRequestURI,
									   const string& strQuery,
									   const string& strHeaders,
									   DIMEParser *pParser)
{
	// check readiness
	if (IsInitialized() == false)
	{
		return false;
	}
	Clear();
	m_nHTTPMajorVersion = 1;
	m_nHTTPMinorVersion = 0;
	m_bHTTPVersionFound = false;

	// fill out address structure
	SOCKADDR_IN addr_in;
	addr_in.sin_port = htons(nPort > 0 ? nPort : m_nPort);
	addr_in.sin_family = AF_INET;

	WMHostEnt *pWMHostent = NULL;
	HostEntCache::iterator i = m_mapHostEnts.find(strHost);
	if (i == m_mapHostEnts.end())
	{
		// not found
		LPHOSTENT pHostent = gethostbyname(strHost.c_str());
		// any luck?
		if (pHostent == NULL)
		{
			m_nLastError = WSAEADDRNOTAVAIL;
			return false;
		}
		// create new entry
		pWMHostent = new WMHostEnt(pHostent);
		m_mapHostEnts.insert(HostEntCache::value_type(strHost, pWMHostent));
	}
	else
	{
		// found in cache
		pWMHostent = (*i).second;
	}
	// fill out address member in address structure
	addr_in.sin_addr = *((LPIN_ADDR)*pWMHostent->h_addr_list);
	// copy parameters	
	m_strMethod = strMethod;
	m_strURI = URIEscape(strRequestURI);
	if (strQuery.length() > 0)
	{
		m_strURI += '?';
		m_strURI += URIEscape(strQuery);
	}
	m_strHeaders = strHeaders;
	m_bHeaderRead = false;

	m_bHeaderSent = false;
	m_bIsDIMERequest = true;
	m_bIsDIMEResponse = false;
	m_pDIMEParser = pParser;
	// now connect
	return Connect(&addr_in);
}

bool WMDIMERequestSock::SendHTTPRequest(int nPort,
									   const string& strMethod,
									   const string& strHost,
									   const string& strRequestURI,
									   const string& strQuery,
									   const string& strHeaders,
									   const string& strBody,
									   DIMEParser *pParser)
{
	m_bHeaderSent = false;
	m_bIsDIMERequest = false;
	m_bIsDIMEResponse = false;
	m_pDIMEParser = pParser;

	return WMHTTPRequestSock::SendHTTPRequest( nPort,
											   strMethod,
											   strHost,
											   strRequestURI,
											   strQuery,
											   strHeaders,
											   strBody);
}
