//=============================================================================
//
//   File : kvi_http.cpp
//   Creation date : Sat Aug 17 13:43:32 2002 GMT by Szymon Stefanek
//
//   This file is part of the KVirc irc client distribution
//   Copyright (C) 2002-2006 Szymon Stefanek (pragma at kvirc dot net)
//
//   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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
//=============================================================================

#define __KVILIB__


#include <tqdir.h>
#include <tqtimer.h>
//#include <zlib.h>

#include "kvi_http.h"
#include "kvi_locale.h"
#include "kvi_netutils.h"
#include "kvi_dns.h"
#include "kvi_error.h"
#include "kvi_debug.h"
#include "kvi_socket.h"
#include "kvi_time.h"
#ifdef COMPILE_SSL_SUPPORT
	#include "kvi_ssl.h"
#endif


#define KVI_HTTP_REQUEST_THREAD_EVENT_CONNECTED (KVI_THREAD_USER_EVENT_BASE + 0xCAFE)
#define KVI_HTTP_REQUEST_THREAD_EVENT_REQUESTSENT (KVI_THREAD_USER_EVENT_BASE + 0xCAFF)

KviHttpRequest::KviHttpRequest()
: TQObject()
{
	m_pDns = 0;
	m_pThread = 0;
	m_pFile = 0;
	m_pPrivateData = 0;
	m_bHeaderProcessed = false;
	m_pBuffer = new KviDataBuffer();

	resetStatus();
	resetData();
}

KviHttpRequest::~KviHttpRequest()
{
	resetInternalStatus();
	delete m_pBuffer;
}

void KviHttpRequest::abort()
{
	resetInternalStatus();
	m_szLastError = __tr2qs("Aborted");
	emit terminated(false);
}

void KviHttpRequest::resetInternalStatus()
{
	if(m_pThread)delete m_pThread;
	if(m_pDns)delete m_pDns;

	m_pDns = 0;
	m_pThread = 0;

	if(!m_pFile)return;
	m_pFile->close();
	delete m_pFile;
	m_pFile = 0;

	m_pBuffer->clear();
	m_bHeaderProcessed = false;

	KviThreadManager::killPendingEvents(this);
}

void KviHttpRequest::resetStatus()
{
	m_szLastError = __tr2qs("No request");
	m_uTotalSize = 0;
	m_uReceivedSize = 0;
}

void KviHttpRequest::resetData()
{
	m_szFileName = "";
	m_eProcessingType = WholeFile;
	m_eExistingFileAction = RenameIncoming;
	m_url = "";
	m_uMaxContentLength = 0;
	m_uContentOffset = 0;
	m_bChunkedTransferEncoding = false;
	m_bGzip = false;
	m_bIgnoreRemainingData = false;
	m_uRemainingChunkSize = 0;
}

void KviHttpRequest::reset()
{
	resetStatus();
	resetData();
	resetInternalStatus();
}

bool KviHttpRequest::get(const KviUrl &u,ProcessingType p,const TQString &szFileName)
{
	reset();
	setUrl(u);
	setProcessingType(p);
	setFileName(szFileName);
	return start();
}

bool KviHttpRequest::start()
{
	// ensure that the file is closed
	resetInternalStatus();
	resetStatus();

	if(m_eProcessingType == StoreToFile)
	{
		if(m_szFileName.isEmpty())
		{
			m_szLastError = __tr2qs("No filename specified for the \"StoreToFile\" processing type");
			return false;
		}

		if((m_eExistingFileAction == Resume) && (m_uContentOffset == 0))
		{
			// determine the content offset automatically
			if(KviFile::exists(m_szFileName))
			{
				// we check it
				TQFileInfo fi(m_szFileName);
				m_uContentOffset = fi.size();
			}
		}
	}

	if(m_url.host().isEmpty())
	{
		resetInternalStatus();
		m_szLastError = __tr2qs("Invalid URL: Missing hostname");
		return false;
	}

	if((!kvi_strEqualCI(m_url.protocol().ptr(),"http")) && (!kvi_strEqualCI(m_url.protocol().ptr(),"https")))
	{
		resetInternalStatus();
		m_szLastError=__tr2qs("Unsupported protocol %1").arg(m_url.protocol().ptr());
		return false;
	}

	if(kvi_isValidStringIp(m_url.host().ptr()))
	{
		m_szIp = m_url.host();
		TQTimer::singleShot(10,this,TQ_SLOT(haveServerIp()));
		return true;
	}

	return startDnsLookup();
}

bool KviHttpRequest::startDnsLookup()
{
	m_pDns = new KviDns();
	connect(m_pDns,TQ_SIGNAL(lookupDone(KviDns *)),this,TQ_SLOT(dnsLookupDone(KviDns *)));

	if(!m_pDns->lookup(m_url.host().ptr(),KviDns::IpV4))
	{
		resetInternalStatus();
		m_szLastError = __tr2qs("Unable to start the DNS lookup");
		return false;
	}

	TQString tmp;
	KviTQString::sprintf(tmp,__tr2qs("Looking up host %s"),m_url.host().ptr());
	emit status(tmp); // FIXME

	emit resolvingHost(TQString(m_url.host().ptr()));

	return true;
}

