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

/*! @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