/***************************************************************************
                           chttp.cpp  -  description
                             -------------------
    begin                : Mon Jul 29 2002
    copyright            : (C) 2002-2003 by Mathias Küster
    email                : mathen@users.berlios.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "chttp.h"

#include <stdio.h>
#include <stdlib.h>

#include "core/clist.h"
#include "core/cmanager.h"
#include "dcobject.h"

// for encoding of proxy user/pass
#include "core/cbase64.h"

/** */
CHttp::CHttp()
{
	m_pHttpCallback = 0;
	m_pCallback     = 0;
	m_eUrlMethod    = eumGET;

	m_pMessageList = new CList<CDCMessage>();
}

/** */
CHttp::~CHttp()
{
	if ( m_pCallback )
	{
		CManager::Instance()->Remove( m_pCallback );
		delete m_pCallback;
		m_pCallback = 0;
	}

	Disconnect();

	delete m_pHttpCallback;
	m_pHttpCallback = 0;

	delete m_pMessageList;
}

/** */
int CHttp::Callback()
{
	Thread();
	return 0;
}

/** */
int CHttp::GetUrl( CString url, CString postdata )
{
	int i,i1;
	CString s(url);
	CString sRealHost;
	CString sRealPort;

	//Disconnect();

	m_nErrorCode = 0;
	m_bData      = false;
	m_sHeader.Empty();
	m_sLocation.Empty();

	m_baData.SetSize(0);

	if ( s.IsEmpty() )
	{
		return -1;
	}

	if ( postdata.IsEmpty() )
	{
		m_eUrlMethod = eumGET;
		m_sPostData.Empty();
	}
	else
	{
		m_eUrlMethod = eumPOST;
		m_sPostData  = postdata;
	}

	CString header = s.Mid(0,7);

	header = header.ToUpper();

	if ( header == "HTTP://" )
	{
		s = s.Mid(7,s.Length()-7);
	}

	if ( (i = s.Find(':')) >= 0 )
	{
		m_sHost = s.Mid(0,i);
	}

	if ( (i1 = s.Find('/')) < 0 )
	{
		printf("CHTTP: no '/' found\n");
		return -1;
	}

	if ( i >= 0 )
	{
		m_sPort = s.Mid(i+1,i1-i-1);
	}
	else
	{
		m_sHost = s.Mid(0,i1);
		m_sPort = "80";
	}

	char *proxy = getenv("http_proxy");

	if( proxy )
	{
		unsigned int port;
		if ( ParseProxy(proxy,m_sProxyUser,m_sProxyPass,m_sProxy,port) )
		{
			m_sProxyPort = CString::number(port);
			printf("CHTTP: PROXY: '%s':'%s'", m_sProxy.Data(),m_sProxyPort.Data());
			if ( (m_sProxyUser.NotEmpty()) || (m_sProxyPass.NotEmpty()) )
			{
				printf(" '%s':'%s' UNTESTED\n", m_sProxyUser.Data(),m_sProxyPass.Data());
			}
			else
			{
				printf("\n");
			}
		}
		else
		{
			printf("CHTTP: PROXY parse error\n");
		}

		m_sUrl    = url;
		sRealHost = m_sProxy;
		sRealPort = m_sProxyPort;
	}
	else
	{
		/* unset proxy variables */
		m_sProxy.Empty();
		m_sProxyPort.Empty();
		m_sProxyUser.Empty();
		m_sProxyPass.Empty();
		
		m_sUrl    = s.Mid(i1,s.Length()-i1);
		sRealHost = m_sHost;
		sRealPort = m_sPort;
	}

	printf("CHTTP: HOST : '%s:%s'\n",m_sHost.Data(),m_sPort.Data());
	printf("CHTTP: URL  : '%s'\n",m_sUrl.Data());

	if ( Connect( sRealHost, sRealPort.asINT() ) == ecsERROR )
	{
		return -1;
	}

	if ( !m_pCallback )
	{
		m_pCallback = new CCallback0<CHttp>( this, &CHttp::Callback );
		CManager::Instance()->Add( m_pCallback );
	}

	return 0;
}

/** */
bool CHttp::GetData( CByteArray * ba )
{
	bool err = false;

	if ( (m_nErrorCode == 200) && m_bData && (m_eMode == estNONE) )
	{
		if ( (m_nContentLength == -1) || (m_nContentLength == m_baData.Size()) )
		{
			if ( ba )
			{
				ba->SetSize(0);
				ba->Append( m_baData.Data(), m_baData.Size() );
			}
			
			err = true;
		}
	}

	return err;
}