void KviHttpRequest::dnsLookupDone(KviDns *d)
{
	if(d->state() == KviDns::Success)
	{
		m_szIp = d->firstIpAddress();
		delete m_pDns;
		m_pDns = 0;
		TQString tmp;
		KviTQString::sprintf(tmp,__tr2qs("Host %s resolved to %Q"),m_url.host().ptr(),&m_szIp);
		emit status(tmp);
		haveServerIp();
	} else {
		int iErr = d->error();
		resetInternalStatus();
		m_szLastError = KviError::getDescription(iErr);
		emit terminated(false);
	}
}

void KviHttpRequest::haveServerIp()
{
	unsigned short uPort = m_url.port();
	if(uPort == 0)uPort = 80;

	TQString tmp;
	KviTQString::sprintf(tmp,"%Q:%u",&m_szIp,uPort);
	emit contactingHost(tmp);

	if(m_pThread)delete m_pThread;

	m_pThread = new KviHttpRequestThread(
						this,
						m_url.host().ptr(),
						m_szIp,
						uPort,
						m_url.path().ptr(),
						m_uContentOffset,
						(m_eProcessingType == HeadersOnly) ? KviHttpRequestThread::Head : (m_szPostData.isEmpty() ? KviHttpRequestThread::Get : KviHttpRequestThread::Post),
						m_szPostData,
						kvi_strEqualCI(m_url.protocol().ptr(),"https"));

	if(!m_pThread->start())
	{
		resetInternalStatus();
		m_szLastError = __tr2qs("Unable to start the request slave thread");
		emit terminated(false);
		return;
	}

	KviTQString::sprintf(tmp,__tr2qs("Contacting host %Q on port %u"),&m_szIp,uPort);
	emit status(tmp);
}

bool KviHttpRequest::event(TQEvent *e)
{
	if(e->type() == KVI_THREAD_EVENT)
	{
		switch(((KviThreadEvent *)e)->id())
		{
			case KVI_THREAD_EVENT_BINARYDATA:
			{
				KviDataBuffer * b = ((KviThreadDataEvent<KviDataBuffer> *)e)->getData();
				processData(b);
				delete b;
				return true;
			}
			break;
			case KVI_HTTP_REQUEST_THREAD_EVENT_CONNECTED:
				emit connectionEstabilished();
				emit status(__tr2qs("Connection established, sending request"));
				return true;
			break;
			case KVI_HTTP_REQUEST_THREAD_EVENT_REQUESTSENT:
			{
				TQString * req = ((KviThreadDataEvent<TQString> *)e)->getData();
				TQStringList sl = TQStringList::split("\r\n",*req);
				emit requestSent(sl);
				delete req;
				return true;
			}
			break;
			case KVI_THREAD_EVENT_SUCCESS:
				if(!m_pThread && !m_bHeaderProcessed)
				{
					// the thread has already been deleted
					// probably because the response was something like a 404
					// just ignore the event
					return true;
				}
				switch(m_eProcessingType)
				{
					case WholeFile:
						// happens always
						emit binaryData(*m_pBuffer);
					break;
					case Blocks:
						// an unprocessed block ?.. should never happend.. but well :D
						if(m_pBuffer->size() > 0)emit binaryData(*m_pBuffer);
					break;
					case Lines:
						if(m_pBuffer->size() > 0)
						{
							// something left in the buffer and has no trailing LF
							KviStr tmp((const char *)(m_pBuffer->data()),m_pBuffer->size());
							emit data(tmp);
						}
					break;
					case StoreToFile:
						// same as above... should never happen.. but well :D
						if(m_pFile && m_pBuffer->size() > 0)m_pFile->writeBlock((const char *)(m_pBuffer->data()),m_pBuffer->size());
					break;
					default:
						// nothing... just make gcc happy
					break;
				}
				resetInternalStatus();
				m_szLastError = __tr2qs("Success");
				emit terminated(true);
				return true;
			break;
			case KVI_THREAD_EVENT_ERROR:
			{
				KviStr * err = ((KviThreadDataEvent<KviStr> *)e)->getData();
				m_szLastError = __tr2qs_no_xgettext(err->ptr());
				delete err;
				resetInternalStatus();
				emit terminated(false);
				return true;
			}
			break;
			case KVI_THREAD_EVENT_MESSAGE:
			{
				KviStr * msg = ((KviThreadDataEvent<KviStr> *)e)->getData();
				emit status(__tr2qs_no_xgettext(msg->ptr()));
				delete msg;
				return true;
			}
			break;
		}
	}
	return TQObject::event(e);
}

void KviHttpRequest::emitLines(KviDataBuffer * pDataBuffer)
{
	int idx = pDataBuffer->find((const unsigned char *)"\n",1);
	while(idx != -1)
	{
		KviStr tmp((const char *)(m_pBuffer->data()),idx);
		tmp.stripRight('\r');
		pDataBuffer->remove(idx + 1);
		idx = pDataBuffer->find((const unsigned char *)"\n",1);
		emit data(tmp);
	}
}

