/* Copyright (c) 2003 Malte Starostik Copyright (c) 2011 Dawit Alemayehu This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "proxyscout.h" #include "config-kpac.h" #include "discovery.h" #include "script.h" #include #include #include #include #include #include #ifndef KPAC_NO_SOLID #include #endif #include #include #include #include K_PLUGIN_FACTORY(ProxyScoutFactory, registerPlugin(); ) K_EXPORT_PLUGIN(ProxyScoutFactory("KProxyScoutd")) namespace KPAC { enum ProxyType { Unknown = -1, Proxy, Socks, Direct }; static ProxyType proxyTypeFor(const QString& mode) { if (mode.compare(QLatin1String("PROXY"), Qt::CaseInsensitive) == 0) return Proxy; if (mode.compare(QLatin1String("DIRECT"), Qt::CaseInsensitive) == 0) return Direct; if (mode.compare(QLatin1String("SOCKS"), Qt::CaseInsensitive) == 0 || mode.compare(QLatin1String("SOCKS5"), Qt::CaseInsensitive) == 0) return Socks; return Unknown; } ProxyScout::QueuedRequest::QueuedRequest( const QDBusMessage &reply, const KUrl& u, bool sendall ) : transaction( reply ), url( u ), sendAll(sendall) { } ProxyScout::ProxyScout(QObject* parent, const QList&) : KDEDModule(parent), m_componentData("proxyscout"), m_downloader( 0 ), m_script( 0 ), m_suspendTime( 0 ), m_debugArea (KDebug::registerArea("proxyscout")), m_watcher( 0 ) { #ifndef KPAC_NO_SOLID connect (Solid::Networking::notifier(), SIGNAL(shouldDisconnect()), SLOT(disconnectNetwork())); #endif } ProxyScout::~ProxyScout() { delete m_script; } QStringList ProxyScout::proxiesForUrl( const QString& checkUrl, const QDBusMessage &msg ) { KUrl url(checkUrl); if (m_suspendTime) { if ( std::time( 0 ) - m_suspendTime < 300 ) { return QStringList (QLatin1String("DIRECT")); } m_suspendTime = 0; } // Never use a proxy for the script itself if (m_downloader && url.equals(m_downloader->scriptUrl(), KUrl::CompareWithoutTrailingSlash)) { return QStringList (QLatin1String("DIRECT")); } if (m_script) { return handleRequest(url); } if (m_downloader || startDownload()) { msg.setDelayedReply(true); m_requestQueue.append( QueuedRequest( msg, url, true ) ); return QStringList(); // return value will be ignored } return QStringList(QLatin1String("DIRECT")); } QString ProxyScout::proxyForUrl( const QString& checkUrl, const QDBusMessage &msg ) { KUrl url(checkUrl); if (m_suspendTime) { if ( std::time( 0 ) - m_suspendTime < 300 ) { return QLatin1String("DIRECT"); } m_suspendTime = 0; } // Never use a proxy for the script itself if (m_downloader && url.equals(m_downloader->scriptUrl(), KUrl::CompareWithoutTrailingSlash)) { return QLatin1String("DIRECT"); } if (m_script) { return handleRequest(url).first(); } if (m_downloader || startDownload()) { msg.setDelayedReply(true); m_requestQueue.append( QueuedRequest( msg, url ) ); return QString(); // return value will be ignored } return QLatin1String("DIRECT"); } void ProxyScout::blackListProxy( const QString& proxy ) { m_blackList[ proxy ] = std::time( 0 ); } void ProxyScout::reset() { delete m_script; m_script = 0; delete m_downloader; m_downloader = 0; delete m_watcher; m_watcher = 0; m_blackList.clear(); m_suspendTime = 0; KProtocolManager::reparseConfiguration(); } bool ProxyScout::startDownload() { switch ( KProtocolManager::proxyType() ) { case KProtocolManager::WPADProxy: if (m_downloader && !qobject_cast(m_downloader)) { delete m_downloader; m_downloader = 0; } if (!m_downloader) { m_downloader = new Discovery(this); connect(m_downloader, SIGNAL(result(bool)), this, SLOT(downloadResult(bool))); } break; case KProtocolManager::PACProxy: { if (m_downloader && !qobject_cast(m_downloader)) { delete m_downloader; m_downloader = 0; } if (!m_downloader) { m_downloader = new Downloader(this); connect(m_downloader, SIGNAL(result(bool)), this, SLOT(downloadResult(bool))); } const KUrl url (KProtocolManager::proxyConfigScript()); if (url.isLocalFile()) { if (!m_watcher) { m_watcher = new QFileSystemWatcher(this); connect (m_watcher, SIGNAL(fileChanged(QString)), SLOT(proxyScriptFileChanged(QString))); } proxyScriptFileChanged(url.path()); } else { delete m_watcher; m_watcher = 0; m_downloader->download( url ); } break; } default: return false; } return true; } void ProxyScout::disconnectNetwork() { // NOTE: We only connect to Solid's network notifier's shouldDisconnect // signal because we only want to redo WPAD when a network interface is // brought out of hibernation or restarted for whatever reason. reset(); } void ProxyScout::downloadResult( bool success ) { if ( success ) { try { if (!m_script) { m_script = new Script(m_downloader->script()); } } catch ( const Script::Error& e ) { kWarning() << "Error:" << e.message(); KNotification *notify= new KNotification ( "script-error" ); notify->setText( i18n("The proxy configuration script is invalid:\n%1" , e.message() ) ); notify->setComponentData(m_componentData); notify->sendEvent(); success = false; } } else { KNotification *notify = new KNotification ("download-error"); notify->setText( m_downloader->error() ); notify->setComponentData(m_componentData); notify->sendEvent(); } if (success) { for (RequestQueue::Iterator it = m_requestQueue.begin(), itEnd = m_requestQueue.end(); it != itEnd; ++it) { if ((*it).sendAll) { const QVariant result (handleRequest((*it).url)); QDBusConnection::sessionBus().send((*it).transaction.createReply(result)); } else { const QVariant result (handleRequest((*it).url).first()); QDBusConnection::sessionBus().send((*it).transaction.createReply(result)); } } } else { for (RequestQueue::Iterator it = m_requestQueue.begin(), itEnd = m_requestQueue.end(); it != itEnd; ++it) { QDBusConnection::sessionBus().send((*it).transaction.createReply(QString::fromLatin1("DIRECT"))); } } m_requestQueue.clear(); // Suppress further attempts for 5 minutes if ( !success ) { m_suspendTime = std::time( 0 ); } } void ProxyScout::proxyScriptFileChanged(const QString& path) { // Should never get called if we do not have a watcher... Q_ASSERT(m_watcher); // Remove the current file being watched... if (!m_watcher->files().isEmpty()) { m_watcher->removePaths(m_watcher->files()); } // NOTE: QFileSystemWatcher only adds a path if it either exists or // is not already being monitored. m_watcher->addPath(path); // Reload... m_downloader->download(KUrl::fromPath(path)); } QStringList ProxyScout::handleRequest( const KUrl& url ) { try { QStringList proxyList; const QString result = m_script->evaluate(url).trimmed(); const QStringList proxies = result.split(QLatin1Char(';'), QString::SkipEmptyParts); const int size = proxies.count(); for (int i = 0; i < size; ++i) { QString mode, address; const QString proxy = proxies.at(i).trimmed(); const int index = proxy.indexOf(QLatin1Char(' ')); if (index == -1) { // Only "DIRECT" should match this! mode = proxy; address = proxy; } else { mode = proxy.left(index); address = proxy.mid(index + 1).trimmed(); } const ProxyType type = proxyTypeFor(mode); if (type == Unknown) { continue; } if (type == Proxy || type == Socks) { const int index = address.indexOf(QLatin1Char(':')); if (index == -1 || !KProtocolInfo::isKnownProtocol(address.left(index))) { const QString protocol ((type == Proxy ? QLatin1String("http://") : QLatin1String("socks://"))); const KUrl url (protocol + address); if (url.isValid()) { address = url.url(); } else { continue; } } } if (type == Direct || !m_blackList.contains(address)) { proxyList << address; } else if (std::time(0) - m_blackList[address] > 1800) { // 30 minutes // black listing expired m_blackList.remove( address ); proxyList << address; } } if (!proxyList.isEmpty()) { kDebug(m_debugArea) << proxyList; return proxyList; } // FIXME: blacklist } catch ( const Script::Error& e ) { kError() << e.message(); KNotification *n=new KNotification( "evaluation-error" ); n->setText( i18n( "The proxy configuration script returned an error:\n%1" , e.message() ) ); n->setComponentData(m_componentData); n->sendEvent(); } return QStringList (QLatin1String("DIRECT")); } } #include "moc_proxyscout.cpp" // vim: ts=4 sw=4 et