Home | History | Annotate | Download | only in proxy
      1 // Copyright (c) 2012 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/bind.h"
     11 #include "base/logging.h"
     12 #include "base/mac/foundation_util.h"
     13 #include "base/mac/scoped_cftyperef.h"
     14 #include "base/message_loop/message_loop.h"
     15 #include "base/strings/sys_string_conversions.h"
     16 #include "net/base/net_errors.h"
     17 #include "net/proxy/proxy_config.h"
     18 #include "net/proxy/proxy_info.h"
     19 #include "net/proxy/proxy_server.h"
     20 
     21 namespace net {
     22 
     23 namespace {
     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 = base::mac::GetValueFromDictionary<CFNumberRef>(dict,
     31                                                                       key);
     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::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 = base::mac::GetValueFromDictionary<CFStringRef>(
     63         config_dict.get(), kSCPropNetProxiesProxyAutoConfigURLString);
     64     if (pac_url_ref)
     65       config->set_pac_url(GURL(base::SysCFStringRefToUTF8(pac_url_ref)));
     66   }
     67 
     68   // proxies (for now ftp, http, https, and SOCKS)
     69 
     70   if (GetBoolFromDictionary(config_dict.get(),
     71                             kSCPropNetProxiesFTPEnable,
     72                             false)) {
     73     ProxyServer proxy_server =
     74         ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP,
     75                                     config_dict.get(),
     76                                     kSCPropNetProxiesFTPProxy,
     77                                     kSCPropNetProxiesFTPPort);
     78     if (proxy_server.is_valid()) {
     79       config->proxy_rules().type =
     80           ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
     81       config->proxy_rules().proxies_for_ftp.SetSingleProxyServer(proxy_server);
     82     }
     83   }
     84   if (GetBoolFromDictionary(config_dict.get(),
     85                             kSCPropNetProxiesHTTPEnable,
     86                             false)) {
     87     ProxyServer proxy_server =
     88         ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP,
     89                                     config_dict.get(),
     90                                     kSCPropNetProxiesHTTPProxy,
     91                                     kSCPropNetProxiesHTTPPort);
     92     if (proxy_server.is_valid()) {
     93       config->proxy_rules().type =
     94           ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
     95       config->proxy_rules().proxies_for_http.SetSingleProxyServer(proxy_server);
     96     }
     97   }
     98   if (GetBoolFromDictionary(config_dict.get(),
     99                             kSCPropNetProxiesHTTPSEnable,
    100                             false)) {
    101     ProxyServer proxy_server =
    102         ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP,
    103                                     config_dict.get(),
    104                                     kSCPropNetProxiesHTTPSProxy,
    105                                     kSCPropNetProxiesHTTPSPort);
    106     if (proxy_server.is_valid()) {
    107       config->proxy_rules().type =
    108           ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
    109       config->proxy_rules().proxies_for_https.
    110           SetSingleProxyServer(proxy_server);
    111     }
    112   }
    113   if (GetBoolFromDictionary(config_dict.get(),
    114                             kSCPropNetProxiesSOCKSEnable,
    115                             false)) {
    116     ProxyServer proxy_server =
    117         ProxyServer::FromDictionary(ProxyServer::SCHEME_SOCKS5,
    118                                     config_dict.get(),
    119                                     kSCPropNetProxiesSOCKSProxy,
    120                                     kSCPropNetProxiesSOCKSPort);
    121     if (proxy_server.is_valid()) {
    122       config->proxy_rules().type =
    123           ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
    124       config->proxy_rules().fallback_proxies.SetSingleProxyServer(proxy_server);
    125     }
    126   }
    127 
    128   // proxy bypass list
    129 
    130   CFArrayRef bypass_array_ref = base::mac::GetValueFromDictionary<CFArrayRef>(
    131       config_dict.get(), kSCPropNetProxiesExceptionsList);
    132   if (bypass_array_ref) {
    133     CFIndex bypass_array_count = CFArrayGetCount(bypass_array_ref);
    134     for (CFIndex i = 0; i < bypass_array_count; ++i) {
    135       CFStringRef bypass_item_ref = base::mac::CFCast<CFStringRef>(
    136           CFArrayGetValueAtIndex(bypass_array_ref, i));
    137       if (!bypass_item_ref) {
    138         LOG(WARNING) << "Expected value for item " << i
    139                      << " in the kSCPropNetProxiesExceptionsList"
    140                         " to be a CFStringRef but it was not";
    141 
    142       } else {
    143         config->proxy_rules().bypass_rules.AddRuleFromString(
    144             base::SysCFStringRefToUTF8(bypass_item_ref));
    145       }
    146     }
    147   }
    148 
    149   // proxy bypass boolean
    150 
    151   if (GetBoolFromDictionary(config_dict.get(),
    152                             kSCPropNetProxiesExcludeSimpleHostnames,
    153                             false)) {
    154     config->proxy_rules().bypass_rules.AddRuleToBypassLocal();
    155   }
    156 
    157   // Source
    158   config->set_source(PROXY_CONFIG_SOURCE_SYSTEM);
    159 }
    160 
    161 }  // namespace
    162 
    163 // Reference-counted helper for posting a task to
    164 // ProxyConfigServiceMac::OnProxyConfigChanged between the notifier and IO
    165 // thread. This helper object may outlive the ProxyConfigServiceMac.
    166 class ProxyConfigServiceMac::Helper
    167     : public base::RefCountedThreadSafe<ProxyConfigServiceMac::Helper> {
    168  public:
    169   explicit Helper(ProxyConfigServiceMac* parent) : parent_(parent) {
    170     DCHECK(parent);
    171   }
    172 
    173   // Called when the parent is destroyed.
    174   void Orphan() {
    175     parent_ = NULL;
    176   }
    177 
    178   void OnProxyConfigChanged(const ProxyConfig& new_config) {
    179     if (parent_)
    180       parent_->OnProxyConfigChanged(new_config);
    181   }
    182 
    183  private:
    184   friend class base::RefCountedThreadSafe<Helper>;
    185   ~Helper() {}
    186 
    187   ProxyConfigServiceMac* parent_;
    188 };
    189 
    190 void ProxyConfigServiceMac::Forwarder::SetDynamicStoreNotificationKeys(
    191     SCDynamicStoreRef store) {
    192   proxy_config_service_->SetDynamicStoreNotificationKeys(store);
    193 }
    194 
    195 void ProxyConfigServiceMac::Forwarder::OnNetworkConfigChange(
    196     CFArrayRef changed_keys) {
    197   proxy_config_service_->OnNetworkConfigChange(changed_keys);
    198 }
    199 
    200 ProxyConfigServiceMac::ProxyConfigServiceMac(
    201     base::SingleThreadTaskRunner* io_thread_task_runner)
    202     : forwarder_(this),
    203       has_fetched_config_(false),
    204       helper_(new Helper(this)),
    205       io_thread_task_runner_(io_thread_task_runner) {
    206   DCHECK(io_thread_task_runner_.get());
    207   config_watcher_.reset(new NetworkConfigWatcherMac(&forwarder_));
    208 }
    209 
    210 ProxyConfigServiceMac::~ProxyConfigServiceMac() {
    211   DCHECK(io_thread_task_runner_->BelongsToCurrentThread());
    212   // Delete the config_watcher_ to ensure the notifier thread finishes before
    213   // this object is destroyed.
    214   config_watcher_.reset();
    215   helper_->Orphan();
    216 }
    217 
    218 void ProxyConfigServiceMac::AddObserver(Observer* observer) {
    219   DCHECK(io_thread_task_runner_->BelongsToCurrentThread());
    220   observers_.AddObserver(observer);
    221 }
    222 
    223 void ProxyConfigServiceMac::RemoveObserver(Observer* observer) {
    224   DCHECK(io_thread_task_runner_->BelongsToCurrentThread());
    225   observers_.RemoveObserver(observer);
    226 }
    227 
    228 net::ProxyConfigService::ConfigAvailability
    229     ProxyConfigServiceMac::GetLatestProxyConfig(ProxyConfig* config) {
    230   DCHECK(io_thread_task_runner_->BelongsToCurrentThread());
    231 
    232   // Lazy-initialize by fetching the proxy setting from this thread.
    233   if (!has_fetched_config_) {
    234     GetCurrentProxyConfig(&last_config_fetched_);
    235     has_fetched_config_ = true;
    236   }
    237 
    238   *config = last_config_fetched_;
    239   return has_fetched_config_ ? CONFIG_VALID : CONFIG_PENDING;
    240 }
    241 
    242 void ProxyConfigServiceMac::SetDynamicStoreNotificationKeys(
    243     SCDynamicStoreRef store) {
    244   // Called on notifier thread.
    245 
    246   CFStringRef proxies_key = SCDynamicStoreKeyCreateProxies(NULL);
    247   CFArrayRef key_array = CFArrayCreate(
    248       NULL, (const void **)(&proxies_key), 1, &kCFTypeArrayCallBacks);
    249 
    250   bool ret = SCDynamicStoreSetNotificationKeys(store, key_array, NULL);
    251   // TODO(willchan): Figure out a proper way to handle this rather than crash.
    252   CHECK(ret);
    253 
    254   CFRelease(key_array);
    255   CFRelease(proxies_key);
    256 }
    257 
    258 void ProxyConfigServiceMac::OnNetworkConfigChange(CFArrayRef changed_keys) {
    259   // Called on notifier thread.
    260 
    261   // Fetch the new system proxy configuration.
    262   ProxyConfig new_config;
    263   GetCurrentProxyConfig(&new_config);
    264 
    265   // Call OnProxyConfigChanged() on the IO thread to notify our observers.
    266   io_thread_task_runner_->PostTask(
    267       FROM_HERE,
    268       base::Bind(&Helper::OnProxyConfigChanged, helper_.get(), new_config));
    269 }
    270 
    271 void ProxyConfigServiceMac::OnProxyConfigChanged(
    272     const ProxyConfig& new_config) {
    273   DCHECK(io_thread_task_runner_->BelongsToCurrentThread());
    274 
    275   // Keep track of the last value we have seen.
    276   has_fetched_config_ = true;
    277   last_config_fetched_ = new_config;
    278 
    279   // Notify all the observers.
    280   FOR_EACH_OBSERVER(Observer, observers_,
    281                     OnProxyConfigChanged(new_config, CONFIG_VALID));
    282 }
    283 
    284 }  // namespace net
    285