Qt 4.8
qnetworkproxy_mac.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 **
3 ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
5 **
6 ** This file is part of the QtNetwork module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and Digia. For licensing terms and
14 ** conditions see http://qt.digia.com/licensing. For further information
15 ** use the contact form at http://qt.digia.com/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights. These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file. Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "qnetworkproxy.h"
43 
44 #ifndef QT_NO_NETWORKPROXY
45 
46 #include <CoreFoundation/CoreFoundation.h>
47 #include <SystemConfiguration/SystemConfiguration.h>
48 
49 #include <QtCore/QRegExp>
50 #include <QtCore/QStringList>
51 #include <QtCore/QUrl>
52 #include <QtCore/qendian.h>
53 #include <QtCore/qstringlist.h>
54 #include "private/qcore_mac_p.h"
55 
56 /*
57  * MacOS X has a proxy configuration module in System Preferences (on
58  * MacOS X 10.5, it's in Network, Advanced), where one can set the
59  * proxy settings for:
60  *
61  * \list
62  * <li> FTP proxy
63  * <li> Web Proxy (HTTP)
64  * <li> Secure Web Proxy (HTTPS)
65  * <li> Streaming Proxy (RTSP)
66  * <li> SOCKS Proxy
67  * <li> Gopher Proxy
68  * <li> URL for Automatic Proxy Configuration (PAC scripts)
69  * <li> Bypass list (by default: *.local, 169.254/16)
70  * \endlist
71  *
72  * The matching configuration can be obtained by calling SCDynamicStoreCopyProxies
73  * (from <SystemConfiguration/SCDynamicStoreCopySpecific.h>). See
74  * Apple's documentation:
75  *
76  * http://developer.apple.com/DOCUMENTATION/Networking/Reference/SysConfig/SCDynamicStoreCopySpecific/CompositePage.html#//apple_ref/c/func/SCDynamicStoreCopyProxies
77  *
78  */
79 
81 
82 static bool isHostExcluded(CFDictionaryRef dict, const QString &host)
83 {
84  if (host.isEmpty())
85  return true;
86 
87  bool isSimple = !host.contains(QLatin1Char('.')) && !host.contains(QLatin1Char(':'));
88  CFNumberRef excludeSimples;
89  if (isSimple &&
90  (excludeSimples = (CFNumberRef)CFDictionaryGetValue(dict, kSCPropNetProxiesExcludeSimpleHostnames))) {
91  int enabled;
92  if (CFNumberGetValue(excludeSimples, kCFNumberIntType, &enabled) && enabled)
93  return true;
94  }
95 
96  QHostAddress ipAddress;
97  bool isIpAddress = ipAddress.setAddress(host);
98 
99  // not a simple host name
100  // does it match the list of exclusions?
101  CFArrayRef exclusionList = (CFArrayRef)CFDictionaryGetValue(dict, kSCPropNetProxiesExceptionsList);
102  if (!exclusionList)
103  return false;
104 
105  CFIndex size = CFArrayGetCount(exclusionList);
106  for (CFIndex i = 0; i < size; ++i) {
107  CFStringRef cfentry = (CFStringRef)CFArrayGetValueAtIndex(exclusionList, i);
108  QString entry = QCFString::toQString(cfentry);
109 
110  if (isIpAddress && ipAddress.isInSubnet(QHostAddress::parseSubnet(entry))) {
111  return true; // excluded
112  } else {
113  // do wildcard matching
115  if (rx.exactMatch(host))
116  return true;
117  }
118  }
119 
120  // host was not excluded
121  return false;
122 }
123 
125  CFStringRef enableKey, CFStringRef hostKey,
126  CFStringRef portKey)
127 {
128  CFNumberRef protoEnabled;
129  CFNumberRef protoPort;
130  CFStringRef protoHost;
131  if (enableKey
132  && (protoEnabled = (CFNumberRef)CFDictionaryGetValue(dict, enableKey))
133  && (protoHost = (CFStringRef)CFDictionaryGetValue(dict, hostKey))
134  && (protoPort = (CFNumberRef)CFDictionaryGetValue(dict, portKey))) {
135  int enabled;
136  if (CFNumberGetValue(protoEnabled, kCFNumberIntType, &enabled) && enabled) {
137  QString host = QCFString::toQString(protoHost);
138 
139  int port;
140  CFNumberGetValue(protoPort, kCFNumberIntType, &port);
141 
142  return QNetworkProxy(type, host, port);
143  }
144  }
145 
146  // proxy not enabled
147  return QNetworkProxy();
148 }
149 
150 
151 static QNetworkProxy proxyFromDictionary(CFDictionaryRef dict)
152 {
154  QString hostName;
155  quint16 port = 0;
156  QString user;
157  QString password;
158 
159  CFStringRef cfProxyType = (CFStringRef)CFDictionaryGetValue(dict, kCFProxyTypeKey);
160  if (CFStringCompare(cfProxyType, kCFProxyTypeNone, 0) == kCFCompareEqualTo) {
161  proxyType = QNetworkProxy::NoProxy;
162  } else if (CFStringCompare(cfProxyType, kCFProxyTypeFTP, 0) == kCFCompareEqualTo) {
163  proxyType = QNetworkProxy::FtpCachingProxy;
164  } else if (CFStringCompare(cfProxyType, kCFProxyTypeHTTP, 0) == kCFCompareEqualTo) {
165  proxyType = QNetworkProxy::HttpProxy;
166  } else if (CFStringCompare(cfProxyType, kCFProxyTypeHTTPS, 0) == kCFCompareEqualTo) {
167  proxyType = QNetworkProxy::HttpProxy;
168  } else if (CFStringCompare(cfProxyType, kCFProxyTypeSOCKS, 0) == kCFCompareEqualTo) {
169  proxyType = QNetworkProxy::Socks5Proxy;
170  }
171 
172  hostName = QCFString::toQString((CFStringRef)CFDictionaryGetValue(dict, kCFProxyHostNameKey));
173  user = QCFString::toQString((CFStringRef)CFDictionaryGetValue(dict, kCFProxyUsernameKey));
174  password = QCFString::toQString((CFStringRef)CFDictionaryGetValue(dict, kCFProxyPasswordKey));
175 
176  CFNumberRef portNumber = (CFNumberRef)CFDictionaryGetValue(dict, kCFProxyPortNumberKey);
177  if (portNumber) {
178  CFNumberGetValue(portNumber, kCFNumberSInt16Type, &port);
179  }
180 
181  return QNetworkProxy(proxyType, hostName, port, user, password);
182 }
183 
184 const char * cfurlErrorDescription(SInt32 errorCode)
185 {
186  switch (errorCode) {
187  case kCFURLUnknownError:
188  return "Unknown Error";
189  case kCFURLUnknownSchemeError:
190  return "Unknown Scheme";
191  case kCFURLResourceNotFoundError:
192  return "Resource Not Found";
193  case kCFURLResourceAccessViolationError:
194  return "Resource Access Violation";
195  case kCFURLRemoteHostUnavailableError:
196  return "Remote Host Unavailable";
197  case kCFURLImproperArgumentsError:
198  return "Improper Arguments";
199  case kCFURLUnknownPropertyKeyError:
200  return "Unknown Property Key";
201  case kCFURLPropertyKeyUnavailableError:
202  return "Property Key Unavailable";
203  case kCFURLTimeoutError:
204  return "Timeout";
205  default:
206  return "Really Unknown Error";
207  }
208 }
209 
211 {
212  QList<QNetworkProxy> result;
213 
214  // obtain a dictionary to the proxy settings:
215  CFDictionaryRef dict = SCDynamicStoreCopyProxies(NULL);
216  if (!dict) {
217  qWarning("QNetworkProxyFactory::systemProxyForQuery: SCDynamicStoreCopyProxies returned NULL");
218  return result; // failed
219  }
220 
221  if (isHostExcluded(dict, query.peerHostName())) {
222  CFRelease(dict);
223  return result; // no proxy for this host
224  }
225 
226  // is there a PAC enabled? If so, use it first.
227  CFNumberRef pacEnabled;
228  if ((pacEnabled = (CFNumberRef)CFDictionaryGetValue(dict, kSCPropNetProxiesProxyAutoConfigEnable))) {
229  int enabled;
230  if (CFNumberGetValue(pacEnabled, kCFNumberIntType, &enabled) && enabled) {
231  // PAC is enabled
232  CFStringRef cfPacLocation = (CFStringRef)CFDictionaryGetValue(dict, kSCPropNetProxiesProxyAutoConfigURLString);
233 
234 #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
236  QCFType<CFDataRef> pacData;
237  QCFType<CFURLRef> pacUrl = CFURLCreateWithString(kCFAllocatorDefault, cfPacLocation, NULL);
238  SInt32 errorCode;
239  if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, pacUrl, &pacData, NULL, NULL, &errorCode)) {
240  QString pacLocation = QCFString::toQString(cfPacLocation);
241  qWarning("Unable to get the PAC script at \"%s\" (%s)", qPrintable(pacLocation), cfurlErrorDescription(errorCode));
242  return result;
243  }
244 
245  QCFType<CFStringRef> pacScript = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, pacData, kCFStringEncodingISOLatin1);
246  if (!pacScript) {
247  // This should never happen, but the documentation says it may return NULL if there was a problem creating the object.
248  QString pacLocation = QCFString::toQString(cfPacLocation);
249  qWarning("Unable to read the PAC script at \"%s\"", qPrintable(pacLocation));
250  return result;
251  }
252 
253  QByteArray encodedURL = query.url().toEncoded(); // converted to UTF-8
254  if (encodedURL.isEmpty()) {
255  return result; // Invalid URL, abort
256  }
257 
258  QCFType<CFURLRef> targetURL = CFURLCreateWithBytes(kCFAllocatorDefault, (UInt8*)encodedURL.data(), encodedURL.size(), kCFStringEncodingUTF8, NULL);
259  if (!targetURL) {
260  return result; // URL creation problem, abort
261  }
262 
263  QCFType<CFErrorRef> pacError;
264  QCFType<CFArrayRef> proxies = CFNetworkCopyProxiesForAutoConfigurationScript(pacScript, targetURL, &pacError);
265  if (!proxies) {
266  QString pacLocation = QCFString::toQString(cfPacLocation);
267  QCFType<CFStringRef> pacErrorDescription = CFErrorCopyDescription(pacError);
268  qWarning("Execution of PAC script at \"%s\" failed: %s", qPrintable(pacLocation), qPrintable(QCFString::toQString(pacErrorDescription)));
269  return result;
270  }
271 
272  CFIndex size = CFArrayGetCount(proxies);
273  for (CFIndex i = 0; i < size; ++i) {
274  CFDictionaryRef proxy = (CFDictionaryRef)CFArrayGetValueAtIndex(proxies, i);
275  result << proxyFromDictionary(proxy);
276  }
277  return result;
278  } else
279 #endif
280  {
281  QString pacLocation = QCFString::toQString(cfPacLocation);
282  qWarning("Mac system proxy: PAC script at \"%s\" not handled", qPrintable(pacLocation));
283  }
284  }
285  }
286 
287  // no PAC, decide which proxy we're looking for based on the query
288  bool isHttps = false;
289  QString protocol = query.protocolTag().toLower();
290 
291  // try the protocol-specific proxy
292  QNetworkProxy protocolSpecificProxy;
293  if (protocol == QLatin1String("ftp")) {
294  protocolSpecificProxy =
296  kSCPropNetProxiesFTPEnable,
297  kSCPropNetProxiesFTPProxy,
298  kSCPropNetProxiesFTPPort);
299  } else if (protocol == QLatin1String("http")) {
300  protocolSpecificProxy =
302  kSCPropNetProxiesHTTPEnable,
303  kSCPropNetProxiesHTTPProxy,
304  kSCPropNetProxiesHTTPPort);
305  } else if (protocol == QLatin1String("https")) {
306  isHttps = true;
307  protocolSpecificProxy =
309  kSCPropNetProxiesHTTPSEnable,
310  kSCPropNetProxiesHTTPSProxy,
311  kSCPropNetProxiesHTTPSPort);
312  }
313  if (protocolSpecificProxy.type() != QNetworkProxy::DefaultProxy)
314  result << protocolSpecificProxy;
315 
316  // let's add SOCKSv5 if present too
318  kSCPropNetProxiesSOCKSEnable,
319  kSCPropNetProxiesSOCKSProxy,
320  kSCPropNetProxiesSOCKSPort);
321  if (socks5.type() != QNetworkProxy::DefaultProxy)
322  result << socks5;
323 
324  // let's add the HTTPS proxy if present (and if we haven't added
325  // yet)
326  if (!isHttps) {
328  kSCPropNetProxiesHTTPSEnable,
329  kSCPropNetProxiesHTTPSProxy,
330  kSCPropNetProxiesHTTPSPort);
331  if (https.type() != QNetworkProxy::DefaultProxy && https != protocolSpecificProxy)
332  result << https;
333  }
334 
335  CFRelease(dict);
336  return result;
337 }
338 
340 {
341  QList<QNetworkProxy> result = macQueryInternal(query);
342  if (result.isEmpty())
343  result << QNetworkProxy::NoProxy;
344 
345  return result;
346 }
347 
348 #endif
349 
QNetworkProxy::ProxyType type() const
Returns the proxy type for this instance.
QBool contains(QChar c, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition: qstring.h:904
const char * cfurlErrorDescription(SInt32 errorCode)
const struct __CFString * CFStringRef
int type
Definition: qmetatype.cpp:239
#define QT_END_NAMESPACE
This macro expands to.
Definition: qglobal.h:90
char * data()
Returns a pointer to the data stored in the byte array.
Definition: qbytearray.h:429
The QRegExp class provides pattern matching using regular expressions.
Definition: qregexp.h:61
QString peerHostName() const
Returns the host name or IP address being of the outgoing connection being requested, or an empty string if the remote hostname is not known.
The QByteArray class provides an array of bytes.
Definition: qbytearray.h:135
static QString toQString(CFStringRef cfstr)
Definition: qcore_mac.cpp:47
static QList< QNetworkProxy > systemProxyForQuery(const QNetworkProxyQuery &query=QNetworkProxyQuery())
This function takes the query request, query, examines the details of the type of socket or request a...
QLatin1String(DBUS_INTERFACE_DBUS))) Q_GLOBAL_STATIC_WITH_ARGS(QString
The QString class provides a Unicode character string.
Definition: qstring.h:83
bool isEmpty() const
Returns true if the list contains no items; otherwise returns false.
Definition: qlist.h:152
The QNetworkProxy class provides a network layer proxy.
#define QT_BEGIN_NAMESPACE
This macro expands to.
Definition: qglobal.h:89
QString protocolTag() const
Returns the protocol tag for this QNetworkProxyQuery object, or an empty QString in case the protocol...
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition: qstring.h:704
static QPair< QHostAddress, int > parseSubnet(const QString &subnet)
Parses the IP and subnet information contained in subnet and returns the network prefix for that netw...
unsigned short quint16
Definition: qglobal.h:936
Q_CORE_EXPORT void qWarning(const char *,...)
static bool isHostExcluded(CFDictionaryRef dict, const QString &host)
bool isInSubnet(const QHostAddress &subnet, int netmask) const
Returns true if this IP is in the subnet described by the network prefix subnet and netmask netmask...
The QNetworkProxyQuery class is used to query the proxy settings for a socket.
Definition: qnetworkproxy.h:60
QByteArray toEncoded(FormattingOptions options=None) const
Returns the encoded representation of the URL if it&#39;s valid; otherwise an empty QByteArray is returne...
Definition: qurl.cpp:5949
void setAddress(quint32 ip4Addr)
Set the IPv4 address specified by ip4Addr.
QList< QNetworkProxy > macQueryInternal(const QNetworkProxyQuery &query)
QString toLower() const Q_REQUIRED_RESULT
Returns a lowercase copy of the string.
Definition: qstring.cpp:5389
int size() const
Returns the number of bytes in this byte array.
Definition: qbytearray.h:402
QUrl url() const
Returns the URL component of this QNetworkProxyQuery object in case of a query of type QNetworkProxyQ...
bool exactMatch(const QString &str) const
Returns true if str is matched exactly by this regular expression; otherwise returns false...
Definition: qregexp.cpp:4094
static const MacVersion MacintoshVersion
the version of the Macintosh operating system on which the application is run (Mac only)...
Definition: qglobal.h:1646
bool isEmpty() const
Returns true if the byte array has size 0; otherwise returns false.
Definition: qbytearray.h:421
The QHostAddress class provides an IP address.
Definition: qhostaddress.h:70
static QNetworkProxy proxyFromDictionary(CFDictionaryRef dict, QNetworkProxy::ProxyType type, CFStringRef enableKey, CFStringRef hostKey, CFStringRef portKey)
const struct __CFArray * CFArrayRef
#define qPrintable(string)
Definition: qglobal.h:1750
The QLatin1Char class provides an 8-bit ASCII/Latin-1 character.
Definition: qchar.h:55
#define enabled
ProxyType
This enum describes the types of network proxying provided in Qt.