/* 
 * Copyright (C) 2012 Yee Young Han <websearch@naver.com> (http://blog.naver.com/websearch)
 *
 * 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 3 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 "SipStackDefine.h"
#include "SipTcp.h"
#include "TlsFunction.h"
#include "HttpClient.h"
#include "Log.h"
#include "MemoryDebug.h"

CHttpClient::CHttpClient() : m_iRecvTimeout(10), m_iStatusCode(0)
{
	InitNetwork();
}

CHttpClient::~CHttpClient()
{
}

/**
 * @ingroup HttpStack
 * @brief	HTTP GET ¸í·ÉÀ» ½ÇÇàÇÑ´Ù.
 * @param pszUrl								HTTP URL (¿¹:http://www.naver.com)
 * @param strOutputContentType	¼ö½Å Content-Type
 * @param strOutputBody					¼ö½Å body
 * @returns ¼º°øÇÏ¸é true ¸¦ ¸®ÅÏÇÏ°í ½ÇÆÐÇÏ¸é false ¸¦ ¸®ÅÏÇÑ´Ù.
 */
bool CHttpClient::DoGet( const char * pszUrl, std::string & strOutputContentType, std::string & strOutputBody )
{
	strOutputContentType.clear();
	strOutputBody.clear();

	if( pszUrl == NULL )
	{
		CLog::Print( LOG_ERROR, "%s pszUrl is null", __FUNCTION__ );
		return false;
	}

	CHttpUri clsUri;
	int iUrlLen = strlen( pszUrl );

	if( clsUri.Parse( pszUrl, iUrlLen ) == -1 )
	{
		CLog::Print( LOG_ERROR, "%s clsUri.Parse(%s) error", __FUNCTION__, pszUrl );
		return false;
	}

	CHttpMessage clsRequest;
	CHttpPacket	clsPacket;

	clsRequest.SetRequest( "GET", &clsUri );

	if( Execute( &clsUri, &clsRequest, &clsPacket ) )
	{
		CHttpMessage * pclsMessage = clsPacket.GetHttpMessage();

		strOutputContentType = pclsMessage->m_strContentType;
		strOutputBody = pclsMessage->m_strBody;
		return true;
	}

	return false;
}

/**
 * @ingroup HttpStack
 * @brief	HTTP GET ¸í·ÉÀ» ½ÇÇàÇÑ´Ù.
 * @param pszUrl								HTTP URL (¿¹:http://wsf.cdyne.com/WeatherWS/Weather.asmx)
 * @param pszInputContentType		Àü¼Û Content-Type
 * @param pszInputBody					Àü¼Û body
 * @param strOutputContentType	¼ö½Å Content-Type
 * @param strOutputBody					¼ö½Å body
 * @returns ¼º°øÇÏ¸é true ¸¦ ¸®ÅÏÇÏ°í ½ÇÆÐÇÏ¸é false ¸¦ ¸®ÅÏÇÑ´Ù.
 */
bool CHttpClient::DoGet( const char * pszUrl, const char * pszInputContentType, const char * pszInputBody, std::string & strOutputContentType, std::string & strOutputBody )
{
	strOutputContentType.clear();
	strOutputBody.clear();

	if( pszUrl == NULL || pszInputContentType == NULL || pszInputBody == NULL )
	{
		CLog::Print( LOG_ERROR, "%s pszUrl or input content type or input body is null", __FUNCTION__ );
		return false;
	}

	CHttpUri clsUri;
	int iUrlLen = strlen( pszUrl );
	int iContentLength = strlen( pszInputBody );

	if( iContentLength <= 0 )
	{
		CLog::Print( LOG_ERROR, "%s pszInputBody's length(%d) error", __FUNCTION__, iContentLength );
		return false;
	}

	if( clsUri.Parse( pszUrl, iUrlLen ) == -1 )
	{
		CLog::Print( LOG_ERROR, "%s clsUri.Parse(%s) error", __FUNCTION__, pszUrl );
		return false;
	}

	CHttpMessage clsRequest;
	CHttpPacket clsPacket;

	clsRequest.SetRequest( "GET", &clsUri );
	clsRequest.m_strContentType = pszInputContentType;
	clsRequest.m_iContentLength = iContentLength;
	clsRequest.m_strBody = pszInputBody;

	if( Execute( &clsUri, &clsRequest, &clsPacket ) )
	{
		CHttpMessage * pclsMessage = clsPacket.GetHttpMessage();

		strOutputContentType = pclsMessage->m_strContentType;
		strOutputBody = pclsMessage->m_strBody;
		return true;
	}

	return false;
}

