diff --git a/src/kpac/CMakeLists.txt b/src/kpac/CMakeLists.txt index b58150b6..d46d895a 100644 --- a/src/kpac/CMakeLists.txt +++ b/src/kpac/CMakeLists.txt @@ -1,76 +1,76 @@ kde_enable_exceptions() include(ConfigureChecks.cmake) # needed for kpac notifications about problems find_package(KF5Notifications ${KF5_DEP_VERSION}) set_package_properties(KF5Notifications PROPERTIES DESCRIPTION "Notifications library" TYPE OPTIONAL PURPOSE "Required to have error notification support in kpac") if (KF5Notifications_FOUND) add_definitions(-DHAVE_KF5NOTIFICATIONS) endif() configure_file(config-kpac.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kpac.h) ########### next target ############### find_package(Qt5Qml ${REQUIRED_QT_VERSION}) if (Qt5Qml_FOUND) set(kded_proxyscout_PART_SRCS proxyscout.cpp script.cpp downloader.cpp discovery.cpp ) kcoreaddons_add_plugin(kded_proxyscout INSTALL_NAMESPACE "kf5/kded" JSON proxyscout.json SOURCES ${kded_proxyscout_PART_SRCS}) set_target_properties(kded_proxyscout PROPERTIES OUTPUT_NAME proxyscout ) target_link_libraries(kded_proxyscout Qt5::Network Qt5::Qml ${KPAC_EXTRA_LIBS} KF5::KIOCore # KIO::get KF5::DBusAddons # KDEDModule KF5::I18n ) if (KF5Notifications_FOUND) target_link_libraries(kded_proxyscout KF5::Notifications) endif() # this needs -lresolv e.g on Slackware, but not on FreeBSD if (HAVE_RESOLV_LIBRARY) target_link_libraries(kded_proxyscout resolv) endif () endif() -set_package_properties(Qt5Script PROPERTIES DESCRIPTION "QtScript, one of the Qt5 modules" +set_package_properties(Qt5Qml PROPERTIES DESCRIPTION "QtQml, one of the Qt5 modules" TYPE OPTIONAL PURPOSE "Required to build support for auto-configuration of network proxies" ) ########### next target ############### add_executable(kpac_dhcp_helper kpac_dhcp_helper.c) ecm_mark_nongui_executable(kpac_dhcp_helper) if (HAVE_NSL_LIBRARY) # Assume Solaris target_link_libraries(kpac_dhcp_helper nsl) endif () if (HAVE_SOCKET_LIBRARY) target_link_libraries(kpac_dhcp_helper socket) endif () install(TARGETS kpac_dhcp_helper DESTINATION ${KDE_INSTALL_LIBEXECDIR_KF5} ) ########### install files ############### install( FILES proxyscout.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFY5RCDIR} ) diff --git a/src/kpac/script.cpp b/src/kpac/script.cpp index f9369e40..2d77de3c 100644 --- a/src/kpac/script.cpp +++ b/src/kpac/script.cpp @@ -1,709 +1,713 @@ /* 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 "script.h" #include #include #include #include #include #include #include #include #include #include #include #include #define QL1S(x) QLatin1String(x) namespace { static int findString(const QString &s, const char *const *values) { int index = 0; for (const char *const *p = values; *p; ++p, ++index) { if (s.compare(QLatin1String(*p), Qt::CaseInsensitive) == 0) { return index; } } return -1; } static const QDateTime getTime(QString tz) { if (tz.compare(QLatin1String("gmt"), Qt::CaseInsensitive) == 0) { return QDateTime::currentDateTimeUtc(); } return QDateTime::currentDateTime(); } template static bool checkRange(T value, T min, T max) { return ((min <= max && value >= min && value <= max) || (min > max && (value <= min || value >= max))); } static bool isLocalHostAddress(const QHostAddress &address) { if (address == QHostAddress::LocalHost) { return true; } if (address == QHostAddress::LocalHostIPv6) { return true; } return false; } static bool isIPv6Address(const QHostAddress &address) { return address.protocol() == QAbstractSocket::IPv6Protocol; } static bool isIPv4Address(const QHostAddress &address) { return (address.protocol() == QAbstractSocket::IPv4Protocol); } static bool isSpecialAddress(const QHostAddress &address) { // Catch all the special addresses and return false. if (address == QHostAddress::Null) { return true; } if (address == QHostAddress::Any) { return true; } if (address == QHostAddress::AnyIPv6) { return true; } if (address == QHostAddress::Broadcast) { return true; } return false; } static bool addressLessThanComparison(const QHostAddress &addr1, const QHostAddress &addr2) { if (addr1.protocol() == QAbstractSocket::IPv4Protocol && addr2.protocol() == QAbstractSocket::IPv4Protocol) { return addr1.toIPv4Address() < addr2.toIPv4Address(); } if (addr1.protocol() == QAbstractSocket::IPv6Protocol && addr2.protocol() == QAbstractSocket::IPv6Protocol) { const Q_IPV6ADDR ipv6addr1 = addr1.toIPv6Address(); const Q_IPV6ADDR ipv6addr2 = addr2.toIPv6Address(); for (int i = 0; i < 16; ++i) { if (ipv6addr1[i] != ipv6addr2[i]) { return ((ipv6addr1[i] & 0xff) - (ipv6addr2[i] & 0xff)); } } } return false; } static QString addressListToString(const QList &addressList, const QHash &actualEntryMap) { QString result; Q_FOREACH (const QHostAddress &address, addressList) { if (!result.isEmpty()) { result += QLatin1Char(';'); } result += actualEntryMap.value(address.toString()); } return result; } class Address { public: struct Error {}; static Address resolve(const QString &host) { return Address(host); } QList addresses() const { return m_addressList; } QHostAddress address() const { if (m_addressList.isEmpty()) { return QHostAddress(); } return m_addressList.first(); } private: Address(const QString &host) { // Always try to see if it's already an IP first, to avoid Qt doing a // needless reverse lookup QHostAddress address(host); if (address.isNull()) { QHostInfo hostInfo = KIO::HostInfo::lookupCachedHostInfoFor(host); if (hostInfo.hostName().isEmpty() || hostInfo.error() != QHostInfo::NoError) { hostInfo = QHostInfo::fromName(host); KIO::HostInfo::cacheLookup(hostInfo); } m_addressList = hostInfo.addresses(); } else { m_addressList.clear(); m_addressList.append(address); } } QList m_addressList; }; class ScriptHelper : public QObject { Q_OBJECT QJSEngine *m_engine; public: ScriptHelper(QJSEngine *engine, QObject *parent) : QObject(parent), m_engine(engine) { } // isPlainHostName(host) // @returns true if @p host doesn't contains a domain part Q_INVOKABLE QJSValue IsPlainHostName(QString string) { return QJSValue(string.indexOf(QLatin1Char('.')) == -1); } // dnsDomainIs(host, domain) // @returns true if the domain part of @p host matches @p domain Q_INVOKABLE QJSValue DNSDomainIs(QString host, QString domain) { return QJSValue(host.endsWith(domain, Qt::CaseInsensitive)); } // localHostOrDomainIs(host, fqdn) // @returns true if @p host is unqualified or equals @p fqdn Q_INVOKABLE QJSValue LocalHostOrDomainIs(QString host, QString fqdn) { if (!host.contains(QLatin1Char('.'))) { return QJSValue(true); } return QJSValue(host.compare(fqdn, Qt::CaseInsensitive) == 0); } // isResolvable(host) // @returns true if host is resolvable to a IPv4 address. Q_INVOKABLE QJSValue IsResolvable(QString host) { try { const Address info = Address::resolve(host); bool hasResolvableIPv4Address = false; Q_FOREACH (const QHostAddress &address, info.addresses()) { if (!isSpecialAddress(address) && isIPv4Address(address)) { hasResolvableIPv4Address = true; break; } } return QJSValue(hasResolvableIPv4Address); } catch (const Address::Error &) { return QJSValue(false); } } // isInNet(host, subnet, mask) // @returns true if the IPv4 address of host is within the specified subnet // and mask, false otherwise. Q_INVOKABLE QJSValue IsInNet(QString host, QString subnet, QString mask) { try { const Address info = Address::resolve(host); bool isInSubNet = false; const QString subnetStr = subnet + QLatin1Char('/') + mask; const QPair subnet = QHostAddress::parseSubnet(subnetStr); Q_FOREACH (const QHostAddress &address, info.addresses()) { if (!isSpecialAddress(address) && isIPv4Address(address) && address.isInSubnet(subnet)) { isInSubNet = true; break; } } return QJSValue(isInSubNet); } catch (const Address::Error &) { return QJSValue(false); } } // dnsResolve(host) // @returns the IPv4 address for host or an empty string if host is not resolvable. Q_INVOKABLE QJSValue DNSResolve(QString host) { try { const Address info = Address::resolve(host); QString resolvedAddress(QLatin1String("")); Q_FOREACH (const QHostAddress &address, info.addresses()) { if (!isSpecialAddress(address) && isIPv4Address(address)) { resolvedAddress = address.toString(); break; } } return QJSValue(resolvedAddress); } catch (const Address::Error &) { return QJSValue(QString(QLatin1String(""))); } } // myIpAddress() // @returns the local machine's IPv4 address. Note that this will return // the address for the first interfaces that match its criteria even if the // machine has multiple interfaces. Q_INVOKABLE QJSValue MyIpAddress() { QString ipAddress; const QList addresses = QNetworkInterface::allAddresses(); for (const QHostAddress& address : addresses) { if (isIPv4Address(address) && !isSpecialAddress(address) && !isLocalHostAddress(address)) { ipAddress = address.toString(); break; } } return QJSValue(ipAddress); } // dnsDomainLevels(host) // @returns the number of dots ('.') in @p host Q_INVOKABLE QJSValue DNSDomainLevels(QString host) { if (host.isNull()) { return QJSValue(0); } return QJSValue(host.count(QLatin1Char('.'))); } // shExpMatch(str, pattern) // @returns true if @p str matches the shell @p pattern Q_INVOKABLE QJSValue ShExpMatch(QString str, QString patternStr) { QRegExp pattern(patternStr, Qt::CaseSensitive, QRegExp::Wildcard); return QJSValue(pattern.exactMatch(str)); } // weekdayRange(day [, "GMT" ]) // weekdayRange(day1, day2 [, "GMT" ]) // @returns true if the current day equals day or between day1 and day2 resp. // If the last argument is "GMT", GMT timezone is used, otherwise local time Q_INVOKABLE QJSValue WeekdayRange(QString day1, QString arg2 = QString(), QString tz = QString()) { static const char *const days[] = { "sun", "mon", "tue", "wed", "thu", "fri", "sat", nullptr }; const int d1 = findString(day1, days); if (d1 == -1) { return QJSValue::UndefinedValue; } int d2 = findString(arg2, days); if (d2 == -1) { d2 = d1; tz = arg2; } // Adjust the days of week coming from QDateTime since it starts // counting with Monday as 1 and ends with Sunday as day 7. int dayOfWeek = getTime(tz).date().dayOfWeek(); if (dayOfWeek == 7) { dayOfWeek = 0; } return QJSValue(checkRange(dayOfWeek, d1, d2)); } // dateRange(day [, "GMT" ]) // dateRange(day1, day2 [, "GMT" ]) // dateRange(month [, "GMT" ]) // dateRange(month1, month2 [, "GMT" ]) // dateRange(year [, "GMT" ]) // dateRange(year1, year2 [, "GMT" ]) // dateRange(day1, month1, day2, month2 [, "GMT" ]) // dateRange(month1, year1, month2, year2 [, "GMT" ]) // dateRange(day1, month1, year1, day2, month2, year2 [, "GMT" ]) // @returns true if the current date (GMT or local time according to // presence of "GMT" as last argument) is within the given range -QJSValue dateRangeInternal(QJSValue args) +Q_INVOKABLE QJSValue DateRangeInternal(QJSValue args) { static const char *const months[] = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec", nullptr }; - QVector values; QJSValueIterator it(args); QString tz; bool onlySeenNumbers = true; int initialNumbers = 0; while (it.next()) { int value = -1; if (it.value().isNumber()) { value = it.value().toInt(); if (onlySeenNumbers) initialNumbers++; } else { onlySeenNumbers = false; // QDate starts counting months from 1, so we add 1 here. value = findString(it.value().toString(), months) + 1; if (value <= 0) tz = it.value().toString(); } if (value > 0) { values.append(value); } else { break; } } - if (values.count() < 1 || values.count() > 6) + // Our variable args calling indirection means argument.length was appended + if (values.count() == initialNumbers) + --initialNumbers; + values.resize(values.size() - 1); + + if (values.count() < 1 || values.count() > 7) return QJSValue::UndefinedValue; const QDate now = getTime(tz).date(); // day1, month1, year1, day2, month2, year2 if (values.size() == 6) { const QDate d1(values[2], values[1], values[0]); const QDate d2(values[5], values[4], values[3]); return QJSValue(checkRange(now, d1, d2)); } // day1, month1, day2, month2 else if (values.size() == 4 && values[ 1 ] < 13 && values[ 3 ] < 13) { const QDate d1(now.year(), values[1], values[0]); const QDate d2(now.year(), values[3], values[2]); return QJSValue(checkRange(now, d1, d2)); } // month1, year1, month2, year2 else if (values.size() == 4) { const QDate d1(values[1], values[0], now.day()); const QDate d2(values[3], values[2], now.day()); return QJSValue(checkRange(now, d1, d2)); } // year1, year2 else if (values.size() == 2 && values[0] >= 1000 && values[1] >= 1000) { return QJSValue(checkRange(now.year(), values[0], values[1])); } // day1, day2 else if (values.size() == 2 && initialNumbers == 2) { return QJSValue(checkRange(now.day(), values[0], values[1])); } // month1, month2 else if (values.size() == 2) { return QJSValue(checkRange(now.month(), values[0], values[1])); } // year else if (values.size() == 1 && values[ 0 ] >= 1000) { return QJSValue(checkRange(now.year(), values[0], values[0])); } // day else if (values.size() == 1 && initialNumbers == 1) { return QJSValue(checkRange(now.day(), values[0], values[0])); } // month else if (values.size() == 1) { return QJSValue(checkRange(now.month(), values[0], values[0])); } return QJSValue::UndefinedValue; } // timeRange(hour [, "GMT" ]) // timeRange(hour1, hour2 [, "GMT" ]) // timeRange(hour1, min1, hour2, min2 [, "GMT" ]) // timeRange(hour1, min1, sec1, hour2, min2, sec2 [, "GMT" ]) // @returns true if the current time (GMT or local based on presence // of "GMT" argument) is within the given range Q_INVOKABLE QJSValue TimeRange(int hour, QString tz = QString()) { const QTime now = getTime(tz).time(); return m_engine->toScriptValue(checkRange(now.hour(), hour, hour)); } Q_INVOKABLE QJSValue TimeRange(int hour1, int hour2, QString tz = QString()) { const QTime now = getTime(tz).time(); return m_engine->toScriptValue(checkRange(now.hour(), hour1, hour2)); } Q_INVOKABLE QJSValue TimeRange(int hour1, int min1, int hour2, int min2, QString tz = QString()) { const QTime now = getTime(tz).time(); const QTime t1(hour1, min1); const QTime t2(hour2, min2); return m_engine->toScriptValue(checkRange(now, t1, t2)); } Q_INVOKABLE QJSValue TimeRange(int hour1, int min1, int sec1, int hour2, int min2, int sec2, QString tz = QString()) { const QTime now = getTime(tz).time(); const QTime t1(hour1, min1, sec1); const QTime t2(hour2, min2, sec2); return m_engine->toScriptValue(checkRange(now, t1, t2)); } /* * Implementation of Microsoft's IPv6 Extension for PAC * * Documentation: * http://msdn.microsoft.com/en-us/library/gg308477(v=vs.85).aspx * http://msdn.microsoft.com/en-us/library/gg308478(v=vs.85).aspx * http://msdn.microsoft.com/en-us/library/gg308474(v=vs.85).aspx * http://blogs.msdn.com/b/wndp/archive/2006/07/13/ipv6-pac-extensions-v0-9.aspx */ // isResolvableEx(host) // @returns true if host is resolvable to an IPv4 or IPv6 address. Q_INVOKABLE QJSValue IsResolvableEx(QString host) { try { const Address info = Address::resolve(host); bool hasResolvableIPAddress = false; Q_FOREACH (const QHostAddress &address, info.addresses()) { if (isIPv4Address(address) || isIPv6Address(address)) { hasResolvableIPAddress = true; break; } } return QJSValue(hasResolvableIPAddress); } catch (const Address::Error &) { return QJSValue(false); } } // isInNetEx(ipAddress, ipPrefix ) // @returns true if ipAddress is within the specified ipPrefix. Q_INVOKABLE QJSValue IsInNetEx(QString ipAddress, QString ipPrefix) { try { const Address info = Address::resolve(ipAddress); bool isInSubNet = false; const QString subnetStr = ipPrefix; const QPair subnet = QHostAddress::parseSubnet(subnetStr); Q_FOREACH (const QHostAddress &address, info.addresses()) { if (isSpecialAddress(address)) { continue; } if (address.isInSubnet(subnet)) { isInSubNet = true; break; } } return QJSValue(isInSubNet); } catch (const Address::Error &) { return QJSValue(false); } } // dnsResolveEx(host) // @returns a semi-colon delimited string containing IPv6 and IPv4 addresses // for host or an empty string if host is not resolvable. Q_INVOKABLE QJSValue DNSResolveEx(QString host) { try { const Address info = Address::resolve(host); QStringList addressList; QString resolvedAddress(QLatin1String("")); Q_FOREACH (const QHostAddress &address, info.addresses()) { if (!isSpecialAddress(address)) { addressList << address.toString(); } } if (!addressList.isEmpty()) { resolvedAddress = addressList.join(QLatin1Char(';')); } return QJSValue(resolvedAddress); } catch (const Address::Error &) { return QJSValue(QString(QLatin1String(""))); } } // myIpAddressEx() // @returns a semi-colon delimited string containing all IP addresses for localhost (IPv6 and/or IPv4), // or an empty string if unable to resolve localhost to an IP address. Q_INVOKABLE QJSValue MyIpAddressEx() { QStringList ipAddressList; const QList addresses = QNetworkInterface::allAddresses(); for (const QHostAddress& address : addresses) { if (!isSpecialAddress(address) && !isLocalHostAddress(address)) { ipAddressList << address.toString(); } } return m_engine->toScriptValue(ipAddressList.join(QLatin1Char(';'))); } // sortIpAddressList(ipAddressList) // @returns a sorted ipAddressList. If both IPv4 and IPv6 addresses are present in // the list. The sorted IPv6 addresses will precede the sorted IPv4 addresses. Q_INVOKABLE QJSValue SortIpAddressList(QString ipAddressListStr) { QHash actualEntryMap; QList ipV4List, ipV6List; const QStringList ipAddressList = ipAddressListStr.split(QLatin1Char(';')); for (const QString &ipAddress : ipAddressList) { QHostAddress address(ipAddress); switch (address.protocol()) { case QAbstractSocket::IPv4Protocol: ipV4List << address; actualEntryMap.insert(address.toString(), ipAddress); break; case QAbstractSocket::IPv6Protocol: ipV6List << address; actualEntryMap.insert(address.toString(), ipAddress); break; default: break; } } QString sortedAddress(QLatin1String("")); if (!ipV6List.isEmpty()) { std::sort(ipV6List.begin(), ipV6List.end(), addressLessThanComparison); sortedAddress += addressListToString(ipV6List, actualEntryMap); } if (!ipV4List.isEmpty()) { std::sort(ipV4List.begin(), ipV4List.end(), addressLessThanComparison); if (!sortedAddress.isEmpty()) { sortedAddress += QLatin1Char(';'); } sortedAddress += addressListToString(ipV4List, actualEntryMap); } return QJSValue(sortedAddress); } // getClientVersion // @return the version number of this engine for future extension. We too start // this at version 1.0. Q_INVOKABLE QJSValue GetClientVersion() { const QString version(QStringLiteral("1.0")); return QJSValue(version); } }; // class ScriptHelper void registerFunctions(QJSEngine *engine) { QJSValue value = engine->globalObject(); auto scriptHelper = new ScriptHelper(engine, engine); QJSValue functions = engine->newQObject(scriptHelper); value.setProperty(QStringLiteral("isPlainHostName"), functions.property(QStringLiteral("IsPlainHostName"))); value.setProperty(QStringLiteral("dnsDomainIs"), functions.property(QStringLiteral("DNSDomainIs"))); value.setProperty(QStringLiteral("localHostOrDomainIs"), functions.property(QStringLiteral("LocalHostOrDomainIs"))); value.setProperty(QStringLiteral("isResolvable"), functions.property(QStringLiteral("IsResolvable"))); value.setProperty(QStringLiteral("isInNet"), functions.property(QStringLiteral("IsInNet"))); value.setProperty(QStringLiteral("dnsResolve"), functions.property(QStringLiteral("DNSResolve"))); value.setProperty(QStringLiteral("myIpAddress"), functions.property(QStringLiteral("MyIpAddress"))); value.setProperty(QStringLiteral("dnsDomainLevels"), functions.property(QStringLiteral("DNSDomainLevels"))); value.setProperty(QStringLiteral("shExpMatch"), functions.property(QStringLiteral("ShExpMatch"))); value.setProperty(QStringLiteral("weekdayRange"), functions.property(QStringLiteral("WeekdayRange"))); value.setProperty(QStringLiteral("timeRange"), functions.property(QStringLiteral("TimeRange"))); - value.setProperty(QStringLiteral("dateRangeInternal"), functions.property(QStringLiteral("dateRangeInternal"))); + value.setProperty(QStringLiteral("dateRangeInternal"), functions.property(QStringLiteral("DateRangeInternal"))); engine->evaluate(QStringLiteral("dateRange = function() { return dateRangeInternal(Array.prototype.slice.call(arguments)); };")); // Microsoft's IPv6 PAC Extensions... value.setProperty(QStringLiteral("isResolvableEx"), functions.property(QStringLiteral("IsResolvableEx"))); value.setProperty(QStringLiteral("isInNetEx"), functions.property(QStringLiteral("IsInNetEx"))); value.setProperty(QStringLiteral("dnsResolveEx"), functions.property(QStringLiteral("DNSResolveEx"))); value.setProperty(QStringLiteral("myIpAddressEx"), functions.property(QStringLiteral("MyIpAddressEx"))); value.setProperty(QStringLiteral("sortIpAddressList"), functions.property(QStringLiteral("SortIpAddressList"))); value.setProperty(QStringLiteral("getClientVersion"), functions.property(QStringLiteral("GetClientVersion"))); } } // namespace namespace KPAC { Script::Script(const QString &code) { m_engine = new QJSEngine; registerFunctions(m_engine); const QJSValue result = m_engine->evaluate(code); if (result.isError()) { throw Error(result.toString()); } } Script::~Script() { delete m_engine; } QString Script::evaluate(const QUrl &url) { QJSValue func = m_engine->globalObject().property(QStringLiteral("FindProxyForURL")); if (!func.isCallable()) { func = m_engine->globalObject().property(QStringLiteral("FindProxyForURLEx")); if (!func.isCallable()) { throw Error(i18n("Could not find 'FindProxyForURL' or 'FindProxyForURLEx'")); return QString(); } } QUrl cleanUrl = url; cleanUrl.setUserInfo(QString()); if (cleanUrl.scheme() == QLatin1String("https")) { cleanUrl.setPath(QString()); cleanUrl.setQuery(QString()); } QJSValueList args; args << cleanUrl.url(); args << cleanUrl.host(); QJSValue result = func.call(args); if (result.isError()) { - throw Error(i18n("Got an invalid reply when calling %1", func.toString())); + throw Error(i18n("Got an invalid reply when calling %1 -> %2", func.toString(), result.toString())); } return result.toString(); } } // namespace KPAC #include "script.moc"