• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
GetBoolFromDictionary(CFDictionaryRef dict,CFStringRef key,bool default_value)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 
GetCurrentProxyConfig(ProxyConfig * config)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:
Helper(ProxyConfigServiceMac * parent)170   explicit Helper(ProxyConfigServiceMac* parent) : parent_(parent) {
171     DCHECK(parent);
172   }
173 
174   // Called when the parent is destroyed.
Orphan()175   void Orphan() {
176     parent_ = NULL;
177   }
178 
OnProxyConfigChanged(const ProxyConfig & new_config)179   void OnProxyConfigChanged(const ProxyConfig& new_config) {
180     if (parent_)
181       parent_->OnProxyConfigChanged(new_config);
182   }
183 
184  private:
185   ProxyConfigServiceMac* parent_;
186 };
187 
ProxyConfigServiceMac(MessageLoop * io_loop)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 
~ProxyConfigServiceMac()197 ProxyConfigServiceMac::~ProxyConfigServiceMac() {
198   DCHECK_EQ(io_loop_, MessageLoop::current());
199   helper_->Orphan();
200   io_loop_ = NULL;
201 }
202 
AddObserver(Observer * observer)203 void ProxyConfigServiceMac::AddObserver(Observer* observer) {
204   DCHECK_EQ(io_loop_, MessageLoop::current());
205   observers_.AddObserver(observer);
206 }
207 
RemoveObserver(Observer * observer)208 void ProxyConfigServiceMac::RemoveObserver(Observer* observer) {
209   DCHECK_EQ(io_loop_, MessageLoop::current());
210   observers_.RemoveObserver(observer);
211 }
212 
213 net::ProxyConfigService::ConfigAvailability
GetLatestProxyConfig(ProxyConfig * config)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 
SetDynamicStoreNotificationKeys(SCDynamicStoreRef store)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 
OnNetworkConfigChange(CFArrayRef changed_keys)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 
OnProxyConfigChanged(const ProxyConfig & new_config)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