From a9326eadc5638545a175c12c11b0600a6c8498cb Mon Sep 17 00:00:00 2001 From: nowrep Date: Fri, 29 Mar 2013 19:22:55 +0100 Subject: [PATCH] Added support for Proxy Auto-Config (PAC). .pac files will be downloaded and executed to get proxy configuration for each url. Closes #747 --- CHANGELOG | 1 + src/lib/lib.pro | 9 +- src/lib/network/networkmanager.cpp | 5 + src/lib/network/networkmanager.h | 2 + src/lib/network/networkproxyfactory.cpp | 14 +- src/lib/network/networkproxyfactory.h | 10 +- src/lib/network/pac/pacdatetime.h | 186 +++++++++++++ src/lib/network/pac/pacmanager.cpp | 158 +++++++++++ src/lib/network/pac/pacmanager.h | 57 ++++ src/lib/network/pac/proxyautoconfig.cpp | 272 +++++++++++++++++++ src/lib/network/pac/proxyautoconfig.h | 77 ++++++ src/lib/preferences/preferences.cpp | 23 +- src/lib/preferences/preferences.h | 2 + src/lib/preferences/preferences.ui | 335 ++++++++++++++---------- tests/autotests/autotests.pro | 6 +- tests/autotests/main.cpp | 33 ++- tests/autotests/pactest.cpp | 264 +++++++++++++++++++ tests/autotests/pactest.h | 74 ++++++ 18 files changed, 1366 insertions(+), 162 deletions(-) create mode 100644 src/lib/network/pac/pacdatetime.h create mode 100644 src/lib/network/pac/pacmanager.cpp create mode 100644 src/lib/network/pac/pacmanager.h create mode 100644 src/lib/network/pac/proxyautoconfig.cpp create mode 100644 src/lib/network/pac/proxyautoconfig.h create mode 100644 tests/autotests/pactest.cpp create mode 100644 tests/autotests/pactest.h diff --git a/CHANGELOG b/CHANGELOG index 3b35b94df..ef47e19ce 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ Version 1.5.0 * development version + * added support for Proxy Auto-Config (PAC) * added option to open another private window from private window * added delete action in edit context menu on page diff --git a/src/lib/lib.pro b/src/lib/lib.pro index fe5aa4af3..e16a6fbb3 100644 --- a/src/lib/lib.pro +++ b/src/lib/lib.pro @@ -212,7 +212,9 @@ SOURCES += \ navigation/navigationcontainer.cpp \ tools/horizontallistwidget.cpp \ tools/mactoolbutton.cpp \ - tools/actioncopy.cpp + tools/actioncopy.cpp \ + network/pac/proxyautoconfig.cpp \ + network/pac/pacmanager.cpp HEADERS += \ webview/tabpreview.h \ @@ -384,7 +386,10 @@ HEADERS += \ tools/horizontallistwidget.h \ tools/mactoolbutton.h \ tools/qzregexp.h \ - tools/actioncopy.h + tools/actioncopy.h \ + network/pac/proxyautoconfig.h \ + network/pac/pacmanager.h \ + network/pac/pacdatetime.h FORMS += \ preferences/autofillmanager.ui \ diff --git a/src/lib/network/networkmanager.cpp b/src/lib/network/networkmanager.cpp index de5f02c0b..848f3c7c6 100644 --- a/src/lib/network/networkmanager.cpp +++ b/src/lib/network/networkmanager.cpp @@ -596,6 +596,11 @@ void NetworkManager::addLocalCertificate(const QSslCertificate &cert) } } +NetworkProxyFactory* NetworkManager::proxyFactory() const +{ + return m_proxyFactory; +} + bool NetworkManager::registerSchemeHandler(const QString &scheme, SchemeHandler* handler) { if (m_schemeHandlers.contains(scheme)) { diff --git a/src/lib/network/networkmanager.h b/src/lib/network/networkmanager.h index a9f0295ce..6e926a723 100644 --- a/src/lib/network/networkmanager.h +++ b/src/lib/network/networkmanager.h @@ -52,6 +52,8 @@ public: void setIgnoreAllWarnings(bool state) { m_ignoreAllWarnings = state; } bool isIgnoringAllWarnings() { return m_ignoreAllWarnings; } + NetworkProxyFactory* proxyFactory() const; + bool registerSchemeHandler(const QString &scheme, SchemeHandler* handler); void disconnectObjects(); diff --git a/src/lib/network/networkproxyfactory.cpp b/src/lib/network/networkproxyfactory.cpp index 5273d74f9..6b46c5144 100644 --- a/src/lib/network/networkproxyfactory.cpp +++ b/src/lib/network/networkproxyfactory.cpp @@ -1,6 +1,6 @@ /* ============================================================ * QupZilla - WebKit based browser -* Copyright (C) 2010-2012 David Rosca +* Copyright (C) 2010-2013 David Rosca * * 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 @@ -18,9 +18,11 @@ #include "networkproxyfactory.h" #include "mainapplication.h" #include "settings.h" +#include "pac/pacmanager.h" NetworkProxyFactory::NetworkProxyFactory() : QNetworkProxyFactory() + , m_pacManager(new PacManager) , m_proxyPreference(SystemProxy) { } @@ -45,6 +47,13 @@ void NetworkProxyFactory::loadSettings() m_proxyExceptions = settings.value("ProxyExceptions", QStringList() << "localhost" << "127.0.0.1").toStringList(); settings.endGroup(); + + m_pacManager->loadSettings(); +} + +PacManager* NetworkProxyFactory::pacManager() const +{ + return m_pacManager; } QList NetworkProxyFactory::queryProxy(const QNetworkProxyQuery &query) @@ -63,6 +72,9 @@ QList NetworkProxyFactory::queryProxy(const QNetworkProxyQuery &q proxy = QNetworkProxy::NoProxy; break; + case ProxyAutoConfig: + return m_pacManager->queryProxy(query.url()); + case DefinedProxy: proxy = m_proxyType; diff --git a/src/lib/network/networkproxyfactory.h b/src/lib/network/networkproxyfactory.h index 26377607f..16891cf4c 100644 --- a/src/lib/network/networkproxyfactory.h +++ b/src/lib/network/networkproxyfactory.h @@ -1,6 +1,6 @@ /* ============================================================ * QupZilla - WebKit based browser -* Copyright (C) 2010-2012 David Rosca +* Copyright (C) 2010-2013 David Rosca * * 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 @@ -23,17 +23,23 @@ #include "qz_namespace.h" +class PacManager; + class QT_QUPZILLA_EXPORT NetworkProxyFactory : public QNetworkProxyFactory { public: - enum ProxyPreference { SystemProxy, NoProxy, DefinedProxy }; + enum ProxyPreference { SystemProxy, NoProxy, ProxyAutoConfig, DefinedProxy }; explicit NetworkProxyFactory(); + void loadSettings(); + PacManager* pacManager() const; QList queryProxy(const QNetworkProxyQuery &query = QNetworkProxyQuery()); private: + PacManager* m_pacManager; + ProxyPreference m_proxyPreference; QNetworkProxy::ProxyType m_proxyType; diff --git a/src/lib/network/pac/pacdatetime.h b/src/lib/network/pac/pacdatetime.h new file mode 100644 index 000000000..dd2b8ed58 --- /dev/null +++ b/src/lib/network/pac/pacdatetime.h @@ -0,0 +1,186 @@ +/* + * The following subset of Javascript code was taken from Mozilla (http://www.mozilla.org) + */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: NPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Netscape Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Akhil Arora + * Tomi Leppikangas + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the NPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#define PAC_DATETIME_JAVASCRIPT \ + "var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6};\n" \ + "var months = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11};\n"\ + "function weekdayRange() {\n" \ + " function getDay(weekday) {\n" \ + " if (weekday in wdays) {\n" \ + " return wdays[weekday];\n" \ + " }\n" \ + " return -1;\n" \ + " }\n" \ + " var date = new Date();\n" \ + " var argc = arguments.length;\n" \ + " var wday;\n" \ + " if (argc < 1)\n" \ + " return false;\n" \ + " if (arguments[argc - 1] == 'GMT') {\n" \ + " argc--;\n" \ + " wday = date.getUTCDay();\n" \ + " } else {\n" \ + " wday = date.getDay();\n" \ + " }\n" \ + " var wd1 = getDay(arguments[0]);\n" \ + " var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;\n" \ + " return (wd1 == -1 || wd2 == -1) ? false\n" \ + " : (wd1 <= wday && wday <= wd2);\n" \ + "}\n" \ + "function dateRange() {\n" \ + " function getMonth(name) {\n" \ + " if (name in months) {\n" \ + " return months[name];\n" \ + " }\n" \ + " return -1;\n" \ + " }\n" \ + " var date = new Date();\n" \ + " var argc = arguments.length;\n" \ + " if (argc < 1) {\n" \ + " return false;\n" \ + " }\n" \ + " var isGMT = (arguments[argc - 1] == 'GMT');\n" \ + " if (isGMT) {\n" \ + " argc--;\n" \ + " }\n" \ + " if (argc == 1) {\n" \ + " var tmp = parseInt(arguments[0]);\n" \ + " if (isNaN(tmp)) {\n" \ + " return ((isGMT ? date.getUTCMonth() : date.getMonth()) == getMonth(arguments[0]));\n" \ + " } else if (tmp < 32) {\n" \ + " return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp);\n" \ + " } else {\n" \ + " return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) == tmp);\n" \ + " }\n" \ + " }\n" \ + " var year = date.getFullYear();\n" \ + " var date1, date2;\n" \ + " date1 = new Date(year, 0, 1, 0, 0, 0);\n" \ + " date2 = new Date(year, 11, 31, 23, 59, 59);\n" \ + " var adjustMonth = false;\n" \ + " for (var i = 0; i < (argc >> 1); i++) {\n" \ + " var tmp = parseInt(arguments[i]);\n" \ + " if (isNaN(tmp)) {\n" \ + " var mon = getMonth(arguments[i]);\n" \ + " date1.setMonth(mon);\n" \ + " } else if (tmp < 32) {\n" \ + " adjustMonth = (argc <= 2);\n" \ + " date1.setDate(tmp);\n" \ + " } else {\n" \ + " date1.setFullYear(tmp);\n" \ + " }\n" \ + " }\n" \ + " for (var i = (argc >> 1); i < argc; i++) {\n" \ + " var tmp = parseInt(arguments[i]);\n" \ + " if (isNaN(tmp)) {\n" \ + " var mon = getMonth(arguments[i]);\n" \ + " date2.setMonth(mon);\n" \ + " } else if (tmp < 32) {\n" \ + " date2.setDate(tmp);\n" \ + " } else {\n" \ + " date2.setFullYear(tmp);\n" \ + " }\n" \ + " }\n" \ + " if (adjustMonth) {\n" \ + " date1.setMonth(date.getMonth());\n" \ + " date2.setMonth(date.getMonth());\n" \ + " }\n" \ + " if (isGMT) {\n" \ + " var tmp = date;\n" \ + " tmp.setFullYear(date.getUTCFullYear());\n" \ + " tmp.setMonth(date.getUTCMonth());\n" \ + " tmp.setDate(date.getUTCDate());\n" \ + " tmp.setHours(date.getUTCHours());\n" \ + " tmp.setMinutes(date.getUTCMinutes());\n" \ + " tmp.setSeconds(date.getUTCSeconds());\n" \ + " date = tmp;\n" \ + " }\n" \ + " return ((date1 <= date) && (date <= date2));\n" \ + "}\n" \ + "function timeRange() {\n" \ + " var argc = arguments.length;\n" \ + " var date = new Date();\n" \ + " var isGMT= false;\n" \ + " if (argc < 1) {\n" \ + " return false;\n" \ + " }\n" \ + " if (arguments[argc - 1] == 'GMT') {\n" \ + " isGMT = true;\n" \ + " argc--;\n" \ + " }\n" \ + " var hour = isGMT ? date.getUTCHours() : date.getHours();\n" \ + " var date1, date2;\n" \ + " date1 = new Date();\n" \ + " date2 = new Date();\n" \ + " if (argc == 1) {\n" \ + " return (hour == arguments[0]);\n" \ + " } else if (argc == 2) {\n" \ + " return ((arguments[0] <= hour) && (hour <= arguments[1]));\n" \ + " } else {\n" \ + " switch (argc) {\n" \ + " case 6:\n" \ + " date1.setSeconds(arguments[2]);\n" \ + " date2.setSeconds(arguments[5]);\n" \ + " case 4:\n" \ + " var middle = argc >> 1;\n" \ + " date1.setHours(arguments[0]);\n" \ + " date1.setMinutes(arguments[1]);\n" \ + " date2.setHours(arguments[middle]);\n" \ + " date2.setMinutes(arguments[middle + 1]);\n" \ + " if (middle == 2) {\n" \ + " date2.setSeconds(59);\n" \ + " }\n" \ + " break;\n" \ + " default:\n" \ + " throw 'timeRange: bad number of arguments'\n" \ + " }\n" \ + " }\n" \ + " if (isGMT) {\n" \ + " date.setFullYear(date.getUTCFullYear());\n" \ + " date.setMonth(date.getUTCMonth());\n" \ + " date.setDate(date.getUTCDate());\n" \ + " date.setHours(date.getUTCHours());\n" \ + " date.setMinutes(date.getUTCMinutes());\n" \ + " date.setSeconds(date.getUTCSeconds());\n" \ + " }\n" \ + " return ((date1 <= date) && (date <= date2));\n" \ + "}\n" \ + "" diff --git a/src/lib/network/pac/pacmanager.cpp b/src/lib/network/pac/pacmanager.cpp new file mode 100644 index 000000000..e4ff2ab43 --- /dev/null +++ b/src/lib/network/pac/pacmanager.cpp @@ -0,0 +1,158 @@ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2013 David Rosca +* +* 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 3 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, see . +* ============================================================ */ +#include "pacmanager.h" +#include "proxyautoconfig.h" +#include "mainapplication.h" +#include "networkmanager.h" +#include "followredirectreply.h" +#include "settings.h" +#include "qztools.h" + +#include +#include +#include +#include + +#include + +PacManager::PacManager(QObject* parent) + : QObject(parent) + , m_pacrunner(0) + , m_reply(0) + , m_loaded(false) +{ +} + +void PacManager::loadSettings() +{ + QUrl oldUrl = m_url; + + Settings settings; + settings.beginGroup("Web-Proxy"); + m_url = settings.value("PacUrl", QUrl()).toUrl(); + settings.endGroup(); + + if (m_loaded && oldUrl != m_url) { + downloadPacFile(); + } + + m_loaded = true; +} + +void PacManager::downloadPacFile() +{ + if (m_reply) { + qWarning() << "PacManager: PAC file is already being downloaded!"; + return; + } + + m_reply = new FollowRedirectReply(m_url, mApp->networkManager()); + connect(m_reply, SIGNAL(finished()), this, SLOT(replyFinished())); +} + +QList PacManager::queryProxy(const QUrl &url) +{ + if (!m_pacrunner) { + reloadScript(); + } + + QString proxyString = m_pacrunner->findProxyForUrl(url.toEncoded(), url.host()); + return parseProxies(proxyString.trimmed()); +} + +void PacManager::replyFinished() +{ + if (m_reply->error() != QNetworkReply::NoError) { + qWarning() << "PacManager: Cannot download PAC file from" << m_url; + m_reply->deleteLater(); + m_reply = 0; + return; + } + + QByteArray data = m_reply->readAll(); + m_reply->deleteLater(); + m_reply = 0; + + QFile file(mApp->currentProfilePath() + "proxy.pac"); + + if (!file.open(QFile::WriteOnly)) { + qWarning() << "PacManager: Cannot open PAC file for writing" << file.fileName(); + return; + } + + file.write(data); + file.close(); + + reloadScript(); +} + +void PacManager::reloadScript() +{ + if (!m_pacrunner) { + m_pacrunner = new ProxyAutoConfig(this); + } + + QFile file(mApp->currentProfilePath() + "proxy.pac"); + + if (!file.open(QFile::ReadOnly)) { + qWarning() << "PacManager: Cannot open PAC file for reading" << file.fileName(); + return; + } + + m_pacrunner->setConfig(file.readAll()); +} + +QList PacManager::parseProxies(const QString &string) +{ + QList proxies; + string.trimmed(); + + if (string.isEmpty()) { + return proxies; + } + + QStringList parts = string.split(QLatin1Char(';'), QString::SkipEmptyParts); + if (parts.isEmpty()) { + parts.append(string); + } + + foreach (const QString &s, parts) { + QStringList l = s.split(QLatin1Char(' '), QString::SkipEmptyParts); + + if (l.count() != 2) { + if (l.count() == 1 && l.at(0) == QLatin1String("DIRECT")) { + proxies.append(QNetworkProxy::NoProxy); + } + continue; + } + + QString type = l.at(0); + QUrl url = QUrl::fromEncoded("proxy://" + l.at(1).toUtf8()); + + if (type == QLatin1String("PROXY")) { + QNetworkProxy proxy(QNetworkProxy::HttpProxy, url.host(), url.port(8080)); + proxies.append(proxy); + } + else if (type == QLatin1String("SOCKS")) { + QNetworkProxy proxy(QNetworkProxy::Socks5Proxy, url.host(), url.port(1080)); + proxies.append(proxy); + } + } + + return proxies; +} diff --git a/src/lib/network/pac/pacmanager.h b/src/lib/network/pac/pacmanager.h new file mode 100644 index 000000000..368c0d766 --- /dev/null +++ b/src/lib/network/pac/pacmanager.h @@ -0,0 +1,57 @@ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2013 David Rosca +* +* 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 3 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, see . +* ============================================================ */ +#ifndef PACMANAGER_H +#define PACMANAGER_H + +#include +#include +#include + +#include "qz_namespace.h" + +class QNetworkProxy; + +class FollowRedirectReply; +class ProxyAutoConfig; + +class QT_QUPZILLA_EXPORT PacManager : public QObject +{ + Q_OBJECT +public: + explicit PacManager(QObject* parent = 0); + + void loadSettings(); + void downloadPacFile(); + + QList queryProxy(const QUrl &url); + +private slots: + void replyFinished(); + +private: + void reloadScript(); + QList parseProxies(const QString &string); + + ProxyAutoConfig* m_pacrunner; + FollowRedirectReply* m_reply; + + bool m_loaded; + QUrl m_url; +}; + +#endif // PACMANAGER_H diff --git a/src/lib/network/pac/proxyautoconfig.cpp b/src/lib/network/pac/proxyautoconfig.cpp new file mode 100644 index 000000000..0b6696fd6 --- /dev/null +++ b/src/lib/network/pac/proxyautoconfig.cpp @@ -0,0 +1,272 @@ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2013 David Rosca +* +* 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 3 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, see . +* ============================================================ */ +#include "proxyautoconfig.h" +#include "pacdatetime.h" +#include "qztools.h" +#include "qzregexp.h" + +#include +#include +#include +#include +#include +#include + +/** + * Class implementing the proxy auto-configuration (PAC) JavaScript api. + * + * Based on qt-examples: https://gitorious.org/qt-examples/qt-examples/blobs/master/pac-files + */ +ProxyAutoConfig::ProxyAutoConfig(QObject* parent) + : QObject(parent) + , m_engine(new QScriptEngine(this)) +{ + install(); +} + +void ProxyAutoConfig::setConfig(const QString &config) +{ + m_engine->evaluate(config); +} + +// string findProxyForUrl url host +QString ProxyAutoConfig::findProxyForUrl(const QString &url, const QString &host) +{ + QScriptValue global = m_engine->globalObject(); + QScriptValue fun = global.property("FindProxyForURL"); + if (!fun.isFunction()) { + return QString("DIRECT"); + } + + QScriptValueList args; + args << m_engine->toScriptValue(url) << m_engine->toScriptValue(host); + + QScriptValue val = fun.call(global, args); + + if (val.isError()) { + qWarning() << "PAC Error:" << val.toString(); + return QString("DIRECT"); + } + + return val.toString(); +} + +QScriptValue ProxyAutoConfig::evaluate(const QString &source) +{ + return m_engine->evaluate(source); +} + +void ProxyAutoConfig::install() +{ + QScriptValue globalObject = m_engine->globalObject(); + + QScriptValue fun; + + fun = m_engine->newFunction(debug); + globalObject.setProperty("debug", fun); + + fun = m_engine->newFunction(isPlainHostName); + globalObject.setProperty("isPlainHostName", fun); + + fun = m_engine->newFunction(dnsDomainIs); + globalObject.setProperty("dnsDomainIs", fun); + + fun = m_engine->newFunction(localHostOrDomainIs); + globalObject.setProperty("localHostOrDomainIs", fun); + + fun = m_engine->newFunction(isResolvable); + globalObject.setProperty("isResolvable", fun); + + fun = m_engine->newFunction(isInNet); + globalObject.setProperty("isInNet", fun); + + fun = m_engine->newFunction(dnsResolve); + globalObject.setProperty("dnsResolve", fun); + + fun = m_engine->newFunction(myIpAddress); + globalObject.setProperty("myIpAddress", fun); + + fun = m_engine->newFunction(dnsDomainLevels); + globalObject.setProperty("dnsDomainLevels", fun); + + fun = m_engine->newFunction(shExpMatch); + globalObject.setProperty("shExpMatch", fun); + + m_engine->evaluate(PAC_DATETIME_JAVASCRIPT); +} + +QScriptValue ProxyAutoConfig::debug(QScriptContext* context, QScriptEngine* engine) +{ + if (context->argumentCount() != 1) { + return context->throwError("Debug takes one argument"); + } + qDebug() << context->argument(0).toString(); + return engine->undefinedValue(); +} + +// bool isPlainHostName host +QScriptValue ProxyAutoConfig::isPlainHostName(QScriptContext* context, QScriptEngine* engine) +{ + if (context->argumentCount() != 1) { + return context->throwError("isPlainHostName takes one argument"); + } + + bool ret = !context->argument(0).toString().contains(QLatin1Char('.')); + return QScriptValue(engine, ret); +} + +// bool dnsDomainIs host domain +QScriptValue ProxyAutoConfig::dnsDomainIs(QScriptContext* context, QScriptEngine* engine) +{ + if (context->argumentCount() != 2) { + return context->throwError("dnsDomainIs takes two arguments"); + } + + QString host = context->argument(0).toString(); + QString domain = context->argument(1).toString(); + + if (host.startsWith(QLatin1Char('.'))) { + host = host.mid(1); + } + + if (domain.startsWith(QLatin1Char('.'))) { + domain = domain.mid(1); + } + + return QScriptValue(engine, QzTools::matchDomain(domain, host)); +} + +// bool localHostOrDomainIs host hostdom +QScriptValue ProxyAutoConfig::localHostOrDomainIs(QScriptContext* context, QScriptEngine* engine) +{ + if (context->argumentCount() != 2) { + return context->throwError("localHostOrDomainIs takes two arguments"); + } + + QString host = context->argument(0).toString(); + QString hostdom = context->argument(1).toString(); + bool ret = !host.contains(QLatin1Char('.')) ? hostdom.startsWith(host) : host == hostdom; + + return QScriptValue(engine, ret); +} + +static QList hostResolve(const QString &host) +{ + QHostInfo info = QHostInfo::fromName(host); + return info.addresses(); +} + +// bool isResolvable host +QScriptValue ProxyAutoConfig::isResolvable(QScriptContext* context, QScriptEngine* engine) +{ + if (context->argumentCount() != 1) { + return context->throwError("isResolvable takes one arguments"); + } + + QString host = context->argument(0).toString(); + return QScriptValue(engine, !hostResolve(host).isEmpty()); +} + +// bool isInNet host pattern mask +QScriptValue ProxyAutoConfig::isInNet(QScriptContext* context, QScriptEngine* engine) +{ + if (context->argumentCount() != 3) { + return context->throwError("isInNet takes three arguments"); + } + + QHostAddress host(context->argument(0).toString()); + QHostAddress pattern(context->argument(1).toString()); + QHostAddress mask(context->argument(2).toString()); + + if (host.isNull()) { + QList addresses = hostResolve(context->argument(0).toString()); + host = addresses.isEmpty() ? QHostAddress() : addresses.first(); + } + + if ((pattern.toIPv4Address() & mask.toIPv4Address()) == (host.toIPv4Address() & mask.toIPv4Address())) { + return QScriptValue(engine, true); + } + + return QScriptValue(engine, false); +} + +// string dnsResolve hostname +QScriptValue ProxyAutoConfig::dnsResolve(QScriptContext* context, QScriptEngine* engine) +{ + if (context->argumentCount() != 1) { + return context->throwError("dnsResolve takes one arguments"); + } + + QString host = context->argument(0).toString(); + QList addresses = hostResolve(host); + if (addresses.isEmpty()) { + return engine->nullValue(); + } + + return QScriptValue(engine, addresses.first().toString()); +} + +// string myIpAddress +QScriptValue ProxyAutoConfig::myIpAddress(QScriptContext* context, QScriptEngine* engine) +{ + if (context->argumentCount() != 0) { + return context->throwError("myIpAddress takes no arguments"); + } + + foreach (QHostAddress address, QNetworkInterface::allAddresses()) { + if (address != QHostAddress::LocalHost + && address != QHostAddress::LocalHostIPv6) { + return QScriptValue(engine, address.toString()); + } + } + + return engine->undefinedValue(); +} + +// int dnsDomainLevels host +QScriptValue ProxyAutoConfig::dnsDomainLevels(QScriptContext* context, QScriptEngine* engine) +{ + if (context->argumentCount() != 1) { + return context->throwError("dnsDomainLevels takes one argument"); + } + + QString host = context->argument(0).toString(); + + return QScriptValue(engine, host.count(QLatin1Char('.'))); +} + +// bool shExpMatch str shexp +QScriptValue ProxyAutoConfig::shExpMatch(QScriptContext* context, QScriptEngine* engine) +{ + if (context->argumentCount() != 2) { + return context->throwError("shExpMatch takes two arguments"); + } + + QString str = context->argument(0).toString(); + QString shexp = context->argument(1).toString(); + + shexp.replace(QLatin1Char('.'), QLatin1String("\\.")) + .replace(QLatin1Char('*'), QLatin1String(".*")) + .replace(QLatin1Char('?'), QLatin1Char('.')); + shexp = QString("^%1$").arg(shexp); + + QzRegExp re(shexp); + bool ret = re.indexIn(str) != -1; + + return QScriptValue(engine, ret); +} diff --git a/src/lib/network/pac/proxyautoconfig.h b/src/lib/network/pac/proxyautoconfig.h new file mode 100644 index 000000000..922c415de --- /dev/null +++ b/src/lib/network/pac/proxyautoconfig.h @@ -0,0 +1,77 @@ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2013 David Rosca +* +* 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 3 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, see . +* ============================================================ */ +#ifndef PROXYAUTOCONFIG_H +#define PROXYAUTOCONFIG_H + +#include +#include + +class QScriptContext; +class QScriptEngine; + +/** + * Class implementing the proxy auto-configuration (PAC) JavaScript api. + * + * Based on qt-examples: https://gitorious.org/qt-examples/qt-examples/blobs/master/pac-files + */ +class ProxyAutoConfig : public QObject +{ + Q_OBJECT + +public: + explicit ProxyAutoConfig(QObject* parent = 0); + + // Call this to set the script to be executed. Note that the argument should be + // the content of the .pac file to be used, not the URL where it is located. + void setConfig(const QString &config); + + // Returns the result + QString findProxyForUrl(const QString &url, const QString &host); + +protected: + QScriptValue evaluate(const QString &source); + +private: + void install(); + + // Debug + static QScriptValue debug(QScriptContext* context, QScriptEngine* engine); + + // Hostname based conditions + static QScriptValue isPlainHostName(QScriptContext* context, QScriptEngine* engine); + static QScriptValue dnsDomainIs(QScriptContext* context, QScriptEngine* engine); + static QScriptValue localHostOrDomainIs(QScriptContext* context, QScriptEngine* engine); + static QScriptValue isResolvable(QScriptContext* context, QScriptEngine* engine); + static QScriptValue isInNet(QScriptContext* context, QScriptEngine* engine); + + // Related utility functions + static QScriptValue dnsResolve(QScriptContext* context, QScriptEngine* engine); + static QScriptValue myIpAddress(QScriptContext* context, QScriptEngine* engine); + static QScriptValue dnsDomainLevels(QScriptContext* context, QScriptEngine* engine); + + // URL/hostname based conditions + static QScriptValue shExpMatch(QScriptContext* context, QScriptEngine* engine); + + // Time based conditions + // Implemented in JavaScript + +private: + QScriptEngine* m_engine; +}; + +#endif // PROXYAUTOCONFIG_H diff --git a/src/lib/preferences/preferences.cpp b/src/lib/preferences/preferences.cpp index f9691d27a..0dfc41c72 100644 --- a/src/lib/preferences/preferences.cpp +++ b/src/lib/preferences/preferences.cpp @@ -46,6 +46,7 @@ #include "useragentdialog.h" #include "registerqappassociation.h" #include "html5permissions/html5permissionsdialog.h" +#include "pac/pacmanager.h" #include #include @@ -411,6 +412,7 @@ Preferences::Preferences(QupZilla* mainClass, QWidget* parent) ui->systemProxy->setChecked(proxyPreference == NetworkProxyFactory::SystemProxy); ui->noProxy->setChecked(proxyPreference == NetworkProxyFactory::NoProxy); ui->manualProxy->setChecked(proxyPreference == NetworkProxyFactory::DefinedProxy); + ui->pacProxy->setChecked(proxyPreference == NetworkProxyFactory::ProxyAutoConfig); if (proxyType == QNetworkProxy::HttpProxy) { ui->proxyType->setCurrentIndex(0); } @@ -429,14 +431,18 @@ Preferences::Preferences(QupZilla* mainClass, QWidget* parent) ui->httpsProxyUsername->setText(settings.value("HttpsUsername", "").toString()); ui->httpsProxyPassword->setText(settings.value("HttpsPassword", "").toString()); + ui->pacUrl->setText(settings.value("PacUrl", QUrl()).toUrl().toString()); ui->proxyExceptions->setText(settings.value("ProxyExceptions", QStringList() << "localhost" << "127.0.0.1").toStringList().join(",")); settings.endGroup(); useDifferentProxyForHttpsChanged(ui->useHttpsProxy->isChecked()); setManualProxyConfigurationEnabled(proxyPreference == NetworkProxyFactory::DefinedProxy); + setProxyAutoConfigEnabled(proxyPreference == NetworkProxyFactory::ProxyAutoConfig); connect(ui->manualProxy, SIGNAL(toggled(bool)), this, SLOT(setManualProxyConfigurationEnabled(bool))); + connect(ui->pacProxy, SIGNAL(toggled(bool)), this, SLOT(setProxyAutoConfigEnabled(bool))); connect(ui->useHttpsProxy, SIGNAL(toggled(bool)), this, SLOT(useDifferentProxyForHttpsChanged(bool))); + connect(ui->reloadPac, SIGNAL(clicked()), this, SLOT(reloadPacFileClicked())); //CONNECTS connect(ui->buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonClicked(QAbstractButton*))); @@ -609,6 +615,12 @@ void Preferences::setManualProxyConfigurationEnabled(bool state) ui->useHttpsProxy->setEnabled(state); } +void Preferences::setProxyAutoConfigEnabled(bool state) +{ + ui->pacUrl->setEnabled(state); + ui->reloadPac->setEnabled(state); +} + void Preferences::saveHistoryChanged(bool stat) { ui->deleteHistoryOnClose->setEnabled(stat); @@ -702,6 +714,11 @@ void Preferences::changeCachePathClicked() ui->cachePath->setText(path); } +void Preferences::reloadPacFileClicked() +{ + mApp->networkManager()->proxyFactory()->pacManager()->downloadPacFile(); +} + void Preferences::showPassManager(bool state) { m_autoFillManager->setVisible(state); @@ -805,7 +822,7 @@ void Preferences::saveSettings() switch (ui->newTab->currentIndex()) { case 0: - settings.setValue("newTabUrl", ""); + settings.setValue("newTabUrl", QString()); break; case 1: @@ -974,6 +991,9 @@ void Preferences::saveSettings() else if (ui->noProxy->isChecked()) { proxyPreference = NetworkProxyFactory::NoProxy; } + else if (ui->pacProxy->isChecked()) { + proxyPreference = NetworkProxyFactory::ProxyAutoConfig; + } else { proxyPreference = NetworkProxyFactory::DefinedProxy; } @@ -1000,6 +1020,7 @@ void Preferences::saveSettings() settings.setValue("HttpsUsername", ui->httpsProxyUsername->text()); settings.setValue("HttpsPassword", ui->httpsProxyPassword->text()); + settings.setValue("PacUrl", ui->pacUrl->text()); settings.setValue("ProxyExceptions", ui->proxyExceptions->text().split(QLatin1Char(','), QString::SkipEmptyParts)); settings.endGroup(); diff --git a/src/lib/preferences/preferences.h b/src/lib/preferences/preferences.h index b01d4e6c7..28e777f56 100644 --- a/src/lib/preferences/preferences.h +++ b/src/lib/preferences/preferences.h @@ -70,10 +70,12 @@ private slots: void allowCacheChanged(bool state); void showPassManager(bool state); void setManualProxyConfigurationEnabled(bool state); + void setProxyAutoConfigEnabled(bool state); void useExternalDownManagerChanged(bool state); void useDifferentProxyForHttpsChanged(bool state); void showTabPreviewsChanged(bool state); void changeCachePathClicked(); + void reloadPacFileClicked(); void newTabChanged(int value); void afterLaunchChanged(int value); diff --git a/src/lib/preferences/preferences.ui b/src/lib/preferences/preferences.ui index 88b364a97..c22935fe6 100644 --- a/src/lib/preferences/preferences.ui +++ b/src/lib/preferences/preferences.ui @@ -7,7 +7,7 @@ 0 0 800 - 500 + 531 @@ -153,6 +153,9 @@ Qt::NoFocus + + 0 + @@ -1426,10 +1429,108 @@ Proxy Configuration - - - - + + + + + + + Proxy Auto-Config (.pac) file + + + + + + + Reload + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 50 + 20 + + + + + + + + + + <b>Exceptions</b> + + + + + + + + + Don't use on: + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + System proxy configuration + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 40 + 20 + + + + + + + + Manual configuration + + + + + + @@ -1443,54 +1544,51 @@ - - - - - - - - - Port: - - - - - - - - 50 - 16777215 - - - - - + + - + + + + Port: + + + + + + + + 50 + 16777215 + + + + + + + + + Username: - + - + Password: - + - - - - + Qt::Horizontal @@ -1503,129 +1601,96 @@ - - - - Don't use on: - - - - - - - Manual configuration - - - - - - - System proxy configuration - - - - - - - Do not use proxy - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 20 - 20 - - - - - - - - <b>Exceptions</b> - - - - + + + + + + Use different proxy for https connection + + + + + + Server: - + + + + + + + Port: + + + + + + + + 50 + 16777215 + + + + + + + + + Username: - + + + + Password: - - - - + - - - - - - - - - Port: - - - - - - - - 50 - 16777215 - - - - - - - - - - Use different proxy for https connection + + + + Qt::Horizontal - + + + 40 + 20 + + + - - - - Qt::Vertical + + + + Use script for automatic configuration: - - - 20 - 40 - + + + + + + Do not use proxy - + @@ -2626,13 +2691,9 @@ allowCache cacheMB saveHistory - proxyType deleteHistoryOnClose proxyServer - proxyExceptions proxyPort - proxyUsername - proxyPassword manualProxy systemProxy noProxy diff --git a/tests/autotests/autotests.pro b/tests/autotests/autotests.pro index 0ecf4b8f3..1e510f3ac 100644 --- a/tests/autotests/autotests.pro +++ b/tests/autotests/autotests.pro @@ -51,7 +51,8 @@ HEADERS += \ cookiestest.h \ downloadstest.h \ adblocktest.h \ - updatertest.h + updatertest.h \ + pactest.h SOURCES += \ qztoolstest.cpp \ @@ -60,4 +61,5 @@ SOURCES += \ cookiestest.cpp \ downloadstest.cpp \ adblocktest.cpp \ - updatertest.cpp + updatertest.cpp \ + pactest.cpp diff --git a/tests/autotests/main.cpp b/tests/autotests/main.cpp index 0974b7a19..76af7da83 100644 --- a/tests/autotests/main.cpp +++ b/tests/autotests/main.cpp @@ -21,31 +21,30 @@ #include "downloadstest.h" #include "adblocktest.h" #include "updatertest.h" +#include "pactest.h" #include +#define RUN_TEST(X) \ + { \ + qDebug() << ""; \ + X t; \ + int r = QTest::qExec(&t, argc, argv); \ + if (r != 0) return 1; \ + } + int main(int argc, char *argv[]) { QApplication app(argc, argv); QTEST_DISABLE_KEYPAD_NAVIGATION; - QzToolsTest qzToolsTest; - QTest::qExec(&qzToolsTest, argc, argv); - - FormCompleterTest formCompleterTest; - QTest::qExec(&formCompleterTest, argc, argv); - - CookiesTest cookiesTest; - QTest::qExec(&cookiesTest, argc, argv); - - DownloadsTest downloadsTest; - QTest::qExec(&downloadsTest, argc, argv); - - AdBlockTest adblockTest; - QTest::qExec(&adblockTest, argc, argv); - - UpdaterTest updaterTest; - QTest::qExec(&updaterTest, argc, argv); + RUN_TEST(QzToolsTest) + RUN_TEST(FormCompleterTest) + RUN_TEST(CookiesTest) + RUN_TEST(DownloadsTest) + RUN_TEST(AdBlockTest) + RUN_TEST(UpdaterTest) + RUN_TEST(PacTest) return 0; } diff --git a/tests/autotests/pactest.cpp b/tests/autotests/pactest.cpp new file mode 100644 index 000000000..a79f14694 --- /dev/null +++ b/tests/autotests/pactest.cpp @@ -0,0 +1,264 @@ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2013 David Rosca +* +* 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 3 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, see . +* ============================================================ */ +#include "pactest.h" + +#include +#include + +void PacTest::initTestCase() +{ + m_runner = new ProxyAutoConfig_Tst; +} + +void PacTest::cleanupTestCase() +{ + delete m_runner; +} + +// Tests according to +// http://web.archive.org/web/20061218002753/wp.netscape.com/eng/mozilla/2.0/relnotes/demo/proxy-live.html + +void PacTest::isPlainHostNameTest_data() +{ + QTest::addColumn("host"); + QTest::addColumn("result"); + + QTest::newRow("doc1") << "www" << true; + QTest::newRow("doc2") << "www.netscape.com" << false; +} + +void PacTest::isPlainHostNameTest() +{ + QFETCH(QString, host); + QFETCH(bool, result); + + QString source = QString("isPlainHostName('%1')").arg(host); + QCOMPARE(m_runner->evaluate(source).toBool(), result); +} + +void PacTest::dnsDomainIsTest_data() +{ + QTest::addColumn("host"); + QTest::addColumn("domain"); + QTest::addColumn("result"); + + QTest::newRow("doc1") << "www.netscape.com" << ".netscape.com" << true; + QTest::newRow("doc2") << "www" << ".netscape.com" << false; + QTest::newRow("doc3") << "www.mcom.com" << ".netscape.com" << false; +} + +void PacTest::dnsDomainIsTest() +{ + QFETCH(QString, host); + QFETCH(QString, domain); + QFETCH(bool, result); + + QString source = QString("dnsDomainIs('%1','%2')").arg(host, domain); + QCOMPARE(m_runner->evaluate(source).toBool(), result); +} + +void PacTest::localHostOrDomainIs_data() +{ + QTest::addColumn("host"); + QTest::addColumn("hostdom"); + QTest::addColumn("result"); + + QTest::newRow("doc1") << "www.netscape.com" << "www.netscape.com" << true; + QTest::newRow("doc2") << "www" << "www.netscape.com" << true; + QTest::newRow("doc3") << "www.mcom.com" << "www.netscape.com" << false; + QTest::newRow("doc4") << "home.netscape.com" << "www.netscape.com" << false; +} + +void PacTest::localHostOrDomainIs() +{ + QFETCH(QString, host); + QFETCH(QString, hostdom); + QFETCH(bool, result); + + QString source = QString("localHostOrDomainIs('%1','%2')").arg(host, hostdom); + QCOMPARE(m_runner->evaluate(source).toBool(), result); +} + +void PacTest::isResolvableTest_data() +{ + QTest::addColumn("host"); + QTest::addColumn("result"); + + QTest::newRow("doc1") << "www.netscape.com" << true; + QTest::newRow("doc2") << "bogus.domain.foobar" << false; +} + +void PacTest::isResolvableTest() +{ + QFETCH(QString, host); + QFETCH(bool, result); + + QString source = QString("isResolvable('%1')").arg(host); + QCOMPARE(m_runner->evaluate(source).toBool(), result); +} + +void PacTest::isInNetTest_data() +{ + QTest::addColumn("host"); + QTest::addColumn("pattern"); + QTest::addColumn("mask"); + QTest::addColumn("result"); + + // is true if the IP address of host matches exactly 198.95.249.79. + QTest::newRow("doc1") << "198.95.249.79" << "198.95.249.79" << "255.255.255.255" << true; + QTest::newRow("doc1-2") << "198.95.249.80" << "198.95.249.79" << "255.255.255.255" << false; + QTest::newRow("doc1-3") << "198.95.248.79" << "198.95.249.79" << "255.255.255.255" << false; + QTest::newRow("doc1-4") << "198.20.249.80" << "198.95.249.79" << "255.255.255.255" << false; + QTest::newRow("doc1-5") << "123.95.249.80" << "198.95.249.79" << "255.255.255.255" << false; + + // is true if the IP address of the host matches 198.95.*.*. + QTest::newRow("doc2") << "198.95.249.79" << "198.95.0.0" << "255.255.0.0" << true; + QTest::newRow("doc2-2") << "198.95.0.0" << "198.95.0.0" << "255.255.0.0" << true; + QTest::newRow("doc2-3") << "198.94.249.79" << "198.95.0.0" << "255.255.0.0" << false; + QTest::newRow("doc2-3") << "198.94.249.79" << "198.95.0.0" << "255.255.0.0" << false; + QTest::newRow("doc2-3") << "148.94.249.79" << "198.95.0.0" << "255.255.0.0" << false; + QTest::newRow("doc2-3") << "128.94.249.79" << "198.95.0.0" << "255.255.0.0" << false; + QTest::newRow("doc2-3") << "23.94.249.79" << "198.95.0.0" << "255.255.0.0" << false; + + // is true if the IP address of host matches 173.194.70.* (google.com) + // if host is passed as hostname, the function needs to resolve it + QTest::newRow("resolve1") << "google.com" << "173.194.70.0" << "255.255.255.0" << true; + QTest::newRow("resolve1-2") << "yahoo.com" << "173.194.70.0" << "255.255.255.0" << false; + QTest::newRow("resolve1-3") << "netscape.com" << "173.194.70.0" << "255.255.255.0" << false; + QTest::newRow("resolve1-4") << "mozilla.com" << "173.194.70.0" << "255.255.255.0" << false; +} + +void PacTest::isInNetTest() +{ + QFETCH(QString, host); + QFETCH(QString, pattern); + QFETCH(QString, mask); + QFETCH(bool, result); + + QString source = QString("isInNet('%1','%2','%3')").arg(host, pattern, mask); + QCOMPARE(m_runner->evaluate(source).toBool(), result); +} + +void PacTest::dnsResolveTest_data() +{ + QTest::addColumn("host"); + QTest::addColumn("result"); + + QTest::newRow("localhost") << "localhost" << "127.0.0.1"; + QTest::newRow("qz") << "qupzilla.com" << "88.208.118.158"; // This may change... +} + +void PacTest::dnsResolveTest() +{ + QFETCH(QString, host); + QFETCH(QString, result); + + QString source = QString("dnsResolve('%1')").arg(host); + QCOMPARE(m_runner->evaluate(source).toString(), result); +} + +void PacTest::dnsDomainLevelsTest_data() +{ + QTest::addColumn("host"); + QTest::addColumn("result"); + + QTest::newRow("doc1") << "www" << 0; + QTest::newRow("doc2") << "www.netscape.com" << 2; +} + +void PacTest::dnsDomainLevelsTest() +{ + QFETCH(QString, host); + QFETCH(int, result); + + QString source = QString("dnsDomainLevels('%1')").arg(host); + QCOMPARE(m_runner->evaluate(source).toString().toInt(), result); +} + +void PacTest::shExpMatchTest_data() +{ + QTest::addColumn("str"); + QTest::addColumn("shexp"); + QTest::addColumn("result"); + + QTest::newRow("doc1") << "http://home.netscape.com/people/ari/index.html" << "*/ari/*" << true; + QTest::newRow("doc2") << "http://home.netscape.com/people/montulli/index.html" << "*/ari/*" << false; + + QTest::newRow("glob1") << "com/people" << "*om/*" << true; + QTest::newRow("glob2") << "com/people" << "com/*" << true; + QTest::newRow("glob3") << "com/people" << "om/*" << false; + + QTest::newRow("char1") << "com/people" << "co?/*" << true; + QTest::newRow("char2") << "com/people" << "?com/*" << false; + QTest::newRow("char3") << "com/people" << "?scom/*" << false; + QTest::newRow("char4") << "com/people" << "com/pe?ple*" << true; + + QTest::newRow("dot1") << "com/people.org" << "co?/*.org" << true; + QTest::newRow("dot2") << "com/people.org" << "co?/*.or" << false; + QTest::newRow("dot3") << "com/people.org" << "com/people.*g" << true; + QTest::newRow("dot4") << "com/people.org" << "com/*.*g" << true; +} + +void PacTest::shExpMatchTest() +{ + QFETCH(QString, str); + QFETCH(QString, shexp); + QFETCH(bool, result); + + QString source = QString("shExpMatch('%1','%2')").arg(str, shexp); + QCOMPARE(m_runner->evaluate(source).toBool(), result); +} + +static QString dayName(int weekday) +{ + switch (weekday) { + case 1: return "MON"; + case 2: return "TUE"; + case 3: return "WED"; + case 4: return "THU"; + case 5: return "FRI"; + case 6: return "SAT"; + case 7: return "SUN"; + default: return "MON"; + } +} + +void PacTest::dateTimeTest() +{ + int day = QDateTime::currentDateTime().date().day(); + int hour = QDateTime::currentDateTime().time().hour(); + int week = QDateTime::currentDateTime().date().dayOfWeek(); + + QString source = QString("weekdayRange('%1')").arg(dayName(week)); + QCOMPARE(m_runner->evaluate(source).toBool(), true); + + source = QString("weekdayRange('%1')").arg(dayName(week + 1)); + QCOMPARE(m_runner->evaluate(source).toBool(), false); + + source = QString("dateRange('%1')").arg(day); + QCOMPARE(m_runner->evaluate(source).toBool(), true); + + source = QString("dateRange('%1')").arg(day + 1); + QCOMPARE(m_runner->evaluate(source).toBool(), false); + + source = QString("timeRange('%1')").arg(hour); + QCOMPARE(m_runner->evaluate(source).toBool(), true); + + source = QString("timeRange('%1')").arg(hour + 1); + QCOMPARE(m_runner->evaluate(source).toBool(), false); +} diff --git a/tests/autotests/pactest.h b/tests/autotests/pactest.h new file mode 100644 index 000000000..ce9984f61 --- /dev/null +++ b/tests/autotests/pactest.h @@ -0,0 +1,74 @@ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2013 David Rosca +* +* 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 3 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, see . +* ============================================================ */ +#ifndef PACTEST_H +#define PACTEST_H + +#include + +#include "pac/proxyautoconfig.h" + +class ProxyAutoConfig_Tst : public ProxyAutoConfig +{ +public: + QScriptValue evaluate(const QString &source) + { + return ProxyAutoConfig::evaluate(source); + } +}; + +class PacTest : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + + void isPlainHostNameTest_data(); + void isPlainHostNameTest(); + + void dnsDomainIsTest_data(); + void dnsDomainIsTest(); + + void localHostOrDomainIs_data(); + void localHostOrDomainIs(); + + void isResolvableTest_data(); + void isResolvableTest(); + + void isInNetTest_data(); + void isInNetTest(); + + void dnsResolveTest_data(); + void dnsResolveTest(); + + // myIpAddress - how to test it? + + void dnsDomainLevelsTest_data(); + void dnsDomainLevelsTest(); + + void shExpMatchTest_data(); + void shExpMatchTest(); + + void dateTimeTest(); + +private: + ProxyAutoConfig_Tst *m_runner; +}; + +#endif // PACTEST_H