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/StringConcatenate.h>
43
44#if defined(BUILDING_ON_TIGER) || defined(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#ifdef BUILDING_ON_TIGER
57#define CFN_EXPORT extern
58#endif
59
60namespace WebCore {
61
62SocketStreamHandle::SocketStreamHandle(const KURL& url, SocketStreamHandleClient* client)
63    : SocketStreamHandleBase(url, client)
64    , m_connectingSubstate(New)
65    , m_connectionType(Unknown)
66    , m_sentStoredCredentials(false)
67{
68    LOG(Network, "SocketStreamHandle %p new client %p", this, m_client);
69
70    ASSERT(url.protocolIs("ws") || url.protocolIs("wss"));
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
83void 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
113CFStringRef SocketStreamHandle::copyPACExecutionDescription(void*)
114{
115    return CFSTR("WebSocket proxy PAC file execution");
116}
117
118struct MainThreadPACCallbackInfo {
119    MainThreadPACCallbackInfo(SocketStreamHandle* handle, CFArrayRef proxyList) : handle(handle), proxyList(proxyList) { }
120    RefPtr<SocketStreamHandle> handle;
121    CFArrayRef proxyList;
122};
123
124void 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
132void 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
142void 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
155void 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
168void 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
194void SocketStreamHandle::chooseProxyFromArray(CFArrayRef proxyArray)
195{
196    if (!proxyArray) {
197        m_connectionType = Direct;
198        return;
199    }
200
201    CFIndex proxyArrayCount = CFArrayGetCount(proxyArray);
202
203    // PAC is always the first entry, if present.
204    if (proxyArrayCount) {
205        CFDictionaryRef proxyInfo = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(proxyArray, 0));
206        CFTypeRef proxyType = CFDictionaryGetValue(proxyInfo, kCFProxyTypeKey);
207        if (proxyType && CFGetTypeID(proxyType) == CFStringGetTypeID()) {
208            if (CFEqual(proxyType, kCFProxyTypeAutoConfigurationURL)) {
209                CFTypeRef pacFileURL = CFDictionaryGetValue(proxyInfo, kCFProxyAutoConfigurationURLKey);
210                if (pacFileURL && CFGetTypeID(pacFileURL) == CFURLGetTypeID()) {
211                    executePACFileURL(static_cast<CFURLRef>(pacFileURL));
212                    return;
213                }
214            }
215        }
216    }
217
218    CFDictionaryRef chosenProxy = 0;
219    for (CFIndex i = 0; i < proxyArrayCount; ++i) {
220        CFDictionaryRef proxyInfo = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(proxyArray, i));
221        CFTypeRef proxyType = CFDictionaryGetValue(proxyInfo, kCFProxyTypeKey);
222        if (proxyType && CFGetTypeID(proxyType) == CFStringGetTypeID()) {
223            if (CFEqual(proxyType, kCFProxyTypeSOCKS)) {
224                m_connectionType = SOCKSProxy;
225                chosenProxy = proxyInfo;
226                break;
227            }
228            if (CFEqual(proxyType, kCFProxyTypeHTTPS)) {
229                m_connectionType = CONNECTProxy;
230                chosenProxy = proxyInfo;
231                // Keep looking for proxies, as a SOCKS one is preferable.
232            }
233        }
234    }
235
236    if (chosenProxy) {
237        ASSERT(m_connectionType != Unknown);
238        ASSERT(m_connectionType != Direct);
239
240        CFTypeRef proxyHost = CFDictionaryGetValue(chosenProxy, kCFProxyHostNameKey);
241        CFTypeRef proxyPort = CFDictionaryGetValue(chosenProxy, kCFProxyPortNumberKey);
242
243        if (proxyHost && CFGetTypeID(proxyHost) == CFStringGetTypeID() && proxyPort && CFGetTypeID(proxyPort) == CFNumberGetTypeID()) {
244            m_proxyHost = static_cast<CFStringRef>(proxyHost);
245            m_proxyPort = static_cast<CFNumberRef>(proxyPort);
246            return;
247        }
248    }
249
250    m_connectionType = Direct;
251}
252
253#else // BUILDING_ON_TIGER
254
255void SocketStreamHandle::chooseProxy()
256{
257    // We don't need proxy information often, so there is no need to set up a permanent dynamic store session.
258    RetainPtr<CFDictionaryRef> proxyDictionary(AdoptCF, SCDynamicStoreCopyProxies(0));
259
260    // SOCKS or HTTPS (AKA CONNECT) proxies are supported.
261    // WebSocket protocol relies on handshake being transferred unchanged, so we need a proxy that will not modify headers.
262    // Since HTTP proxies must add Via headers, they are highly unlikely to work.
263    // Many CONNECT proxies limit connectivity to port 443, so we prefer SOCKS, if configured.
264
265    if (!proxyDictionary) {
266        m_connectionType = Direct;
267        return;
268    }
269
270    // FIXME: check proxy bypass list and ExcludeSimpleHostnames.
271    // FIXME: Support PAC files.
272
273    CFTypeRef socksEnableCF = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesSOCKSEnable);
274    int socksEnable;
275    if (socksEnableCF && CFGetTypeID(socksEnableCF) == CFNumberGetTypeID() && CFNumberGetValue(static_cast<CFNumberRef>(socksEnableCF), kCFNumberIntType, &socksEnable) && socksEnable) {
276        CFTypeRef proxyHost = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesSOCKSProxy);
277        CFTypeRef proxyPort = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesSOCKSPort);
278        if (proxyHost && CFGetTypeID(proxyHost) == CFStringGetTypeID() && proxyPort && CFGetTypeID(proxyPort) == CFNumberGetTypeID()) {
279            m_proxyHost = static_cast<CFStringRef>(proxyHost);
280            m_proxyPort = static_cast<CFNumberRef>(proxyPort);
281            m_connectionType = SOCKSProxy;
282            return;
283        }
284    }
285
286    CFTypeRef httpsEnableCF = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesHTTPSEnable);
287    int httpsEnable;
288    if (httpsEnableCF && CFGetTypeID(httpsEnableCF) == CFNumberGetTypeID() && CFNumberGetValue(static_cast<CFNumberRef>(httpsEnableCF), kCFNumberIntType, &httpsEnable) && httpsEnable) {
289        CFTypeRef proxyHost = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesHTTPSProxy);
290        CFTypeRef proxyPort = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesHTTPSPort);
291
292        if (proxyHost && CFGetTypeID(proxyHost) == CFStringGetTypeID() && proxyPort && CFGetTypeID(proxyPort) == CFNumberGetTypeID()) {
293            m_proxyHost = static_cast<CFStringRef>(proxyHost);
294            m_proxyPort = static_cast<CFNumberRef>(proxyPort);
295            m_connectionType = CONNECTProxy;
296            return;
297        }
298    }
299
300    m_connectionType = Direct;
301}
302#endif // BUILDING_ON_TIGER
303
304void SocketStreamHandle::createStreams()
305{
306    if (m_connectionType == Unknown)
307        chooseProxy();
308
309    // If it's still unknown, then we're resolving a PAC file asynchronously.
310    if (m_connectionType == Unknown)
311        return;
312
313    RetainPtr<CFStringRef> host(AdoptCF, m_url.host().createCFString());
314
315    // Creating streams to final destination, not to proxy.
316    CFReadStreamRef readStream = 0;
317    CFWriteStreamRef writeStream = 0;
318    CFStreamCreatePairWithSocketToHost(0, host.get(), port(), &readStream, &writeStream);
319
320    m_readStream.adoptCF(readStream);
321    m_writeStream.adoptCF(writeStream);
322
323    switch (m_connectionType) {
324    case Unknown:
325        ASSERT_NOT_REACHED();
326        break;
327    case Direct:
328        break;
329    case SOCKSProxy: {
330        // FIXME: SOCKS5 doesn't do challenge-response, should we try to apply credentials from Keychain right away?
331        // But SOCKS5 credentials don't work at the time of this writing anyway, see <rdar://6776698>.
332        const void* proxyKeys[] = { kCFStreamPropertySOCKSProxyHost, kCFStreamPropertySOCKSProxyPort };
333        const void* proxyValues[] = { m_proxyHost.get(), m_proxyPort.get() };
334        RetainPtr<CFDictionaryRef> connectDictionary(AdoptCF, CFDictionaryCreate(0, proxyKeys, proxyValues, WTF_ARRAY_LENGTH(proxyKeys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
335        CFReadStreamSetProperty(m_readStream.get(), kCFStreamPropertySOCKSProxy, connectDictionary.get());
336        break;
337        }
338    case CONNECTProxy:
339        wkSetCONNECTProxyForStream(m_readStream.get(), m_proxyHost.get(), m_proxyPort.get());
340        break;
341    }
342
343    if (shouldUseSSL()) {
344        const void* keys[] = { kCFStreamSSLPeerName, kCFStreamSSLLevel };
345        const void* values[] = { host.get(), kCFStreamSocketSecurityLevelNegotiatedSSL };
346        RetainPtr<CFDictionaryRef> settings(AdoptCF, CFDictionaryCreate(0, keys, values, WTF_ARRAY_LENGTH(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
347        CFReadStreamSetProperty(m_readStream.get(), kCFStreamPropertySSLSettings, settings.get());
348        CFWriteStreamSetProperty(m_writeStream.get(), kCFStreamPropertySSLSettings, settings.get());
349    }
350}
351
352static bool getStoredCONNECTProxyCredentials(const ProtectionSpace& protectionSpace, String& login, String& password)
353{
354    // Try system credential storage first, matching HTTP behavior (CFNetwork only asks the client for password if it couldn't find it in Keychain).
355    Credential storedCredential = CredentialStorage::getFromPersistentStorage(protectionSpace);
356    if (storedCredential.isEmpty())
357        storedCredential = CredentialStorage::get(protectionSpace);
358
359    if (storedCredential.isEmpty())
360        return false;
361
362    login = storedCredential.user();
363    password = storedCredential.password();
364
365    return true;
366}
367
368static ProtectionSpaceAuthenticationScheme authenticationSchemeFromAuthenticationMethod(CFStringRef method)
369{
370    if (CFEqual(method, kCFHTTPAuthenticationSchemeBasic))
371        return ProtectionSpaceAuthenticationSchemeHTTPBasic;
372    if (CFEqual(method, kCFHTTPAuthenticationSchemeDigest))
373        return ProtectionSpaceAuthenticationSchemeHTTPDigest;
374#ifndef BUILDING_ON_TIGER
375    if (CFEqual(method, kCFHTTPAuthenticationSchemeNTLM))
376        return ProtectionSpaceAuthenticationSchemeNTLM;
377    if (CFEqual(method, kCFHTTPAuthenticationSchemeNegotiate))
378        return ProtectionSpaceAuthenticationSchemeNegotiate;
379#endif
380    ASSERT_NOT_REACHED();
381    return ProtectionSpaceAuthenticationSchemeUnknown;
382}
383
384void SocketStreamHandle::addCONNECTCredentials(CFHTTPMessageRef proxyResponse)
385{
386    RetainPtr<CFHTTPAuthenticationRef> authentication(AdoptCF, CFHTTPAuthenticationCreateFromResponse(0, proxyResponse));
387
388    if (!CFHTTPAuthenticationRequiresUserNameAndPassword(authentication.get())) {
389        // That's all we can offer...
390        m_client->didFail(this, SocketStreamError()); // FIXME: Provide a sensible error.
391        return;
392    }
393
394    int port = 0;
395    CFNumberGetValue(m_proxyPort.get(), kCFNumberIntType, &port);
396    RetainPtr<CFStringRef> methodCF(AdoptCF, CFHTTPAuthenticationCopyMethod(authentication.get()));
397    RetainPtr<CFStringRef> realmCF(AdoptCF, CFHTTPAuthenticationCopyRealm(authentication.get()));
398    ProtectionSpace protectionSpace(String(m_proxyHost.get()), port, ProtectionSpaceProxyHTTPS, String(realmCF.get()), authenticationSchemeFromAuthenticationMethod(methodCF.get()));
399    String login;
400    String password;
401    if (!m_sentStoredCredentials && getStoredCONNECTProxyCredentials(protectionSpace, login, password)) {
402        // Try to apply stored credentials, if we haven't tried those already.
403        RetainPtr<CFStringRef> loginCF(AdoptCF, login.createCFString());
404        RetainPtr<CFStringRef> passwordCF(AdoptCF, password.createCFString());
405        // Creating a temporary request to make CFNetwork apply credentials to it. Unfortunately, this cannot work with NTLM authentication.
406        RetainPtr<CFHTTPMessageRef> dummyRequest(AdoptCF, CFHTTPMessageCreateRequest(0, CFSTR("GET"), m_httpsURL.get(), kCFHTTPVersion1_1));
407
408        Boolean appliedCredentials = CFHTTPMessageApplyCredentials(dummyRequest.get(), authentication.get(), loginCF.get(), passwordCF.get(), 0);
409        ASSERT_UNUSED(appliedCredentials, appliedCredentials);
410
411        RetainPtr<CFStringRef> proxyAuthorizationString(AdoptCF, CFHTTPMessageCopyHeaderFieldValue(dummyRequest.get(), CFSTR("Proxy-Authorization")));
412
413        if (!proxyAuthorizationString) {
414            // Fails e.g. for NTLM auth.
415            m_client->didFail(this, SocketStreamError()); // FIXME: Provide a sensible error.
416            return;
417        }
418
419        // Setting the authorization results in a new connection attempt.
420        wkSetCONNECTProxyAuthorizationForStream(m_readStream.get(), proxyAuthorizationString.get());
421        m_sentStoredCredentials = true;
422        return;
423    }
424
425    // FIXME: Ask the client if credentials could not be found.
426
427    m_client->didFail(this, SocketStreamError()); // FIXME: Provide a sensible error.
428}
429
430CFStringRef SocketStreamHandle::copyCFStreamDescription(void* info)
431{
432    SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(info);
433    return ("WebKit socket stream, " + handle->m_url.string()).createCFString();
434}
435
436struct MainThreadEventCallbackInfo {
437    MainThreadEventCallbackInfo(CFStreamEventType type, SocketStreamHandle* handle) : type(type), handle(handle) { }
438    CFStreamEventType type;
439    RefPtr<SocketStreamHandle> handle;
440};
441
442void SocketStreamHandle::readStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void* clientCallBackInfo)
443{
444    SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(clientCallBackInfo);
445    ASSERT_UNUSED(stream, stream == handle->m_readStream.get());
446#if PLATFORM(WIN)
447    MainThreadEventCallbackInfo info(type, handle);
448    callOnMainThreadAndWait(readStreamCallbackMainThread, &info);
449#else
450    ASSERT(isMainThread());
451    handle->readStreamCallback(type);
452#endif
453}
454
455void SocketStreamHandle::writeStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void* clientCallBackInfo)
456{
457    SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(clientCallBackInfo);
458    ASSERT_UNUSED(stream, stream == handle->m_writeStream.get());
459#if PLATFORM(WIN)
460    MainThreadEventCallbackInfo info(type, handle);
461    callOnMainThreadAndWait(writeStreamCallbackMainThread, &info);
462#else
463    ASSERT(isMainThread());
464    handle->writeStreamCallback(type);
465#endif
466}
467
468#if PLATFORM(WIN)
469void SocketStreamHandle::readStreamCallbackMainThread(void* invocation)
470{
471    MainThreadEventCallbackInfo* info = static_cast<MainThreadEventCallbackInfo*>(invocation);
472    info->handle->readStreamCallback(info->type);
473}
474
475void SocketStreamHandle::writeStreamCallbackMainThread(void* invocation)
476{
477    MainThreadEventCallbackInfo* info = static_cast<MainThreadEventCallbackInfo*>(invocation);
478    info->handle->writeStreamCallback(info->type);
479}
480#endif // PLATFORM(WIN)
481
482void SocketStreamHandle::readStreamCallback(CFStreamEventType type)
483{
484    switch(type) {
485    case kCFStreamEventNone:
486        break;
487    case kCFStreamEventOpenCompleted:
488        break;
489    case kCFStreamEventHasBytesAvailable: {
490        if (m_connectingSubstate == WaitingForConnect) {
491            if (m_connectionType == CONNECTProxy) {
492                RetainPtr<CFHTTPMessageRef> proxyResponse(AdoptCF, wkCopyCONNECTProxyResponse(m_readStream.get(), m_httpsURL.get()));
493                if (proxyResponse && (407 == CFHTTPMessageGetResponseStatusCode(proxyResponse.get()))) {
494                    addCONNECTCredentials(proxyResponse.get());
495                    return;
496                }
497            }
498        } else if (m_connectingSubstate == WaitingForCredentials)
499            break;
500
501        if (m_connectingSubstate == WaitingForConnect) {
502            m_connectingSubstate = Connected;
503            m_state = Open;
504
505            RefPtr<SocketStreamHandle> protect(this); // The client can close the handle, potentially removing the last reference.
506            m_client->didOpen(this);
507            if (m_state == Closed)
508                break;
509            // Fall through.
510        } else if (m_state == Closed)
511            break;
512
513        ASSERT(m_state == Open);
514        ASSERT(m_connectingSubstate == Connected);
515
516        CFIndex length;
517        UInt8 localBuffer[1024]; // Used if CFReadStreamGetBuffer couldn't return anything.
518        const UInt8* ptr = CFReadStreamGetBuffer(m_readStream.get(), 0, &length);
519        if (!ptr) {
520            length = CFReadStreamRead(m_readStream.get(), localBuffer, sizeof(localBuffer));
521            ptr = localBuffer;
522        }
523
524        m_client->didReceiveData(this, reinterpret_cast<const char*>(ptr), length);
525
526        break;
527    }
528    case kCFStreamEventCanAcceptBytes:
529        ASSERT_NOT_REACHED();
530        break;
531    case kCFStreamEventErrorOccurred: {
532#ifndef BUILDING_ON_TIGER
533        RetainPtr<CFErrorRef> error(AdoptCF, CFReadStreamCopyError(m_readStream.get()));
534        reportErrorToClient(error.get());
535#else
536        CFStreamError error = CFReadStreamGetError(m_readStream.get());
537        m_client->didFail(this, SocketStreamError(error.error)); // FIXME: Provide a sensible error.
538#endif
539        break;
540    }
541    case kCFStreamEventEndEncountered:
542        platformClose();
543        break;
544    }
545}
546
547void SocketStreamHandle::writeStreamCallback(CFStreamEventType type)
548{
549    switch(type) {
550    case kCFStreamEventNone:
551        break;
552    case kCFStreamEventOpenCompleted:
553        break;
554    case kCFStreamEventHasBytesAvailable:
555        ASSERT_NOT_REACHED();
556        break;
557    case kCFStreamEventCanAcceptBytes: {
558        // Possibly, a spurious event from CONNECT handshake.
559        if (!CFWriteStreamCanAcceptBytes(m_writeStream.get()))
560            return;
561
562        if (m_connectingSubstate == WaitingForCredentials)
563            break;
564
565        if (m_connectingSubstate == WaitingForConnect) {
566            m_connectingSubstate = Connected;
567            m_state = Open;
568
569            RefPtr<SocketStreamHandle> protect(this); // The client can close the handle, potentially removing the last reference.
570            m_client->didOpen(this);
571            break;
572        }
573
574        ASSERT(m_state == Open);
575        ASSERT(m_connectingSubstate == Connected);
576
577        sendPendingData();
578        break;
579    }
580    case kCFStreamEventErrorOccurred: {
581#ifndef BUILDING_ON_TIGER
582        RetainPtr<CFErrorRef> error(AdoptCF, CFWriteStreamCopyError(m_writeStream.get()));
583        reportErrorToClient(error.get());
584#else
585        CFStreamError error = CFWriteStreamGetError(m_writeStream.get());
586        m_client->didFail(this, SocketStreamError(error.error)); // FIXME: Provide a sensible error.
587#endif
588        break;
589    }
590    case kCFStreamEventEndEncountered:
591        // FIXME: Currently, we handle closing in read callback, but these can come independently (e.g. a server can stop listening, but keep sending data).
592        break;
593    }
594}
595
596#ifndef BUILDING_ON_TIGER
597void SocketStreamHandle::reportErrorToClient(CFErrorRef error)
598{
599    CFIndex errorCode = CFErrorGetCode(error);
600    String description;
601
602#if PLATFORM(MAC)
603    if (CFEqual(CFErrorGetDomain(error), kCFErrorDomainOSStatus)) {
604        const char* descriptionOSStatus = GetMacOSStatusCommentString(static_cast<OSStatus>(errorCode));
605        if (descriptionOSStatus && descriptionOSStatus[0] != '\0')
606            description = makeString("OSStatus Error ", String::number(errorCode), ": ", descriptionOSStatus);
607    }
608#endif
609
610    if (description.isNull()) {
611        RetainPtr<CFStringRef> descriptionCF(AdoptCF, CFErrorCopyDescription(error));
612        description = String(descriptionCF.get());
613    }
614
615    m_client->didFail(this, SocketStreamError(static_cast<int>(errorCode), m_url.string(), description));
616}
617#endif // BUILDING_ON_TIGER
618
619SocketStreamHandle::~SocketStreamHandle()
620{
621    LOG(Network, "SocketStreamHandle %p dtor", this);
622
623#ifndef BUILDING_ON_TIGER
624    ASSERT(!m_pacRunLoopSource);
625#endif
626}
627
628int SocketStreamHandle::platformSend(const char* data, int length)
629{
630    if (!CFWriteStreamCanAcceptBytes(m_writeStream.get()))
631        return 0;
632
633    return CFWriteStreamWrite(m_writeStream.get(), reinterpret_cast<const UInt8*>(data), length);
634}
635
636void SocketStreamHandle::platformClose()
637{
638    LOG(Network, "SocketStreamHandle %p platformClose", this);
639
640#ifndef BUILDING_ON_TIGER
641    if (m_pacRunLoopSource)
642        removePACRunLoopSource();
643#endif
644
645    ASSERT(!m_readStream == !m_writeStream);
646    if (!m_readStream)
647        return;
648
649    CFReadStreamUnscheduleFromRunLoop(m_readStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
650    CFWriteStreamUnscheduleFromRunLoop(m_writeStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
651
652    CFReadStreamClose(m_readStream.get());
653    CFWriteStreamClose(m_writeStream.get());
654
655    m_readStream = 0;
656    m_writeStream = 0;
657
658    m_client->didClose(this);
659}
660
661void SocketStreamHandle::receivedCredential(const AuthenticationChallenge&, const Credential&)
662{
663}
664
665void SocketStreamHandle::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge&)
666{
667}
668
669void SocketStreamHandle::receivedCancellation(const AuthenticationChallenge&)
670{
671}
672
673unsigned short SocketStreamHandle::port() const
674{
675    if (unsigned short urlPort = m_url.port())
676        return urlPort;
677    if (shouldUseSSL())
678        return 443;
679    return 80;
680}
681
682}  // namespace WebCore
683