//    header += "Accept: ";
//    TQString acceptHeader = metaData("accept");
//    if (!acceptHeader.isEmpty())
//      header += acceptHeader;
//    else
//      header += DEFAULT_ACCEPT_HEADER;
//    header += "\r\n";
//
//#ifdef DO_GZIP
//    if (m_request.allowCompressedPage)
//      header += "Accept-Encoding: x-gzip, x-deflate, gzip, deflate, identity\r\n";
//#endif
//
//    if (!m_request.charsets.isEmpty())
//      header += "Accept-Charset: " + m_request.charsets + "\r\n";
//
//    if (!m_request.languages.isEmpty())
//      header += "Accept-Language: " + m_request.languages + "\r\n";
//
//
//    /* support for virtual hosts and required by HTTP 1.1 */
//    header += "Host: ";
//      header += "Pragma: no-cache\r\n"; /* for HTTP/1.0 caches */
//      header += "Cache-control: no-cache\r\n"; /* for HTTP >=1.1 caches */

//        header += "Referer: "; //Don't try to correct spelling!
//        header += m_request.referrer;
//        header += "\r\n";
bool KviHttpRequest::openFile()
{
	if(m_eProcessingType != StoreToFile)return true;

	bool bAppend = false;

	// take action when the file is existing
	if(KviFile::exists(m_szFileName))
	{
		switch(m_eExistingFileAction)
		{
			case Resume:
			{
				bAppend = true;
			}
			break;
			case RenameIncoming:
			{
				int i=0;
				TQString tmp = m_szFileName;
				do {
					i++;
					m_szFileName = tmp + TQString(".kvirnm-%1").arg(i);
				} while(KviFile::exists(m_szFileName));
			}
			break;
			case RenameExisting:
			{
				int i=0;
				TQString tmp;
				do {
					i++;
					tmp = m_szFileName + TQString(".kvirnm-%1").arg(i);
				} while(KviFile::exists(tmp));
				TQDir d;
				if(!d.rename(m_szFileName,tmp))
				{
					// fail :(
					resetInternalStatus();
					m_szLastError = __tr2qs("Failed to rename the existing file, please rename manually and retry");
					emit terminated(false);
					return false;
				}
			}
			break;
			case Overwrite:
			default:
				// nothing
			break;
		}
	}

	m_pFile = new KviFile(m_szFileName);

	if(!m_pFile->openForWriting(bAppend))
	{
		resetInternalStatus();
		KviTQString::sprintf(m_szLastError,__tr2qs("Can't open file \"%Q\" for writing"),&m_szFileName);
		emit terminated(false);
		return false;
	}

	return true;
}





