// HTTPWinSocket.cpp: implementation of the WMHTTPWinSocket class.
//
//////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2000, 2001 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.
//
//////////////////////////////////////////////////////////////////////
//
// WMHTTPRequestSock class - a class for HTTP protocol support.
// Uses Winsock2, supports HTTP Version 1.0.
// 
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "windows.h"
#include "HTTPWinSocket.h"
#include "string.h"

//////////////////////////////////////////////////////////////////////
// WMHostEnt Methods
//////////////////////////////////////////////////////////////////////

WMHostEnt::WMHostEnt()
{
}

WMHostEnt::WMHostEnt(LPHOSTENT pHostEnt)
{
	int n = (int)strlen(pHostEnt->h_name);
	h_name = new char [n+1];
	strcpy(h_name, pHostEnt->h_name);

	h_aliases = NULL;
	int i = 0;
	while (pHostEnt->h_aliases[i])
	{
		i++;
	}
	if (i)
	{
		h_aliases = new char *[i + 1];
		i = 0;
		while (pHostEnt->h_aliases[i])
		{
			n = (int)strlen(pHostEnt->h_aliases[i]);
			h_aliases[i] = new char [n+1];
			strcpy(h_aliases[i], pHostEnt->h_aliases[i]);
			i++;
		}
	}
	else
	{
		h_aliases = new char *[1];
	}
	h_aliases[i] = NULL;

	h_addrtype = pHostEnt->h_addrtype;

	h_length = pHostEnt->h_length;

	h_addr_list = NULL;
	i = 0;
	while (pHostEnt->h_addr_list[i])
		i++;
	if (i)
	{
		h_addr_list = new char *[i + 1];
		i = 0;
		while (pHostEnt->h_addr_list[i])
		{
			n = (int)strlen(pHostEnt->h_addr_list[i]);
			h_addr_list[i] = new char [n+1];
			strcpy(h_addr_list[i], pHostEnt->h_addr_list[i]);
			i++;
		}
	}
	else
	{
		h_addr_list = new char *[1];
	}
	h_addr_list[i] = NULL;
}

WMHostEnt::~WMHostEnt()
{
	if (h_name)
	{
		delete h_name;
	}

	int i = 0;
	if (h_aliases)
	{
		while (h_aliases[i])
		{
			delete [] h_aliases[i];
			h_aliases[i] = NULL;
			i++;
		}
		delete [] h_aliases;
		h_aliases = NULL;
	}

	h_addrtype = 0;

	h_length = 0;

	i = 0;
	if (h_addr_list)
	{
		while (h_addr_list[i])
		{
			delete [] h_addr_list[i];
			h_addr_list[i] = NULL;
			i++;
		}
		delete [] h_addr_list;
		h_addr_list = NULL;
	}
}

//////////////////////////////////////////////////////////////////////
// WMHTTPRequestSock Methods
//////////////////////////////////////////////////////////////////////

WMHTTPRequestSock::WMHTTPRequestSock(int nPort, DWORD dwTimeOut) : WMWinSocket2(dwTimeOut)
{
	m_nPort = nPort;
	m_dwContentLength = 0;
	m_pszBuffer = NULL;
	m_dwIndex = 0;
	m_nHTTPMajorVersion = 1;
	m_nHTTPMinorVersion = 0;
}

WMHTTPRequestSock::~WMHTTPRequestSock()
{
	// empty HOSTENT map
	map<string, WMHostEnt *>::iterator i;
	for (i = m_mapHostEnts.begin(); i != m_mapHostEnts.end(); i++)
	{
		delete (*i).second;
	}
	// release buffer
	if (m_pszBuffer != NULL)
	{
		free(m_pszBuffer);
	}
}

void WMHTTPRequestSock::Clear()
{
	WMWinSocket2::Clear();
	// release buffer
	if (m_pszBuffer != NULL)
	{
		free(m_pszBuffer);
		m_pszBuffer = NULL;
	}
	m_dwContentLength = 0;
	m_dwIndex = 0;
	// clear headers
	m_listHeaders.clear();
}

bool WMHTTPRequestSock::SendHTTPRequest(int nPort,
									   const string& strMethod,
									   const string& strHost,
									   const string& strRequestURI,
									   const string& strQuery,
									   const string& strHeaders,
									   const string& strBody)
{
	// check readiness
	if (IsInitialized() == false)
		return false;
	Clear();
	m_bHeaderRead = false;
	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_strBody = strBody;
	// now connect
	return Connect(&addr_in);
}

