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