Skip to content
Snippets Groups Projects
NetworkProxyOSX.cpp 8.08 KiB
Newer Older
// Compile on OSX only.
#if defined(__APPLE__)

#include "MantidKernel/NetworkProxy.h"
#include <SystemConfiguration/SystemConfiguration.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CFNetwork/CFProxySupport.h>
#include <sstream>
#include <string>
#include <vector>

namespace Mantid {
namespace Kernel {

/**
 * Helper function to convert CFStringRefs to std::string
 * @param str : CRFStringRef variable
 * @return std::string containing the contents of the input string
std::string toString(CFStringRef str) {
  if (!str)
    return std::string();
  CFIndex length = CFStringGetLength(str);
  const UniChar *chars = CFStringGetCharactersPtr(str);
  if (chars)
    return std::string(reinterpret_cast<const char *>(chars), length);

  std::vector<UniChar> buffer(length);
  CFStringGetCharacters(str, CFRangeMake(0, length), buffer.data());
  return std::string(reinterpret_cast<const char *>(buffer.data()), length);
}

/**
 * Helper enums.
 */
enum ProxyType {
  DefaultProxy,
  Socks5Proxy,
  NoProxy,
  HttpProxy,
  HttpCachingProxy,
  FtpCachingProxy
};

/// Typedef Collection of proxy information.
typedef std::vector<ProxyInfo> ProxyInfoVec;

/**
 * Extract proxy information from a CFDistionaryRef
 * @param dict : CFDictionary item
 * @return ProxyInfo object.
 */
ProxyInfo proxyFromDictionary(CFDictionaryRef dict) {
  ProxyInfo proxyInfo;
  ProxyType proxyType = NoProxy;

Hahn, Steven's avatar
Hahn, Steven committed
  CFStringRef cfProxyType = reinterpret_cast<CFStringRef>(
      CFDictionaryGetValue(dict, kCFProxyTypeKey));

  if (CFStringCompare(cfProxyType, kCFProxyTypeFTP, 0) == kCFCompareEqualTo) {
    proxyType = FtpCachingProxy;
  } else if (CFStringCompare(cfProxyType, kCFProxyTypeHTTP, 0) ==
             kCFCompareEqualTo) {
    proxyType = HttpProxy;
  } else if (CFStringCompare(cfProxyType, kCFProxyTypeHTTPS, 0) ==
             kCFCompareEqualTo) {
    proxyType = HttpProxy;
  } else if (CFStringCompare(cfProxyType, kCFProxyTypeSOCKS, 0) ==
             kCFCompareEqualTo) {
    proxyType = Socks5Proxy;
  }

  int port = 0;
Hahn, Steven's avatar
Hahn, Steven committed
  std::string hostName = toString(reinterpret_cast<CFStringRef>(
      CFDictionaryGetValue(dict, kCFProxyHostNameKey)));
  CFNumberRef portNumber = reinterpret_cast<CFNumberRef>(
      CFDictionaryGetValue(dict, kCFProxyPortNumberKey));
  if (portNumber) {
    CFNumberGetValue(portNumber, kCFNumberSInt16Type, &port);
  }
  if (proxyType != NoProxy) {
    proxyInfo = ProxyInfo(hostName, port, proxyType == HttpProxy);
  }

  return proxyInfo;
}

/**
 * Get proxy information from a proxy script.
 * @param dict : Dictionary to search through.
 * @param targetURLString : Target remote URL
 * @param logger : Log object
 * @return Collection of proxy information.
 */
ProxyInfoVec proxyInformationFromPac(CFDictionaryRef dict,
                                     const std::string &targetURLString,
                                     Logger &logger) {
  ProxyInfoVec proxyInfoVec;

  // is there a PAC enabled? If so, use it first.
  CFNumberRef pacEnabled;
  if ((pacEnabled = reinterpret_cast<CFNumberRef>(CFDictionaryGetValue(
           dict, kSCPropNetProxiesProxyAutoConfigEnable)))) {
    int enabled;
    if (CFNumberGetValue(pacEnabled, kCFNumberIntType, &enabled) && enabled) {
      // PAC is enabled
Hahn, Steven's avatar
Hahn, Steven committed
      CFStringRef cfPacLocation =
          reinterpret_cast<CFStringRef>(CFDictionaryGetValue(
              dict, kSCPropNetProxiesProxyAutoConfigURLString));
      CFDataRef pacData;
      CFURLRef pacURL =
          CFURLCreateWithString(kCFAllocatorDefault, cfPacLocation, nullptr);
      SInt32 errorCode;
Hahn, Steven's avatar
Hahn, Steven committed
      if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, pacURL,
                                                    &pacData, nullptr, nullptr,
                                                    &errorCode)) {
        logger.debug() << "Unable to get the PAC script at "
                       << toString(cfPacLocation) << "Error code: " << errorCode
        return proxyInfoVec;
      CFStringRef pacScript = CFStringCreateFromExternalRepresentation(
          kCFAllocatorDefault, pacData, kCFStringEncodingISOLatin1);

      CFURLRef targetURL = CFURLCreateWithBytes(
          kCFAllocatorDefault, reinterpret_cast<UInt8 *>(
                                   const_cast<char *>(targetURLString.c_str())),
          targetURLString.size(), kCFStringEncodingUTF8, nullptr);
      if (!targetURL) {
        logger.debug("Problem with Target URI for proxy script");
        return proxyInfoVec;
      CFErrorRef pacError;
      CFArrayRef proxies = CFNetworkCopyProxiesForAutoConfigurationScript(
          pacScript, targetURL, &pacError);
      if (!proxies) {
        std::string pacLocation = toString(cfPacLocation);
        CFStringRef pacErrorDescription = CFErrorCopyDescription(pacError);
        logger.debug() << "Execution of PAC script at \"%s\" failed: %s"
                       << pacLocation << toString(pacErrorDescription) << '\n';
      CFIndex size = CFArrayGetCount(proxies);
      for (CFIndex i = 0; i < size; ++i) {
Hahn, Steven's avatar
Hahn, Steven committed
        CFDictionaryRef proxy = reinterpret_cast<CFDictionaryRef>(
            CFArrayGetValueAtIndex(proxies, i));
        proxyInfoVec.push_back(proxyFromDictionary(proxy));
  }
  return proxyInfoVec;
}

/**
 * Proxy from dictionary.
 * @param dict
 * @param enableKey
 * @param hostKey
 * @param portKey
 * @return return Proxy object.
 */
ProxyInfo proxyFromDictionary(CFDictionaryRef dict, CFStringRef enableKey,
                              CFStringRef hostKey, CFStringRef portKey) {
  ProxyInfo proxyInfo;
  CFNumberRef protoEnabled;
  CFNumberRef protoPort;
  CFStringRef protoHost;
Hahn, Steven's avatar
Hahn, Steven committed
  if (enableKey && (protoEnabled = reinterpret_cast<CFNumberRef>(
                        CFDictionaryGetValue(dict, enableKey))) &&
      (protoHost = reinterpret_cast<CFStringRef>(
           CFDictionaryGetValue(dict, hostKey))) &&
      (protoPort = reinterpret_cast<CFNumberRef>(
           CFDictionaryGetValue(dict, portKey)))) {
    int enabled;
    if (CFNumberGetValue(protoEnabled, kCFNumberIntType, &enabled) && enabled) {
      std::string host = toString(protoHost);

      int port;
      CFNumberGetValue(protoPort, kCFNumberIntType, &port);
      proxyInfo = ProxyInfo(host, port, HttpProxy);
  }

  // proxy not enabled
  return proxyInfo;
}

/**
 * Specifially look for http proxy settings.
 * @param dict :
 * @return Return the proxy info object.
 */
ProxyInfo httpProxyFromSystem(CFDictionaryRef dict) {
  ProxyInfo tempProxy = proxyFromDictionary(dict, kSCPropNetProxiesHTTPEnable,
                                            kSCPropNetProxiesHTTPProxy,
                                            kSCPropNetProxiesHTTPPort);

  return tempProxy;
}

/**
 * Find the http proxy.
 * Look through the proxy settings script first.
 * @param targetURLString : Target remote URL string
 * @param logger : ref to log object.
 * @return Proxy object.
 */
ProxyInfo findHttpProxy(const std::string &targetURLString,
                        Mantid::Kernel::Logger &logger) {
  ProxyInfo httpProxy;
  CFDictionaryRef dict = SCDynamicStoreCopyProxies(nullptr);
  if (!dict) {
    logger.debug("NetworkProxyOSX SCDynamicStoreCopyProxies returned NULL");
  }

  // Query the proxy pac first.
  ProxyInfoVec info = proxyInformationFromPac(dict, targetURLString, logger);

  bool foundHttpProxy = false;
  for (const auto &proxyInfo : info) {
    if (proxyInfo.isHttpProxy()) {
      foundHttpProxy = true;
      httpProxy = proxyInfo;
      break;
    }
  }
  // Query the http proxy settings second.
  if (!foundHttpProxy) {
    ProxyInfo tempProxy = httpProxyFromSystem(dict);
    if (tempProxy.isHttpProxy()) {
      httpProxy = tempProxy;
      foundHttpProxy = true;
    }
  }
  if (!foundHttpProxy) {
    logger.debug("NetworkProxyOSX. No system HTTP Proxy set!");
  return httpProxy;
}
//----------------------------------------------------------------------------------------------
/** Constructor
 */
NetworkProxy::NetworkProxy() : m_logger("network_proxy_logger_osx") {}
ProxyInfo NetworkProxy::getHttpProxy(const std::string &targetURLString) {
  return findHttpProxy(targetURLString, m_logger);
}
} // namespace Kernel
} // namespace Mantid

#endif