bool KviHttpRequest::processHeader(KviStr &szHeader)
{
	int idx = szHeader.findFirstIdx("\r\n");
	KviStr szResponse;
	if(idx != -1)
	{
		szResponse = szHeader.left(idx);
		szHeader.cutLeft(idx + 2);
	} else {
		szResponse = szHeader;
		szHeader = "";
	}

	szResponse.stripWhiteSpace();

	bool bValid = false;

	unsigned int uStatus = 0;

	// check the response value
	if(kvi_strEqualCSN(szResponse.ptr(),"HTTP",4))
	{
		KviStr szR = szResponse;
		szR.cutToFirst(' ');
		szR.stripWhiteSpace();
		int idx = szR.findFirstIdx(' ');
		KviStr szNumber;
		if(idx != -1)szNumber = szR.left(idx);
		else szNumber = szR;
		bool bOk;
		uStatus = szNumber.toUInt(&bOk);
		if(bOk)bValid = true;
	}

	if(!bValid)
	{
		// the response is invalid ?
		resetInternalStatus();
		m_szLastError=__tr2qs("Invalid HTTP response: %s").arg(szResponse.ptr());
		emit terminated(false);
		return false;
	}

	TQString tmp;
	KviTQString::sprintf(tmp,__tr2qs("Received HTTP response: %s"),szResponse.ptr());

	emit status(tmp);
	emit receivedResponse(TQString(szResponse.ptr()));

	KviPointerList<KviStr> hlist;
	hlist.setAutoDelete(true);

	idx = szHeader.findFirstIdx("\r\n");
	while(idx != -1)
	{
		if(idx > 0)
		{
			hlist.append(new KviStr(szHeader.ptr(),idx));
			szHeader.cutLeft(idx + 2);
		}
		idx = szHeader.findFirstIdx("\r\n");
	}
	if(szHeader.hasData())hlist.append(new KviStr(szHeader));

	KviPointerHashTable<const char *,KviStr> hdr(11,false,true);
	hdr.setAutoDelete(true);

	for(KviStr * s = hlist.first();s;s = hlist.next())
	{
		idx = s->findFirstIdx(":");
		if(idx != -1)
		{
			KviStr szName = s->left(idx);
			s->cutLeft(idx + 1);
			s->stripWhiteSpace();
			hdr.replace(szName.ptr(),new KviStr(*s));
			//tqDebug("FOUND HEADER (%s)=(%s)",szName.ptr(),s->ptr());
		}
	}

	KviStr * size = hdr.find("Content-length");
	if(size)
	{
		bool bOk;
		m_uTotalSize = size->toUInt(&bOk);
		if(!bOk)m_uTotalSize = 0;
	}

	KviStr * contentEncoding = hdr.find("Content-encoding");
	if(contentEncoding)
	{
		m_bGzip = contentEncoding->equalsCI("gzip");
	}
	
	KviStr * transferEncoding = hdr.find("Transfer-Encoding");
	if(transferEncoding)
	{
		if(kvi_strEqualCI(transferEncoding->ptr(),"chunked"))
		{
			// be prepared to handle the chunked transfer encoding as required by HTTP/1.1
			m_bChunkedTransferEncoding = true;
			m_uRemainingChunkSize = 0;
		}
	}

	emit header(&hdr);

	// check the status

	//				case 200: // OK
	//				case 206: // Partial content

	//				case 100: // Continue ??
	//				case 101: // Switching protocols ???
	//				case 201: // Created
	//				case 202: // Accepted
	//				case 203: // Non-Authoritative Information
	//				case 204: // No content
	//				case 205: // Reset content
	//				case 300: // Multiple choices
	//				case 301: // Moved permanently
	//				case 302: // Found
	//				case 303: // See Other
	//				case 304: // Not modified
	//				case 305: // Use Proxy
	//				case 306: // ???
	//				case 307: // Temporary Redirect
	//				case 400: // Bad request
	//				case 401: // Unauthorized
	//				case 402: // Payment Required
	//				case 403: // Forbidden
	//				case 404: // Not found
	//				case 405: // Method not allowed
	//				case 406: // Not acceptable
	//				case 407: // Proxy authentication required
	//				case 408: // Request timeout
	//				case 409: // Conflict
	//				case 410: // Gone
	//				case 411: // Length required
	//				case 412: // Precondition failed
	//				case 413: // Request entity too large
	//				case 414: // Request-URI Too Long
	//				case 415: // Unsupported media type
	//				case 416: // Requested range not satisfiable
	//				case 417: // Expectation Failed
	//				case 500: // Internal server error
	//				case 501: // Not implemented
	//				case 502: // Bad gateway
	//				case 503: // Service unavailable
	//				case 504: // Gateway timeout
	//				case 505: // HTTP Version not supported

	if((uStatus != 200) && (uStatus != 206))
	{
		// this is not "OK" and not "Partial content"
		// Error , redirect or something confusing
		if(m_eProcessingType != HeadersOnly)
		{
			// this is an error then
			resetInternalStatus();
			m_szLastError = szResponse.ptr();
			emit terminated(false);
			return false;
		} // else the server will terminate (it was a HEAD request)
	}

	if((m_uMaxContentLength > 0) && (m_uTotalSize > ((unsigned int)m_uMaxContentLength)))
	{
		resetInternalStatus();
		m_szLastError=__tr2qs("Stream exceeding maximum length");
		emit terminated(false);
		return false;
	}

	// fixme: could check for data type etc...

	return true;
}
#define BUFFER_SIZE 32768

