blob: 92f91956b90d3545b5157971f330825ce3c2eef9 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtNetwork module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qnetworkproxy.h"
#ifndef QT_NO_NETWORKPROXY
#include <CFNetwork/CFNetwork.h>
#include <CoreFoundation/CoreFoundation.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <QtCore/QRegExp>
#include <QtCore/QStringList>
#include <QtCore/QUrl>
#include <QtCore/qendian.h>
#include <QtCore/qstringlist.h>
#include "private/qcore_mac_p.h"
/*
* MacOS X has a proxy configuration module in System Preferences (on
* MacOS X 10.5, it's in Network, Advanced), where one can set the
* proxy settings for:
*
* \list
* \li FTP proxy
* \li Web Proxy (HTTP)
* \li Secure Web Proxy (HTTPS)
* \li Streaming Proxy (RTSP)
* \li SOCKS Proxy
* \li Gopher Proxy
* \li URL for Automatic Proxy Configuration (PAC scripts)
* \li Bypass list (by default: *.local, 169.254/16)
* \endlist
*
* The matching configuration can be obtained by calling SCDynamicStoreCopyProxies
* (from <SystemConfiguration/SCDynamicStoreCopySpecific.h>). See
* Apple's documentation:
*
* http://developer.apple.com/DOCUMENTATION/Networking/Reference/SysConfig/SCDynamicStoreCopySpecific/CompositePage.html#//apple_ref/c/func/SCDynamicStoreCopyProxies
*
*/
QT_BEGIN_NAMESPACE
static bool isHostExcluded(CFDictionaryRef dict, const QString &host)
{
if (host.isEmpty())
return true;
bool isSimple = !host.contains(QLatin1Char('.')) && !host.contains(QLatin1Char(':'));
CFNumberRef excludeSimples;
if (isSimple &&
(excludeSimples = (CFNumberRef)CFDictionaryGetValue(dict, kSCPropNetProxiesExcludeSimpleHostnames))) {
int enabled;
if (CFNumberGetValue(excludeSimples, kCFNumberIntType, &enabled) && enabled)
return true;
}
QHostAddress ipAddress;
bool isIpAddress = ipAddress.setAddress(host);
// not a simple host name
// does it match the list of exclusions?
CFArrayRef exclusionList = (CFArrayRef)CFDictionaryGetValue(dict, kSCPropNetProxiesExceptionsList);
if (!exclusionList)
return false;
CFIndex size = CFArrayGetCount(exclusionList);
for (CFIndex i = 0; i < size; ++i) {
CFStringRef cfentry = (CFStringRef)CFArrayGetValueAtIndex(exclusionList, i);
QString entry = QString::fromCFString(cfentry);
if (isIpAddress && ipAddress.isInSubnet(QHostAddress::parseSubnet(entry))) {
return true; // excluded
} else {
// do wildcard matching
QRegExp rx(entry, Qt::CaseInsensitive, QRegExp::Wildcard);
if (rx.exactMatch(host))
return true;
}
}
// host was not excluded
return false;
}
static QNetworkProxy proxyFromDictionary(CFDictionaryRef dict, QNetworkProxy::ProxyType type,
CFStringRef enableKey, CFStringRef hostKey,
CFStringRef portKey)
{
CFNumberRef protoEnabled;
CFNumberRef protoPort;
CFStringRef protoHost;
if (enableKey
&& (protoEnabled = (CFNumberRef)CFDictionaryGetValue(dict, enableKey))
&& (protoHost = (CFStringRef)CFDictionaryGetValue(dict, hostKey))
&& (protoPort = (CFNumberRef)CFDictionaryGetValue(dict, portKey))) {
int enabled;
if (CFNumberGetValue(protoEnabled, kCFNumberIntType, &enabled) && enabled) {
QString host = QString::fromCFString(protoHost);
int port;
CFNumberGetValue(protoPort, kCFNumberIntType, &port);
return QNetworkProxy(type, host, port);
}
}
// proxy not enabled
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 = QString::fromCFString((CFStringRef)CFDictionaryGetValue(dict, kCFProxyHostNameKey));
user = QString::fromCFString((CFStringRef)CFDictionaryGetValue(dict, kCFProxyUsernameKey));
password = QString::fromCFString((CFStringRef)CFDictionaryGetValue(dict, kCFProxyPasswordKey));
CFNumberRef portNumber = (CFNumberRef)CFDictionaryGetValue(dict, kCFProxyPortNumberKey);
if (portNumber) {
CFNumberGetValue(portNumber, kCFNumberSInt16Type, &port);
}
return QNetworkProxy(proxyType, hostName, port, user, password);
}
namespace {
struct PACInfo {
QCFType<CFArrayRef> proxies;
QCFType<CFErrorRef> error;
bool done = false;
};
void proxyAutoConfigCallback(void *client, CFArrayRef proxylist, CFErrorRef error)
{
Q_ASSERT(client);
PACInfo *info = static_cast<PACInfo *>(client);
info->done = true;
if (error) {
CFRetain(error);
info->error = error;
}
if (proxylist) {
CFRetain(proxylist);
info->proxies = proxylist;
}
}
} // anon namespace
QList<QNetworkProxy> macQueryInternal(const QNetworkProxyQuery &query)
{
QList<QNetworkProxy> result;
// obtain a dictionary to the proxy settings:
CFDictionaryRef dict = SCDynamicStoreCopyProxies(NULL);
if (!dict) {
qWarning("QNetworkProxyFactory::systemProxyForQuery: SCDynamicStoreCopyProxies returned NULL");
return result; // failed
}
if (isHostExcluded(dict, query.peerHostName())) {
CFRelease(dict);
return result; // no proxy for this host
}
// is there a PAC enabled? If so, use it first.
CFNumberRef pacEnabled;
if ((pacEnabled = (CFNumberRef)CFDictionaryGetValue(dict, kSCPropNetProxiesProxyAutoConfigEnable))) {
int enabled;
if (CFNumberGetValue(pacEnabled, kCFNumberIntType, &enabled) && enabled) {
// PAC is enabled
// kSCPropNetProxiesProxyAutoConfigURLString returns the URL string
// as entered in the system proxy configuration dialog
CFStringRef pacLocationSetting = (CFStringRef)CFDictionaryGetValue(dict, kSCPropNetProxiesProxyAutoConfigURLString);
QCFType<CFStringRef> cfPacLocation = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, pacLocationSetting, NULL, NULL,
kCFStringEncodingUTF8);
QCFType<CFDataRef> pacData;
QCFType<CFURLRef> pacUrl = CFURLCreateWithString(kCFAllocatorDefault, cfPacLocation, NULL);
if (!pacUrl) {
qWarning("Invalid PAC URL \"%s\"", qPrintable(QString::fromCFString(cfPacLocation)));
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
}
CFStreamClientContext pacCtx;
pacCtx.version = 0;
PACInfo pacInfo;
pacCtx.info = &pacInfo;
pacCtx.retain = NULL;
pacCtx.release = NULL;
pacCtx.copyDescription = NULL;
static CFStringRef pacRunLoopMode = CFSTR("qtPACRunLoopMode");
QCFType<CFRunLoopSourceRef> pacRunLoopSource = CFNetworkExecuteProxyAutoConfigurationURL(pacUrl, targetURL, &proxyAutoConfigCallback, &pacCtx);
CFRunLoopAddSource(CFRunLoopGetCurrent(), pacRunLoopSource, pacRunLoopMode);
while (!pacInfo.done)
CFRunLoopRunInMode(pacRunLoopMode, 1000, /*returnAfterSourceHandled*/ true);
if (!pacInfo.proxies) {
QString pacLocation = QString::fromCFString(cfPacLocation);
QCFType<CFStringRef> pacErrorDescription = CFErrorCopyDescription(pacInfo.error);
qWarning("Execution of PAC script at \"%s\" failed: %s", qPrintable(pacLocation), qPrintable(QString::fromCFString(pacErrorDescription)));
return result;
}
CFIndex size = CFArrayGetCount(pacInfo.proxies);
for (CFIndex i = 0; i < size; ++i) {
CFDictionaryRef proxy = (CFDictionaryRef)CFArrayGetValueAtIndex(pacInfo.proxies, i);
result << proxyFromDictionary(proxy);
}
return result;
}
}
// no PAC, decide which proxy we're looking for based on the query
bool isHttps = false;
QString protocol = query.protocolTag().toLower();
// try the protocol-specific proxy
QNetworkProxy protocolSpecificProxy;
if (protocol == QLatin1String("ftp")) {
protocolSpecificProxy =
proxyFromDictionary(dict, QNetworkProxy::FtpCachingProxy,
kSCPropNetProxiesFTPEnable,
kSCPropNetProxiesFTPProxy,
kSCPropNetProxiesFTPPort);
} else if (protocol == QLatin1String("http")) {
protocolSpecificProxy =
proxyFromDictionary(dict, QNetworkProxy::HttpProxy,
kSCPropNetProxiesHTTPEnable,
kSCPropNetProxiesHTTPProxy,
kSCPropNetProxiesHTTPPort);
} else if (protocol == QLatin1String("https")) {
isHttps = true;
protocolSpecificProxy =
proxyFromDictionary(dict, QNetworkProxy::HttpProxy,
kSCPropNetProxiesHTTPSEnable,
kSCPropNetProxiesHTTPSProxy,
kSCPropNetProxiesHTTPSPort);
}
if (protocolSpecificProxy.type() != QNetworkProxy::DefaultProxy)
result << protocolSpecificProxy;
// let's add SOCKSv5 if present too
QNetworkProxy socks5 = proxyFromDictionary(dict, QNetworkProxy::Socks5Proxy,
kSCPropNetProxiesSOCKSEnable,
kSCPropNetProxiesSOCKSProxy,
kSCPropNetProxiesSOCKSPort);
if (socks5.type() != QNetworkProxy::DefaultProxy)
result << socks5;
// let's add the HTTPS proxy if present (and if we haven't added
// yet)
if (!isHttps) {
QNetworkProxy https = proxyFromDictionary(dict, QNetworkProxy::HttpProxy,
kSCPropNetProxiesHTTPSEnable,
kSCPropNetProxiesHTTPSProxy,
kSCPropNetProxiesHTTPSPort);
if (https.type() != QNetworkProxy::DefaultProxy && https != protocolSpecificProxy)
result << https;
}
CFRelease(dict);
return result;
}
QList<QNetworkProxy> QNetworkProxyFactory::systemProxyForQuery(const QNetworkProxyQuery &query)
{
QList<QNetworkProxy> result = macQueryInternal(query);
if (result.isEmpty())
result << QNetworkProxy::NoProxy;
return result;
}
#endif
QT_END_NAMESPACE