/** */
int CHttp::CallBack_SendObject( CDCMessage * DCMessage )
{
	int err;

	if ( m_pHttpCallback )
	{
		err = m_pHttpCallback->notify(DCMessage);
	}
	else
	{
		err = DC_CallBack( DCMessage );
	}

	if ( err == -1 )
	{
		printf("CHttp: CallBack failed (state)...\n");
		delete DCMessage;
	}

	return err;
}

/** */
void CHttp::ConnectionState( eConnectionState state )
{
	CMessageConnectionState *Object;

	Object = new CMessageConnectionState();

	Object->m_eState   = state;
	Object->m_sMessage = GetSocketError();

	if ( state == estCONNECTED )
	{
		m_eMode = estTRANSFERHANDSHAKE;
	}
	else if ( state == estDISCONNECTED )
	{
		m_eMode = estNONE;
	}

	// callback over notify because connection class is locked
	m_pMessageList->Add(Object);
}

/** */
void CHttp::DataAvailable( const char * buffer, int len )
{
	int i,i1,i2;

	if ( m_eMode == estTRANSFERDOWNLOAD )
	{
		if ( m_bData == false )
		{
			for(i=0;i<len;i++)
			{
				m_sHeader += buffer[i];

				if ((m_sHeader.Length() > 4) &&
				    (m_sHeader.Data()[m_sHeader.Length()-4] == 0x0d) &&
				    (m_sHeader.Data()[m_sHeader.Length()-3] == 0x0a) &&
				    (m_sHeader.Data()[m_sHeader.Length()-2] == 0x0d) &&
				    (m_sHeader.Data()[m_sHeader.Length()-1] == 0x0a) )
				{
					//m_sHeader += CString().Set(buffer,i);
					//i+=4;

					// parse header ...
					if ( (i1 = m_sHeader.Find("HTTP/1.0 ")) == -1 )
					{
						if ( (i1 = m_sHeader.Find("HTTP/1.1 ")) == -1 )
						{
							printf("wrong proto '%s'\n",m_sHeader.Data());
							Disconnect();
							return;
						}
					}

					if ( (i2 = m_sHeader.Find(' ',i1+9)) == -1 )
					{
						printf("wrong proto '%s'\n",m_sHeader.Data());
						Disconnect();
					}
					else
					{
						m_nErrorCode = m_sHeader.Mid(i1+9,i2-i1-9).asINT();

						if ( m_nErrorCode == 200 )
						{
							printf("no error\n");
							m_bData = true;
						}
						else if ( m_nErrorCode == 302 )
						{
							printf("redirect 302\n");
						}
						else
						{
							printf("http error %d\n",m_nErrorCode);
							Disconnect();
						}
					}

					m_nContentLength = -1;

					if ( m_nErrorCode == 200 )
					{
						if ( (i1 = m_sHeader.Find("Content-Length: ")) != -1 )
						{
							if ( (i2 = m_sHeader.Find(0x0d,i1)) != -1 )
							{
								m_nContentLength = m_sHeader.Mid(i1+16,i2-i1-16).asLONG();
							}
						}
					}
					else if ( m_nErrorCode == 302 )
					{
						if ( (i1 = m_sHeader.Find("Location: ")) != -1 )
						{
							if ( (i2 = m_sHeader.Find(0x0d,i1)) != -1 )
							{
								m_sLocation = m_sHeader.Mid(i1+10,i2-i1-10);
							}

							if ( m_sLocation.IsEmpty() )
							{
								printf("http wrong location\n");
								m_nErrorCode = 0;
							}
						}
					}

					i++;
					break;
				}
			}

			if ( m_bData && (m_nErrorCode != 0) )
			{
				AppendData( buffer+i,len-i );
			}
		}
		else
		{
			AppendData( buffer, len );
		}

		//printf("DATA: %ld\n",baData.Size());
	}
}

/** */
void CHttp::AppendData( const char * buffer, int len )
{
	CMessageTransfer  * msg = new CMessageTransfer();

	if ( m_nContentLength != -1 )
	{
		msg->m_nLength = m_nContentLength;
	}

	msg->m_nTransfered = m_baData.Size();

	m_pMessageList->Add(msg);

	m_baData.Append((const unsigned char*)buffer,len);
}

