a3588323878fdd04959097da748e7c3931b18045
[framework/web/webkit-efl.git] / Source / WebCore / platform / network / cf / SocketStreamHandleCFNet.cpp
1 /*
2  * Copyright (C) 2009 Apple Inc.  All rights reserved.
3  * Copyright (C) 2009 Google Inc.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
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
14  * distribution.
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.
18  *
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.
30  */
31
32 #include "config.h"
33 #include "SocketStreamHandle.h"
34
35 #include "Credential.h"
36 #include "CredentialStorage.h"
37 #include "Logging.h"
38 #include "ProtectionSpace.h"
39 #include "SocketStreamError.h"
40 #include "SocketStreamHandleClient.h"
41 #include <wtf/MainThread.h>
42 #include <wtf/text/WTFString.h>
43
44 #ifdef BUILDING_ON_LEOPARD
45 #include <SystemConfiguration/SystemConfiguration.h>
46 #endif
47
48 #if PLATFORM(WIN)
49 #include "LoaderRunLoopCF.h"
50 #include <CFNetwork/CFNetwork.h>
51 #include <WebKitSystemInterface/WebKitSystemInterface.h>
52 #else
53 #include "WebCoreSystemInterface.h"
54 #endif
55
56
57 namespace WebCore {
58
59 SocketStreamHandle::SocketStreamHandle(const KURL& url, SocketStreamHandleClient* client)
60     : SocketStreamHandleBase(url, client)
61     , m_connectingSubstate(New)
62     , m_connectionType(Unknown)
63     , m_sentStoredCredentials(false)
64 {
65     LOG(Network, "SocketStreamHandle %p new client %p", this, m_client);
66
67     ASSERT(url.protocolIs("ws") || url.protocolIs("wss"));
68
69     KURL httpsURL(KURL(), "https://" + m_url.host());
70     m_httpsURL.adoptCF(httpsURL.createCFURL());
71
72     createStreams();
73     ASSERT(!m_readStream == !m_writeStream);
74     if (!m_readStream) // Doing asynchronous PAC file processing, streams will be created later.
75         return;
76
77     scheduleStreams();
78 }
79
80 void SocketStreamHandle::scheduleStreams()
81 {
82     ASSERT(m_readStream);
83     ASSERT(m_writeStream);
84
85     CFStreamClientContext clientContext = { 0, this, 0, 0, copyCFStreamDescription };
86     // FIXME: Pass specific events we're interested in instead of -1.
87     CFReadStreamSetClient(m_readStream.get(), static_cast<CFOptionFlags>(-1), readStreamCallback, &clientContext);
88     CFWriteStreamSetClient(m_writeStream.get(), static_cast<CFOptionFlags>(-1), writeStreamCallback, &clientContext);
89
90 #if PLATFORM(WIN)
91     CFReadStreamScheduleWithRunLoop(m_readStream.get(), loaderRunLoop(), kCFRunLoopDefaultMode);
92     CFWriteStreamScheduleWithRunLoop(m_writeStream.get(), loaderRunLoop(), kCFRunLoopDefaultMode);
93 #else
94     CFReadStreamScheduleWithRunLoop(m_readStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
95     CFWriteStreamScheduleWithRunLoop(m_writeStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
96 #endif
97
98     CFReadStreamOpen(m_readStream.get());
99     CFWriteStreamOpen(m_writeStream.get());
100
101     if (m_pacRunLoopSource)
102         removePACRunLoopSource();
103
104     m_connectingSubstate = WaitingForConnect;
105 }
106
107 CFStringRef SocketStreamHandle::copyPACExecutionDescription(void*)
108 {
109     return CFSTR("WebSocket proxy PAC file execution");
110 }
111
112 struct MainThreadPACCallbackInfo {
113     MainThreadPACCallbackInfo(SocketStreamHandle* handle, CFArrayRef proxyList) : handle(handle), proxyList(proxyList) { }
114     RefPtr<SocketStreamHandle> handle;
115     CFArrayRef proxyList;
116 };
117
118 void SocketStreamHandle::pacExecutionCallback(void* client, CFArrayRef proxyList, CFErrorRef)
119 {
120     SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(client);
121     MainThreadPACCallbackInfo info(handle, proxyList);
122     // If we're already on main thread (e.g. on Mac), callOnMainThreadAndWait() will be just a function call.
123     callOnMainThreadAndWait(pacExecutionCallbackMainThread, &info);
124 }
125
126 void SocketStreamHandle::pacExecutionCallbackMainThread(void* invocation)
127 {
128     MainThreadPACCallbackInfo* info = static_cast<MainThreadPACCallbackInfo*>(invocation);
129     ASSERT(info->handle->m_connectingSubstate == ExecutingPACFile);
130     // This time, the array won't have PAC as a first entry.
131     info->handle->chooseProxyFromArray(info->proxyList);
132     info->handle->createStreams();
133     info->handle->scheduleStreams();
134 }
135
136 void SocketStreamHandle::executePACFileURL(CFURLRef pacFileURL)
137 {
138     // CFNetwork returns an empty proxy array for WebScoket schemes, so use m_httpsURL.
139     CFStreamClientContext clientContext = { 0, this, 0, 0, copyPACExecutionDescription };
140     m_pacRunLoopSource.adoptCF(CFNetworkExecuteProxyAutoConfigurationURL(pacFileURL, m_httpsURL.get(), pacExecutionCallback, &clientContext));
141 #if PLATFORM(WIN)
142     CFRunLoopAddSource(loaderRunLoop(), m_pacRunLoopSource.get(), kCFRunLoopDefaultMode);
143 #else
144     CFRunLoopAddSource(CFRunLoopGetCurrent(), m_pacRunLoopSource.get(), kCFRunLoopCommonModes);
145 #endif
146     m_connectingSubstate = ExecutingPACFile;
147 }
148
149 void SocketStreamHandle::removePACRunLoopSource()
150 {
151     ASSERT(m_pacRunLoopSource);
152
153     CFRunLoopSourceInvalidate(m_pacRunLoopSource.get());
154 #if PLATFORM(WIN)
155     CFRunLoopRemoveSource(loaderRunLoop(), m_pacRunLoopSource.get(), kCFRunLoopDefaultMode);
156 #else
157     CFRunLoopRemoveSource(CFRunLoopGetCurrent(), m_pacRunLoopSource.get(), kCFRunLoopCommonModes);
158 #endif
159     m_pacRunLoopSource = 0;
160 }
161
162 void SocketStreamHandle::chooseProxy()
163 {
164 #ifndef BUILDING_ON_LEOPARD
165     RetainPtr<CFDictionaryRef> proxyDictionary(AdoptCF, CFNetworkCopySystemProxySettings());
166 #else
167     // We don't need proxy information often, so there is no need to set up a permanent dynamic store session.
168     RetainPtr<CFDictionaryRef> proxyDictionary(AdoptCF, SCDynamicStoreCopyProxies(0));
169 #endif
170
171     // SOCKS or HTTPS (AKA CONNECT) proxies are supported.
172     // WebSocket protocol relies on handshake being transferred unchanged, so we need a proxy that will not modify headers.
173     // Since HTTP proxies must add Via headers, they are highly unlikely to work.
174     // Many CONNECT proxies limit connectivity to port 443, so we prefer SOCKS, if configured.
175
176     if (!proxyDictionary) {
177         m_connectionType = Direct;
178         return;
179     }
180
181     // CFNetworkCopyProxiesForURL doesn't know about WebSocket schemes, so pretend to use http.
182     // 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.
183     RetainPtr<CFArrayRef> proxyArray(AdoptCF, CFNetworkCopyProxiesForURL(m_httpsURL.get(), proxyDictionary.get()));
184
185     chooseProxyFromArray(proxyArray.get());
186 }
187
188 void SocketStreamHandle::chooseProxyFromArray(CFArrayRef proxyArray)
189 {
190     if (!proxyArray) {
191         m_connectionType = Direct;
192         return;
193     }
194
195     CFIndex proxyArrayCount = CFArrayGetCount(proxyArray);
196
197     // PAC is always the first entry, if present.
198     if (proxyArrayCount) {
199         CFDictionaryRef proxyInfo = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(proxyArray, 0));
200         CFTypeRef proxyType = CFDictionaryGetValue(proxyInfo, kCFProxyTypeKey);
201         if (proxyType && CFGetTypeID(proxyType) == CFStringGetTypeID()) {
202             if (CFEqual(proxyType, kCFProxyTypeAutoConfigurationURL)) {
203                 CFTypeRef pacFileURL = CFDictionaryGetValue(proxyInfo, kCFProxyAutoConfigurationURLKey);
204                 if (pacFileURL && CFGetTypeID(pacFileURL) == CFURLGetTypeID()) {
205                     executePACFileURL(static_cast<CFURLRef>(pacFileURL));
206                     return;
207                 }
208             }
209         }
210     }
211
212     CFDictionaryRef chosenProxy = 0;
213     for (CFIndex i = 0; i < proxyArrayCount; ++i) {
214         CFDictionaryRef proxyInfo = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(proxyArray, i));
215         CFTypeRef proxyType = CFDictionaryGetValue(proxyInfo, kCFProxyTypeKey);
216         if (proxyType && CFGetTypeID(proxyType) == CFStringGetTypeID()) {
217             if (CFEqual(proxyType, kCFProxyTypeSOCKS)) {
218                 m_connectionType = SOCKSProxy;
219                 chosenProxy = proxyInfo;
220                 break;
221             }
222             if (CFEqual(proxyType, kCFProxyTypeHTTPS)) {
223                 m_connectionType = CONNECTProxy;
224                 chosenProxy = proxyInfo;
225                 // Keep looking for proxies, as a SOCKS one is preferable.
226             }
227         }
228     }
229
230     if (chosenProxy) {
231         ASSERT(m_connectionType != Unknown);
232         ASSERT(m_connectionType != Direct);
233
234         CFTypeRef proxyHost = CFDictionaryGetValue(chosenProxy, kCFProxyHostNameKey);
235         CFTypeRef proxyPort = CFDictionaryGetValue(chosenProxy, kCFProxyPortNumberKey);
236
237         if (proxyHost && CFGetTypeID(proxyHost) == CFStringGetTypeID() && proxyPort && CFGetTypeID(proxyPort) == CFNumberGetTypeID()) {
238             m_proxyHost = static_cast<CFStringRef>(proxyHost);
239             m_proxyPort = static_cast<CFNumberRef>(proxyPort);
240             return;
241         }
242     }
243
244     m_connectionType = Direct;
245 }
246
247
248 void SocketStreamHandle::createStreams()
249 {
250     if (m_connectionType == Unknown)
251         chooseProxy();
252
253     // If it's still unknown, then we're resolving a PAC file asynchronously.
254     if (m_connectionType == Unknown)
255         return;
256
257     RetainPtr<CFStringRef> host(AdoptCF, m_url.host().createCFString());
258
259     // Creating streams to final destination, not to proxy.
260     CFReadStreamRef readStream = 0;
261     CFWriteStreamRef writeStream = 0;
262     CFStreamCreatePairWithSocketToHost(0, host.get(), port(), &readStream, &writeStream);
263
264     m_readStream.adoptCF(readStream);
265     m_writeStream.adoptCF(writeStream);
266
267     switch (m_connectionType) {
268     case Unknown:
269         ASSERT_NOT_REACHED();
270         break;
271     case Direct:
272         break;
273     case SOCKSProxy: {
274         // FIXME: SOCKS5 doesn't do challenge-response, should we try to apply credentials from Keychain right away?
275         // But SOCKS5 credentials don't work at the time of this writing anyway, see <rdar://6776698>.
276         const void* proxyKeys[] = { kCFStreamPropertySOCKSProxyHost, kCFStreamPropertySOCKSProxyPort };
277         const void* proxyValues[] = { m_proxyHost.get(), m_proxyPort.get() };
278         RetainPtr<CFDictionaryRef> connectDictionary(AdoptCF, CFDictionaryCreate(0, proxyKeys, proxyValues, WTF_ARRAY_LENGTH(proxyKeys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
279         CFReadStreamSetProperty(m_readStream.get(), kCFStreamPropertySOCKSProxy, connectDictionary.get());
280         break;
281         }
282     case CONNECTProxy:
283         wkSetCONNECTProxyForStream(m_readStream.get(), m_proxyHost.get(), m_proxyPort.get());
284         break;
285     }
286
287     if (shouldUseSSL()) {
288         const void* keys[] = { kCFStreamSSLPeerName, kCFStreamSSLLevel };
289         const void* values[] = { host.get(), kCFStreamSocketSecurityLevelNegotiatedSSL };
290         RetainPtr<CFDictionaryRef> settings(AdoptCF, CFDictionaryCreate(0, keys, values, WTF_ARRAY_LENGTH(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
291         CFReadStreamSetProperty(m_readStream.get(), kCFStreamPropertySSLSettings, settings.get());
292         CFWriteStreamSetProperty(m_writeStream.get(), kCFStreamPropertySSLSettings, settings.get());
293     }
294 }
295
296 static bool getStoredCONNECTProxyCredentials(const ProtectionSpace& protectionSpace, String& login, String& password)
297 {
298     // Try system credential storage first, matching HTTP behavior (CFNetwork only asks the client for password if it couldn't find it in Keychain).
299     Credential storedCredential = CredentialStorage::getFromPersistentStorage(protectionSpace);
300     if (storedCredential.isEmpty())
301         storedCredential = CredentialStorage::get(protectionSpace);
302
303     if (storedCredential.isEmpty())
304         return false;
305
306     login = storedCredential.user();
307     password = storedCredential.password();
308
309     return true;
310 }
311
312 static ProtectionSpaceAuthenticationScheme authenticationSchemeFromAuthenticationMethod(CFStringRef method)
313 {
314     if (CFEqual(method, kCFHTTPAuthenticationSchemeBasic))
315         return ProtectionSpaceAuthenticationSchemeHTTPBasic;
316     if (CFEqual(method, kCFHTTPAuthenticationSchemeDigest))
317         return ProtectionSpaceAuthenticationSchemeHTTPDigest;
318     if (CFEqual(method, kCFHTTPAuthenticationSchemeNTLM))
319         return ProtectionSpaceAuthenticationSchemeNTLM;
320     if (CFEqual(method, kCFHTTPAuthenticationSchemeNegotiate))
321         return ProtectionSpaceAuthenticationSchemeNegotiate;
322     ASSERT_NOT_REACHED();
323     return ProtectionSpaceAuthenticationSchemeUnknown;
324 }
325
326 void SocketStreamHandle::addCONNECTCredentials(CFHTTPMessageRef proxyResponse)
327 {
328     RetainPtr<CFHTTPAuthenticationRef> authentication(AdoptCF, CFHTTPAuthenticationCreateFromResponse(0, proxyResponse));
329
330     if (!CFHTTPAuthenticationRequiresUserNameAndPassword(authentication.get())) {
331         // That's all we can offer...
332         m_client->didFailSocketStream(this, SocketStreamError()); // FIXME: Provide a sensible error.
333         return;
334     }
335
336     int port = 0;
337     CFNumberGetValue(m_proxyPort.get(), kCFNumberIntType, &port);
338     RetainPtr<CFStringRef> methodCF(AdoptCF, CFHTTPAuthenticationCopyMethod(authentication.get()));
339     RetainPtr<CFStringRef> realmCF(AdoptCF, CFHTTPAuthenticationCopyRealm(authentication.get()));
340     ProtectionSpace protectionSpace(String(m_proxyHost.get()), port, ProtectionSpaceProxyHTTPS, String(realmCF.get()), authenticationSchemeFromAuthenticationMethod(methodCF.get()));
341     String login;
342     String password;
343     if (!m_sentStoredCredentials && getStoredCONNECTProxyCredentials(protectionSpace, login, password)) {
344         // Try to apply stored credentials, if we haven't tried those already.
345         RetainPtr<CFStringRef> loginCF(AdoptCF, login.createCFString());
346         RetainPtr<CFStringRef> passwordCF(AdoptCF, password.createCFString());
347         // Creating a temporary request to make CFNetwork apply credentials to it. Unfortunately, this cannot work with NTLM authentication.
348         RetainPtr<CFHTTPMessageRef> dummyRequest(AdoptCF, CFHTTPMessageCreateRequest(0, CFSTR("GET"), m_httpsURL.get(), kCFHTTPVersion1_1));
349
350         Boolean appliedCredentials = CFHTTPMessageApplyCredentials(dummyRequest.get(), authentication.get(), loginCF.get(), passwordCF.get(), 0);
351         ASSERT_UNUSED(appliedCredentials, appliedCredentials);
352
353         RetainPtr<CFStringRef> proxyAuthorizationString(AdoptCF, CFHTTPMessageCopyHeaderFieldValue(dummyRequest.get(), CFSTR("Proxy-Authorization")));
354
355         if (!proxyAuthorizationString) {
356             // Fails e.g. for NTLM auth.
357             m_client->didFailSocketStream(this, SocketStreamError()); // FIXME: Provide a sensible error.
358             return;
359         }
360
361         // Setting the authorization results in a new connection attempt.
362         wkSetCONNECTProxyAuthorizationForStream(m_readStream.get(), proxyAuthorizationString.get());
363         m_sentStoredCredentials = true;
364         return;
365     }
366
367     // FIXME: Ask the client if credentials could not be found.
368
369     m_client->didFailSocketStream(this, SocketStreamError()); // FIXME: Provide a sensible error.
370 }
371
372 CFStringRef SocketStreamHandle::copyCFStreamDescription(void* info)
373 {
374     SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(info);
375     return String("WebKit socket stream, " + handle->m_url.string()).createCFString();
376 }
377
378 struct MainThreadEventCallbackInfo {
379     MainThreadEventCallbackInfo(CFStreamEventType type, SocketStreamHandle* handle) : type(type), handle(handle) { }
380     CFStreamEventType type;
381     RefPtr<SocketStreamHandle> handle;
382 };
383
384 void SocketStreamHandle::readStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void* clientCallBackInfo)
385 {
386     SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(clientCallBackInfo);
387     ASSERT_UNUSED(stream, stream == handle->m_readStream.get());
388 #if PLATFORM(WIN)
389     MainThreadEventCallbackInfo info(type, handle);
390     callOnMainThreadAndWait(readStreamCallbackMainThread, &info);
391 #else
392     ASSERT(isMainThread());
393     handle->readStreamCallback(type);
394 #endif
395 }
396
397 void SocketStreamHandle::writeStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void* clientCallBackInfo)
398 {
399     SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(clientCallBackInfo);
400     ASSERT_UNUSED(stream, stream == handle->m_writeStream.get());
401 #if PLATFORM(WIN)
402     MainThreadEventCallbackInfo info(type, handle);
403     callOnMainThreadAndWait(writeStreamCallbackMainThread, &info);
404 #else
405     ASSERT(isMainThread());
406     handle->writeStreamCallback(type);
407 #endif
408 }
409
410 #if PLATFORM(WIN)
411 void SocketStreamHandle::readStreamCallbackMainThread(void* invocation)
412 {
413     MainThreadEventCallbackInfo* info = static_cast<MainThreadEventCallbackInfo*>(invocation);
414     info->handle->readStreamCallback(info->type);
415 }
416
417 void SocketStreamHandle::writeStreamCallbackMainThread(void* invocation)
418 {
419     MainThreadEventCallbackInfo* info = static_cast<MainThreadEventCallbackInfo*>(invocation);
420     info->handle->writeStreamCallback(info->type);
421 }
422 #endif // PLATFORM(WIN)
423
424 void SocketStreamHandle::readStreamCallback(CFStreamEventType type)
425 {
426     switch(type) {
427     case kCFStreamEventNone:
428         break;
429     case kCFStreamEventOpenCompleted:
430         break;
431     case kCFStreamEventHasBytesAvailable: {
432         if (m_connectingSubstate == WaitingForConnect) {
433             if (m_connectionType == CONNECTProxy) {
434                 RetainPtr<CFHTTPMessageRef> proxyResponse(AdoptCF, wkCopyCONNECTProxyResponse(m_readStream.get(), m_httpsURL.get()));
435                 if (proxyResponse && (407 == CFHTTPMessageGetResponseStatusCode(proxyResponse.get()))) {
436                     addCONNECTCredentials(proxyResponse.get());
437                     return;
438                 }
439             }
440         } else if (m_connectingSubstate == WaitingForCredentials)
441             break;
442
443         if (m_connectingSubstate == WaitingForConnect) {
444             m_connectingSubstate = Connected;
445             m_state = Open;
446
447             RefPtr<SocketStreamHandle> protect(this); // The client can close the handle, potentially removing the last reference.
448             m_client->didOpenSocketStream(this);
449             if (m_state == Closed)
450                 break;
451             // Fall through.
452         } else if (m_state == Closed)
453             break;
454
455         ASSERT(m_state == Open);
456         ASSERT(m_connectingSubstate == Connected);
457
458         CFIndex length;
459         UInt8 localBuffer[1024]; // Used if CFReadStreamGetBuffer couldn't return anything.
460         const UInt8* ptr = CFReadStreamGetBuffer(m_readStream.get(), 0, &length);
461         if (!ptr) {
462             length = CFReadStreamRead(m_readStream.get(), localBuffer, sizeof(localBuffer));
463             ptr = localBuffer;
464         }
465
466         m_client->didReceiveSocketStreamData(this, reinterpret_cast<const char*>(ptr), length);
467
468         break;
469     }
470     case kCFStreamEventCanAcceptBytes:
471         ASSERT_NOT_REACHED();
472         break;
473     case kCFStreamEventErrorOccurred: {
474         RetainPtr<CFErrorRef> error(AdoptCF, CFReadStreamCopyError(m_readStream.get()));
475         reportErrorToClient(error.get());
476         break;
477     }
478     case kCFStreamEventEndEncountered:
479         platformClose();
480         break;
481     }
482 }
483
484 void SocketStreamHandle::writeStreamCallback(CFStreamEventType type)
485 {
486     switch(type) {
487     case kCFStreamEventNone:
488         break;
489     case kCFStreamEventOpenCompleted:
490         break;
491     case kCFStreamEventHasBytesAvailable:
492         ASSERT_NOT_REACHED();
493         break;
494     case kCFStreamEventCanAcceptBytes: {
495         // Possibly, a spurious event from CONNECT handshake.
496         if (!CFWriteStreamCanAcceptBytes(m_writeStream.get()))
497             return;
498
499         if (m_connectingSubstate == WaitingForCredentials)
500             break;
501
502         if (m_connectingSubstate == WaitingForConnect) {
503             m_connectingSubstate = Connected;
504             m_state = Open;
505
506             RefPtr<SocketStreamHandle> protect(this); // The client can close the handle, potentially removing the last reference.
507             m_client->didOpenSocketStream(this);
508             break;
509         }
510
511         ASSERT(m_state == Open);
512         ASSERT(m_connectingSubstate == Connected);
513
514         sendPendingData();
515         break;
516     }
517     case kCFStreamEventErrorOccurred: {
518         RetainPtr<CFErrorRef> error(AdoptCF, CFWriteStreamCopyError(m_writeStream.get()));
519         reportErrorToClient(error.get());
520         break;
521     }
522     case kCFStreamEventEndEncountered:
523         // FIXME: Currently, we handle closing in read callback, but these can come independently (e.g. a server can stop listening, but keep sending data).
524         break;
525     }
526 }
527
528 void SocketStreamHandle::reportErrorToClient(CFErrorRef error)
529 {
530     CFIndex errorCode = CFErrorGetCode(error);
531     String description;
532
533 #if PLATFORM(MAC)
534     if (CFEqual(CFErrorGetDomain(error), kCFErrorDomainOSStatus)) {
535         const char* descriptionOSStatus = GetMacOSStatusCommentString(static_cast<OSStatus>(errorCode));
536         if (descriptionOSStatus && descriptionOSStatus[0] != '\0')
537             description = "OSStatus Error " + String::number(errorCode) + ": " + descriptionOSStatus;
538     }
539 #endif
540
541     if (description.isNull()) {
542         RetainPtr<CFStringRef> descriptionCF(AdoptCF, CFErrorCopyDescription(error));
543         description = String(descriptionCF.get());
544     }
545
546     m_client->didFailSocketStream(this, SocketStreamError(static_cast<int>(errorCode), m_url.string(), description));
547 }
548
549 SocketStreamHandle::~SocketStreamHandle()
550 {
551     LOG(Network, "SocketStreamHandle %p dtor", this);
552
553     ASSERT(!m_pacRunLoopSource);
554 }
555
556 int SocketStreamHandle::platformSend(const char* data, int length)
557 {
558     if (!CFWriteStreamCanAcceptBytes(m_writeStream.get()))
559         return 0;
560
561     return CFWriteStreamWrite(m_writeStream.get(), reinterpret_cast<const UInt8*>(data), length);
562 }
563
564 void SocketStreamHandle::platformClose()
565 {
566     LOG(Network, "SocketStreamHandle %p platformClose", this);
567
568     if (m_pacRunLoopSource) 
569         removePACRunLoopSource();
570
571     ASSERT(!m_readStream == !m_writeStream);
572     if (!m_readStream) {
573         if (m_connectingSubstate == New || m_connectingSubstate == ExecutingPACFile)
574             m_client->didCloseSocketStream(this);
575         return;
576     }
577
578 #if PLATFORM(WIN)
579     CFReadStreamUnscheduleFromRunLoop(m_readStream.get(), loaderRunLoop(), kCFRunLoopDefaultMode);
580     CFWriteStreamUnscheduleFromRunLoop(m_writeStream.get(), loaderRunLoop(), kCFRunLoopDefaultMode);
581 #else
582     CFReadStreamUnscheduleFromRunLoop(m_readStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
583     CFWriteStreamUnscheduleFromRunLoop(m_writeStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
584 #endif
585
586     CFReadStreamClose(m_readStream.get());
587     CFWriteStreamClose(m_writeStream.get());
588     
589     m_readStream = 0;
590     m_writeStream = 0;
591
592     m_client->didCloseSocketStream(this);
593 }
594
595 void SocketStreamHandle::receivedCredential(const AuthenticationChallenge&, const Credential&)
596 {
597 }
598
599 void SocketStreamHandle::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge&)
600 {
601 }
602
603 void SocketStreamHandle::receivedCancellation(const AuthenticationChallenge&)
604 {
605 }
606
607 unsigned short SocketStreamHandle::port() const
608 {
609     if (unsigned short urlPort = m_url.port())
610         return urlPort;
611     if (shouldUseSSL())
612         return 443;
613     return 80;
614 }
615
616 }  // namespace WebCore