void KviHttpRequest::processData(KviDataBuffer * data)
{
//	unsigned char obuffer[BUFFER_SIZE];
	if(m_bChunkedTransferEncoding && m_bIgnoreRemainingData)
	{
		// In chunked transfer encoding mode there may be additional headers
		// after the last chunk of data. We simply ignore them.
		return;
	}

	if(!m_bHeaderProcessed)
	{
		// time to process the header
		m_pBuffer->append(*data);
	
		int idx = m_pBuffer->find((const unsigned char *)"\r\n\r\n",4);
		if(idx == -1)
		{
			// header not complete
			if(m_pBuffer->size() > 4096)
			{
				resetInternalStatus();
				m_szLastError = __tr2qs("Header too long: exceeded 4096 bytes");
				emit terminated(false);
			}
			return;
		}
		KviStr szHeader((const char *)(m_pBuffer->data()),idx);
		m_pBuffer->remove(idx + 4);

		if(!processHeader(szHeader))return;
		m_bHeaderProcessed = true;

		if(m_eProcessingType == StoreToFile)
		{
			if(!openFile())return;
		}

		m_uReceivedSize = m_pBuffer->size();
		

		// here the header is complete and the eventual remaining data is in m_pBuffer. data has been already used.
		
	} else {
		// header already processed
		m_uReceivedSize += data->size();
		
		// here the header is complete and some data *might* be already in m_pBuffer. data is unused yet.
		
		// Optimisation: If the transfer is NOT chunked (so we don't have to parse it)
		// and the requested processing type is either Blocks or StoreToFile
		// then we just can avoid to copy the data to m_pBuffer.
		// This is a good optimisation since for large files we can save allocating
		// space for and moving megabytes of data...
		

		if((!m_bChunkedTransferEncoding) && ((m_eProcessingType == Blocks) || (m_eProcessingType == StoreToFile)))
		{	
			switch(m_eProcessingType)
			{
				case Blocks:
					emit binaryData(*data);
				break;
				case StoreToFile:
					m_pFile->writeBlock((const char *)(data->data()),data->size());
				break;
			}
	
			if(((m_uTotalSize > 0) && (m_uReceivedSize > m_uTotalSize)) || ((m_uMaxContentLength > 0) && (m_uReceivedSize > m_uMaxContentLength)))
			{
				resetInternalStatus();
				m_szLastError=__tr2qs("Stream exceeded expected length");
				emit terminated(false);
			}
			
			return;
		}
		
		// need to append to m_pBuffer and process it
		m_pBuffer->append(*data);
	}

	// we're processing data in m_pBuffer here
	if(m_bChunkedTransferEncoding)
	{
		// The transfer encoding is chunked: the buffer contains
		// chunks of data with an initial header composed
		// of a hexadecimal length, an optional bullshit and a single CRLF
		// The transfer terminates when we read a last chunk of size 0
		// that may be followed by optional headers...
		// This sux :)
		while(m_pBuffer->size() > 0) // <-- note that we may exit from this loop also for other conditions (there is a goto below)
		{
			// we process chunks of parts of chunks at a time.
			if(m_uRemainingChunkSize > 0)
			{
				// process the current chunk data
				unsigned int uProcessSize = m_uRemainingChunkSize;
				unsigned int uBufferSize = static_cast<unsigned int>(m_pBuffer->size());
				if(uProcessSize > uBufferSize)uProcessSize = uBufferSize;
				m_uRemainingChunkSize -= uProcessSize;

				switch(m_eProcessingType)
				{
					case Blocks:
						if(uBufferSize == uProcessSize)
						{
							// avoid copying to a new buffer
							emit binaryData(*m_pBuffer);
						} else {
							// must copy
							KviDataBuffer tmp(uProcessSize,m_pBuffer->data());
							emit binaryData(tmp);
							m_pBuffer->remove(uProcessSize);
						}
					break;
					case Lines:
						if(uBufferSize == uProcessSize)
						{
							// avoid copying to a new buffer
							emitLines(m_pBuffer);
						} else {
							// must copy
							KviDataBuffer tmp(uProcessSize,m_pBuffer->data());
							emitLines(&tmp);
							m_pBuffer->remove(uProcessSize);
						}
					break;
					case StoreToFile:
						m_pFile->writeBlock((const char *)(m_pBuffer->data()),uProcessSize);
						m_pBuffer->remove(uProcessSize);
					break;
					default:
						// nothing.. just make gcc happy
					break;
				}
				// now either the buffer is empty or there is another chunk header: continue looping
			} else {
				// We're looking for the beginning of a chunk now.
				// Note that we might be at the end of a previous chunk that has a CRLF terminator
				// we need to skip it.
				int crlf = m_pBuffer->find((const unsigned char *)"\r\n",2);
				if(crlf != -1)
				{
					if(crlf == 0)
					{
						// This is a plain CRLF at the beginning of the buffer BEFORE a chunk header.
						// It comes from the previous chunk terminator. Skip it.
						m_pBuffer->remove(2);
					} else {
						// got a chunk header
						KviStr szHeader((const char *)(m_pBuffer->data()),crlf);
						szHeader.cutFromFirst(' ');
						// now szHeader should contain a hexadecimal chunk length... (why the hell it is hex and not decimal ????)
						TQString szHexHeader = szHeader.ptr();
						bool bOk;
						m_uRemainingChunkSize = szHexHeader.toLong(&bOk,16);
						if(!bOk)
						{
							resetInternalStatus();
							m_szLastError = __tr2qs("Protocol error: invalid chunk size");
							emit terminated(false);
							return;
						}
						m_pBuffer->remove(crlf+2);
						if(m_uRemainingChunkSize == 0)
						{
							// this is the last chunk of data. It may be followed by optional headers
							// but we actually don't need them (since we're surely not in HEAD mode)
							m_bIgnoreRemainingData = true;
							m_pBuffer->clear();
							goto check_stream_length;
						}
					}
					// the rest is valid data of a non-zero chunk: continue looping
				} else {
					// chunk header not complete
					if(m_pBuffer->size() > 4096)
					{
						resetInternalStatus();
						m_szLastError = __tr2qs("Chunk header too long: exceeded 4096 bytes");
						emit terminated(false);
						return;
					}
					goto check_stream_length;
				}
			}
		}
	} else {
		// the transfer encoding is not chunked: m_pBuffer contains only valid data
		switch(m_eProcessingType)
		{
			case Blocks:
				if(m_pBuffer->size() > 0)emit binaryData(*m_pBuffer);
				m_pBuffer->clear();
			break;
			case Lines:
				if(m_pBuffer->size() > 0)emitLines(m_pBuffer);
			break;
			case StoreToFile:
				m_pFile->writeBlock((const char *)(m_pBuffer->data()),m_pBuffer->size());
				m_pBuffer->clear();
			break;
			default:
				// nothing.. just make gcc happy
			break;
		}
	}

check_stream_length:

	if(((m_uTotalSize > 0) && (m_uReceivedSize > m_uTotalSize)) || ((m_uMaxContentLength > 0) && (m_uReceivedSize > m_uMaxContentLength)))
	{
		resetInternalStatus();
		m_szLastError=__tr2qs("Stream exceeded expected length");
		emit terminated(false);
	}
	return;
}
















