1 // Copyright (c) 2008 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_resolver_mac.h" 6 7 #include <CoreFoundation/CoreFoundation.h> 8 9 #include "base/logging.h" 10 #include "base/mac_util.h" 11 #include "base/scoped_cftyperef.h" 12 #include "base/string_util.h" 13 #include "base/sys_string_conversions.h" 14 #include "net/base/net_errors.h" 15 #include "net/proxy/proxy_info.h" 16 #include "net/proxy/proxy_server.h" 17 18 namespace { 19 20 // Utility function to map a CFProxyType to a ProxyServer::Scheme. 21 // If the type is unknown, returns ProxyServer::SCHEME_INVALID. 22 net::ProxyServer::Scheme GetProxyServerScheme(CFStringRef proxy_type) { 23 if (CFEqual(proxy_type, kCFProxyTypeNone)) 24 return net::ProxyServer::SCHEME_DIRECT; 25 if (CFEqual(proxy_type, kCFProxyTypeHTTP)) 26 return net::ProxyServer::SCHEME_HTTP; 27 if (CFEqual(proxy_type, kCFProxyTypeSOCKS)) { 28 // We can't tell whether this was v4 or v5. We will assume it is 29 // v5 since that is the only version OS X supports. 30 return net::ProxyServer::SCHEME_SOCKS5; 31 } 32 return net::ProxyServer::SCHEME_INVALID; 33 } 34 35 // Callback for CFNetworkExecuteProxyAutoConfigurationURL. |client| is a pointer 36 // to a CFTypeRef. This stashes either |error| or |proxies| in that location. 37 void ResultCallback(void* client, CFArrayRef proxies, CFErrorRef error) { 38 DCHECK((proxies != NULL) == (error == NULL)); 39 40 CFTypeRef* result_ptr = reinterpret_cast<CFTypeRef*>(client); 41 DCHECK(result_ptr != NULL); 42 DCHECK(*result_ptr == NULL); 43 44 if (error != NULL) { 45 *result_ptr = CFRetain(error); 46 } else { 47 *result_ptr = CFRetain(proxies); 48 } 49 CFRunLoopStop(CFRunLoopGetCurrent()); 50 } 51 52 } // namespace 53 54 namespace net { 55 56 // Gets the proxy information for a query URL from a PAC. Implementation 57 // inspired by http://developer.apple.com/samplecode/CFProxySupportTool/ 58 int ProxyResolverMac::GetProxyForURL(const GURL& query_url, 59 ProxyInfo* results, 60 CompletionCallback* /*callback*/, 61 RequestHandle* /*request*/, 62 LoadLog* load_log) { 63 scoped_cftyperef<CFStringRef> query_ref( 64 base::SysUTF8ToCFStringRef(query_url.spec())); 65 scoped_cftyperef<CFURLRef> query_url_ref( 66 CFURLCreateWithString(kCFAllocatorDefault, 67 query_ref.get(), 68 NULL)); 69 if (!query_url_ref.get()) 70 return ERR_FAILED; 71 scoped_cftyperef<CFStringRef> pac_ref( 72 base::SysUTF8ToCFStringRef(pac_url_.spec())); 73 scoped_cftyperef<CFURLRef> pac_url_ref( 74 CFURLCreateWithString(kCFAllocatorDefault, 75 pac_ref.get(), 76 NULL)); 77 if (!pac_url_ref.get()) 78 return ERR_FAILED; 79 80 // Work around <rdar://problem/5530166>. This dummy call to 81 // CFNetworkCopyProxiesForURL initializes some state within CFNetwork that is 82 // required by CFNetworkExecuteProxyAutoConfigurationURL. 83 84 CFArrayRef dummy_result = CFNetworkCopyProxiesForURL(query_url_ref.get(), 85 NULL); 86 if (dummy_result) 87 CFRelease(dummy_result); 88 89 // We cheat here. We need to act as if we were synchronous, so we pump the 90 // runloop ourselves. Our caller moved us to a new thread anyway, so this is 91 // OK to do. (BTW, CFNetworkExecuteProxyAutoConfigurationURL returns a 92 // runloop source we need to release despite its name.) 93 94 CFTypeRef result = NULL; 95 CFStreamClientContext context = { 0, &result, NULL, NULL, NULL }; 96 scoped_cftyperef<CFRunLoopSourceRef> runloop_source( 97 CFNetworkExecuteProxyAutoConfigurationURL(pac_url_ref.get(), 98 query_url_ref.get(), 99 ResultCallback, 100 &context)); 101 if (!runloop_source) 102 return ERR_FAILED; 103 104 const CFStringRef private_runloop_mode = 105 CFSTR("org.chromium.ProxyResolverMac"); 106 107 CFRunLoopAddSource(CFRunLoopGetCurrent(), runloop_source.get(), 108 private_runloop_mode); 109 CFRunLoopRunInMode(private_runloop_mode, DBL_MAX, false); 110 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runloop_source.get(), 111 private_runloop_mode); 112 DCHECK(result != NULL); 113 114 if (CFGetTypeID(result) == CFErrorGetTypeID()) { 115 // TODO(avi): do something better than this 116 CFRelease(result); 117 return ERR_FAILED; 118 } 119 DCHECK(CFGetTypeID(result) == CFArrayGetTypeID()); 120 scoped_cftyperef<CFArrayRef> proxy_array_ref((CFArrayRef)result); 121 122 // This string will be an ordered list of <proxy-uri> entries, separated by 123 // semi-colons. It is the format that ProxyInfo::UseNamedProxy() expects. 124 // proxy-uri = [<proxy-scheme>"://"]<proxy-host>":"<proxy-port> 125 // (This also includes entries for direct connection, as "direct://"). 126 std::string proxy_uri_list; 127 128 CFIndex proxy_array_count = CFArrayGetCount(proxy_array_ref.get()); 129 for (CFIndex i = 0; i < proxy_array_count; ++i) { 130 CFDictionaryRef proxy_dictionary = 131 (CFDictionaryRef)CFArrayGetValueAtIndex(proxy_array_ref.get(), i); 132 DCHECK(CFGetTypeID(proxy_dictionary) == CFDictionaryGetTypeID()); 133 134 // The dictionary may have the following keys: 135 // - kCFProxyTypeKey : The type of the proxy 136 // - kCFProxyHostNameKey 137 // - kCFProxyPortNumberKey : The meat we're after. 138 // - kCFProxyUsernameKey 139 // - kCFProxyPasswordKey : Despite the existence of these keys in the 140 // documentation, they're never populated. Even if a 141 // username/password were to be set in the network 142 // proxy system preferences, we'd need to fetch it 143 // from the Keychain ourselves. CFProxy is such a 144 // tease. 145 // - kCFProxyAutoConfigurationURLKey : If the PAC file specifies another 146 // PAC file, I'm going home. 147 148 CFStringRef proxy_type = 149 (CFStringRef)mac_util::GetValueFromDictionary(proxy_dictionary, 150 kCFProxyTypeKey, 151 CFStringGetTypeID()); 152 ProxyServer proxy_server = ProxyServer::FromDictionary( 153 GetProxyServerScheme(proxy_type), 154 proxy_dictionary, 155 kCFProxyHostNameKey, 156 kCFProxyPortNumberKey); 157 if (!proxy_server.is_valid()) 158 continue; 159 160 if (!proxy_uri_list.empty()) 161 proxy_uri_list += ";"; 162 proxy_uri_list += proxy_server.ToURI(); 163 } 164 165 if (!proxy_uri_list.empty()) 166 results->UseNamedProxy(proxy_uri_list); 167 // Else do nothing (results is already guaranteed to be in the default state). 168 169 return OK; 170 } 171 172 } // namespace net 173