/** */
void CHttp::DataSend()
{
	CString header;

	if ( m_eMode == estTRANSFERHANDSHAKE )
	{
		// send request
		if ( m_eUrlMethod == eumGET )
		{
			header = "GET ";
		}
		else
		{
			header = "POST ";
		}

		header += m_sUrl;
		header += " HTTP/1.0";
		header += "\xd\xa";
		header += "User-Agent: DCGUI v";
		header += DCLIB_VERSION_STRING;
		header += "\xd\xa";
		header += "Referer: http://";
		header += m_sHost;
		header += ':';
		header += m_sPort;
		header += '/';
		header += "\xd\xa";
		if ( (m_sProxy.NotEmpty()) && (m_sProxyUser.NotEmpty()) )
		{
			header += "Proxy-Authorization: Basic ";
			header += CBase64::Encode( m_sProxyUser + ":" + m_sProxyPass );
			header += "\xd\xa";
		}
		header += "Host: ";
		header += m_sHost;
		header += "\xd\xa";

		if ( m_eUrlMethod == eumPOST )
		{
			header += "Content-Type: text/plain";
			header += "\xd\xa";
			header += "Content-Length: ";
			header += CString::number(m_sPostData.Length());
			header += "\xd\xa";
		}

		header += "\xd\xa";

		if ( m_eUrlMethod == eumPOST )
		{
			header += m_sPostData;
		}

		if ( Write( (const unsigned char*)header.Data(), header.Length() ) != 0 )
		{
			m_eMode = estNONE;
		}
		else
		{
			m_eMode = estTRANSFERDOWNLOAD;
		}
	}
}

/** */
void CHttp::DataTimeout()
{
	Disconnect(true);
	printf("data timeout\n");
}

/** */
void CHttp::Notify()
{
	CDCMessage * dcmessage = 0;

	while ( (dcmessage=m_pMessageList->Next(0)) != 0 )
	{
		m_pMessageList->Remove(dcmessage);
		CallBack_SendObject(dcmessage);
	}
}

/** */
CString CHttp::Encode( CString s )
{
	CString s1;

	s1 = s.Replace("&","&amp;");
	s1 = s1.Replace(">","&gt;");
	s1 = s1.Replace("<","&lt;");
	s1 = s1.Replace("\x7f","&#x25a1;");
	s1 = s1.Replace("=","&#61;");
	s1 = s1.Replace(" ","&#32;");

	return s1;
}

/** */
CString CHttp::Decode( CString s )
{
	CString s1;

	s1 = s.Replace("&gt;",">");
	s1 = s1.Replace("&lt;","<");
	s1 = s1.Replace("&#x25a1;","\x7f");
	s1 = s1.Replace("&#61;","=");
	s1 = s1.Replace("&#32;"," ");
	s1 = s1.Replace("&amp;","&");

	return s1;
}

/** http_proxy=http://username:password@host:port/ */
bool CHttp::ParseProxy( char * data, CString & user, CString & pass, CString & host, unsigned int & port )
{
	if ( data == 0 )
	{
		return false;
	}
	
	int i;
	CString temp;
	CString tempuser;
	CString temppass;
	CString temphost;
	unsigned int tempport = 8080;
	
	CString datastring(data);
	datastring = datastring.ToLower();
	
	// check scheme
	i = datastring.Find("://");
	if ( i != -1 )
	{
		temp = datastring.Mid(0,i);
		if ( temp != "http" )
		{
			printf("CHttp::ParseProxy unsupported scheme '%s'\n", temp.Data());
			return false;
		}
		datastring = datastring.Mid(i+3);
	}
	
	// check for optional user:password
	i = datastring.Find('@');
	if ( i != -1 )
	{
		tempuser = datastring.Mid(0,i);
		datastring = datastring.Mid(i+1);
		
		int i2 = tempuser.Find(':');
		if ( i2 != -1 )
		{
			temppass = tempuser.Mid(i2+1);
			tempuser = tempuser.Mid(0,i2);
		}
	}
	
	// remove any path
	// what is this for, does a path in a proxy address make any sense?
	i = datastring.Find('/');
	if ( i < 0 )
	{
		//printf("CHTTP: no '/' at the end of your proxy found.\n");
		//printf("CHTTP: Check your http_proxy environment variable !\n");
		//printf("CHTTP: Trying without ...\n");
	}
	else
	{
		datastring = datastring.Mid(0,i);
	}
	
	// check for optional port
	i = datastring.Find(':');
	if ( i != -1 )
	{
		temphost = datastring.Mid(0,i);
		tempport = datastring.Mid(i+1).asUINT();
	}
	else
	{
		temphost = datastring;
	}
	
	// sanity check
	if ( (temphost.IsEmpty()) || (tempport > 65535) || (tempport <= 0) )
	{
		printf("CHttp::ParseProxy validation failed\n");
		return false;
	}
	else
	{
		user = tempuser;
		pass = temppass;
		host = temphost;
		port = tempport;
		return true;
	}
}