bool WMHTTPRequestSock::OnSockRead()
{
	int nNumBytesRcvd = 0;
	if (Recv(m_szRcvBuffer, BUFFER_SIZE, &nNumBytesRcvd) == false)
		return 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';
	if (m_bHeaderRead == false)
	{
		m_bHeaderRead = ReadHeader();
		if (m_bHeaderRead == true && m_nHTTPMajorVersion > 0)
			ParseHeader();
	}
	return true;
}

bool WMHTTPRequestSock::OnSockWrite()
{
	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";
	strTemp += m_strBody;
	int nLength = (int)strTemp.length();
	char *pData = (char *)malloc(nLength);
	if (pData != NULL)
	{
		memcpy(pData, strTemp.c_str(), nLength);
		Send(pData, nLength);
		free(pData);
	}
	return true;
}

void WMHTTPRequestSock::OnSockClose()
{
}

bool WMHTTPRequestSock::ReadHeader()
{
	// check for EOL
	char *p = m_pszBuffer;
	for (DWORD i = 0; i < m_dwIndex; i++)
	{
		if (p[i] == '\n')
			break;
	}
	if (i == m_dwIndex)
		// no EOL here...
		return false;

	// what HTTP protocol version?
	if (m_bHTTPVersionFound == false)
	{
		string strStatusLine(m_pszBuffer, i - 1);
		m_bHTTPVersionFound = true;
		if (ParseStatusLine(strStatusLine) == false)
		{
			return false;
		}
		if (m_nHTTPMajorVersion == 0)
		{
			// simple response
			return true;
		}
	}
	// ok, EOL
	m_pszBuffer[m_dwIndex] = '\0';

	// break up buffer into line(s)
	char *pBOL = m_pszBuffer;
	unsigned long ulIndex = 0;
	for (char *pEOL = strchr(pBOL, '\r');
			pEOL;
			pEOL = strchr(pBOL, '\r'))
	{
		// make into null terminated string
		*pEOL = '\0';
		// add to string list
		string strLine(pBOL,strlen(pBOL));
		m_listHeaders.push_back(strLine);
		// skip '\0'
		*pEOL++;
		// skip '\n'
		*pEOL++;
		// update index
		ulIndex += (unsigned long)strLine.length() + 2;
		// point to next text
		pBOL = pEOL;
		// check for empty line signalling EOH
		// in a full request
		if (strLine.length() == 0)
		{
			break;
		}
	}

	// is there any left over?
	int nLen = m_dwIndex - ulIndex;
	if (nLen > 0)
	{
		// yes, so save it
		m_dwIndex = nLen;
		char *p = (char *)malloc(m_dwIndex + 1);
		memcpy(p, pBOL, m_dwIndex);
		free(m_pszBuffer);
		m_pszBuffer = p;
		m_pszBuffer[m_dwIndex] = '\0';
	}
	else
	{
		// reset for next chunk from client
		m_dwIndex = 0;
		free(m_pszBuffer);
		m_pszBuffer = NULL;
	}
	// look for the null line indicating EOH
	string strLastLine = m_listHeaders.back();
	if (strLastLine.length() == 0)
	{
		// get rid of null line
		m_listHeaders.pop_back();
		// deal with any continued lines
		for (list<string>::iterator iL = m_listHeaders.begin();
				iL != m_listHeaders.end();
				iL++)
		{
			// get current string
			string strCurrent = *iL;
			if (strCurrent[0] == ' ' || strCurrent[0] == '\t')
			{
				// folded line
				if (iL != m_listHeaders.begin())
				{
					// store position of current string
					list<string>::iterator iCurrent = iL;
					// get previous string position in pos
					iL--;
					// get previous string
					string strPrev = *iL;
					// append current string
					strPrev += strCurrent;
					// delete current string
					m_listHeaders.erase(iCurrent);
				}
			}
		}
		return true;
	}
	return false;
}

bool WMHTTPRequestSock::ParseHeader()
{
	// parse status line
	if (m_nHTTPMajorVersion != 0)
	{
		// status line
		m_listHeaders.pop_front();
	}
	// walk list of header lines
	for (list<string>::iterator iL = m_listHeaders.begin();
			iL != m_listHeaders.end();
			iL++)
	{
		string strTemp = *iL;
		string::size_type i;
		if ((i = strTemp.find(": ")) == string::npos)
		{
			// ill formed
			continue;
		}
		string strField = strTemp.substr(0, i);
		// see if content-length is given
		if (_stricmp("CONTENT-LENGTH", strField.c_str()) == 0)
		{
			string strContentLength = strTemp.substr(16);
			m_dwContentLength = (DWORD)atoi(strContentLength.c_str());
		}
		// see if content-type is given
		else if (_stricmp("CONTENT-TYPE", strField.c_str()) == 0)
		{
			m_strContentType = strTemp.substr(14);
		}
	}
	return true;
}