/**
 * @ingroup HttpStack
 * @brief	HTTP POST ¸í·ÉÀ» ½ÇÇàÇÑ´Ù.
 * @param pszUrl								HTTP URL (¿¹:http://wsf.cdyne.com/WeatherWS/Weather.asmx)
 * @param pszInputContentType		Àü¼Û Content-Type
 * @param pszInputBody					Àü¼Û body
 * @param strOutputContentType	¼ö½Å Content-Type
 * @param strOutputBody					¼ö½Å body
 * @returns ¼º°øÇÏ¸é true ¸¦ ¸®ÅÏÇÏ°í ½ÇÆÐÇÏ¸é false ¸¦ ¸®ÅÏÇÑ´Ù.
 */
bool CHttpClient::DoPost( const char * pszUrl, const char * pszInputContentType, const char * pszInputBody, std::string & strOutputContentType, std::string & strOutputBody )
{
	return DoPost( pszUrl, NULL, pszInputContentType, pszInputBody, strOutputContentType, strOutputBody );
}

/**
 * @ingroup HttpStack
 * @brief	HTTP POST ¸í·ÉÀ» ½ÇÇàÇÑ´Ù.
 * @param pszUrl								HTTP URL (¿¹:http://wsf.cdyne.com/WeatherWS/Weather.asmx)
 * @param pclsHeaderList				Àü¼Û Çì´õ¿¡ Æ÷ÇÔµÉ Çì´õ Ç×¸ñ ¸®½ºÆ®
 * @param pszInputContentType		Àü¼Û Content-Type
 * @param pszInputBody					Àü¼Û body
 * @param strOutputContentType	¼ö½Å Content-Type
 * @param strOutputBody					¼ö½Å body
 * @returns ¼º°øÇÏ¸é true ¸¦ ¸®ÅÏÇÏ°í ½ÇÆÐÇÏ¸é false ¸¦ ¸®ÅÏÇÑ´Ù.
 */
bool CHttpClient::DoPost( const char * pszUrl, HTTP_HEADER_LIST * pclsHeaderList, const char * pszInputContentType, const char * pszInputBody, std::string & strOutputContentType, std::string & strOutputBody )
{
	strOutputContentType.clear();
	strOutputBody.clear();

	if( pszUrl == NULL || pszInputContentType == NULL || pszInputBody == NULL )
	{
		CLog::Print( LOG_ERROR, "%s pszUrl or input content type or input body is null", __FUNCTION__ );
		return false;
	}

	CHttpUri clsUri;
	int iUrlLen = strlen( pszUrl );
	int iContentLength = strlen( pszInputBody );

	if( iContentLength <= 0 )
	{
		CLog::Print( LOG_ERROR, "%s pszInputBody's length(%d) error", __FUNCTION__, iContentLength );
		return false;
	}

	if( clsUri.Parse( pszUrl, iUrlLen ) == -1 )
	{
		CLog::Print( LOG_ERROR, "%s clsUri.Parse(%s) error", __FUNCTION__, pszUrl );
		return false;
	}

	CHttpMessage clsRequest;
	CHttpPacket clsPacket;

	clsRequest.SetRequest( "POST", &clsUri );
	clsRequest.m_strContentType = pszInputContentType;
	clsRequest.m_iContentLength = iContentLength;
	clsRequest.m_strBody = pszInputBody;

	if( pclsHeaderList )
	{
		clsRequest.m_clsHeaderList.insert( clsRequest.m_clsHeaderList.end(), pclsHeaderList->begin(), pclsHeaderList->end() );
	}

	if( Execute( &clsUri, &clsRequest, &clsPacket ) )
	{
		CHttpMessage * pclsMessage = clsPacket.GetHttpMessage();

		strOutputContentType = pclsMessage->m_strContentType;
		strOutputBody = pclsMessage->m_strBody;
		return true;
	}

	return false;
}

/**
 * @ingroup HttpClientApi
 * @brief HTTP POST ±â¹ÝÀ¸·Î SOAP ¸í·ÉÀ» ½ÇÇàÇÑ´Ù.
 * @param pszUrl				HTTP URL (¿¹:http://wsf.cdyne.com/WeatherWS/Weather.asmx)
 * @param pszSoapAction HTTP SOAPAction Çì´õ¿¡ ÀúÀåµÉ ¹®ÀÚ¿­ (¿¹:http://ws.cdyne.com/WeatherWS/GetWeatherInformation)
 * @param pszInputBody	Àü¼Û body
 * @param strOutputBody ¼ö½Å body
 * @returns ¼º°øÇÏ¸é true ¸¦ ¸®ÅÏÇÏ°í ½ÇÆÐÇÏ¸é false ¸¦ ¸®ÅÏÇÑ´Ù.
 */
