/*! @file @id $Id$ */ // 1 2 3 4 5 6 7 8 // 45678901234567890123456789012345678901234567890123456789012345678901234567890 #ifndef PROXYFACE_HXX #define PROXYFACE_HXX #if HAVE_QT == 1 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #include #include #include #include #include #include #include #include #include #pragma GCC diagnostic pop #include #ifndef PROXYFACE_LOG #define PROXYFACE_LOG qDebug()<<__PRETTY_FUNCTION__ #endif #endif #include #include #include //! auto proxy configuration namespace proxy { std::string version(); //! exceptions namespace exc { //! unspecific error class error: std::exception { public: virtual const char* what() const throw() { return "Auto Proxy Detection Error"; } }; } //! Proxy types enum Type { DIRECT, //< direct connection, no proxy DEFAULT, //< default proxy HTTP, //< HTTP proxy host SOCKS //>port; } Type type; std::string host; unsigned int port; }; //! List of proxies, type -> url and port typedef std::list List; /*! @page dyn Interaction Diagrams @section dynState State Diagram @dot digraph A { // actors "start" [shape="circle"]; // loops subgraph clusterMainLoop { label="Main Loop"; "«thread loop»"; "«timer loop»"; "«network loop»"; } node [shape=box]; // method declarations "Interface::proxy" [URL="\ref Interface::proxy"]; "Interface::ping" [URL="\ref Interface::ping"]; "Interface::run" [URL="\ref Interface::run"]; "Interface::setupProxyCheck" [URL="\ref Interface::setupProxyCheck"]; // slot declarations { node [shape=octagon]; "Interface::timeout" [URL="\ref Interface::timeout"]; "Interface::threadFinished" [URL="\ref Interface::threadFinished"]; "Interface::replyFinished" [URL="\ref Interface::replyFinished"]; "Interface::uploadProgress" [URL="\ref Interface::uploadProgress"]; "Interface::downloadProgress" [URL="\ref Interface::downloadProgress"]; "Interface::sslErrors" [URL="\ref Interface::sslErrors"]; } // signal declarations { node [shape=invhouse]; "Interface::ProxyError" [URL="\ref Interface::ProxyError"]; "Interface::authenticationRequired" [URL="\ref Interface::authenticationRequired"]; "Interface::proxyAuthenticationRequired" [URL="\ref Interface::proxyAuthenticationRequired"]; "Interface::temporaryError" [URL="\ref Interface::temporaryError"]; "Interface::proxyFound" [URL="\ref Interface::proxyFound"]; } // method interactions "start" -> { "Interface::proxy" "Interface::ping"}; "Interface::ping" -> "Interface::setupProxyCheck"; "Interface::proxy" -> "Interface::timeout" [label="for test only:\ndirect call, no _timeout1"]; "Interface::run" -> "«thread loop»" [label="proxies(url)",URL="\ref Interface::proxies"]; "Interface::setupProxyCheck" -> "Interface::threadFinished"; // networkmanager "Interface::setupProxyCheck" -> "QNetworkAccessManager" [label="new"]; "QNetworkAccessManager" -> "«network loop»" [label="QNetworkReply* QNetworkAccessManager::get()"]; // slot interactions "Interface::timeout" -> "Interface::run" [label="first time\n(own thread)"]; "Interface::timeout" -> "Interface::ProxyError" [label="second time"]; "Interface::threadFinished" -> "Interface::setupProxyCheck" [label="1.) for all proxies"]; "Interface::threadFinished" -> "«timer loop»" [label="2.) _timeout2.start()"]; "Interface::replyFinished" -> "Interface::temporaryError" [label="error"]; "Interface::temporaryError" -> "«network loop»" [label="wait for timeout"]; "Interface::replyFinished" -> "Interface::proxyFound" [label="success"]; // signaling objects "«timer loop»" -> "Interface::timeout" [label="_timeout1",URL="\ref Interface::_timeout1"]; "«timer loop»" -> "Interface::timeout" [label="_timeout2",URL="\ref Interface::_timeout2"]; "«thread loop»" -> { "Interface::threadFinished"; } [label="finished"]; "«network loop»" -> { "Interface::replyFinished"; "Interface::uploadProgress"; "Interface::downloadProgress"; "Interface::authenticationRequired"; "Interface::proxyAuthenticationRequired"; "Interface::sslErrors"; } } @enddot @section dynMSC Message State Chart @msc |||; application, Interface, QNetworkAccessManager, QNetworkReply; application -> Interface [label="Interface()"]; application -> Interface [label="proxy(url)"]; |||; --- [label="for each proxy"]; Interface -> QNetworkAccessManager [label="new"]; QNetworkAccessManager -> QNetworkReply [label="get(url)"]; Interface -> Interface [label="_timeout2.start()"]; --- [label=""]; |||; QNetworkReply -> Interface [label="replyFinished()"]; Interface -> application [label="proxyFound()"]; |||; @endmsc */ //! Unified Interface for accessing proxy::Face /*! Abstract interface, impementation for Unix and Windoze differs. Instanciate proxy::Face, which is a typedef to your platform's implementation. @code proxy::Face pf; // keep for program life time (because of caching) proxy::List pf.proxies("http://swisssign.com"); [...] // set proxy, read from http://swisssign.com @endcode @example getproxylist.cxx */ class Interface #if HAVE_QT == 1 : public QThread #endif { #if HAVE_QT == 1 Q_OBJECT #endif public: //! Keep your instance as long as possible, because of caching. Interface() #if HAVE_QT == 1 : _timeout1Paused(false), _timeout2Paused(false) #endif { #if HAVE_QT == 1 PROXYFACE_LOG; if (!connect(&_timeout1, SIGNAL(timeout()), SLOT(timeout()))) qFatal("connect failed"); if (!connect(&_timeout2, SIGNAL(timeout()), SLOT(timeout()))) qFatal("connect failed"); #ifndef Q_OS_WIN32 if (!connect(this, SIGNAL(finished()), SLOT(threadFinished()))) qFatal("connect failed"); #endif #endif } virtual ~Interface() {} //! Get list of proxies for a given URL. virtual List proxies(const std::string& url) = 0; #if HAVE_QT == 1 //! Reset, stop all outstanding checks void reset() { _timeout1.stop(); _timeout2.stop(); for (Requests::iterator it(_requests.begin()); it!=_requests.end(); ++it) clean(it->second.first); _requests.clear(); } //! Pause timeouts and restart them later. /*! Use while waiting for user input at proxy authentication. @see restart() */ void pause() { if (_timeout1.isActive()) { _timeout1.stop(); _timeout1Paused = true; } if (_timeout2.isActive()) { _timeout2.stop(); _timeout2Paused = true; } } //! Restart paused timeouts. /*! Use while waiting for user input at proxy authentication. @see pause() */ void restart() { if (_timeout1Paused) { _timeout1.start(); _timeout1Paused = false; } if (_timeout2Paused) { _timeout2.start(); _timeout2Paused = false; } } //! Network Ping: Check access to a given address using default proxy. void ping(const std::string& url, int timeout2=30000) { if (_requests.size() || _timeout1.isActive() || _timeout2.isActive()) // detection is already running, wait for that, don't restart return; reset(); _timeout2.setSingleShot(true); _timeout2.setInterval(timeout2); _url = url; _direct = false; // simulate second try QNetworkProxy defaultProxy; setupProxyCheck(defaultProxy, url); _timeout2.start(); } void ping(const QUrl& url, int timeout2=30000) { ping(url.toString().toStdString(), timeout2); } void ping(const QString& url, int timeout2=30000) { ping(url.toStdString(), timeout2); } //! If Qt Network is available you may check the proxies found /*! First checks for direct access to the target. If this is not possible, starts scan for proxies. Returns immediately and signals proxyFound() if a valid configuration has been found, or proxyError() in case of timeout. You should wait for either of the signals, before you can call this method again. @param url the url to find a proxy for @param timeout [ms] time to give up search */ void proxy(const std::string& url, int timeout1=5000, int timeout2=30000) { PROXYFACE_LOG; qDebug()<<"Search proxy for URL, direct and default" <<"url="<&)), SLOT(sslErrors(QNetworkReply*, const QList&)))) qFatal("connect failed"); QNetworkReply* reply(0); _requests.insert (std::make_pair(reply=manager->get (QNetworkRequest (QUrl(QString::fromStdString(url)))), std::make_pair(manager, prxy))); if (!connect(reply, SIGNAL(uploadProgress(qint64, qint64)), SLOT(uploadProgress(qint64, qint64)))) qFatal("connect failed"); if (!connect(reply, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64)))) qFatal("connect failed"); } void clean(QNetworkAccessManager* manager) { if (!disconnect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)))) qFatal("disconnect failed"); disconnect(manager, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)), this, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*))); disconnect(manager, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*)), this, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*))); disconnect(manager, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*)), this, SLOT(proxyAuthenticationRequiredLog(const QNetworkProxy&, QAuthenticator*))); disconnect(manager, SIGNAL(sslErrors(QNetworkReply*, const QList&)), this, SLOT(sslErrors(QNetworkReply*, const QList&))); //delete manager; } private Q_SLOTS: void uploadProgress(qint64 a, qint64 b) { qDebug()<<"upload: "<realm(); } void timeout() { PROXYFACE_LOG; reset(); qDebug()<<"Proxy detection timed out"<<"url="<<_url.data(); if (_direct) { qDebug()<<"Direct or preconfigured proxy not available," " try autoproxy negotiation"; _direct = false; #ifndef Q_OS_WIN32 start(); // autoproxy detection in own thread #else run(); #endif } else { qDebug()<<"No proxy at all, giving up - offline?"; proxyError(QNetworkReply::TimeoutError); } } void replyFinished(QNetworkReply* reply) { PROXYFACE_LOG; qDebug()<<"Proxydetection got reply with status:" <error()<errorString(); if (reply->error()!=QNetworkReply::NoError) { temporaryError(reply->error(), reply->errorString(), toString(_requests[reply].second)); return; // wait for timeout } QNetworkProxy prxy(_requests[reply].second); QUrl url(reply->url()); reset(); qDebug()<<"SUCCESS - Valid proxy found for url:" <<"url="<& l) { PROXYFACE_LOG; qDebug()<<"## "<<__PRETTY_FUNCTION__; for (QList::const_iterator it(l.begin()); it!=l.end(); ++it) qDebug()<<" SSL-Error -> "<errorString(); } void threadFinished() { PROXYFACE_LOG; for (List::const_iterator it(_proxies.begin()); it!=_proxies.end(); ++it) { QNetworkProxy prxy((it->type==DEFAULT?QNetworkProxy::DefaultProxy :(it->type==HTTP?QNetworkProxy::HttpProxy :(it->type==SOCKS?QNetworkProxy::Socks5Proxy :QNetworkProxy::NoProxy))), QString::fromStdString(it->host), (quint16)it->port); setupProxyCheck(prxy, _url); } QNetworkProxy directProxy(QNetworkProxy::NoProxy); setupProxyCheck(directProxy, _url); QNetworkProxy defaultProxy; setupProxyCheck(defaultProxy, _url); _timeout2.start(); } protected: void run() { PROXYFACE_LOG; _proxies = proxies(_url); #ifdef Q_OS_WIN32 threadFinished(); #endif } private: typedef std::map > Requests; Requests _requests; bool _direct; QTimer _timeout1; QTimer _timeout2; bool _timeout1Paused; bool _timeout2Paused; List _proxies; std::string _url; #endif }; } # ifdef WIN32 # ifdef QT_NETWORK_LIB___HAS_A_BUG /// @bug Crashes on Windows Qt 5.2.1 // use Qt if available # include namespace proxy { typedef QtProxy Face; } # else // use windoze proprietary winhttp # include namespace proxy { typedef Windoze Face; } # endif # else # if HAVE_QT == 1 // use Qt if available (not yet linux) # include namespace proxy { typedef QtProxy Face; } # else // no Qt support, nor proprietary: use http://code.google.com/p/libproxy # include namespace proxy { typedef Unix Face; } # endif # endif #endif