Home | History | Annotate | Download | only in proxy
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "net/proxy/proxy_config_service_mac.h"
      6 
      7 #include <CoreFoundation/CoreFoundation.h>
      8 #include <SystemConfiguration/SystemConfiguration.h>
      9 
     10 #include "base/logging.h"
     11 #include "base/mac/mac_util.h"
     12 #include "base/mac/scoped_cftyperef.h"
     13 #include "base/sys_string_conversions.h"
     14 #include "net/base/net_errors.h"
     15 #include "net/proxy/proxy_config.h"
     16 #include "net/proxy/proxy_info.h"
     17 #include "net/proxy/proxy_server.h"
     18 
     19 namespace net {
     20 
     21 namespace {
     22 
     23 const int kPollIntervalSec = 5;
     24 
     25 // Utility function to pull out a boolean value from a dictionary and return it,
     26 // returning a default value if the key is not present.
     27 bool GetBoolFromDictionary(CFDictionaryRef dict,
     28                            CFStringRef key,
     29                            bool default_value) {
     30   CFNumberRef number = (CFNumberRef)base::mac::GetValueFromDictionary(
     31       dict, key, CFNumberGetTypeID());
     32   if (!number)
     33     return default_value;
     34 
     35   int int_value;
     36   if (CFNumberGetValue(number, kCFNumberIntType, &int_value))
     37     return int_value;
     38   else
     39     return default_value;
     40 }
     41 
     42 void GetCurrentProxyConfig(ProxyConfig* config) {
     43   base::mac::ScopedCFTypeRef<CFDictionaryRef> config_dict(
     44       SCDynamicStoreCopyProxies(NULL));
     45   DCHECK(config_dict);
     46 
     47   // auto-detect
     48 
     49   // There appears to be no UI for this configuration option, and we're not sure
     50   // if Apple's proxy code even takes it into account. But the constant is in
     51   // the header file so we'll use it.
     52   config->set_auto_detect(
     53       GetBoolFromDictionary(config_dict.get(),
     54                             kSCPropNetProxiesProxyAutoDiscoveryEnable,
     55                             false));
     56 
     57   // PAC file
     58 
     59   if (GetBoolFromDictionary(config_dict.get(),
     60                             kSCPropNetProxiesProxyAutoConfigEnable,
     61                             false)) {
     62     CFStringRef pac_url_ref = (CFStringRef)base::mac::GetValueFromDictionary(
     63         config_dict.get(),
     64         kSCPropNetProxiesProxyAutoConfigURLString,
     65         CFStringGetTypeID());
     66     if (pac_url_ref)
     67       config->set_pac_url(GURL(base::SysCFStringRefToUTF8(pac_url_ref)));
     68   }
     69 
     70   // proxies (for now ftp, http, https, and SOCKS)
     71 
     72   if (GetBoolFromDictionary(config_dict.get(),
     73                             kSCPropNetProxiesFTPEnable,
     74                             false)) {
     75     ProxyServer proxy_server =
     76         ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP,
     77                                     config_dict.get(),
     78                                     kSCPropNetProxiesFTPProxy,
     79                                     kSCPropNetProxiesFTPPort);
     80     if (proxy_server.is_valid()) {
     81       config->proxy_rules().type =
     82           ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
     83       config->proxy_rules().proxy_for_ftp = proxy_server;
     84     }
     85   }
     86   if (GetBoolFromDictionary(config_dict.get(),
     87                             kSCPropNetProxiesHTTPEnable,
     88                             false)) {
     89     ProxyServer proxy_server =
     90         ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP,
     91                                     config_dict.get(),
     92                                     kSCPropNetProxiesHTTPProxy,
     93                                     kSCPropNetProxiesHTTPPort);
     94     if (proxy_server.is_valid()) {
     95       config->proxy_rules().type =
     96           ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
     97       config->proxy_rules().proxy_for_http = proxy_server;
     98     }
     99   }
    100   if (GetBoolFromDictionary(config_dict.get(),
    101                             kSCPropNetProxiesHTTPSEnable,
    102                             false)) {
    103     ProxyServer proxy_server =
    104         ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP,
    105                                     config_dict.get(),
    106                                     kSCPropNetProxiesHTTPSProxy,
    107                                     kSCPropNetProxiesHTTPSPort);
    108     if (proxy_server.is_valid()) {
    109       config->proxy_rules().type =
    110           ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
    111       config->proxy_rules().proxy_for_https = proxy_server;
    112     }
    113   }
    114   if (GetBoolFromDictionary(config_dict.get(),
    115                             kSCPropNetProxiesSOCKSEnable,
    116                             false)) {
    117     ProxyServer proxy_server =
    118         ProxyServer::FromDictionary(ProxyServer::SCHEME_SOCKS5,
    119                                     config_dict.get(),
    120                                     kSCPropNetProxiesSOCKSProxy,
    121                                     kSCPropNetProxiesSOCKSPort);
    122     if (proxy_server.is_valid()) {
    123       config->proxy_rules().type =
    124           ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
    125       config->proxy_rules().fallback_proxy = proxy_server;
    126     }
    127   }
    128 
    129   // proxy bypass list
    130 
    131   CFArrayRef bypass_array_ref =
    132       (CFArrayRef)base::mac::GetValueFromDictionary(
    133           config_dict.get(),
    134           kSCPropNetProxiesExceptionsList,
    135           CFArrayGetTypeID());
    136   if (bypass_array_ref) {
    137     CFIndex bypass_array_count = CFArrayGetCount(bypass_array_ref);
    138     for (CFIndex i = 0; i < bypass_array_count; ++i) {
    139       CFStringRef bypass_item_ref =
    140           (CFStringRef)CFArrayGetValueAtIndex(bypass_array_ref, i);
    141       if (CFGetTypeID(bypass_item_ref) != CFStringGetTypeID()) {
    142         LOG(WARNING) << "Expected value for item " << i
    143                      << " in the kSCPropNetProxiesExceptionsList"
    144                         " to be a CFStringRef but it was not";
    145 
    146       } else {
    147         config->proxy_rules().bypass_rules.AddRuleFromString(
    148             base::SysCFStringRefToUTF8(bypass_item_ref));
    149       }
    150     }
    151   }
    152 
    153   // proxy bypass boolean
    154 
    155   if (GetBoolFromDictionary(config_dict.get(),
    156                             kSCPropNetProxiesExcludeSimpleHostnames,
    157                             false)) {
    158     config->proxy_rules().bypass_rules.AddRuleToBypassLocal();
    159   }
    160 }
    161 
    162 }  // namespace
    163 
    164 // Reference-counted helper for posting a task to
    165 // ProxyConfigServiceMac::OnProxyConfigChanged between the notifier and IO
    166 // thread. This helper object may outlive the ProxyConfigServiceMac.
    167 class ProxyConfigServiceMac::Helper
    168     : public base::RefCountedThreadSafe<ProxyConfigServiceMac::Helper> {
    169  public:
    170   explicit Helper(ProxyConfigServiceMac* parent) : parent_(parent) {
    171     DCHECK(parent);
    172   }
    173 
    174   // Called when the parent is destroyed.
    175   void Orphan() {
    176     parent_ = NULL;
    177   }
    178 
    179   void OnProxyConfigChanged(const ProxyConfig& new_config) {
    180     if (parent_)
    181       parent_->OnProxyConfigChanged(new_config);
    182   }
    183 
    184  private:
    185   ProxyConfigServiceMac* parent_;
    186 };
    187 
    188 ProxyConfigServiceMac::ProxyConfigServiceMac(MessageLoop* io_loop)
    189     : forwarder_(this),
    190       config_watcher_(&forwarder_),
    191       has_fetched_config_(false),
    192       helper_(new Helper(this)),
    193       io_loop_(io_loop) {
    194   DCHECK(io_loop);
    195 }
    196 
    197 ProxyConfigServiceMac::~ProxyConfigServiceMac() {
    198   DCHECK_EQ(io_loop_, MessageLoop::current());
    199   helper_->Orphan();
    200   io_loop_ = NULL;
    201 }
    202 
    203 void ProxyConfigServiceMac::AddObserver(Observer* observer) {
    204   DCHECK_EQ(io_loop_, MessageLoop::current());
    205   observers_.AddObserver(observer);
    206 }
    207 
    208 void ProxyConfigServiceMac::RemoveObserver(Observer* observer) {
    209   DCHECK_EQ(io_loop_, MessageLoop::current());
    210   observers_.RemoveObserver(observer);
    211 }
    212 
    213 net::ProxyConfigService::ConfigAvailability
    214     ProxyConfigServiceMac::GetLatestProxyConfig(ProxyConfig* config) {
    215   DCHECK_EQ(io_loop_, MessageLoop::current());
    216 
    217   // Lazy-initialize by fetching the proxy setting from this thread.
    218   if (!has_fetched_config_) {
    219     GetCurrentProxyConfig(&last_config_fetched_);
    220     has_fetched_config_ = true;
    221   }
    222 
    223   *config = last_config_fetched_;
    224   return has_fetched_config_ ? CONFIG_VALID : CONFIG_PENDING;
    225 }
    226 
    227 void ProxyConfigServiceMac::SetDynamicStoreNotificationKeys(
    228     SCDynamicStoreRef store) {
    229   // Called on notifier thread.
    230 
    231   CFStringRef proxies_key = SCDynamicStoreKeyCreateProxies(NULL);
    232   CFArrayRef key_array = CFArrayCreate(
    233       NULL, (const void **)(&proxies_key), 1, &kCFTypeArrayCallBacks);
    234 
    235   bool ret = SCDynamicStoreSetNotificationKeys(store, key_array, NULL);
    236   // TODO(willchan): Figure out a proper way to handle this rather than crash.
    237   CHECK(ret);
    238 
    239   CFRelease(key_array);
    240   CFRelease(proxies_key);
    241 }
    242 
    243 void ProxyConfigServiceMac::OnNetworkConfigChange(CFArrayRef changed_keys) {
    244   // Called on notifier thread.
    245 
    246   // Fetch the new system proxy configuration.
    247   ProxyConfig new_config;
    248   GetCurrentProxyConfig(&new_config);
    249 
    250   // Call OnProxyConfigChanged() on the IO thread to notify our observers.
    251   io_loop_->PostTask(
    252       FROM_HERE,
    253       NewRunnableMethod(
    254           helper_.get(), &Helper::OnProxyConfigChanged, new_config));
    255 }
    256 
    257 void ProxyConfigServiceMac::OnProxyConfigChanged(
    258     const ProxyConfig& new_config) {
    259   DCHECK_EQ(io_loop_, MessageLoop::current());
    260 
    261   // Keep track of the last value we have seen.
    262   has_fetched_config_ = true;
    263   last_config_fetched_ = new_config;
    264 
    265   // Notify all the observers.
    266   FOR_EACH_OBSERVER(Observer, observers_,
    267                     OnProxyConfigChanged(new_config, CONFIG_VALID));
    268 }
    269 
    270 }  // namespace net
    271