KviHttpRequestThread::KviHttpRequestThread(
	KviHttpRequest * r,
	const TQString &szHost,
	const TQString &szIp,
	unsigned short uPort,
	const TQString & szPath,
	unsigned int uContentOffset,
	RequestMethod m,
	const TQString &szPostData,
	bool bUseSSL
) : KviSensitiveThread()
{
	m_pRequest = r;
	m_szHost = szHost;
	m_szIp = szIp;
	m_szPath = szPath;
	m_uPort = uPort > 0 ? uPort : 80;
	m_uContentOffset = uContentOffset;
	m_eRequestMethod = m;
	m_szPostData = szPostData;
	m_sock = KVI_INVALID_SOCKET;
	m_bUseSSL = bUseSSL;
#ifdef COMPILE_SSL_SUPPORT
	m_pSSL = 0;
#endif
}

KviHttpRequestThread::~KviHttpRequestThread()
{
}

bool KviHttpRequestThread::processInternalEvents()
{
	while(KviThreadEvent *e = dequeueEvent())
	{
		switch(e->id())
		{
			case KVI_THREAD_EVENT_TERMINATE:
			{
				delete e;
				return false;
			}
			break;
			default:
				tqDebug("Unrecognized event in http thread");
				delete e;
				return false;
			break;
		}
	}

	return true;
}

bool KviHttpRequestThread::failure(const char *error)
{
	if(error)
	{
		postEvent(m_pRequest,new KviThreadDataEvent<KviStr>(KVI_THREAD_EVENT_ERROR,new KviStr(error)));
	} /*else {
		postEvent(m_pRequest,new KviThreadDataEvent<KviStr>(KVI_THREAD_EVENT_ERROR,new KviStr(__tr2qs("Aborted"))));
	}*/
	return false;
}


bool KviHttpRequestThread::selectForWrite(int iTimeoutInSecs)
{

	kvi_time_t startTime = kvi_unixTime();

	for(;;)
	{
		if(!processInternalEvents())
		{
			return failure(0);
		}

		fd_set writeSet;
	
		FD_ZERO(&writeSet);

		FD_SET(m_sock,&writeSet);

		struct timeval tmv;
		tmv.tv_sec  = 0;
		tmv.tv_usec = 1000; // we wait 1000 usecs for an event
	

		int nRet = kvi_socket_select(m_sock + 1,0,&writeSet,0,&tmv);
	
		if(nRet > 0)
		{
			if(FD_ISSET(m_sock,&writeSet))
			{
				// connected!
				return true;
			}
		} else {
			if(nRet < 0)
			{
				int err = kvi_socket_error();
#ifdef COMPILE_ON_WINDOWS
				if((err != EAGAIN) && (err != EINTR) && (err != WSAEWOULDBLOCK))
#else
				if((err != EAGAIN) && (err != EINTR))
#endif
				{
					return failure(KviError::getUntranslatedDescription(KviError::translateSystemError(err)));
				}
			}
		}


		if((time(0) - startTime) > iTimeoutInSecs)return failure(__tr_no_lookup("Operation timed out"));

		usleep(100000); // 1/10 sec
	}

	return false;
}

bool KviHttpRequestThread::sslFailure()
{
#ifdef COMPILE_SSL_SUPPORT
	KviStr buffer;
	if(m_pSSL->getLastErrorString(buffer))
	{
		failure(buffer.ptr());
	} else {
		failure(__tr_no_lookup("Unexpected SSL error"));
	}
#endif
	return false;
}

bool KviHttpRequestThread::connectToRemoteHost()
{
	m_sock = kvi_socket_create(KVI_SOCKET_PF_INET,KVI_SOCKET_TYPE_STREAM,0); //tcp
	if(m_sock == KVI_INVALID_SOCKET)
		return failure(__tr_no_lookup("Failed to create the socket"));

	if(!kvi_socket_setNonBlocking(m_sock))
		return failure(__tr_no_lookup("Failed to enter non blocking mode"));

	sockaddr_in saddr;

	if(!KviNetUtils::stringIpToBinaryIp(m_szIp,&(saddr.sin_addr)))
		return failure(__tr_no_lookup("Invalid target address"));

	saddr.sin_port = htons(m_uPort);
	saddr.sin_family = AF_INET;

	if(!kvi_socket_connect(m_sock,(struct sockaddr *)&saddr,sizeof(saddr)))
	{
		int err = kvi_socket_error();
		if(!kvi_socket_recoverableConnectError(err))
		{
			return failure(KviError::getUntranslatedDescription(KviError::translateSystemError(err)));
		}
	}

	// now loop selecting for write

	//#warning "This should be a tuneable timeout"
	if(!selectForWrite(60))return false;

	int sockError;
	int iSize=sizeof(sockError);
	if(!kvi_socket_getsockopt(m_sock,SOL_SOCKET,SO_ERROR,(void *)&sockError,&iSize))sockError = -1;
	if(sockError != 0)
	{
		//failed
		if(sockError > 0)sockError = KviError::translateSystemError(sockError);
		else sockError = KviError_unknownError;
		return failure(KviError::getUntranslatedDescription(sockError));
	}

#ifdef COMPILE_SSL_SUPPORT
	if(m_bUseSSL)
	{
		m_pSSL = new KviSSL();
		if(!m_pSSL->initContext(KviSSL::Client))
			return failure(__tr_no_lookup("Failed to initialize the SSL context"));
		if(!m_pSSL->initSocket(m_sock))
			return failure(__tr_no_lookup("Failed to initialize the SSL connection"));
		
		for(;;)
		{
			switch(m_pSSL->connect())
			{
				case KviSSL::Success:
					// done: connected.
					return true;
				break;
				case KviSSL::WantRead:
					if(!selectForRead(60))return false;
				break;
				case KviSSL::WantWrite:
					if(!selectForWrite(60))return false;
				break;
				case KviSSL::RemoteEndClosedConnection:
					return failure(__tr_no_lookup("Remote end has closed the connection"));
				break;
				case KviSSL::SSLError:
					return sslFailure();
				break;
				case KviSSL::SyscallError:
				{
					// syscall problem
					int err = kvi_socket_error();
					if(!kvi_socket_recoverableError(err))
					{
						// Declare problems :)
						return failure(__tr_no_lookup("Unrecoverable SSL error during handshake"));
					} // else can recover ? (EAGAIN , EINTR ?) ... should select for read or for write 
				}
				break;
				default:
					return sslFailure();
				break;
			}
		}
	
		// never here
		return true;
	}
#endif

	return true;
}


