//============================================================================= // // File : KvsObject_http.cpp // Creation date : Fry Sep 5 18:13:45 2008 GMT by Carbone Alesssandro // // This file is part of the KVIrc IRC client distribution // Copyright (C) 2009 Alessandro Carbone (elfonol at gmail dot com) // // 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. // // 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. // //============================================================================= #include "KvsObject_http.h" #include "kvi_debug.h" #include "KviError.h" #include "KviLocale.h" // Qt >= 5.0.0 has no QHttp. // We use the external source code module provided by digia (in qthttp) #include #include #ifndef QT_NO_OPENSSL #include const char * const ssl_errors_tbl[] = { "NoError", "UnableToGetIssuerCertificate", "UnableToDecryptCertificateSignature", "UnableToDecodeIssuerPublicKey", "CertificateSignatureFailed", "CertificateNotYetValid", "CertificateExpired", "InvalidNotBeforeField", "InvalidNotAfterField", "SelfSignedCertificate", "SelfSignedCertificateInChain", "UnableToGetLocalIssuerCertificate", "UnableToVerifyFirstCertificate", "CertificateRevoked", "InvalidCaCertificate", "PathLengthExceeded", "InvalidPurpose", "CertificateUntrusted", "CertificateRejected", "SubjectIssuerMismatch", "AuthorityIssuerSerialNumberMismatch", "NoPeerCertificate", "HostNameMismatch", "UnspecifiedError", "NoSslSupport" }; #endif /* @doc: http @keyterms: http object class @title: http class @type: class @short: An implementation of the client side of HTTP protocol. @inherits: [class]object[/class] @description: This class provides a standard HTTP functionality.[br] @functions: !fn: $get(,[]) Sends a get request for path to the server set by [classfnc]$setHost[/classfnc]. If local_file is not specified [b]readyRead()[/b] signal is emitted every time new content data is available to read. !fn: $post(,, Sends a post request for path to the server set by [classfnc]$setHost[/classfnc]. If local_file is not specified [b]readyRead()[/b] signal is emitted every time new content data is available to read. !fn: $abort() Aborts the current request and deletes all scheduled requests. !fn: $setHost(,) Sets the HTTP server that is used for requests to hostName on port port. The function does not block: the request is scheduled, and its execution is performed asynchronously. The function returns a unique identifier which is passed by [b]requestStartedEvent()[/b] and [b]requestFinishedEvent()[/b]. When the request is started the requestStarted() signal is emitted. When it is finished the requestFinished() signal is emitted. !fn: $currentId() Returns the identifier of the HTTP request being executed or [b]0[/b] if there is no request being executed. !fn: $setProxy(,,[,]) Enables HTTP proxy support, using the proxy server host on port port. Optionals parameters user and password can be provided if the proxy server requires authentication. !fn: $setUser(,) This function sets the user name userName and password password for web pages that require authentication. !fn: $readAll() Reads all the bytes from the response content and returns them as string. !fn: $errorString() Returns a human-readable description of the last error that occurred. !fn: $ignoreSslErrors() !fn: $requestFinishedEvent(,) This event is triggered by KVIrc when the request identified by has finished. !fn: $requestStartedEvent(,) This event is triggered by KVIrc when the request identified by has started. !fn: $responseHeaderReceivedEvent() This event is triggered by KVIrc when he HTTP header of a server response is available. The response is passed ad string. !fn: $doneEvent() This event is triggered when the last pending request has finished; (it is emitted after the last requests requestFinished() signal). !fn: $dataReadProgressEvent(,) This event is triggered when reads data from a HTTP server. is the amount of data has already arrived and is the total amount of data. !fn: $dataSendProgressEvent(,) This event is triggered when sends data to a HTTP server. is the amount of data has already arrived and is the total amount of data. !fn: $stateChangedEvent() This event is triggered by the framework when the state of the connection changes. !fn: $readyReadEvent() This event is triggered by the framework when there is new data read and there is not file specified in [b]get[/b] or [b]post[/b]. You can call [b]readAll[/b] to reads all the bytes from the response data. !fn: $sslErrorsEvent() Returns the list of errors that occurred during the SSL handshake. Unless you call ignoreSslErrors() from within a slot connected to the signal emitted by this event, the framework will tear down the connection immediately after emitting the signal. @signals: !sg: $requestStarted() This signal is emitted by the default implementation of [classfnc]$requestStartedEvent[/classfnc]. !sg: $requestFinished() This signal is emitted by the default implementation of [classfnc]$requestFinishedEvent[/classfnc]. !sg: $dataSendProgress() This signal is emitted by the default implementation of [classfnc]$dataSendProgressEvent[/classfnc]. !sg: $dataReadProgress() This signal is emitted by the default implementation of [classfnc]$dataReadProgressEvent[/classfnc]. !sg: $done() This signal is emitted by the default implementation of [classfnc]$doneEvent[/classfnc]. !sg: $readyRead() This signal is emitted by the default implementation of [classfnc]$readyReadEvent[/classfnc]. !sg: $sslErrors() This signal is emitted by the default implementation of [classfnc]$sslErrorsEvent[/classfnc]. !sg: $stateChanged() This signal is emitted by the default implementation of [classfnc]$stateChangedEvent[/classfnc]. !sg: $responseHeaderReceived() This signal is emitted by the default implementation of [classfnc]$responseHeaderReceivedEvent[/classfnc]. */ KVSO_BEGIN_REGISTERCLASS(KvsObject_http, "http", "object") KVSO_REGISTER_HANDLER(KvsObject_http, "get", functionGet) KVSO_REGISTER_HANDLER(KvsObject_http, "post", functionPost) KVSO_REGISTER_HANDLER(KvsObject_http, "abort", functionAbort) KVSO_REGISTER_HANDLER(KvsObject_http, "setHost", functionSetHost) KVSO_REGISTER_HANDLER(KvsObject_http, "setProxy", functionSetProxy) KVSO_REGISTER_HANDLER(KvsObject_http, "currentId", functionCurrentId) KVSO_REGISTER_HANDLER(KvsObject_http, "setUser", functionSetUser) KVSO_REGISTER_HANDLER(KvsObject_http, "readAll", functionReadAll) KVSO_REGISTER_HANDLER(KvsObject_http, "errorString", functionErrorString) KVSO_REGISTER_HANDLER(KvsObject_http, "setFollowRedirect", functionFollowRedirect) // events KVSO_REGISTER_HANDLER(KvsObject_http, "doneEvent", functionDoneEvent) KVSO_REGISTER_HANDLER(KvsObject_http, "requestFinishedEvent", functionRequestFinishedEvent) KVSO_REGISTER_HANDLER(KvsObject_http, "requestStartedEvent", functionRequestStartedEvent) KVSO_REGISTER_HANDLER(KvsObject_http, "responseHeaderReceivedEvent", functionResponseHeaderReceivedEvent) KVSO_REGISTER_HANDLER(KvsObject_http, "dataReadProgressEvent", functionDataReadProgressEvent) KVSO_REGISTER_HANDLER(KvsObject_http, "dataSendProgressEvent", functionDataSendProgressEvent) KVSO_REGISTER_HANDLER(KvsObject_http, "stateChangedEvent", functionStateChangedEvent) KVSO_REGISTER_HANDLER(KvsObject_http, "readyReadEvent", functionReadyReadEvent) #ifndef QT_NO_OPENSSL KVSO_REGISTER_HANDLER(KvsObject_http, "sslErrorsEvent", functionSslErrorsEvent) KVSO_REGISTER_HANDLER(KvsObject_http, "ignoreSSlErrors", functionIgnoreSslErrors) #endif KVSO_END_REGISTERCLASS(KvsObject_http) KVSO_BEGIN_CONSTRUCTOR(KvsObject_http, KviKvsObject) m_pHttp = new QHttp(); m_bAbort = false; m_bEnableForceRedirect = false; connect(m_pHttp, SIGNAL(requestFinished(int, bool)), this, SLOT(slotRequestFinished(int, bool))); connect(m_pHttp, SIGNAL(done(bool)), this, SLOT(slotDone(bool))); connect(m_pHttp, SIGNAL(requestStarted(int)), this, SLOT(slotRequestStarted(int))); connect(m_pHttp, SIGNAL(dataSendProgress(int, int)), this, SLOT(slotDataSendProgress(int, int))); connect(m_pHttp, SIGNAL(dataReadProgress(int, int)), this, SLOT(slotDataReadProgress(int, int))); connect(m_pHttp, SIGNAL(responseHeaderReceived(const QHttpResponseHeader &)), this, SLOT(slotResponseHeaderReceived(const QHttpResponseHeader &))); connect(m_pHttp, SIGNAL(readyRead(const QHttpResponseHeader &)), this, SLOT(slotReadyRead(const QHttpResponseHeader &))); #ifndef QT_NO_OPENSSL connect(m_pHttp, SIGNAL(sslErrors(const QList &)), this, SLOT(slotSslErrors(const QList &))); #endif connect(m_pHttp, SIGNAL(stateChanged(int)), this, SLOT(slotStateChanged(int))); KVSO_END_CONSTRUCTOR(KvsObject_http) KVSO_BEGIN_DESTRUCTOR(KvsObject_http) QHashIterator t(getDict); while(t.hasNext()) { t.next(); int key = t.key(); QFile * pFile = getDict.value(key); pFile->close(); delete pFile; } getDict.clear(); delete m_pHttp; KVSO_END_DESTRUCTOR(KvsObject_http) //---------------------- bool KvsObject_http::functionSetHost(KviKvsObjectFunctionCall * c) { CHECK_INTERNAL_POINTER(m_pHttp) QString szHost; QString szConnectionType; kvs_uint_t uRemotePort; KVSO_PARAMETERS_BEGIN(c) KVSO_PARAMETER("host", KVS_PT_STRING, 0, szHost) KVSO_PARAMETER("remote_port", KVS_PT_UNSIGNEDINTEGER, KVS_PF_OPTIONAL, uRemotePort) KVSO_PARAMETERS_END(c) QUrl url(szHost); if(!url.isValid()) { c->warning(__tr2qs_ctx("Host '%Q' is not a valid URL", "objects"), &szHost); return true; } if(!szHost.isEmpty() && url.host().isEmpty()) url.setHost(szHost); if(!uRemotePort) uRemotePort = 80; kvs_uint_t id = 0; QHttp::ConnectionMode mode; if(url.scheme().toLower() == "https") mode = QHttp::ConnectionModeHttps; else { mode = QHttp::ConnectionModeHttp; url.setScheme("http"); } if(mode == QHttp::ConnectionModeHttps) uRemotePort = 443; id = m_pHttp->setHost(url.host(), mode, uRemotePort); c->returnValue()->setInteger(id); return true; } bool KvsObject_http::functionCurrentId(KviKvsObjectFunctionCall * c) { CHECK_INTERNAL_POINTER(m_pHttp) c->returnValue()->setInteger(m_pHttp->currentId()); return true; } bool KvsObject_http::functionSetUser(KviKvsObjectFunctionCall * c) { CHECK_INTERNAL_POINTER(m_pHttp) QString szUser; QString szPass; KVSO_PARAMETERS_BEGIN(c) KVSO_PARAMETER("user", KVS_PT_STRING, 0, szUser) KVSO_PARAMETER("password", KVS_PT_STRING, 0, szPass) KVSO_PARAMETERS_END(c) m_pHttp->setUser(szUser, szPass); return true; } bool KvsObject_http::functionSetProxy(KviKvsObjectFunctionCall * c) { CHECK_INTERNAL_POINTER(m_pHttp) QString szHost; QString szUser, szPass; kvs_uint_t uRemotePort; KVSO_PARAMETERS_BEGIN(c) KVSO_PARAMETER("host", KVS_PT_STRING, 0, szHost) KVSO_PARAMETER("remote_port", KVS_PT_UNSIGNEDINTEGER, KVS_PF_OPTIONAL, uRemotePort) KVSO_PARAMETER("user", KVS_PT_STRING, KVS_PF_OPTIONAL, szUser) KVSO_PARAMETER("pass", KVS_PT_STRING, KVS_PF_OPTIONAL, szPass) KVSO_PARAMETERS_END(c) m_pHttp->setProxy(szHost, uRemotePort, szUser, szPass); return true; } bool KvsObject_http::functionGet(KviKvsObjectFunctionCall * c) { CHECK_INTERNAL_POINTER(m_pHttp) QString szPath, szDest; KVSO_PARAMETERS_BEGIN(c) KVSO_PARAMETER("remote_path", KVS_PT_STRING, 0, szPath) KVSO_PARAMETER("local_filename", KVS_PT_STRING, KVS_PF_OPTIONAL, szDest) KVSO_PARAMETERS_END(c) QFile * pFile = 0; if(!szDest.isEmpty()) { pFile = new QFile(szDest); if(pFile) { pFile->open(QIODevice::WriteOnly); } else { c->warning(__tr2qs_ctx("'%Q' is not a valid file path", "objects"), &szDest); c->returnValue()->setInteger(-1); return true; } } if(szPath.isEmpty()) szPath = "/"; int id = m_pHttp->get(szPath, pFile); if(pFile) getDict[id] = pFile; c->returnValue()->setInteger(id); return true; } bool KvsObject_http::functionPost(KviKvsObjectFunctionCall * c) { CHECK_INTERNAL_POINTER(m_pHttp) QString szPath, szDest, szData; KVSO_PARAMETERS_BEGIN(c) KVSO_PARAMETER("remote_path", KVS_PT_STRING, 0, szPath) KVSO_PARAMETER("post_data", KVS_PT_STRING, 0, szData) KVSO_PARAMETER("local_filename", KVS_PT_STRING, 0, szDest) KVSO_PARAMETERS_END(c) QFile * pFile = 0; if(!szDest.isEmpty()) { pFile = new QFile(szDest); if(pFile) { pFile->open(QIODevice::WriteOnly); } } if(szPath.isEmpty()) szPath = "/"; int id = m_pHttp->post(szPath, szDest.toUtf8(), pFile); if(pFile) getDict[id] = pFile; c->returnValue()->setInteger(id); return true; } bool KvsObject_http::functionAbort(KviKvsObjectFunctionCall * c) { CHECK_INTERNAL_POINTER(m_pHttp) m_bAbort = true; m_pHttp->abort(); return true; } bool KvsObject_http::functionReadAll(KviKvsObjectFunctionCall * c) { CHECK_INTERNAL_POINTER(m_pHttp) c->returnValue()->setString(m_pHttp->readAll()); return true; } bool KvsObject_http::functionErrorString(KviKvsObjectFunctionCall * c) { CHECK_INTERNAL_POINTER(m_pHttp) c->returnValue()->setString(m_pHttp->errorString()); return true; } bool KvsObject_http::functionFollowRedirect(KviKvsObjectFunctionCall * c) { bool bEnabled; KVSO_PARAMETERS_BEGIN(c) KVSO_PARAMETER("enable", KVS_PT_BOOLEAN, 0, bEnabled) KVSO_PARAMETERS_END(c) m_bEnableForceRedirect = bEnabled; return true; } //signals & slots bool KvsObject_http::functionRequestFinishedEvent(KviKvsObjectFunctionCall * c) { emitSignal("requestFinished", c, c->params()); return true; } void KvsObject_http::slotRequestFinished(int id, bool error) { if(m_bAbort) { m_bAbort = false; QHashIterator t(getDict); while(t.hasNext()) { t.next(); int key = t.key(); QFile * pFile = getDict.value(key); pFile->close(); delete pFile; } getDict.clear(); return; } QFile * pFile = getDict.value(id); if(pFile) { QString name = pFile->fileName(); pFile->close(); getDict.remove(id); int res = m_pHttp->lastResponse().statusCode(); if((res == 301 || res == 302 || res == 307) && m_bEnableForceRedirect) redirect(name, m_pHttp->lastResponse()); delete pFile; } KviKvsVariantList lParams; lParams.append(new KviKvsVariant((kvs_int_t)id)); lParams.append(new KviKvsVariant(error)); callFunction(this, "requestFinishedEvent", 0, &lParams); } bool KvsObject_http::functionRequestStartedEvent(KviKvsObjectFunctionCall * c) { emitSignal("requestStarted", c, c->params()); return true; } void KvsObject_http::slotRequestStarted(int id) { KviKvsVariantList lParams; lParams.append(new KviKvsVariant((kvs_int_t)id)); callFunction(this, "requestStartedEvent", 0, &lParams); } void KvsObject_http::slotDataReadProgress(int done, int total) { KviKvsVariantList lParams; lParams.append(new KviKvsVariant((kvs_int_t)done)); lParams.append(new KviKvsVariant((kvs_int_t)total)); callFunction(this, "dataReadProgressEvent", 0, &lParams); } bool KvsObject_http::functionDataReadProgressEvent(KviKvsObjectFunctionCall * c) { emitSignal("dataReadProgress", c, c->params()); return true; } void KvsObject_http::slotDataSendProgress(int done, int total) { KviKvsVariantList lParams; lParams.append(new KviKvsVariant((kvs_int_t)done)); lParams.append(new KviKvsVariant((kvs_int_t)total)); callFunction(this, "dataSendProgressEvent", 0, &lParams); } bool KvsObject_http::functionDataSendProgressEvent(KviKvsObjectFunctionCall * c) { emitSignal("dataSendProgress", c, c->params()); return true; } bool KvsObject_http::functionDoneEvent(KviKvsObjectFunctionCall * c) { emitSignal("done", c, c->params()); return true; } void KvsObject_http::slotDone(bool error) { KviKvsVariantList lParams; lParams.append(new KviKvsVariant(error)); callFunction(this, "doneEvent", 0, &lParams); } bool KvsObject_http::functionResponseHeaderReceivedEvent(KviKvsObjectFunctionCall * c) { emitSignal("responseHeaderReceived", c, c->params()); return true; } bool KvsObject_http::functionReadyReadEvent(KviKvsObjectFunctionCall * c) { emitSignal("readyRead", c, c->params()); return true; } void KvsObject_http::redirect(QString & file, const QHttpResponseHeader & r) { QUrl url(r.value("location")); m_pHttp->setHost(url.host()); QFile * pFile = 0; pFile = new QFile(file); pFile->open(QIODevice::WriteOnly); int id = m_pHttp->get(url.path(), pFile); getDict[id] = pFile; } void KvsObject_http::slotResponseHeaderReceived(const QHttpResponseHeader & r) { QString szResponse; switch(r.statusCode()) { case 200: szResponse = "OK"; break; case 301: szResponse = "Moved Permanently"; break; case 302: szResponse = "Found"; break; case 303: szResponse = "See Other"; break; case 307: szResponse = "Temporary Redirect"; break; default: szResponse = r.reasonPhrase(); m_bAbort = true; } if(r.statusCode() == 400) { m_bAbort = true; m_pHttp->abort(); } KviKvsVariantList lParams; lParams.append(new KviKvsVariant(szResponse)); callFunction(this, "responseHeaderReceivedEvent", 0, &lParams); } void KvsObject_http::slotReadyRead(const QHttpResponseHeader & r) { QString szResponse; switch(r.statusCode()) { case 200: szResponse = "OK"; break; case 301: szResponse = "Moved Permanently"; break; case 302: szResponse = "Found"; break; case 303: szResponse = "See Other"; break; case 307: szResponse = "Temporary Redirect"; break; default: szResponse = r.reasonPhrase(); m_bAbort = true; } KviKvsVariantList lParams; lParams.append(new KviKvsVariant(szResponse)); callFunction(this, "readyreadEvent", 0, &lParams); } void KvsObject_http::slotStateChanged(int state) { QString szState = ""; if(state == QHttp::Unconnected) szState = "Unconnected"; else if(state == QHttp::HostLookup) szState = "HostLookup"; else if(state == QHttp::Connecting) szState = "Connecting"; else if(state == QHttp::Connected) szState = "Connected"; else if(state == QHttp::Sending) szState = "Sending"; else if(state == QHttp::Reading) szState = "Reading"; else if(state == QHttp::Closing) szState = "Closing"; KviKvsVariantList lParams; lParams.append(new KviKvsVariant(szState)); callFunction(this, "stateChangedEvent", 0, &lParams); } bool KvsObject_http::functionStateChangedEvent(KviKvsObjectFunctionCall * c) { emitSignal("stateChanged", c, c->params()); return true; } #ifndef QT_NO_OPENSSL bool KvsObject_http::functionIgnoreSslErrors(KviKvsObjectFunctionCall *) { if(m_pHttp) m_pHttp->ignoreSslErrors(); return true; } void KvsObject_http::slotSslErrors(QList sslerrors) { KviKvsArray * pArray = 0; pArray = new KviKvsArray(); for(int i = 0; i < sslerrors.count(); i++) { pArray->set(i, new KviKvsVariant(ssl_errors_tbl[sslerrors.at(i).error()])); } KviKvsVariantList lParams; lParams.append(new KviKvsVariant(pArray)); callFunction(this, "sslErrorEvent", 0, &lParams); } bool KvsObject_http::functionSslErrorsEvent(KviKvsObjectFunctionCall * c) { emitSignal("sslErrors", c, c->params()); return true; } #endif