2 * Copyright (C) 2009 Apple Inc. All rights reserved.
3 * Copyright (C) 2009 Google Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
15 * * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 #include "SocketStreamHandle.h"
36 #include "SocketStreamError.h"
37 #include "SocketStreamHandleClient.h"
38 #include <wtf/MainThread.h>
40 #if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD)
41 #include <SystemConfiguration/SystemConfiguration.h>
45 #include "LoaderRunLoopCF.h"
48 #ifdef BUILDING_ON_TIGER
49 #define CFN_EXPORT extern
53 CFN_EXPORT const CFStringRef kCFStreamPropertyCONNECTProxy;
54 CFN_EXPORT const CFStringRef kCFStreamPropertyCONNECTProxyHost;
55 CFN_EXPORT const CFStringRef kCFStreamPropertyCONNECTProxyPort;
60 SocketStreamHandle::SocketStreamHandle(const KURL& url, SocketStreamHandleClient* client)
61 : SocketStreamHandleBase(url, client)
62 , m_connectingSubstate(New)
63 , m_connectionType(Unknown)
65 LOG(Network, "SocketStreamHandle %p new client %p", this, m_client);
67 ASSERT(url.protocolIs("ws") || url.protocolIs("wss"));
70 m_url.setPort(shouldUseSSL() ? 443 : 80);
72 KURL httpsURL(KURL(), "https://" + m_url.host());
73 m_httpsURL.adoptCF(httpsURL.createCFURL());
76 ASSERT(!m_readStream == !m_writeStream);
77 if (!m_readStream) // Doing asynchronous PAC file processing, streams will be created later.
83 void SocketStreamHandle::scheduleStreams()
86 ASSERT(m_writeStream);
88 CFStreamClientContext clientContext = { 0, this, 0, 0, copyCFStreamDescription };
89 // FIXME: Pass specific events we're interested in instead of -1.
90 CFReadStreamSetClient(m_readStream.get(), static_cast<CFOptionFlags>(-1), readStreamCallback, &clientContext);
91 CFWriteStreamSetClient(m_writeStream.get(), static_cast<CFOptionFlags>(-1), writeStreamCallback, &clientContext);
94 CFReadStreamScheduleWithRunLoop(m_readStream.get(), loaderRunLoop(), kCFRunLoopDefaultMode);
95 CFWriteStreamScheduleWithRunLoop(m_writeStream.get(), loaderRunLoop(), kCFRunLoopDefaultMode);
97 CFReadStreamScheduleWithRunLoop(m_readStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
98 CFWriteStreamScheduleWithRunLoop(m_writeStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
101 CFReadStreamOpen(m_readStream.get());
102 CFWriteStreamOpen(m_writeStream.get());
104 #ifndef BUILDING_ON_TIGER
105 if (m_pacRunLoopSource)
106 removePACRunLoopSource();
109 m_connectingSubstate = WaitingForConnect;
112 #ifndef BUILDING_ON_TIGER
113 CFStringRef SocketStreamHandle::copyPACExecutionDescription(void*)
115 return CFSTR("WebSocket proxy PAC file execution");
118 struct MainThreadPACCallbackInfo {
119 MainThreadPACCallbackInfo(SocketStreamHandle* handle, CFArrayRef proxyList) : handle(handle), proxyList(proxyList) { }
120 SocketStreamHandle* handle;
121 CFArrayRef proxyList;
124 void SocketStreamHandle::pacExecutionCallback(void* client, CFArrayRef proxyList, CFErrorRef)
126 SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(client);
127 MainThreadPACCallbackInfo info(handle, proxyList);
128 // If we're already on main thread (e.g. on Mac), callOnMainThreadAndWait() will be just a function call.
129 callOnMainThreadAndWait(pacExecutionCallbackMainThread, &info);
132 void SocketStreamHandle::pacExecutionCallbackMainThread(void* invocation)
134 MainThreadPACCallbackInfo* info = static_cast<MainThreadPACCallbackInfo*>(invocation);
135 ASSERT(info->handle->m_connectingSubstate == ExecutingPACFile);
136 // This time, the array won't have PAC as a first entry.
137 info->handle->chooseProxyFromArray(info->proxyList);
138 info->handle->createStreams();
139 info->handle->scheduleStreams();
142 void SocketStreamHandle::executePACFileURL(CFURLRef pacFileURL)
144 // CFNetwork returns an empty proxy array for WebScoket schemes, so use m_httpsURL.
145 CFStreamClientContext clientContext = { 0, this, 0, 0, copyPACExecutionDescription };
146 m_pacRunLoopSource.adoptCF(CFNetworkExecuteProxyAutoConfigurationURL(pacFileURL, m_httpsURL.get(), pacExecutionCallback, &clientContext));
148 CFRunLoopAddSource(loaderRunLoop(), m_pacRunLoopSource.get(), kCFRunLoopDefaultMode);
150 CFRunLoopAddSource(CFRunLoopGetCurrent(), m_pacRunLoopSource.get(), kCFRunLoopCommonModes);
152 m_connectingSubstate = ExecutingPACFile;
155 void SocketStreamHandle::removePACRunLoopSource()
157 ASSERT(m_pacRunLoopSource);
159 CFRunLoopSourceInvalidate(m_pacRunLoopSource.get());
161 CFRunLoopRemoveSource(loaderRunLoop(), m_pacRunLoopSource.get(), kCFRunLoopDefaultMode);
163 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), m_pacRunLoopSource.get(), kCFRunLoopCommonModes);
165 m_pacRunLoopSource = 0;
168 void SocketStreamHandle::chooseProxy()
170 #ifndef BUILDING_ON_LEOPARD
171 RetainPtr<CFDictionaryRef> proxyDictionary(AdoptCF, CFNetworkCopySystemProxySettings());
173 // We don't need proxy information often, so there is no need to set up a permanent dynamic store session.
174 RetainPtr<CFDictionaryRef> proxyDictionary(AdoptCF, SCDynamicStoreCopyProxies(0));
177 // SOCKS or HTTPS (AKA CONNECT) proxies are supported.
178 // WebSocket protocol relies on handshake being transferred unchanged, so we need a proxy that will not modify headers.
179 // Since HTTP proxies must add Via headers, they are highly unlikely to work.
180 // Many CONNECT proxies limit connectivity to port 443, so we prefer SOCKS, if configured.
182 if (!proxyDictionary) {
183 m_connectionType = Direct;
187 // CFNetworkCopyProxiesForURL doesn't know about WebSocket schemes, so pretend to use http.
188 // Always use "https" to get HTTPS proxies in result - we'll try to use those for ws:// even though many are configured to reject connections to ports other than 443.
189 RetainPtr<CFArrayRef> proxyArray(AdoptCF, CFNetworkCopyProxiesForURL(m_httpsURL.get(), proxyDictionary.get()));
191 chooseProxyFromArray(proxyArray.get());
194 void SocketStreamHandle::chooseProxyFromArray(CFArrayRef proxyArray)
197 m_connectionType = Direct;
199 CFIndex proxyArrayCount = CFArrayGetCount(proxyArray);
201 // PAC is always the first entry, if present.
202 if (proxyArrayCount) {
203 CFDictionaryRef proxyInfo = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(proxyArray, 0));
204 CFTypeRef proxyType = CFDictionaryGetValue(proxyInfo, kCFProxyTypeKey);
205 if (proxyType && CFGetTypeID(proxyType) == CFStringGetTypeID()) {
206 if (CFEqual(proxyType, kCFProxyTypeAutoConfigurationURL)) {
207 CFTypeRef pacFileURL = CFDictionaryGetValue(proxyInfo, kCFProxyAutoConfigurationURLKey);
208 if (pacFileURL && CFGetTypeID(pacFileURL) == CFURLGetTypeID()) {
209 executePACFileURL(static_cast<CFURLRef>(pacFileURL));
216 CFDictionaryRef chosenProxy = 0;
217 for (CFIndex i = 0; i < proxyArrayCount; ++i) {
218 CFDictionaryRef proxyInfo = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(proxyArray, i));
219 CFTypeRef proxyType = CFDictionaryGetValue(proxyInfo, kCFProxyTypeKey);
220 if (proxyType && CFGetTypeID(proxyType) == CFStringGetTypeID()) {
221 if (CFEqual(proxyType, kCFProxyTypeSOCKS)) {
222 m_connectionType = SOCKSProxy;
223 chosenProxy = proxyInfo;
226 if (CFEqual(proxyType, kCFProxyTypeHTTPS)) {
227 m_connectionType = CONNECTProxy;
228 chosenProxy = proxyInfo;
229 // Keep looking for proxies, as a SOCKS one is preferable.
235 ASSERT(m_connectionType != Unknown);
236 ASSERT(m_connectionType != Direct);
238 CFTypeRef proxyHost = CFDictionaryGetValue(chosenProxy, kCFProxyHostNameKey);
239 CFTypeRef proxyPort = CFDictionaryGetValue(chosenProxy, kCFProxyPortNumberKey);
241 if (proxyHost && CFGetTypeID(proxyHost) == CFStringGetTypeID() && proxyPort && CFGetTypeID(proxyPort) == CFNumberGetTypeID()) {
242 m_proxyHost = static_cast<CFStringRef>(proxyHost);
243 m_proxyPort = static_cast<CFNumberRef>(proxyPort);
248 m_connectionType = Direct;
251 #else // BUILDING_ON_TIGER
253 void SocketStreamHandle::chooseProxy()
255 // We don't need proxy information often, so there is no need to set up a permanent dynamic store session.
256 RetainPtr<CFDictionaryRef> proxyDictionary(AdoptCF, SCDynamicStoreCopyProxies(0));
258 // SOCKS or HTTPS (AKA CONNECT) proxies are supported.
259 // WebSocket protocol relies on handshake being transferred unchanged, so we need a proxy that will not modify headers.
260 // Since HTTP proxies must add Via headers, they are highly unlikely to work.
261 // Many CONNECT proxies limit connectivity to port 443, so we prefer SOCKS, if configured.
263 if (!proxyDictionary) {
264 m_connectionType = Direct;
268 // FIXME: check proxy bypass list and ExcludeSimpleHostnames.
269 // FIXME: Support PAC files.
271 CFTypeRef socksEnableCF = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesSOCKSEnable);
273 if (socksEnableCF && CFGetTypeID(socksEnableCF) == CFNumberGetTypeID() && CFNumberGetValue(static_cast<CFNumberRef>(socksEnableCF), kCFNumberIntType, &socksEnable) && socksEnable) {
274 CFTypeRef proxyHost = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesSOCKSProxy);
275 CFTypeRef proxyPort = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesSOCKSPort);
276 if (proxyHost && CFGetTypeID(proxyHost) == CFStringGetTypeID() && proxyPort && CFGetTypeID(proxyPort) == CFNumberGetTypeID()) {
277 m_proxyHost = static_cast<CFStringRef>(proxyHost);
278 m_proxyPort = static_cast<CFNumberRef>(proxyPort);
279 m_connectionType = SOCKSProxy;
284 CFTypeRef httpsEnableCF = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesHTTPSEnable);
286 if (httpsEnableCF && CFGetTypeID(httpsEnableCF) == CFNumberGetTypeID() && CFNumberGetValue(static_cast<CFNumberRef>(httpsEnableCF), kCFNumberIntType, &httpsEnable) && httpsEnable) {
287 CFTypeRef proxyHost = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesHTTPSProxy);
288 CFTypeRef proxyPort = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesHTTPSPort);
290 if (proxyHost && CFGetTypeID(proxyHost) == CFStringGetTypeID() && proxyPort && CFGetTypeID(proxyPort) == CFNumberGetTypeID()) {
291 m_proxyHost = static_cast<CFStringRef>(proxyHost);
292 m_proxyPort = static_cast<CFNumberRef>(proxyPort);
293 m_connectionType = CONNECTProxy;
298 m_connectionType = Direct;
300 #endif // BUILDING_ON_TIGER
302 void SocketStreamHandle::createStreams()
304 if (m_connectionType == Unknown)
307 // If it's still unknown, then we're resolving a PAC file asynchronously.
308 if (m_connectionType == Unknown)
311 RetainPtr<CFStringRef> host(AdoptCF, m_url.host().createCFString());
313 // Creating streams to final destination, not to proxy.
314 CFReadStreamRef readStream = 0;
315 CFWriteStreamRef writeStream = 0;
316 CFStreamCreatePairWithSocketToHost(0, host.get(), m_url.port(), &readStream, &writeStream);
318 m_readStream.adoptCF(readStream);
319 m_writeStream.adoptCF(writeStream);
321 switch (m_connectionType) {
323 ASSERT_NOT_REACHED();
328 // FIXME: SOCKS5 doesn't do challenge-response, should we try to apply credentials from Keychain right away?
329 // But SOCKS5 credentials don't work at the time of this writing anyway, see <rdar://6776698>.
330 const void* proxyKeys[] = { kCFStreamPropertySOCKSProxyHost, kCFStreamPropertySOCKSProxyPort };
331 const void* proxyValues[] = { m_proxyHost.get(), m_proxyPort.get() };
332 RetainPtr<CFDictionaryRef> connectDictionary(AdoptCF, CFDictionaryCreate(0, proxyKeys, proxyValues, sizeof(proxyKeys) / sizeof(*proxyKeys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
333 CFReadStreamSetProperty(m_readStream.get(), kCFStreamPropertySOCKSProxy, connectDictionary.get());
337 const void* proxyKeys[] = { kCFStreamPropertyCONNECTProxyHost, kCFStreamPropertyCONNECTProxyPort };
338 const void* proxyValues[] = { m_proxyHost.get(), m_proxyPort.get() };
339 RetainPtr<CFDictionaryRef> connectDictionary(AdoptCF, CFDictionaryCreate(0, proxyKeys, proxyValues, sizeof(proxyKeys) / sizeof(*proxyKeys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
340 CFReadStreamSetProperty(m_readStream.get(), kCFStreamPropertyCONNECTProxy, connectDictionary.get());
345 if (shouldUseSSL()) {
346 const void* keys[] = { kCFStreamSSLPeerName, kCFStreamSSLLevel };
347 const void* values[] = { host.get(), kCFStreamSocketSecurityLevelNegotiatedSSL };
348 RetainPtr<CFDictionaryRef> settings(AdoptCF, CFDictionaryCreate(0, keys, values, sizeof(keys) / sizeof(*keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
349 CFReadStreamSetProperty(m_readStream.get(), kCFStreamPropertySSLSettings, settings.get());
350 CFWriteStreamSetProperty(m_writeStream.get(), kCFStreamPropertySSLSettings, settings.get());
354 CFStringRef SocketStreamHandle::copyCFStreamDescription(void* info)
356 SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(info);
357 return ("WebKit socket stream, " + handle->m_url.string()).createCFString();
360 struct MainThreadEventCallbackInfo {
361 MainThreadEventCallbackInfo(CFStreamEventType type, SocketStreamHandle* handle) : type(type), handle(handle) { }
362 CFStreamEventType type;
363 SocketStreamHandle* handle;
366 void SocketStreamHandle::readStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void* clientCallBackInfo)
368 SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(clientCallBackInfo);
369 ASSERT_UNUSED(stream, stream == handle->m_readStream.get());
371 MainThreadEventCallbackInfo info(type, handle);
372 callOnMainThreadAndWait(readStreamCallbackMainThread, &info);
374 ASSERT(isMainThread());
375 handle->readStreamCallback(type);
379 void SocketStreamHandle::writeStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void* clientCallBackInfo)
381 SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(clientCallBackInfo);
382 ASSERT_UNUSED(stream, stream == handle->m_writeStream.get());
384 MainThreadEventCallbackInfo info(type, handle);
385 callOnMainThreadAndWait(writeStreamCallbackMainThread, &info);
387 ASSERT(isMainThread());
388 handle->writeStreamCallback(type);
393 void SocketStreamHandle::readStreamCallbackMainThread(void* invocation)
395 MainThreadEventCallbackInfo* info = static_cast<MainThreadEventCallbackInfo*>(invocation);
396 info->handle->readStreamCallback(info->type);
399 void SocketStreamHandle::writeStreamCallbackMainThread(void* invocation)
401 MainThreadEventCallbackInfo* info = static_cast<MainThreadEventCallbackInfo*>(invocation);
402 info->handle->writeStreamCallback(info->type);
404 #endif // PLATFORM(WIN)
406 void SocketStreamHandle::readStreamCallback(CFStreamEventType type)
409 case kCFStreamEventNone:
411 case kCFStreamEventOpenCompleted:
413 case kCFStreamEventHasBytesAvailable: {
414 if (m_connectingSubstate == WaitingForConnect) {
415 // FIXME: Handle CONNECT proxy credentials here.
416 } else if (m_connectingSubstate == WaitingForCredentials)
419 if (m_connectingSubstate == WaitingForConnect) {
420 m_connectingSubstate = Connected;
422 m_client->didOpen(this);
424 } else if (m_state == Closed)
427 ASSERT(m_state == Open);
428 ASSERT(m_connectingSubstate == Connected);
431 UInt8 localBuffer[1024]; // Used if CFReadStreamGetBuffer couldn't return anything.
432 const UInt8* ptr = CFReadStreamGetBuffer(m_readStream.get(), 0, &length);
434 length = CFReadStreamRead(m_readStream.get(), localBuffer, sizeof(localBuffer));
438 m_client->didReceiveData(this, reinterpret_cast<const char*>(ptr), length);
442 case kCFStreamEventCanAcceptBytes:
443 ASSERT_NOT_REACHED();
445 case kCFStreamEventErrorOccurred: {
446 CFStreamError error = CFReadStreamGetError(m_readStream.get());
447 m_client->didFail(this, SocketStreamError(error.error)); // FIXME: Provide a sensible error.
450 case kCFStreamEventEndEncountered:
451 m_client->didClose(this);
456 void SocketStreamHandle::writeStreamCallback(CFStreamEventType type)
459 case kCFStreamEventNone:
461 case kCFStreamEventOpenCompleted:
463 case kCFStreamEventHasBytesAvailable:
464 ASSERT_NOT_REACHED();
466 case kCFStreamEventCanAcceptBytes: {
467 // Possibly, a spurious event from CONNECT handshake.
468 if (!CFWriteStreamCanAcceptBytes(m_writeStream.get()))
471 if (m_connectingSubstate == WaitingForCredentials)
474 if (m_connectingSubstate == WaitingForConnect) {
475 m_connectingSubstate = Connected;
477 m_client->didOpen(this);
481 ASSERT(m_state = Open);
482 ASSERT(m_connectingSubstate == Connected);
487 case kCFStreamEventErrorOccurred: {
488 CFStreamError error = CFWriteStreamGetError(m_writeStream.get());
489 m_client->didFail(this, SocketStreamError(error.error)); // FIXME: Provide a sensible error.
492 case kCFStreamEventEndEncountered:
493 // FIXME: Currently, we call didClose from read callback, but these can come independently (e.g. a server can stop listening, but keep sending data).
498 SocketStreamHandle::~SocketStreamHandle()
500 LOG(Network, "SocketStreamHandle %p dtor", this);
502 #ifndef BUILDING_ON_TIGER
503 ASSERT(!m_pacRunLoopSource);
507 int SocketStreamHandle::platformSend(const char* data, int length)
509 if (!CFWriteStreamCanAcceptBytes(m_writeStream.get()))
512 return CFWriteStreamWrite(m_writeStream.get(), reinterpret_cast<const UInt8*>(data), length);
515 void SocketStreamHandle::platformClose()
517 LOG(Network, "SocketStreamHandle %p platformClose", this);
519 #ifndef BUILDING_ON_TIGER
520 if (m_pacRunLoopSource)
521 removePACRunLoopSource();
524 ASSERT(!m_readStream == !m_writeStream);
528 CFReadStreamUnscheduleFromRunLoop(m_readStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
529 CFWriteStreamUnscheduleFromRunLoop(m_writeStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
531 CFReadStreamClose(m_readStream.get());
532 CFWriteStreamClose(m_writeStream.get());
538 void SocketStreamHandle::receivedCredential(const AuthenticationChallenge&, const Credential&)
542 void SocketStreamHandle::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge&)
546 void SocketStreamHandle::receivedCancellation(const AuthenticationChallenge&)
550 } // namespace WebCore