8c6ff5698826997769a3d759ff158d421735d7e3
[WebKit-https.git] / 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 "Logging.h"
36 #include "SocketStreamError.h"
37 #include "SocketStreamHandleClient.h"
38 #include <wtf/MainThread.h>
39
40 #if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD)
41 #include <SystemConfiguration/SystemConfiguration.h>
42 #endif
43
44 #if PLATFORM(WIN)
45 #include "LoaderRunLoopCF.h"
46 #endif
47
48 #ifdef BUILDING_ON_TIGER
49 #define CFN_EXPORT extern
50 #endif
51
52 extern "C" {
53 CFN_EXPORT const CFStringRef kCFStreamPropertyCONNECTProxy;
54 CFN_EXPORT const CFStringRef kCFStreamPropertyCONNECTProxyHost;
55 CFN_EXPORT const CFStringRef kCFStreamPropertyCONNECTProxyPort;
56 }
57
58 namespace WebCore {
59
60 SocketStreamHandle::SocketStreamHandle(const KURL& url, SocketStreamHandleClient* client)
61     : SocketStreamHandleBase(url, client)
62     , m_connectingSubstate(New)
63     , m_connectionType(Unknown)
64 {
65     LOG(Network, "SocketStreamHandle %p new client %p", this, m_client);
66
67     ASSERT(url.protocolIs("ws") || url.protocolIs("wss"));
68
69     if (!m_url.port())
70         m_url.setPort(shouldUseSSL() ? 443 : 80);
71
72     KURL httpsURL(KURL(), "https://" + m_url.host());
73     m_httpsURL.adoptCF(httpsURL.createCFURL());
74
75     createStreams();
76     ASSERT(!m_readStream == !m_writeStream);
77     if (!m_readStream) // Doing asynchronous PAC file processing, streams will be created later.
78         return;
79
80     scheduleStreams();
81 }
82
83 void SocketStreamHandle::scheduleStreams()
84 {
85     ASSERT(m_readStream);
86     ASSERT(m_writeStream);
87
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);
92
93 #if PLATFORM(WIN)
94     CFReadStreamScheduleWithRunLoop(m_readStream.get(), loaderRunLoop(), kCFRunLoopDefaultMode);
95     CFWriteStreamScheduleWithRunLoop(m_writeStream.get(), loaderRunLoop(), kCFRunLoopDefaultMode);
96 #else
97     CFReadStreamScheduleWithRunLoop(m_readStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
98     CFWriteStreamScheduleWithRunLoop(m_writeStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
99 #endif
100
101     CFReadStreamOpen(m_readStream.get());
102     CFWriteStreamOpen(m_writeStream.get());
103
104 #ifndef BUILDING_ON_TIGER
105     if (m_pacRunLoopSource)
106         removePACRunLoopSource();
107 #endif
108
109     m_connectingSubstate = WaitingForConnect;
110 }
111
112 #ifndef BUILDING_ON_TIGER
113 CFStringRef SocketStreamHandle::copyPACExecutionDescription(void*)
114 {
115     return CFSTR("WebSocket proxy PAC file execution");
116 }
117
118 struct MainThreadPACCallbackInfo {
119     MainThreadPACCallbackInfo(SocketStreamHandle* handle, CFArrayRef proxyList) : handle(handle), proxyList(proxyList) { }
120     SocketStreamHandle* handle;
121     CFArrayRef proxyList;
122 };
123
124 void SocketStreamHandle::pacExecutionCallback(void* client, CFArrayRef proxyList, CFErrorRef)
125 {
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);
130 }
131
132 void SocketStreamHandle::pacExecutionCallbackMainThread(void* invocation)
133 {
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();
140 }
141
142 void SocketStreamHandle::executePACFileURL(CFURLRef pacFileURL)
143 {
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));
147 #if PLATFORM(WIN)
148     CFRunLoopAddSource(loaderRunLoop(), m_pacRunLoopSource.get(), kCFRunLoopDefaultMode);
149 #else
150     CFRunLoopAddSource(CFRunLoopGetCurrent(), m_pacRunLoopSource.get(), kCFRunLoopCommonModes);
151 #endif
152     m_connectingSubstate = ExecutingPACFile;
153 }
154
155 void SocketStreamHandle::removePACRunLoopSource()
156 {
157     ASSERT(m_pacRunLoopSource);
158
159     CFRunLoopSourceInvalidate(m_pacRunLoopSource.get());
160 #if PLATFORM(WIN)
161     CFRunLoopRemoveSource(loaderRunLoop(), m_pacRunLoopSource.get(), kCFRunLoopDefaultMode);
162 #else
163     CFRunLoopRemoveSource(CFRunLoopGetCurrent(), m_pacRunLoopSource.get(), kCFRunLoopCommonModes);
164 #endif
165     m_pacRunLoopSource = 0;
166 }
167
168 void SocketStreamHandle::chooseProxy()
169 {
170 #ifndef BUILDING_ON_LEOPARD
171     RetainPtr<CFDictionaryRef> proxyDictionary(AdoptCF, CFNetworkCopySystemProxySettings());
172 #else
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));
175 #endif
176
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.
181
182     if (!proxyDictionary) {
183         m_connectionType = Direct;
184         return;
185     }
186
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()));
190
191     chooseProxyFromArray(proxyArray.get());
192 }
193
194 void SocketStreamHandle::chooseProxyFromArray(CFArrayRef proxyArray)
195 {
196     if (!proxyArray)
197         m_connectionType = Direct;
198
199     CFIndex proxyArrayCount = CFArrayGetCount(proxyArray);
200
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));
210                     return;
211                 }
212             }
213         }
214     }
215
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;
224                 break;
225             }
226             if (CFEqual(proxyType, kCFProxyTypeHTTPS)) {
227                 m_connectionType = CONNECTProxy;
228                 chosenProxy = proxyInfo;
229                 // Keep looking for proxies, as a SOCKS one is preferable.
230             }
231         }
232     }
233
234     if (chosenProxy) {
235         ASSERT(m_connectionType != Unknown);
236         ASSERT(m_connectionType != Direct);
237
238         CFTypeRef proxyHost = CFDictionaryGetValue(chosenProxy, kCFProxyHostNameKey);
239         CFTypeRef proxyPort = CFDictionaryGetValue(chosenProxy, kCFProxyPortNumberKey);
240
241         if (proxyHost && CFGetTypeID(proxyHost) == CFStringGetTypeID() && proxyPort && CFGetTypeID(proxyPort) == CFNumberGetTypeID()) {
242             m_proxyHost = static_cast<CFStringRef>(proxyHost);
243             m_proxyPort = static_cast<CFNumberRef>(proxyPort);
244             return;
245         }
246     }
247
248     m_connectionType = Direct;
249 }
250
251 #else // BUILDING_ON_TIGER
252
253 void SocketStreamHandle::chooseProxy()
254 {
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));
257
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.
262
263     if (!proxyDictionary) {
264         m_connectionType = Direct;
265         return;
266     }
267
268     // FIXME: check proxy bypass list and ExcludeSimpleHostnames.
269     // FIXME: Support PAC files.
270
271     CFTypeRef socksEnableCF = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesSOCKSEnable);
272     int socksEnable;
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;
280             return;
281         }
282     }
283
284     CFTypeRef httpsEnableCF = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesHTTPSEnable);
285     int httpsEnable;
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);
289
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;
294             return;
295         }
296     }
297
298     m_connectionType = Direct;
299 }
300 #endif // BUILDING_ON_TIGER
301
302 void SocketStreamHandle::createStreams()
303 {
304     if (m_connectionType == Unknown)
305         chooseProxy();
306
307     // If it's still unknown, then we're resolving a PAC file asynchronously.
308     if (m_connectionType == Unknown)
309         return;
310
311     RetainPtr<CFStringRef> host(AdoptCF, m_url.host().createCFString());
312
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);
317
318     m_readStream.adoptCF(readStream);
319     m_writeStream.adoptCF(writeStream);
320
321     switch (m_connectionType) {
322     case Unknown:
323         ASSERT_NOT_REACHED();
324         break;
325     case Direct:
326         break;
327     case SOCKSProxy: {
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());
334         break;
335         }
336     case CONNECTProxy: {
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());
341         break;
342         }
343     }
344
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());
351     }
352 }
353
354 CFStringRef SocketStreamHandle::copyCFStreamDescription(void* info)
355 {
356     SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(info);
357     return ("WebKit socket stream, " + handle->m_url.string()).createCFString();
358 }
359
360 struct MainThreadEventCallbackInfo {
361     MainThreadEventCallbackInfo(CFStreamEventType type, SocketStreamHandle* handle) : type(type), handle(handle) { }
362     CFStreamEventType type;
363     SocketStreamHandle* handle;
364 };
365
366 void SocketStreamHandle::readStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void* clientCallBackInfo)
367 {
368     SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(clientCallBackInfo);
369     ASSERT_UNUSED(stream, stream == handle->m_readStream.get());
370 #if PLATFORM(WIN)
371     MainThreadEventCallbackInfo info(type, handle);
372     callOnMainThreadAndWait(readStreamCallbackMainThread, &info);
373 #else
374     ASSERT(isMainThread());
375     handle->readStreamCallback(type);
376 #endif
377 }
378
379 void SocketStreamHandle::writeStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void* clientCallBackInfo)
380 {
381     SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(clientCallBackInfo);
382     ASSERT_UNUSED(stream, stream == handle->m_writeStream.get());
383 #if PLATFORM(WIN)
384     MainThreadEventCallbackInfo info(type, handle);
385     callOnMainThreadAndWait(writeStreamCallbackMainThread, &info);
386 #else
387     ASSERT(isMainThread());
388     handle->writeStreamCallback(type);
389 #endif
390 }
391
392 #if PLATFORM(WIN)
393 void SocketStreamHandle::readStreamCallbackMainThread(void* invocation)
394 {
395     MainThreadEventCallbackInfo* info = static_cast<MainThreadEventCallbackInfo*>(invocation);
396     info->handle->readStreamCallback(info->type);
397 }
398
399 void SocketStreamHandle::writeStreamCallbackMainThread(void* invocation)
400 {
401     MainThreadEventCallbackInfo* info = static_cast<MainThreadEventCallbackInfo*>(invocation);
402     info->handle->writeStreamCallback(info->type);
403 }
404 #endif // PLATFORM(WIN)
405
406 void SocketStreamHandle::readStreamCallback(CFStreamEventType type)
407 {
408     switch(type) {
409     case kCFStreamEventNone:
410         break;
411     case kCFStreamEventOpenCompleted:
412         break;
413     case kCFStreamEventHasBytesAvailable: {
414         if (m_connectingSubstate == WaitingForConnect) {
415             // FIXME: Handle CONNECT proxy credentials here.
416         } else if (m_connectingSubstate == WaitingForCredentials)
417             break;
418
419         if (m_connectingSubstate == WaitingForConnect) {
420             m_connectingSubstate = Connected;
421             m_state = Open;
422             m_client->didOpen(this);
423             // Fall through.
424         } else if (m_state == Closed)
425             break;
426
427         ASSERT(m_state == Open);
428         ASSERT(m_connectingSubstate == Connected);
429
430         CFIndex length;
431         UInt8 localBuffer[1024]; // Used if CFReadStreamGetBuffer couldn't return anything.
432         const UInt8* ptr = CFReadStreamGetBuffer(m_readStream.get(), 0, &length);
433         if (!ptr) {
434             length = CFReadStreamRead(m_readStream.get(), localBuffer, sizeof(localBuffer));
435             ptr = localBuffer;
436         }
437
438         m_client->didReceiveData(this, reinterpret_cast<const char*>(ptr), length);
439
440         break;
441     }
442     case kCFStreamEventCanAcceptBytes:
443         ASSERT_NOT_REACHED();
444         break;
445     case kCFStreamEventErrorOccurred: {
446         CFStreamError error = CFReadStreamGetError(m_readStream.get());
447         m_client->didFail(this, SocketStreamError(error.error)); // FIXME: Provide a sensible error.
448         break;
449     }
450     case kCFStreamEventEndEncountered:
451         m_client->didClose(this);
452         break;
453     }
454 }
455
456 void SocketStreamHandle::writeStreamCallback(CFStreamEventType type)
457 {
458     switch(type) {
459     case kCFStreamEventNone:
460         break;
461     case kCFStreamEventOpenCompleted:
462         break;
463     case kCFStreamEventHasBytesAvailable:
464         ASSERT_NOT_REACHED();
465         break;
466     case kCFStreamEventCanAcceptBytes: {
467         // Possibly, a spurious event from CONNECT handshake.
468         if (!CFWriteStreamCanAcceptBytes(m_writeStream.get()))
469             return;
470
471         if (m_connectingSubstate == WaitingForCredentials)
472             break;
473
474         if (m_connectingSubstate == WaitingForConnect) {
475             m_connectingSubstate = Connected;
476             m_state = Open;
477             m_client->didOpen(this);
478             break;
479         }
480
481         ASSERT(m_state = Open);
482         ASSERT(m_connectingSubstate == Connected);
483
484         sendPendingData();
485         break;
486     }
487     case kCFStreamEventErrorOccurred: {
488         CFStreamError error = CFWriteStreamGetError(m_writeStream.get());
489         m_client->didFail(this, SocketStreamError(error.error)); // FIXME: Provide a sensible error.
490         break;
491     }
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).
494         break;
495     }
496 }
497
498 SocketStreamHandle::~SocketStreamHandle()
499 {
500     LOG(Network, "SocketStreamHandle %p dtor", this);
501
502 #ifndef BUILDING_ON_TIGER
503     ASSERT(!m_pacRunLoopSource);
504 #endif
505 }
506
507 int SocketStreamHandle::platformSend(const char* data, int length)
508 {
509     if (!CFWriteStreamCanAcceptBytes(m_writeStream.get()))
510         return 0;
511
512     return CFWriteStreamWrite(m_writeStream.get(), reinterpret_cast<const UInt8*>(data), length);
513 }
514
515 void SocketStreamHandle::platformClose()
516 {
517     LOG(Network, "SocketStreamHandle %p platformClose", this);
518
519 #ifndef BUILDING_ON_TIGER
520     if (m_pacRunLoopSource) 
521         removePACRunLoopSource();
522 #endif
523
524     ASSERT(!m_readStream == !m_writeStream);
525     if (!m_readStream)
526         return;
527
528     CFReadStreamUnscheduleFromRunLoop(m_readStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
529     CFWriteStreamUnscheduleFromRunLoop(m_writeStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
530
531     CFReadStreamClose(m_readStream.get());
532     CFWriteStreamClose(m_writeStream.get());
533     
534     m_readStream = 0;
535     m_writeStream = 0;
536 }
537
538 void SocketStreamHandle::receivedCredential(const AuthenticationChallenge&, const Credential&)
539 {
540 }
541
542 void SocketStreamHandle::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge&)
543 {
544 }
545
546 void SocketStreamHandle::receivedCancellation(const AuthenticationChallenge&)
547 {
548 }
549
550 }  // namespace WebCore