summaryrefslogtreecommitdiffstats
path: root/src/kvilib/net/kvi_http.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/kvilib/net/kvi_http.cpp')
-rw-r--r--src/kvilib/net/kvi_http.cpp1440
1 files changed, 1440 insertions, 0 deletions
diff --git a/src/kvilib/net/kvi_http.cpp b/src/kvilib/net/kvi_http.cpp
new file mode 100644
index 00000000..2e94abbe
--- /dev/null
+++ b/src/kvilib/net/kvi_http.cpp
@@ -0,0 +1,1440 @@
+//=============================================================================
+//
+// 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 <qdir.h>
+#include <qtimer.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()
+: QObject()
+{
+ 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 QString &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
+ QFileInfo 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();
+ QTimer::singleShot(10,this,SLOT(haveServerIp()));
+ return true;
+ }
+
+ return startDnsLookup();
+}
+
+bool KviHttpRequest::startDnsLookup()
+{
+ m_pDns = new KviDns();
+ connect(m_pDns,SIGNAL(lookupDone(KviDns *)),this,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;
+ }
+
+ QString tmp;
+ KviQString::sprintf(tmp,__tr2qs("Looking up host %s"),m_url.host().ptr());
+ emit status(tmp); // FIXME
+
+ emit resolvingHost(QString(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;
+ QString tmp;
+ KviQString::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;
+
+ QString tmp;
+ KviQString::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;
+ }
+
+ KviQString::sprintf(tmp,__tr2qs("Contacting host %Q on port %u"),&m_szIp,uPort);
+ emit status(tmp);
+}
+
+bool KviHttpRequest::event(QEvent *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:
+ {
+ QString * req = ((KviThreadDataEvent<QString> *)e)->getData();
+#ifdef COMPILE_USE_QT4
+ QStringList sl = req->split("\r\n");
+#else
+ QStringList sl = QStringList::split("\r\n",*req);
+#endif
+ 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 QObject::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: ";
+// QString 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;
+ QString tmp = m_szFileName;
+ do {
+ i++;
+ m_szFileName = tmp + QString(".kvirnm-%1").arg(i);
+ } while(KviFile::exists(m_szFileName));
+ }
+ break;
+ case RenameExisting:
+ {
+ int i=0;
+ QString tmp;
+ do {
+ i++;
+ tmp = m_szFileName + QString(".kvirnm-%1").arg(i);
+ } while(KviFile::exists(tmp));
+ QDir 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();
+ KviQString::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;
+ }
+
+ QString tmp;
+ KviQString::sprintf(tmp,__tr2qs("Received HTTP response: %s"),szResponse.ptr());
+
+ emit status(tmp);
+ emit receivedResponse(QString(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));
+ //debug("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;
+ if(uProcessSize > m_pBuffer->size())uProcessSize = m_pBuffer->size();
+ m_uRemainingChunkSize -= uProcessSize;
+
+ switch(m_eProcessingType)
+ {
+ case Blocks:
+ if(m_pBuffer->size() == 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(m_pBuffer->size() == 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 ????)
+ QString 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 QString &szHost,
+ const QString &szIp,
+ unsigned short uPort,
+ const QString & szPath,
+ unsigned int uContentOffset,
+ RequestMethod m,
+ const QString &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:
+ debug("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(),KviQString::toUtf8(m_szPath).data(),KviQString::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";
+ }
+
+ //debug("SENDING REQUEST:\n%s",szRequest.ptr());
+
+ if(!sendBuffer(szRequest.ptr(),szRequest.len(),60))return;
+
+ // now loop reading data
+ postEvent(m_pRequest,new KviThreadDataEvent<QString>(KVI_HTTP_REQUEST_THREAD_EVENT_REQUESTSENT,new QString(szRequest)));
+
+ for(;;)
+ {
+ if(!readDataStep())return;
+ }
+}
+