Implements a Proxy detection (WPAD) interface for Linux, Mac OSX and Windows. Offers a GUI for manual proxy settings and automatic WPAD detection. The GUI is based on QT.
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
590 lines
21 KiB
590 lines
21 KiB
/*! @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 <QtNetwork/QNetworkProxy> |
|
#include <QtNetwork/QNetworkAccessManager> |
|
#include <QtNetwork/QNetworkReply> |
|
#include <QtNetwork/QAuthenticator> |
|
#include <QtNetwork/QSslError> |
|
#include <QtNetwork/QHostInfo> |
|
#include <QtCore/QTimer> |
|
#include <QtCore/QDebug> |
|
#include <QtCore/QThread> |
|
#pragma GCC diagnostic pop |
|
#include <map> |
|
#ifndef PROXYFACE_LOG |
|
#define PROXYFACE_LOG qDebug()<<__PRETTY_FUNCTION__ |
|
#endif |
|
#endif |
|
|
|
#include <list> |
|
#include <string> |
|
#include <sstream> |
|
|
|
//! 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 //<SOCKS5 proxy |
|
}; |
|
|
|
//! One proxy |
|
struct Proxy { |
|
Proxy(): type(DIRECT), port(0) {} |
|
Proxy(Type t, std::string h, unsigned int p): type(t), host(h), port(p) {} |
|
Proxy(Type t, std::string h, std::string p): type(t), host(h), port(0) { |
|
std::stringstream(p)>>port; |
|
} |
|
Type type; |
|
std::string host; |
|
unsigned int port; |
|
}; |
|
|
|
//! List of proxies, type -> url and port |
|
typedef std::list<Proxy> 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="<<url.data()<<"timeout1="<<timeout1; |
|
reset(); |
|
_url = url; |
|
_direct = true; // first try direct access |
|
if (getenv("HTTP_PROXY")) { |
|
QUrl proxy(QString(getenv("HTTP_PROXY"))); |
|
QNetworkProxy envProxy(QNetworkProxy::HttpProxy, |
|
proxy.host(), (quint16)proxy.port(), |
|
proxy.userName(), proxy.password()); |
|
setupProxyCheck(envProxy, url); |
|
} |
|
QNetworkProxy directProxy(QNetworkProxy::NoProxy); |
|
setupProxyCheck(directProxy, url); |
|
QNetworkProxy defaultProxy; |
|
setupProxyCheck(defaultProxy, url); |
|
_timeout2.setSingleShot(true); |
|
_timeout2.setInterval(timeout2); |
|
_timeout1.setSingleShot(true); |
|
_timeout1.setInterval(timeout1); |
|
_timeout1.start(); |
|
} |
|
|
|
//! If Qt Network is available you may check the proxies found |
|
/*! @copydoc proxy(const std::string&, int) */ |
|
void proxy(const QUrl& url, int timeout1=5000, int timeout2=30000) { |
|
PROXYFACE_LOG; |
|
proxy(url.toString().toStdString(), timeout1, timeout2); |
|
} |
|
|
|
//! If Qt Network is available you may check the proxies found |
|
/*! @copydoc proxy(const std::string&, int) */ |
|
void proxy(const QString& url, int timeout1=5000, int timeout2=30000) { |
|
PROXYFACE_LOG; |
|
proxy(url.toStdString(), timeout1, timeout2); |
|
} |
|
|
|
static QString toString(const QNetworkProxy& p) { |
|
switch (p.type()) { |
|
case QNetworkProxy::NoProxy: |
|
return QString("direct://"); |
|
case QNetworkProxy::DefaultProxy: |
|
if (QNetworkProxy::applicationProxy().type() |
|
!=QNetworkProxy::DefaultProxy) |
|
return QString("Default Proxy: %1") |
|
.arg(toString(QNetworkProxy::applicationProxy())); |
|
else |
|
return QString("Default Proxy: **ERROR**" |
|
" application proxy is configured as" |
|
" QNetworkProxy::DefaultProxy"); |
|
case QNetworkProxy::Socks5Proxy: |
|
return QString("socks://%1:%2").arg(p.hostName()).arg(p.port()); |
|
case QNetworkProxy::HttpProxy: |
|
return QString("http://%1:%2").arg(p.hostName()).arg(p.port()); |
|
case QNetworkProxy::HttpCachingProxy: |
|
return QString("http-cache://%1:%2").arg(p.hostName()) |
|
.arg(p.port()); |
|
case QNetworkProxy::FtpCachingProxy: |
|
return QString("ftp-cache://%1:%2").arg(p.hostName()).arg(p.port()); |
|
} |
|
return QString("**ERROR** illegal proxy type"); |
|
} |
|
|
|
Q_SIGNALS: |
|
|
|
//! Signals a valid proxy for the URL requested earlier. |
|
void proxyFound(const QUrl&, const QNetworkProxy&); |
|
//! Signals a timeout, and no valid proxy for the URL requested earlier. |
|
void proxyError(QNetworkReply::NetworkError); |
|
//! Signals an error during proxy detection. |
|
void temporaryError(QNetworkReply::NetworkError, QString, QString); |
|
void authenticationRequired(QNetworkReply*, QAuthenticator*); |
|
void proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*); |
|
|
|
private: |
|
|
|
void setupProxyCheck(const QNetworkProxy& prxy, const std::string& url) { |
|
PROXYFACE_LOG; |
|
qDebug()<<"Testing proxy for url:"<<toString(prxy)<<"url="<<url.data(); |
|
QNetworkAccessManager* manager(new QNetworkAccessManager); |
|
QNetworkProxy p(prxy.type()==QNetworkProxy::DefaultProxy |
|
?QNetworkProxy::applicationProxy():prxy); |
|
qDebug()<<"Testing proxy for url:"<<toString(p)<<"url="<<url.data(); |
|
manager->setProxy(p); |
|
if (!connect(manager, SIGNAL(finished(QNetworkReply*)), |
|
SLOT(replyFinished(QNetworkReply*)))) |
|
qFatal("connect failed"); |
|
if (!connect(manager, |
|
SIGNAL(authenticationRequired(QNetworkReply*, |
|
QAuthenticator*)), |
|
SIGNAL(authenticationRequired(QNetworkReply*, |
|
QAuthenticator*)))) |
|
qFatal("connect failed"); |
|
if (!connect(manager, |
|
SIGNAL(proxyAuthenticationRequired |
|
(const QNetworkProxy&, QAuthenticator*)), |
|
SIGNAL(proxyAuthenticationRequired |
|
(const QNetworkProxy&, QAuthenticator*)))) |
|
qFatal("connect failed"); |
|
if (!connect(manager, |
|
SIGNAL(proxyAuthenticationRequired |
|
(const QNetworkProxy&, QAuthenticator*)), |
|
SLOT(proxyAuthenticationRequiredLog |
|
(const QNetworkProxy&, QAuthenticator*)))) |
|
qFatal("connect failed"); |
|
if (!connect(manager, |
|
SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError>&)), |
|
SLOT(sslErrors(QNetworkReply*, const QList<QSslError>&)))) |
|
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<QSslError>&)), |
|
this, |
|
SLOT(sslErrors(QNetworkReply*, const QList<QSslError>&))); |
|
//delete manager; |
|
} |
|
|
|
private Q_SLOTS: |
|
|
|
void uploadProgress(qint64 a, qint64 b) { |
|
qDebug()<<"upload: "<<a<<"/"<<b; |
|
} |
|
|
|
void downloadProgress(qint64 a, qint64 b) { |
|
qDebug()<<"download: "<<a<<"/"<<b; |
|
} |
|
|
|
void proxyAuthenticationRequiredLog(const QNetworkProxy&, |
|
QAuthenticator* auth) { |
|
qDebug()<<"proxyAuthenticationRequired for "<<auth->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:" |
|
<<reply->error()<<reply->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="<<url<<"proxy="<<toString(prxy); |
|
proxyFound(url, prxy); |
|
} |
|
|
|
void sslErrors(QNetworkReply*, const QList<QSslError>& l) { |
|
PROXYFACE_LOG; |
|
qDebug()<<"## "<<__PRETTY_FUNCTION__; |
|
for (QList<QSslError>::const_iterator it(l.begin()); it!=l.end(); ++it) |
|
qDebug()<<" SSL-Error -> "<<it->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 |
|
<QNetworkReply*, std::pair<QNetworkAccessManager*, QNetworkProxy> > |
|
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 <proxyface/qtproxy.hxx> |
|
namespace proxy { |
|
typedef QtProxy Face; |
|
} |
|
# else |
|
// use windoze proprietary winhttp |
|
# include <proxyface/windoze.hxx> |
|
namespace proxy { |
|
typedef Windoze Face; |
|
} |
|
# endif |
|
# else |
|
# if HAVE_QT == 1 |
|
// use Qt if available (not yet linux) |
|
# include <proxyface/qtproxy.hxx> |
|
namespace proxy { |
|
typedef QtProxy Face; |
|
} |
|
# else |
|
// no Qt support, nor proprietary: use http://code.google.com/p/libproxy |
|
# include <proxyface/unix.hxx> |
|
namespace proxy { |
|
typedef Unix Face; |
|
} |
|
# endif |
|
# endif |
|
|
|
#endif
|
|
|