bool KviHttpRequestThread::sendBuffer(const char * buffer,int bufLen,int iTimeoutInSecs)
{
	const char * ptr = buffer;
	int curLen       = bufLen;

	time_t startTime = time(0);

	for(;;)
	{
		if(!processInternalEvents())return failure();

		int wrtn;
#ifdef COMPILE_SSL_SUPPORT
		if(m_pSSL)
		{
			wrtn = m_pSSL->write((char *)ptr,curLen);
		} else {
#endif
			wrtn = kvi_socket_send(m_sock,ptr,curLen);
#ifdef COMPILE_SSL_SUPPORT
		}
#endif

		if(wrtn > 0)
		{
			curLen -= wrtn;

			if(curLen <= 0)break;

			ptr += wrtn;
		} else {
			if(wrtn < 0)
			{
#ifdef COMPILE_SSL_SUPPORT
				if(m_pSSL)
				{
					// ops...might be an SSL error
					switch(m_pSSL->getProtocolError(wrtn))
					{
						case KviSSL::WantWrite:
							if(!selectForWrite(60))return false;
							break;
						case KviSSL::WantRead:
							if(!selectForRead(60))return false;
							break;
						case KviSSL::SyscallError:
							if(wrtn == 0)
							{
								return failure(__tr_no_lookup("Remote end has closed the connection"));
							} else {
								int iSSLErr = m_pSSL->getLastError(true);
								if(iSSLErr != 0)
								{
									return sslFailure();
								} else {
									goto handle_system_error;
								}
							}
						break;
						case KviSSL::SSLError:
							return sslFailure();
						break;
						default:
							return sslFailure();
						break;
					}
				} else {
#endif //COMPILE_SSL_SUPPORT

handle_system_error:
					int err = kvi_socket_error();
#ifdef COMPILE_ON_WINDOWS
					if((err != EAGAIN) && (err != EINTR) && (err != WSAEWOULDBLOCK))
#else
					if((err != EAGAIN) && (err != EINTR))
#endif
					{
						return failure(KviError::getUntranslatedDescription(KviError::translateSystemError(err)));
					}
#ifdef COMPILE_SSL_SUPPORT
				}
#endif
			}
		}

		int diff = time(0) - startTime;
		if(diff > iTimeoutInSecs)
			return failure(__tr_no_lookup("Operation timed out"));

		usleep(10000);
	}

	return true;
}


int KviHttpRequestThread::selectForReadStep()
{
	// calls select on the main socket
	// returns 1 if there is data available for reading
	// returns 0 if there is no data available but there was no error
	// returns -1 if there was a critical error (socket closed)
	fd_set readSet;

	FD_ZERO(&readSet);

	FD_SET(m_sock,&readSet);

	struct timeval tmv;
	tmv.tv_sec  = 0;
	tmv.tv_usec = 1000; // we wait 1000 usecs for an event


	int nRet = kvi_socket_select(m_sock + 1,&readSet,0,0,&tmv);

	if(nRet > 0)
	{
		if(FD_ISSET(m_sock,&readSet))
		{
			// ok
			return 1;
		}
	} else {
		if(nRet < 0)
		{
			int err = kvi_socket_error();
#ifdef COMPILE_ON_WINDOWS
			if((err != EAGAIN) && (err != EINTR) && (err != WSAEWOULDBLOCK))
#else
			if((err != EAGAIN) && (err != EINTR))
#endif
			{
				failure(KviError::getUntranslatedDescription(KviError::translateSystemError(err)));
				return -1;
			}
		}
	}

	return 0;
}