bool CHttpClient::DoSoap( const char * pszUrl, const char * pszSoapAction, const char * pszInputBody, std::string & strOutputBody )
{
	return DoSoap( pszUrl, pszSoapAction, "text/xml;charset=UTF-8", pszInputBody, strOutputBody );
}

/**
 * @ingroup HttpClientApi
 * @brief HTTP POST ±â¹ÝÀ¸·Î SOAP ¸í·ÉÀ» ½ÇÇàÇÑ´Ù.
 * @param pszUrl				HTTP URL (¿¹:http://wsf.cdyne.com/WeatherWS/Weather.asmx)
 * @param pszSoapAction HTTP SOAPAction Çì´õ¿¡ ÀúÀåµÉ ¹®ÀÚ¿­ (¿¹:http://ws.cdyne.com/WeatherWS/GetWeatherInformation)
 * @param pszInputContentType	HTTP Content-Type
 * @param pszInputBody	Àü¼Û body
 * @param strOutputBody ¼ö½Å body
 * @returns ¼º°øÇÏ¸é true ¸¦ ¸®ÅÏÇÏ°í ½ÇÆÐÇÏ¸é false ¸¦ ¸®ÅÏÇÑ´Ù.
 */
bool CHttpClient::DoSoap( const char * pszUrl, const char * pszSoapAction, const char * pszInputContentType, const char * pszInputBody, std::string & strOutputBody )
{
	std::string strOutputContentType;

	strOutputBody.clear();

	if( pszSoapAction && strlen(pszSoapAction) > 0 )
	{
		HTTP_HEADER_LIST clsHeaderList;
		CHttpHeader clsHeader( "SOAPAction", pszSoapAction );

		clsHeaderList.push_back( clsHeader );

		return DoPost( pszUrl, &clsHeaderList, pszInputContentType, pszInputBody, strOutputContentType, strOutputBody );
	}
	
	return DoPost( pszUrl, NULL, pszInputContentType, pszInputBody, strOutputContentType, strOutputBody );
}

/**
 * @ingroup HttpStack
 * @brief HTTP ÀÀ´ä ¸Þ½ÃÁö ¼ö½Å timeout ½Ã°£À» ¼³Á¤ÇÑ´Ù.
 * @param iRecvTimeout HTTP ÀÀ´ä ¸Þ½ÃÁö ¼ö½Å timeout ½Ã°£ (ÃÊ´ÜÀ§)
 */
void CHttpClient::SetRecvTimeout( int iRecvTimeout )
{
	m_iRecvTimeout = iRecvTimeout;
}

/**
 * @ingroup HttpStack
 * @brief HTTP ÀÀ´ä status code ¸¦ ¸®ÅÏÇÑ´Ù.
 * @returns HTTP ÀÀ´ä status code ¸¦ ¸®ÅÏÇÑ´Ù.
 */
int CHttpClient::GetStatusCode()
{
	return m_iStatusCode;
}

/**
 * @ingroup HttpStack
 * @brief HTTP ¼­¹ö¿¡ ¿¬°áÇÏ¿©¼­ HTTP ¿äÃ» ¸Þ½ÃÁö¸¦ Àü¼ÛÇÑ ÈÄ, HTTP ÀÀ´ä ¸Þ½ÃÁö¸¦ ¼ö½ÅÇÑ´Ù.
 * @param pclsUri				HTTP request URI
 * @param pclsRequest		HTTP request
 * @param pclsPacket		HTTP response ¸¦ ÀúÀåÇÒ ÆÐÅ¶ °´Ã¼
 * @returns ¼º°øÇÏ¸é true ¸¦ ¸®ÅÏÇÏ°í ½ÇÆÐÇÏ¸é false ¸¦ ¸®ÅÏÇÑ´Ù.
 */
