Add Proxy Auto Config support (PAC) on Mac
authorAron Rosenberg <aronrosenberg@gmail.com>
Wed, 4 Apr 2012 23:34:33 +0000 (16:34 -0700)
committerQt by Nokia <qt-info@nokia.com>
Tue, 10 Apr 2012 23:42:12 +0000 (01:42 +0200)
Adds support for fetching and parsing Proxy Auto Config files if one
is specified in the Mac System Preferences

Task-Number: QTBUG-2069
Task-Number: QTIFW-28
Change-Id: I91feb999222187e7467f2c41383904cf0cff8633
Reviewed-by: Shane Kearns <shane.kearns@accenture.com>
src/network/kernel/kernel.pri
src/network/kernel/qnetworkproxy_mac.cpp

index a5508af..f9ea606 100644 (file)
@@ -33,7 +33,7 @@ win32: {
 }
 integrity:SOURCES += kernel/qdnslookup_unix.cpp kernel/qhostinfo_unix.cpp kernel/qnetworkinterface_unix.cpp
 
-mac:LIBS_PRIVATE += -framework SystemConfiguration -framework CoreFoundation
+mac:LIBS_PRIVATE += -framework SystemConfiguration -framework CoreFoundation -framework CoreServices
 mac:SOURCES += kernel/qnetworkproxy_mac.cpp
 else:win32:SOURCES += kernel/qnetworkproxy_win.cpp
 else:SOURCES += kernel/qnetworkproxy_generic.cpp
index d25917e..3e62c0f 100644 (file)
@@ -48,6 +48,7 @@
 
 #include <QtCore/QRegExp>
 #include <QtCore/QStringList>
+#include <QtCore/QUrl>
 #include <QtCore/qendian.h>
 #include <QtCore/qstringlist.h>
 #include "private/qcore_mac_p.h"
@@ -146,6 +147,66 @@ static QNetworkProxy proxyFromDictionary(CFDictionaryRef dict, QNetworkProxy::Pr
     return QNetworkProxy();
 }
 
+
+static QNetworkProxy proxyFromDictionary(CFDictionaryRef dict)
+{
+    QNetworkProxy::ProxyType proxyType = QNetworkProxy::DefaultProxy;
+    QString hostName;
+    quint16 port = 0;
+    QString user;
+    QString password;
+
+    CFStringRef cfProxyType = (CFStringRef)CFDictionaryGetValue(dict, kCFProxyTypeKey);
+    if (CFStringCompare(cfProxyType, kCFProxyTypeNone, 0) == kCFCompareEqualTo) {
+        proxyType = QNetworkProxy::NoProxy;
+    } else if (CFStringCompare(cfProxyType, kCFProxyTypeFTP, 0) == kCFCompareEqualTo) {
+        proxyType = QNetworkProxy::FtpCachingProxy;
+    } else if (CFStringCompare(cfProxyType, kCFProxyTypeHTTP, 0) == kCFCompareEqualTo) {
+        proxyType = QNetworkProxy::HttpProxy;
+    } else if (CFStringCompare(cfProxyType, kCFProxyTypeHTTPS, 0) == kCFCompareEqualTo) {
+        proxyType = QNetworkProxy::HttpProxy;
+    } else if (CFStringCompare(cfProxyType, kCFProxyTypeSOCKS, 0) == kCFCompareEqualTo) {
+        proxyType = QNetworkProxy::Socks5Proxy;
+    }
+
+    hostName = QCFString::toQString((CFStringRef)CFDictionaryGetValue(dict, kCFProxyHostNameKey));
+    user     = QCFString::toQString((CFStringRef)CFDictionaryGetValue(dict, kCFProxyUsernameKey));
+    password = QCFString::toQString((CFStringRef)CFDictionaryGetValue(dict, kCFProxyPasswordKey));
+
+    CFNumberRef portNumber = (CFNumberRef)CFDictionaryGetValue(dict, kCFProxyPortNumberKey);
+    if (portNumber) {
+        CFNumberGetValue(portNumber, kCFNumberSInt16Type, &port);
+    }
+
+    return QNetworkProxy(proxyType, hostName, port, user, password);
+}
+
+const char * cfurlErrorDescription(SInt32 errorCode)
+{
+    switch (errorCode) {
+        case kCFURLUnknownError:
+            return "Unknown Error";
+        case kCFURLUnknownSchemeError:
+            return "Unknown Scheme";
+        case kCFURLResourceNotFoundError:
+            return "Resource Not Found";
+        case kCFURLResourceAccessViolationError:
+            return "Resource Access Violation";
+        case kCFURLRemoteHostUnavailableError:
+            return "Remote Host Unavailable";
+        case kCFURLImproperArgumentsError:
+            return "Improper Arguments";
+        case kCFURLUnknownPropertyKeyError:
+            return "Unknown Property Key";
+        case kCFURLPropertyKeyUnavailableError:
+            return "Property Key Unavailable";
+        case kCFURLTimeoutError:
+            return "Timeout";
+        default:
+            return "Really Unknown Error";
+    }
+}
+
 QList<QNetworkProxy> macQueryInternal(const QNetworkProxyQuery &query)
 {
     QList<QNetworkProxy> result;
@@ -168,11 +229,58 @@ QList<QNetworkProxy> macQueryInternal(const QNetworkProxyQuery &query)
         int enabled;
         if (CFNumberGetValue(pacEnabled, kCFNumberIntType, &enabled) && enabled) {
             // PAC is enabled
-            CFStringRef pacUrl =
-                (CFStringRef)CFDictionaryGetValue(dict, kSCPropNetProxiesProxyAutoConfigURLString);
-            QString url = QCFString::toQString(pacUrl);
+            CFStringRef cfPacLocation = (CFStringRef)CFDictionaryGetValue(dict, kSCPropNetProxiesProxyAutoConfigURLString);
+
+#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
+            if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) {
+                QCFType<CFDataRef> pacData;
+                QCFType<CFURLRef> pacUrl = CFURLCreateWithString(kCFAllocatorDefault, cfPacLocation, NULL);
+                SInt32 errorCode;
+                if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, pacUrl, &pacData, NULL, NULL, &errorCode)) {
+                    QString pacLocation = QCFString::toQString(cfPacLocation);
+                    qWarning("Unable to get the PAC script at \"%s\" (%s)", qPrintable(pacLocation), cfurlErrorDescription(errorCode));
+                    return result;
+                }
 