bool KviHttpRequestThread::selectForRead(int iTimeoutInSecs)
{
	// waits for some data to arrive on the socket
	// up to iTimeoutInSecs seconds
	// returns true if data is available on the socket
	// or false if there was a select() error or no data
	// was available in the specified amount of time

	time_t startTime = time(0);

	for(;;)
	{
		if(!processInternalEvents())
		{
			return failure(); // ensure that the socket is closed
		}

		int nRet = selectForReadStep();

		if(nRet < 0)return false;
		if(nRet > 0)return true;

		int diff = time(0) - startTime;
		if(diff > iTimeoutInSecs)
			return failure(__tr_no_lookup("Operation timed out (while selecting for read)"));

		usleep(100000); // 1/10 sec
	}

	return false;
}

bool KviHttpRequestThread::readDataStep()
{
	unsigned char buffer[2048];
	int readed;

	
#ifdef COMPILE_SSL_SUPPORT
	if(m_pSSL)
	{
		readed = m_pSSL->read((char *)buffer,2048);
		if(readed <= 0)
		{
			// ssl error....?
			switch(m_pSSL->getProtocolError(readed))
			{
				case KviSSL::ZeroReturn:
					readed = 0;
				break;
				case KviSSL::WantRead:
					return selectForRead(120);
				break;
				case KviSSL::WantWrite:
					return selectForWrite(120);
				break;
				case KviSSL::SyscallError:
				{
					int iE = m_pSSL->getLastError(true);
					if(iE != 0)return sslFailure();
				}
				break;
				case KviSSL::SSLError:
					return sslFailure();
				break;
				default:
					return sslFailure();
				break;
			}
		}
	} else {
#endif
		readed = kvi_socket_read(m_sock,buffer,2048);
#ifdef COMPILE_SSL_SUPPORT
	}
#endif

	if(readed > 0)
	{
		postEvent(m_pRequest,new KviThreadDataEvent<KviDataBuffer>(KVI_THREAD_EVENT_BINARYDATA,new KviDataBuffer(readed,buffer)));
	} else {
		if(readed < 0)
		{
			// Read error ?
			int err = kvi_socket_error();
#ifdef COMPILE_ON_WINDOWS
			if((err != EAGAIN) && (err != EINTR) && (err != WSAEWOULDBLOCK))
#else
			if((err != EAGAIN) && (err != EINTR))
#endif
			{
				// yes...read error
				return failure(KviError::getUntranslatedDescription(KviError::translateSystemError(err)));
			}
			return selectForRead(120); // EINTR or EAGAIN...transient problem
		} else {
			// readed == 0
			// Connection closed by remote host
			postEvent(m_pRequest,new KviThreadEvent(KVI_THREAD_EVENT_SUCCESS));
			return false;
		}
	}
	return selectForRead(120);
}

void KviHttpRequestThread::run()
{
	// setup:
	//    nothing needed
	
	// run:
	runInternal();
	
	// cleanup:
#ifdef COMPILE_SSL_SUPPORT
	if(m_pSSL)
	{
		delete m_pSSL;
		m_pSSL = 0;
	}
#endif

	if(kvi_socket_isValid(m_sock))
	{
		kvi_socket_close(m_sock);
		m_sock = KVI_INVALID_SOCKET;
	}
}

void KviHttpRequestThread::runInternal()
{
#ifndef COMPILE_SSL_SUPPORT
	if(m_bUseSSL)
	{
		failure(__tr_no_lookup("This KVIrc executable has no SSL support"));
		return;
	}
#endif

	if(!connectToRemoteHost())return;

	postEvent(m_pRequest,new KviThreadEvent(KVI_HTTP_REQUEST_THREAD_EVENT_CONNECTED));

	// FIXME: Other headers ?

	KviStr szMethod;
	switch(m_eRequestMethod)
	{
		case Head: szMethod = "HEAD"; break;
		case Post: szMethod = "POST"; break;
		case Get: szMethod = "GET"; break;
	}
	
	KviStr szRequest(KviStr::Format,"%s %s HTTP/1.1\r\n" \
				"Host: %s\r\n" \
				"Connection: Close\r\n" \
				"User-Agent: KVIrc-http-slave/1.0.0\r\n" \
				"Accept: */*\r\n",
				szMethod.ptr(),KviTQString::toUtf8(m_szPath).data(),KviTQString::toUtf8(m_szHost).data());

	if(m_uContentOffset > 0)
		szRequest.append(KviStr::Format,"Range: bytes=%u-\r\n",m_uContentOffset);

	if(m_eRequestMethod == Post)
	{
		szRequest.append(KviStr::Format,"Content-Type: application/x-www-form-urlencoded\r\n" \
				"Content-Length: %u\r\n" \
				"Cache-control: no-cache\r\n" \
				"Pragma: no-cache\r\n",m_szPostData.length());
	}

	szRequest += "\r\n";

	if(m_eRequestMethod == Post)
	{
		if(!m_szPostData.isEmpty())
			szRequest.append(m_szPostData);
		szRequest += "\r\n";
	}

	//tqDebug("SENDING REQUEST:\n%s",szRequest.ptr());

	if(!sendBuffer(szRequest.ptr(),szRequest.len(),60))return;

	// now loop reading data
	postEvent(m_pRequest,new KviThreadDataEvent<TQString>(KVI_HTTP_REQUEST_THREAD_EVENT_REQUESTSENT,new TQString(szRequest)));

	for(;;)
	{
		if(!readDataStep())return;
	}
}

#include "kvi_http.moc"
