File indexing completed on 2024-04-28 15:27:12

0001 /*
0002     SPDX-FileCopyrightText: 2003 Malte Starostik <malte@kde.org>
0003     SPDX-FileCopyrightText: 2011 Dawit Alemayehu <adawit@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "script.h"
0009 
0010 #include <QDateTime>
0011 #include <QRegularExpression>
0012 #include <QUrl>
0013 
0014 #include <QHostAddress>
0015 #include <QHostInfo>
0016 #include <QNetworkInterface>
0017 
0018 #include <QJSEngine>
0019 #include <QJSValue>
0020 #include <QJSValueIterator>
0021 
0022 #include <KLocalizedString>
0023 #include <kio/hostinfo.h>
0024 
0025 namespace
0026 {
0027 static int findString(const QString &s, const char *const *values)
0028 {
0029     int index = 0;
0030     for (const char *const *p = values; *p; ++p, ++index) {
0031         if (s.compare(QLatin1String(*p), Qt::CaseInsensitive) == 0) {
0032             return index;
0033         }
0034     }
0035     return -1;
0036 }
0037 
0038 static const QDateTime getTime(QString tz)
0039 {
0040     if (tz.compare(QLatin1String("gmt"), Qt::CaseInsensitive) == 0) {
0041         return QDateTime::currentDateTimeUtc();
0042     }
0043     return QDateTime::currentDateTime();
0044 }
0045 
0046 template<typename T>
0047 static bool checkRange(T value, T min, T max)
0048 {
0049     return ((min <= max && value >= min && value <= max) || (min > max && (value <= min || value >= max)));
0050 }
0051 
0052 static bool isLocalHostAddress(const QHostAddress &address)
0053 {
0054     if (address == QHostAddress::LocalHost) {
0055         return true;
0056     }
0057 
0058     if (address == QHostAddress::LocalHostIPv6) {
0059         return true;
0060     }
0061 
0062     return false;
0063 }
0064 
0065 static bool isIPv6Address(const QHostAddress &address)
0066 {
0067     return address.protocol() == QAbstractSocket::IPv6Protocol;
0068 }
0069 
0070 static bool isIPv4Address(const QHostAddress &address)
0071 {
0072     return (address.protocol() == QAbstractSocket::IPv4Protocol);
0073 }
0074 
0075 static bool isSpecialAddress(const QHostAddress &address)
0076 {
0077     // Catch all the special addresses and return false.
0078     if (address == QHostAddress::Null) {
0079         return true;
0080     }
0081 
0082     if (address == QHostAddress::Any) {
0083         return true;
0084     }
0085 
0086     if (address == QHostAddress::AnyIPv6) {
0087         return true;
0088     }
0089 
0090     if (address == QHostAddress::Broadcast) {
0091         return true;
0092     }
0093 
0094     return false;
0095 }
0096 
0097 static bool addressLessThanComparison(const QHostAddress &addr1, const QHostAddress &addr2)
0098 {
0099     if (addr1.protocol() == QAbstractSocket::IPv4Protocol && addr2.protocol() == QAbstractSocket::IPv4Protocol) {
0100         return addr1.toIPv4Address() < addr2.toIPv4Address();
0101     }
0102 
0103     if (addr1.protocol() == QAbstractSocket::IPv6Protocol && addr2.protocol() == QAbstractSocket::IPv6Protocol) {
0104         const Q_IPV6ADDR ipv6addr1 = addr1.toIPv6Address();
0105         const Q_IPV6ADDR ipv6addr2 = addr2.toIPv6Address();
0106         for (int i = 0; i < 16; ++i) {
0107             if (ipv6addr1[i] != ipv6addr2[i]) {
0108                 return ((ipv6addr1[i] & 0xff) - (ipv6addr2[i] & 0xff));
0109             }
0110         }
0111     }
0112 
0113     return false;
0114 }
0115 
0116 static QString addressListToString(const QList<QHostAddress> &addressList, const QHash<QString, QString> &actualEntryMap)
0117 {
0118     QString result;
0119     for (const QHostAddress &address : addressList) {
0120         if (!result.isEmpty()) {
0121             result += QLatin1Char(';');
0122         }
0123         result += actualEntryMap.value(address.toString());
0124     }
0125     return result;
0126 }
0127 
0128 class Address
0129 {
0130 public:
0131     struct Error {
0132     };
0133     static Address resolve(const QString &host)
0134     {
0135         return Address(host);
0136     }
0137 
0138     const QList<QHostAddress> &addresses() const
0139     {
0140         return m_addressList;
0141     }
0142 
0143     QHostAddress address() const
0144     {
0145         if (m_addressList.isEmpty()) {
0146             return QHostAddress();
0147         }
0148 
0149         return m_addressList.first();
0150     }
0151 
0152 private:
0153     Address(const QString &host)
0154     {
0155         // Always try to see if it's already an IP first, to avoid Qt doing a
0156         // needless reverse lookup
0157         QHostAddress address(host);
0158         if (address.isNull()) {
0159             QHostInfo hostInfo = KIO::HostInfo::lookupCachedHostInfoFor(host);
0160             if (hostInfo.hostName().isEmpty() || hostInfo.error() != QHostInfo::NoError) {
0161                 hostInfo = QHostInfo::fromName(host);
0162                 KIO::HostInfo::cacheLookup(hostInfo);
0163             }
0164             m_addressList = hostInfo.addresses();
0165         } else {
0166             m_addressList.clear();
0167             m_addressList.append(address);
0168         }
0169     }
0170 
0171     QList<QHostAddress> m_addressList;
0172 };
0173 
0174 class ScriptHelper : public QObject
0175 {
0176     Q_OBJECT
0177     QJSEngine *m_engine;
0178 
0179 public:
0180     ScriptHelper(QJSEngine *engine, QObject *parent)
0181         : QObject(parent)
0182         , m_engine(engine)
0183     {
0184     }
0185 
0186     // isPlainHostName(host)
0187     // @returns true if @p host doesn't contains a domain part
0188     Q_INVOKABLE QJSValue IsPlainHostName(QString string)
0189     {
0190         return QJSValue(string.indexOf(QLatin1Char('.')) == -1);
0191     }
0192 
0193     // dnsDomainIs(host, domain)
0194     // @returns true if the domain part of @p host matches @p domain
0195     Q_INVOKABLE QJSValue DNSDomainIs(QString host, QString domain)
0196     {
0197         return QJSValue(host.endsWith(domain, Qt::CaseInsensitive));
0198     }
0199 
0200     // localHostOrDomainIs(host, fqdn)
0201     // @returns true if @p host is unqualified or equals @p fqdn
0202     Q_INVOKABLE QJSValue LocalHostOrDomainIs(QString host, QString fqdn)
0203     {
0204         if (!host.contains(QLatin1Char('.'))) {
0205             return QJSValue(true);
0206         }
0207         return QJSValue(host.compare(fqdn, Qt::CaseInsensitive) == 0);
0208     }
0209 
0210     // isResolvable(host)
0211     // @returns true if host is resolvable to a IPv4 address.
0212     Q_INVOKABLE QJSValue IsResolvable(QString host)
0213     {
0214         try {
0215             const Address info = Address::resolve(host);
0216             bool hasResolvableIPv4Address = false;
0217             for (const QHostAddress &address : info.addresses()) {
0218                 if (!isSpecialAddress(address) && isIPv4Address(address)) {
0219                     hasResolvableIPv4Address = true;
0220                     break;
0221                 }
0222             }
0223 
0224             return QJSValue(hasResolvableIPv4Address);
0225         } catch (const Address::Error &) {
0226             return QJSValue(false);
0227         }
0228     }
0229 
0230     // isInNet(host, subnet, mask)
0231     // @returns true if the IPv4 address of host is within the specified subnet
0232     // and mask, false otherwise.
0233     Q_INVOKABLE QJSValue IsInNet(QString host, QString subnet, QString mask)
0234     {
0235         try {
0236             const Address info = Address::resolve(host);
0237             bool isInSubNet = false;
0238             const QString subnetStr = subnet + QLatin1Char('/') + mask;
0239             const QPair<QHostAddress, int> subnet = QHostAddress::parseSubnet(subnetStr);
0240             for (const QHostAddress &address : info.addresses()) {
0241                 if (!isSpecialAddress(address) && isIPv4Address(address) && address.isInSubnet(subnet)) {
0242                     isInSubNet = true;
0243                     break;
0244                 }
0245             }
0246             return QJSValue(isInSubNet);
0247         } catch (const Address::Error &) {
0248             return QJSValue(false);
0249         }
0250     }
0251 
0252     // dnsResolve(host)
0253     // @returns the IPv4 address for host or an empty string if host is not resolvable.
0254     Q_INVOKABLE QJSValue DNSResolve(QString host)
0255     {
0256         try {
0257             const Address info = Address::resolve(host);
0258             QString resolvedAddress(QLatin1String(""));
0259             for (const QHostAddress &address : info.addresses()) {
0260                 if (!isSpecialAddress(address) && isIPv4Address(address)) {
0261                     resolvedAddress = address.toString();
0262                     break;
0263                 }
0264             }
0265             return QJSValue(resolvedAddress);
0266         } catch (const Address::Error &) {
0267             return QJSValue(QString(QLatin1String("")));
0268         }
0269     }
0270 
0271     // myIpAddress()
0272     // @returns the local machine's IPv4 address. Note that this will return
0273     // the address for the first interfaces that match its criteria even if the
0274     // machine has multiple interfaces.
0275     Q_INVOKABLE QJSValue MyIpAddress()
0276     {
0277         QString ipAddress;
0278         const QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
0279         for (const QHostAddress &address : addresses) {
0280             if (isIPv4Address(address) && !isSpecialAddress(address) && !isLocalHostAddress(address)) {
0281                 ipAddress = address.toString();
0282                 break;
0283             }
0284         }
0285 
0286         return QJSValue(ipAddress);
0287     }
0288 
0289     // dnsDomainLevels(host)
0290     // @returns the number of dots ('.') in @p host
0291     Q_INVOKABLE QJSValue DNSDomainLevels(QString host)
0292     {
0293         if (host.isNull()) {
0294             return QJSValue(0);
0295         }
0296 
0297         return QJSValue(static_cast<int>(host.count(QLatin1Char('.'))));
0298     }
0299 
0300     // shExpMatch(str, pattern)
0301     // @returns true if @p str matches the shell @p pattern
0302     Q_INVOKABLE QJSValue ShExpMatch(QString str, QString patternStr)
0303     {
0304         const QRegularExpression pattern(QRegularExpression::wildcardToRegularExpression(patternStr));
0305         return QJSValue(pattern.match(str).hasMatch());
0306     }
0307 
0308     // weekdayRange(day [, "GMT" ])
0309     // weekdayRange(day1, day2 [, "GMT" ])
0310     // @returns true if the current day equals day or between day1 and day2 resp.
0311     // If the last argument is "GMT", GMT timezone is used, otherwise local time
0312     Q_INVOKABLE QJSValue WeekdayRange(QString day1, QString arg2 = QString(), QString tz = QString())
0313     {
0314         static const char *const days[] = {"sun", "mon", "tue", "wed", "thu", "fri", "sat", nullptr};
0315 
0316         const int d1 = findString(day1, days);
0317         if (d1 == -1) {
0318             return QJSValue::UndefinedValue;
0319         }
0320 
0321         int d2 = findString(arg2, days);
0322         if (d2 == -1) {
0323             d2 = d1;
0324             tz = arg2;
0325         }
0326 
0327         // Adjust the days of week coming from QDateTime since it starts
0328         // counting with Monday as 1 and ends with Sunday as day 7.
0329         int dayOfWeek = getTime(tz).date().dayOfWeek();
0330         if (dayOfWeek == 7) {
0331             dayOfWeek = 0;
0332         }
0333         return QJSValue(checkRange(dayOfWeek, d1, d2));
0334     }
0335 
0336     // dateRange(day [, "GMT" ])
0337     // dateRange(day1, day2 [, "GMT" ])
0338     // dateRange(month [, "GMT" ])
0339     // dateRange(month1, month2 [, "GMT" ])
0340     // dateRange(year [, "GMT" ])
0341     // dateRange(year1, year2 [, "GMT" ])
0342     // dateRange(day1, month1, day2, month2 [, "GMT" ])
0343     // dateRange(month1, year1, month2, year2 [, "GMT" ])
0344     // dateRange(day1, month1, year1, day2, month2, year2 [, "GMT" ])
0345     // @returns true if the current date (GMT or local time according to
0346     // presence of "GMT" as last argument) is within the given range
0347     Q_INVOKABLE QJSValue DateRangeInternal(QJSValue args)
0348     {
0349         static const char *const months[] = {"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec", nullptr};
0350         QVector<int> values;
0351         QJSValueIterator it(args);
0352         QString tz;
0353         bool onlySeenNumbers = true;
0354         int initialNumbers = 0;
0355         while (it.next()) {
0356             int value = -1;
0357             if (it.value().isNumber()) {
0358                 value = it.value().toInt();
0359                 if (onlySeenNumbers) {
0360                     initialNumbers++;
0361                 }
0362             } else {
0363                 onlySeenNumbers = false;
0364                 // QDate starts counting months from 1, so we add 1 here.
0365                 value = findString(it.value().toString(), months) + 1;
0366                 if (value <= 0) {
0367                     tz = it.value().toString();
0368                 }
0369             }
0370 
0371             if (value > 0) {
0372                 values.append(value);
0373             } else {
0374                 break;
0375             }
0376         }
0377         // Our variable args calling indirection means argument.length was appended
0378         if (values.count() == initialNumbers) {
0379             --initialNumbers;
0380         }
0381         values.resize(values.size() - 1);
0382 
0383         if (values.count() < 1 || values.count() > 7) {
0384             return QJSValue::UndefinedValue;
0385         }
0386 
0387         const QDate now = getTime(tz).date();
0388 
0389         // day1, month1, year1, day2, month2, year2
0390         if (values.size() == 6) {
0391             const QDate d1(values[2], values[1], values[0]);
0392             const QDate d2(values[5], values[4], values[3]);
0393             return QJSValue(checkRange(now, d1, d2));
0394         }
0395         // day1, month1, day2, month2
0396         else if (values.size() == 4 && values[1] < 13 && values[3] < 13) {
0397             const QDate d1(now.year(), values[1], values[0]);
0398             const QDate d2(now.year(), values[3], values[2]);
0399             return QJSValue(checkRange(now, d1, d2));
0400         }
0401         // month1, year1, month2, year2
0402         else if (values.size() == 4) {
0403             const QDate d1(values[1], values[0], now.day());
0404             const QDate d2(values[3], values[2], now.day());
0405             return QJSValue(checkRange(now, d1, d2));
0406         }
0407         // year1, year2
0408         else if (values.size() == 2 && values[0] >= 1000 && values[1] >= 1000) {
0409             return QJSValue(checkRange(now.year(), values[0], values[1]));
0410         }
0411         // day1, day2
0412         else if (values.size() == 2 && initialNumbers == 2) {
0413             return QJSValue(checkRange(now.day(), values[0], values[1]));
0414         }
0415         // month1, month2
0416         else if (values.size() == 2) {
0417             return QJSValue(checkRange(now.month(), values[0], values[1]));
0418         }
0419         // year
0420         else if (values.size() == 1 && values[0] >= 1000) {
0421             return QJSValue(checkRange(now.year(), values[0], values[0]));
0422         }
0423         // day
0424         else if (values.size() == 1 && initialNumbers == 1) {
0425             return QJSValue(checkRange(now.day(), values[0], values[0]));
0426         }
0427         // month
0428         else if (values.size() == 1) {
0429             return QJSValue(checkRange(now.month(), values[0], values[0]));
0430         }
0431 
0432         return QJSValue::UndefinedValue;
0433     }
0434 
0435     // timeRange(hour [, "GMT" ])
0436     // timeRange(hour1, hour2 [, "GMT" ])
0437     // timeRange(hour1, min1, hour2, min2 [, "GMT" ])
0438     // timeRange(hour1, min1, sec1, hour2, min2, sec2 [, "GMT" ])
0439     // @returns true if the current time (GMT or local based on presence
0440     // of "GMT" argument) is within the given range
0441     Q_INVOKABLE QJSValue TimeRange(int hour, QString tz = QString())
0442     {
0443         const QTime now = getTime(tz).time();
0444         return m_engine->toScriptValue(checkRange(now.hour(), hour, hour));
0445     }
0446 
0447     Q_INVOKABLE QJSValue TimeRange(int hour1, int hour2, QString tz = QString())
0448     {
0449         const QTime now = getTime(tz).time();
0450         return m_engine->toScriptValue(checkRange(now.hour(), hour1, hour2));
0451     }
0452 
0453     Q_INVOKABLE QJSValue TimeRange(int hour1, int min1, int hour2, int min2, QString tz = QString())
0454     {
0455         const QTime now = getTime(tz).time();
0456         const QTime t1(hour1, min1);
0457         const QTime t2(hour2, min2);
0458         return m_engine->toScriptValue(checkRange(now, t1, t2));
0459     }
0460 
0461     Q_INVOKABLE QJSValue TimeRange(int hour1, int min1, int sec1, int hour2, int min2, int sec2, QString tz = QString())
0462     {
0463         const QTime now = getTime(tz).time();
0464 
0465         const QTime t1(hour1, min1, sec1);
0466         const QTime t2(hour2, min2, sec2);
0467         return m_engine->toScriptValue(checkRange(now, t1, t2));
0468     }
0469 
0470     /*
0471      * Implementation of Microsoft's IPv6 Extension for PAC
0472      *
0473      * Documentation:
0474      * http://msdn.microsoft.com/en-us/library/gg308477(v=vs.85).aspx
0475      * http://msdn.microsoft.com/en-us/library/gg308478(v=vs.85).aspx
0476      * http://msdn.microsoft.com/en-us/library/gg308474(v=vs.85).aspx
0477      * http://blogs.msdn.com/b/wndp/archive/2006/07/13/ipv6-pac-extensions-v0-9.aspx
0478      */
0479 
0480     // isResolvableEx(host)
0481     // @returns true if host is resolvable to an IPv4 or IPv6 address.
0482     Q_INVOKABLE QJSValue IsResolvableEx(QString host)
0483     {
0484         try {
0485             const Address info = Address::resolve(host);
0486             bool hasResolvableIPAddress = false;
0487             for (const QHostAddress &address : info.addresses()) {
0488                 if (isIPv4Address(address) || isIPv6Address(address)) {
0489                     hasResolvableIPAddress = true;
0490                     break;
0491                 }
0492             }
0493             return QJSValue(hasResolvableIPAddress);
0494         } catch (const Address::Error &) {
0495             return QJSValue(false);
0496         }
0497     }
0498 
0499     // isInNetEx(ipAddress, ipPrefix )
0500     // @returns true if ipAddress is within the specified ipPrefix.
0501     Q_INVOKABLE QJSValue IsInNetEx(QString ipAddress, QString ipPrefix)
0502     {
0503         try {
0504             const Address info = Address::resolve(ipAddress);
0505             bool isInSubNet = false;
0506             const QString subnetStr = ipPrefix;
0507             const QPair<QHostAddress, int> subnet = QHostAddress::parseSubnet(subnetStr);
0508             for (const QHostAddress &address : info.addresses()) {
0509                 if (isSpecialAddress(address)) {
0510                     continue;
0511                 }
0512 
0513                 if (address.isInSubnet(subnet)) {
0514                     isInSubNet = true;
0515                     break;
0516                 }
0517             }
0518             return QJSValue(isInSubNet);
0519         } catch (const Address::Error &) {
0520             return QJSValue(false);
0521         }
0522     }
0523 
0524     // dnsResolveEx(host)
0525     // @returns a semi-colon delimited string containing IPv6 and IPv4 addresses
0526     // for host or an empty string if host is not resolvable.
0527     Q_INVOKABLE QJSValue DNSResolveEx(QString host)
0528     {
0529         try {
0530             const Address info = Address::resolve(host);
0531 
0532             QStringList addressList;
0533             QString resolvedAddress(QLatin1String(""));
0534             for (const QHostAddress &address : info.addresses()) {
0535                 if (!isSpecialAddress(address)) {
0536                     addressList << address.toString();
0537                 }
0538             }
0539             if (!addressList.isEmpty()) {
0540                 resolvedAddress = addressList.join(QLatin1Char(';'));
0541             }
0542 
0543             return QJSValue(resolvedAddress);
0544         } catch (const Address::Error &) {
0545             return QJSValue(QString(QLatin1String("")));
0546         }
0547     }
0548 
0549     // myIpAddressEx()
0550     // @returns a semi-colon delimited string containing all IP addresses for localhost (IPv6 and/or IPv4),
0551     // or an empty string if unable to resolve localhost to an IP address.
0552     Q_INVOKABLE QJSValue MyIpAddressEx()
0553     {
0554         QStringList ipAddressList;
0555         const QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
0556         for (const QHostAddress &address : addresses) {
0557             if (!isSpecialAddress(address) && !isLocalHostAddress(address)) {
0558                 ipAddressList << address.toString();
0559             }
0560         }
0561 
0562         return m_engine->toScriptValue(ipAddressList.join(QLatin1Char(';')));
0563     }
0564 
0565     // sortIpAddressList(ipAddressList)
0566     // @returns a sorted ipAddressList. If both IPv4 and IPv6 addresses are present in
0567     // the list. The sorted IPv6 addresses will precede the sorted IPv4 addresses.
0568     Q_INVOKABLE QJSValue SortIpAddressList(QString ipAddressListStr)
0569     {
0570         QHash<QString, QString> actualEntryMap;
0571         QList<QHostAddress> ipV4List;
0572         QList<QHostAddress> ipV6List;
0573         const QStringList ipAddressList = ipAddressListStr.split(QLatin1Char(';'));
0574 
0575         for (const QString &ipAddress : ipAddressList) {
0576             QHostAddress address(ipAddress);
0577             switch (address.protocol()) {
0578             case QAbstractSocket::IPv4Protocol:
0579                 ipV4List << address;
0580                 actualEntryMap.insert(address.toString(), ipAddress);
0581                 break;
0582             case QAbstractSocket::IPv6Protocol:
0583                 ipV6List << address;
0584                 actualEntryMap.insert(address.toString(), ipAddress);
0585                 break;
0586             default:
0587                 break;
0588             }
0589         }
0590 
0591         QString sortedAddress(QLatin1String(""));
0592 
0593         if (!ipV6List.isEmpty()) {
0594             std::sort(ipV6List.begin(), ipV6List.end(), addressLessThanComparison);
0595             sortedAddress += addressListToString(ipV6List, actualEntryMap);
0596         }
0597 
0598         if (!ipV4List.isEmpty()) {
0599             std::sort(ipV4List.begin(), ipV4List.end(), addressLessThanComparison);
0600             if (!sortedAddress.isEmpty()) {
0601                 sortedAddress += QLatin1Char(';');
0602             }
0603             sortedAddress += addressListToString(ipV4List, actualEntryMap);
0604         }
0605 
0606         return QJSValue(sortedAddress);
0607     }
0608 
0609     // getClientVersion
0610     // @return the version number of this engine for future extension. We too start
0611     // this at version 1.0.
0612     Q_INVOKABLE QJSValue GetClientVersion()
0613     {
0614         const QString version(QStringLiteral("1.0"));
0615         return QJSValue(version);
0616     }
0617 }; // class ScriptHelper
0618 
0619 void registerFunctions(QJSEngine *engine)
0620 {
0621     QJSValue value = engine->globalObject();
0622     auto scriptHelper = new ScriptHelper(engine, engine);
0623     QJSValue functions = engine->newQObject(scriptHelper);
0624 
0625     value.setProperty(QStringLiteral("isPlainHostName"), functions.property(QStringLiteral("IsPlainHostName")));
0626     value.setProperty(QStringLiteral("dnsDomainIs"), functions.property(QStringLiteral("DNSDomainIs")));
0627     value.setProperty(QStringLiteral("localHostOrDomainIs"), functions.property(QStringLiteral("LocalHostOrDomainIs")));
0628     value.setProperty(QStringLiteral("isResolvable"), functions.property(QStringLiteral("IsResolvable")));
0629     value.setProperty(QStringLiteral("isInNet"), functions.property(QStringLiteral("IsInNet")));
0630     value.setProperty(QStringLiteral("dnsResolve"), functions.property(QStringLiteral("DNSResolve")));
0631     value.setProperty(QStringLiteral("myIpAddress"), functions.property(QStringLiteral("MyIpAddress")));
0632     value.setProperty(QStringLiteral("dnsDomainLevels"), functions.property(QStringLiteral("DNSDomainLevels")));
0633     value.setProperty(QStringLiteral("shExpMatch"), functions.property(QStringLiteral("ShExpMatch")));
0634     value.setProperty(QStringLiteral("weekdayRange"), functions.property(QStringLiteral("WeekdayRange")));
0635     value.setProperty(QStringLiteral("timeRange"), functions.property(QStringLiteral("TimeRange")));
0636     value.setProperty(QStringLiteral("dateRangeInternal"), functions.property(QStringLiteral("DateRangeInternal")));
0637     engine->evaluate(QStringLiteral("dateRange = function() { return dateRangeInternal(Array.prototype.slice.call(arguments)); };"));
0638 
0639     // Microsoft's IPv6 PAC Extensions...
0640     value.setProperty(QStringLiteral("isResolvableEx"), functions.property(QStringLiteral("IsResolvableEx")));
0641     value.setProperty(QStringLiteral("isInNetEx"), functions.property(QStringLiteral("IsInNetEx")));
0642     value.setProperty(QStringLiteral("dnsResolveEx"), functions.property(QStringLiteral("DNSResolveEx")));
0643     value.setProperty(QStringLiteral("myIpAddressEx"), functions.property(QStringLiteral("MyIpAddressEx")));
0644     value.setProperty(QStringLiteral("sortIpAddressList"), functions.property(QStringLiteral("SortIpAddressList")));
0645     value.setProperty(QStringLiteral("getClientVersion"), functions.property(QStringLiteral("GetClientVersion")));
0646 }
0647 } // namespace
0648 
0649 namespace KPAC
0650 {
0651 Script::Script(const QString &code)
0652 {
0653     m_engine = new QJSEngine;
0654     registerFunctions(m_engine);
0655 
0656     const QJSValue result = m_engine->evaluate(code);
0657     if (result.isError()) {
0658         throw Error(result.toString());
0659     }
0660 }
0661 
0662 Script::~Script()
0663 {
0664     delete m_engine;
0665 }
0666 
0667 QString Script::evaluate(const QUrl &url)
0668 {
0669     QJSValue func = m_engine->globalObject().property(QStringLiteral("FindProxyForURL"));
0670 
0671     if (!func.isCallable()) {
0672         func = m_engine->globalObject().property(QStringLiteral("FindProxyForURLEx"));
0673         if (!func.isCallable()) {
0674             throw Error(i18n("Could not find 'FindProxyForURL' or 'FindProxyForURLEx'"));
0675             return QString();
0676         }
0677     }
0678 
0679     QUrl cleanUrl = url;
0680     cleanUrl.setUserInfo(QString());
0681     if (cleanUrl.scheme() == QLatin1String("https")) {
0682         cleanUrl.setPath(QString());
0683         cleanUrl.setQuery(QString());
0684     }
0685 
0686     QJSValueList args;
0687     args << cleanUrl.url();
0688     args << cleanUrl.host();
0689 
0690     QJSValue result = func.call(args);
0691     if (result.isError()) {
0692         throw Error(i18n("Got an invalid reply when calling %1 -> %2", func.toString(), result.toString()));
0693     }
0694 
0695     return result.toString();
0696 }
0697 } // namespace KPAC
0698 
0699 #include "script.moc"