-            // ### TODO: Use PAC somehow
+                QCFType<CFStringRef> pacScript = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, pacData, kCFStringEncodingISOLatin1);
+                if (!pacScript) {
+                    // This should never happen, but the documentation says it may return NULL if there was a problem creating the object.
+                    QString pacLocation = QCFString::toQString(cfPacLocation);
+                    qWarning("Unable to read the PAC script at \"%s\"", qPrintable(pacLocation));
+                    return result;
+                }
+
+                QByteArray encodedURL = query.url().toEncoded(); // converted to UTF-8
+                if (encodedURL.isEmpty()) {
+                    return result; // Invalid URL, abort
+                }
+
+                QCFType<CFURLRef> targetURL = CFURLCreateWithBytes(kCFAllocatorDefault, (UInt8*)encodedURL.data(), encodedURL.size(), kCFStringEncodingUTF8, NULL);
+                if (!targetURL) {
+                    return result; // URL creation problem, abort
+                }
+
+                QCFType<CFErrorRef> pacError;
+                QCFType<CFArrayRef> proxies = CFNetworkCopyProxiesForAutoConfigurationScript(pacScript, targetURL, &pacError);
+                if (!proxies) {
+                    QString pacLocation = QCFString::toQString(cfPacLocation);
+                    QCFType<CFStringRef> pacErrorDescription = CFErrorCopyDescription(pacError);
+                    qWarning("Execution of PAC script at \"%s\" failed: %s", qPrintable(pacLocation), qPrintable(QCFString::toQString(pacErrorDescription)));
+                    return result;
+                }
+
+                CFIndex size = CFArrayGetCount(proxies);
+                for (CFIndex i = 0; i < size; ++i) {
+                    CFDictionaryRef proxy = (CFDictionaryRef)CFArrayGetValueAtIndex(proxies, i);
+                    result << proxyFromDictionary(proxy);
+                }
+                return result;
+            } else
+#endif
+            {
+                QString pacLocation = QCFString::toQString(cfPacLocation);
+                qWarning("Mac system proxy: PAC script at \"%s\" not handled", qPrintable(pacLocation));
+            }
         }
     }