bool WMHTTPRequestSock::ParseStatusLine(const string& strStatusLine)
{
	list<string> TokenList;

	// parse the status line into a list of tokens
	char *pszBuffer = (char *)malloc(strStatusLine.length() + 1);
	strcpy(pszBuffer, strStatusLine.c_str());
	char *pBOL = pszBuffer;
	for (char *pEOL = strchr(pBOL, ' ');
		  pEOL;
		  pEOL = strchr(pBOL, ' '))
	{
		*pEOL = '\0';
		string strTempToken(pBOL, strlen(pBOL));
		*pEOL++;	// skip '\0'
		pBOL = pEOL;
		TokenList.push_back(strTempToken);
	}
	// save whatever's left as the last token
	string strTempToken(pBOL, strlen(pBOL));
	TokenList.push_back(strTempToken);
	free(pszBuffer);

	list<string>::iterator iL = TokenList.begin();

	// 1) parse the HTTP Version token
	string::size_type i;
	if ((i = (*iL).find("HTTP/")) == string::npos)
	{
		// HTTP Version 0.9
		m_nHTTPMajorVersion = 0;
		m_nHTTPMinorVersion = 9;
		// no status line
		return true;
	}
	else
	{
		char *p;
		char szBuffer[128];
		char *pszSeparator =  ".";
		strcpy(szBuffer, (*iL).substr(i + 5).c_str());
		// get major version number
		p = strtok(szBuffer, pszSeparator);
		if (p == NULL)
		{
			return false;		
		}
		m_nHTTPMajorVersion = atoi(p);
		// get minor version number
		p = strtok(NULL, pszSeparator);
		if (p == NULL)
		{
			return false;		
		}
		m_nHTTPMinorVersion = atoi(p);
	}

	// 2) parse the status
	iL++;
	if (iL == TokenList.end())
	{
		return false;		
	}
	m_strStatus = *iL;

	// 3) parse the reason phrase
	iL++;
	if (iL == TokenList.end())
	{
		return false;		
	}
	m_strReasonPhrase = *iL;
	return true;
}

int WMHTTPRequestSock::GetStatus()
{
	return (atoi(m_strStatus.c_str()));
}

const string& WMHTTPRequestSock::GetReasonPhrase()
{
	return m_strReasonPhrase;
}

WORD WMHTTPRequestSock::GetHTTPVersion()
{
	return MAKEWORD(m_nHTTPMinorVersion, m_nHTTPMajorVersion);
}

int WMHTTPRequestSock::GetContentLength()
{
	return m_dwIndex;
}

const string& WMHTTPRequestSock::GetContentType()
{
	return m_strContentType;
}

char *WMHTTPRequestSock::GetContent()
{
	return m_pszBuffer;
}

string WMHTTPRequestSock::GetHeader(const char *pszName)
{
	list<string>::iterator iL;
	for (iL = m_listHeaders.begin(); iL != m_listHeaders.end(); iL++)
	{
		string::size_type i;
		if ((i = (*iL).find(':')) == string::npos)
		{
			// ill formed
			continue;
		}
		// see if we have a case insensitive match
		if (_stricmp(pszName, (*iL).substr(0, i).c_str()) == 0)
		{
			return (*iL).substr(++i);
		}
	}
	// no luck at all
	return string("");
}

const string WMHTTPRequestSock::URIEscape(const string& strComponent)
{
	string strTemp;
	char c;
	for (unsigned int j = 0; j < strComponent.length(); j++)
	{
		c = strComponent[j];
        if (isalpha(c) || isdigit(c) ||
			// preserve '/', '&', '=', and ':'
            (strchr("/&=:", c) != NULL) ||
			// 'mark' chars RFC2396
			(strchr("-_.!~*'()", c) != NULL))
		{
			strTemp += c;
		}
		else
		{
			strTemp += '%';
			char szBuf[8];
			_snprintf(szBuf, 7, "%X", c);
			strTemp += szBuf[0];
			strTemp += szBuf[1];
		}
	}
	return strTemp;
}