bool CHttpClient::Execute( CHttpUri * pclsUri, CHttpMessage * pclsRequest, CHttpPacket * pclsPacket )
{
	char * pszBuf = NULL;
	int iBufLen, n;
	bool bRes = false;
	Socket hSocket = INVALID_SOCKET;
	SSL * psttSsl = NULL;
	CHttpMessage * pclsResponse = pclsPacket->GetHttpMessage();

	int iNewBufLen = 8192 + pclsRequest->m_strBody.length();
	pszBuf = (char *)malloc( iNewBufLen );
	if( pszBuf == NULL )
	{
		CLog::Print( LOG_ERROR, "%s malloc error", __FUNCTION__ );
		return false;
	}

	memset( pszBuf, 0, iNewBufLen );

	pclsRequest->m_strHttpVersion = "HTTP/1.1";
	
	iBufLen = pclsRequest->ToString( pszBuf, iNewBufLen );
	if( iBufLen <= 0 )
	{
		CLog::Print( LOG_ERROR, "%s clsRequest.ToString() error", __FUNCTION__ );
		goto FUNC_END;
	}

	hSocket = TcpConnect( pclsUri->m_strHost.c_str(), pclsUri->m_iPort );
	if( hSocket == INVALID_SOCKET )
	{
		CLog::Print( LOG_ERROR, "%s TcpConnect(%s:%d) error", __FUNCTION__, pclsUri->m_strHost.c_str(), pclsUri->m_iPort );
		goto FUNC_END;
	}

	CLog::Print( LOG_NETWORK, "TcpConnect(%s:%d) success", pclsUri->m_strHost.c_str(), pclsUri->m_iPort );

	// https ÇÁ·ÎÅäÄÝÀÌ¸é TLS ·Î ¿¬°áÇÑ´Ù.
	if( !strcmp( pclsUri->m_strProtocol.c_str(), "https" ) )
	{
		if( SSLConnect( hSocket, &psttSsl ) == false )
		{
			CLog::Print( LOG_ERROR, "%s SSLConnect error", __FUNCTION__ );
			goto FUNC_END;
		}

		CLog::Print( LOG_NETWORK, "SSLConnect(%s:%d) success", pclsUri->m_strHost.c_str(), pclsUri->m_iPort );
	}

	if( psttSsl )
	{
		if( SSLSend( psttSsl, pszBuf, iBufLen ) != iBufLen )
		{
			CLog::Print( LOG_ERROR, "%s SSLSend error", __FUNCTION__ );
			goto FUNC_END;
		}
	}
	else
	{
		if( TcpSend( hSocket, pszBuf, iBufLen ) != iBufLen )
		{
			CLog::Print( LOG_ERROR, "%s TcpSend error", __FUNCTION__ );
			goto FUNC_END;
		}
	}

	CLog::Print( LOG_NETWORK, "TcpSend(%s:%d) [%s]", pclsUri->m_strHost.c_str(), pclsUri->m_iPort, pszBuf );

	while( 1 )
	{
		memset( pszBuf, 0, iNewBufLen );

		if( psttSsl )
		{
			n = SSLRecv( psttSsl, pszBuf, iNewBufLen );
		}
		else
		{
			n = TcpRecv( hSocket, pszBuf, iNewBufLen, m_iRecvTimeout );
		}

		if( n <= 0 )
		{
			break;
		}

		CLog::Print( LOG_NETWORK, "TcpRecv(%s:%d) [%.*s]", pclsUri->m_strHost.c_str(), pclsUri->m_iPort, n, pszBuf );

		if( pclsPacket->AddPacket( pszBuf, n ) == false )
		{
			CLog::Print( LOG_ERROR, "%s clsPacket.AddPacket error", __FUNCTION__ );
			break;
		}

		if( pclsPacket->IsCompleted() ) break;
	}

	m_iStatusCode = pclsResponse->m_iStatusCode;

	if( pclsResponse->m_iStatusCode / 100 == 2 )
	{
		bRes = true;
	}

FUNC_END:
	if( psttSsl )
	{
		SSLClose( psttSsl );
		psttSsl = NULL;
	}

	if( hSocket != INVALID_SOCKET )
	{
		closesocket( hSocket );
		hSocket = INVALID_SOCKET;
	}

	if( pszBuf )
	{
		free( pszBuf );
		pszBuf = NULL;
	}

	// 3XX ÀÀ´ä¿¡¼­ Location Çì´õ°¡ Á¸ÀçÇÏ¸é ÇØ´ç URL ·Î ´Ù½Ã ½ÃµµÇÑ´Ù.
	if( pclsResponse->m_iStatusCode / 100 == 3 )
	{
		CHttpHeader * pclsHeader = pclsResponse->GetHeader( "Location" );
		if( pclsHeader && pclsHeader->m_strValue.empty() == false )
		{
			CHttpUri clsUri;

			if( clsUri.Parse( pclsHeader->m_strValue.c_str(), pclsHeader->m_strValue.length() ) )
			{
				if( clsUri.m_strHost.empty() )
				{
					clsUri.m_strHost = pclsUri->m_strHost;
					clsUri.m_iPort = pclsUri->m_iPort;
					clsUri.m_strProtocol = pclsUri->m_strProtocol;
					clsUri.m_strPath = pclsHeader->m_strValue;
				}
				
				pclsRequest->SetRequest( pclsRequest->m_strHttpMethod.c_str(), &clsUri );

				return Execute( &clsUri, pclsRequest, pclsPacket );
			}
		}
	}

	return bRes;
}
