diff options
Diffstat (limited to 'WebCore/platform/network/cf/SocketStreamHandleCFNet.cpp')
-rw-r--r-- | WebCore/platform/network/cf/SocketStreamHandleCFNet.cpp | 587 |
1 files changed, 569 insertions, 18 deletions
diff --git a/WebCore/platform/network/cf/SocketStreamHandleCFNet.cpp b/WebCore/platform/network/cf/SocketStreamHandleCFNet.cpp index 6aa33fc..e7e64da 100644 --- a/WebCore/platform/network/cf/SocketStreamHandleCFNet.cpp +++ b/WebCore/platform/network/cf/SocketStreamHandleCFNet.cpp @@ -1,4 +1,5 @@ /* + * Copyright (C) 2009 Apple Inc. All rights reserved. * Copyright (C) 2009 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,58 +32,608 @@ #include "config.h" #include "SocketStreamHandle.h" -#include "KURL.h" +#include "Credential.h" +#include "CredentialStorage.h" #include "Logging.h" -#include "NotImplemented.h" +#include "ProtectionSpace.h" +#include "SocketStreamError.h" #include "SocketStreamHandleClient.h" +#include <wtf/MainThread.h> + +#if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD) +#include <SystemConfiguration/SystemConfiguration.h> +#endif + +#if PLATFORM(WIN) +#include "LoaderRunLoopCF.h" +#include <WebKitSystemInterface/WebKitSystemInterface.h> +#else +#include "WebCoreSystemInterface.h" +#endif + +#ifdef BUILDING_ON_TIGER +#define CFN_EXPORT extern +#endif namespace WebCore { SocketStreamHandle::SocketStreamHandle(const KURL& url, SocketStreamHandleClient* client) : SocketStreamHandleBase(url, client) + , m_connectingSubstate(New) + , m_connectionType(Unknown) + , m_sentStoredCredentials(false) { LOG(Network, "SocketStreamHandle %p new client %p", this, m_client); - notImplemented(); + + ASSERT(url.protocolIs("ws") || url.protocolIs("wss")); + + if (!m_url.port()) + m_url.setPort(shouldUseSSL() ? 443 : 80); + + KURL httpsURL(KURL(), "https://" + m_url.host()); + m_httpsURL.adoptCF(httpsURL.createCFURL()); + + createStreams(); + ASSERT(!m_readStream == !m_writeStream); + if (!m_readStream) // Doing asynchronous PAC file processing, streams will be created later. + return; + + scheduleStreams(); +} + +void SocketStreamHandle::scheduleStreams() +{ + ASSERT(m_readStream); + ASSERT(m_writeStream); + + CFStreamClientContext clientContext = { 0, this, 0, 0, copyCFStreamDescription }; + // FIXME: Pass specific events we're interested in instead of -1. + CFReadStreamSetClient(m_readStream.get(), static_cast<CFOptionFlags>(-1), readStreamCallback, &clientContext); + CFWriteStreamSetClient(m_writeStream.get(), static_cast<CFOptionFlags>(-1), writeStreamCallback, &clientContext); + +#if PLATFORM(WIN) + CFReadStreamScheduleWithRunLoop(m_readStream.get(), loaderRunLoop(), kCFRunLoopDefaultMode); + CFWriteStreamScheduleWithRunLoop(m_writeStream.get(), loaderRunLoop(), kCFRunLoopDefaultMode); +#else + CFReadStreamScheduleWithRunLoop(m_readStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes); + CFWriteStreamScheduleWithRunLoop(m_writeStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes); +#endif + + CFReadStreamOpen(m_readStream.get()); + CFWriteStreamOpen(m_writeStream.get()); + +#ifndef BUILDING_ON_TIGER + if (m_pacRunLoopSource) + removePACRunLoopSource(); +#endif + + m_connectingSubstate = WaitingForConnect; +} + +#ifndef BUILDING_ON_TIGER +CFStringRef SocketStreamHandle::copyPACExecutionDescription(void*) +{ + return CFSTR("WebSocket proxy PAC file execution"); +} + +struct MainThreadPACCallbackInfo { + MainThreadPACCallbackInfo(SocketStreamHandle* handle, CFArrayRef proxyList) : handle(handle), proxyList(proxyList) { } + SocketStreamHandle* handle; + CFArrayRef proxyList; +}; + +void SocketStreamHandle::pacExecutionCallback(void* client, CFArrayRef proxyList, CFErrorRef) +{ + SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(client); + MainThreadPACCallbackInfo info(handle, proxyList); + // If we're already on main thread (e.g. on Mac), callOnMainThreadAndWait() will be just a function call. + callOnMainThreadAndWait(pacExecutionCallbackMainThread, &info); +} + +void SocketStreamHandle::pacExecutionCallbackMainThread(void* invocation) +{ + MainThreadPACCallbackInfo* info = static_cast<MainThreadPACCallbackInfo*>(invocation); + ASSERT(info->handle->m_connectingSubstate == ExecutingPACFile); + // This time, the array won't have PAC as a first entry. + info->handle->chooseProxyFromArray(info->proxyList); + info->handle->createStreams(); + info->handle->scheduleStreams(); +} + +void SocketStreamHandle::executePACFileURL(CFURLRef pacFileURL) +{ + // CFNetwork returns an empty proxy array for WebScoket schemes, so use m_httpsURL. + CFStreamClientContext clientContext = { 0, this, 0, 0, copyPACExecutionDescription }; + m_pacRunLoopSource.adoptCF(CFNetworkExecuteProxyAutoConfigurationURL(pacFileURL, m_httpsURL.get(), pacExecutionCallback, &clientContext)); +#if PLATFORM(WIN) + CFRunLoopAddSource(loaderRunLoop(), m_pacRunLoopSource.get(), kCFRunLoopDefaultMode); +#else + CFRunLoopAddSource(CFRunLoopGetCurrent(), m_pacRunLoopSource.get(), kCFRunLoopCommonModes); +#endif + m_connectingSubstate = ExecutingPACFile; +} + +void SocketStreamHandle::removePACRunLoopSource() +{ + ASSERT(m_pacRunLoopSource); + + CFRunLoopSourceInvalidate(m_pacRunLoopSource.get()); +#if PLATFORM(WIN) + CFRunLoopRemoveSource(loaderRunLoop(), m_pacRunLoopSource.get(), kCFRunLoopDefaultMode); +#else + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), m_pacRunLoopSource.get(), kCFRunLoopCommonModes); +#endif + m_pacRunLoopSource = 0; +} + +void SocketStreamHandle::chooseProxy() +{ +#ifndef BUILDING_ON_LEOPARD + RetainPtr<CFDictionaryRef> proxyDictionary(AdoptCF, CFNetworkCopySystemProxySettings()); +#else + // We don't need proxy information often, so there is no need to set up a permanent dynamic store session. + RetainPtr<CFDictionaryRef> proxyDictionary(AdoptCF, SCDynamicStoreCopyProxies(0)); +#endif + + // SOCKS or HTTPS (AKA CONNECT) proxies are supported. + // WebSocket protocol relies on handshake being transferred unchanged, so we need a proxy that will not modify headers. + // Since HTTP proxies must add Via headers, they are highly unlikely to work. + // Many CONNECT proxies limit connectivity to port 443, so we prefer SOCKS, if configured. + + if (!proxyDictionary) { + m_connectionType = Direct; + return; + } + + // CFNetworkCopyProxiesForURL doesn't know about WebSocket schemes, so pretend to use http. + // 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. + RetainPtr<CFArrayRef> proxyArray(AdoptCF, CFNetworkCopyProxiesForURL(m_httpsURL.get(), proxyDictionary.get())); + + chooseProxyFromArray(proxyArray.get()); +} + +void SocketStreamHandle::chooseProxyFromArray(CFArrayRef proxyArray) +{ + if (!proxyArray) + m_connectionType = Direct; + + CFIndex proxyArrayCount = CFArrayGetCount(proxyArray); + + // PAC is always the first entry, if present. + if (proxyArrayCount) { + CFDictionaryRef proxyInfo = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(proxyArray, 0)); + CFTypeRef proxyType = CFDictionaryGetValue(proxyInfo, kCFProxyTypeKey); + if (proxyType && CFGetTypeID(proxyType) == CFStringGetTypeID()) { + if (CFEqual(proxyType, kCFProxyTypeAutoConfigurationURL)) { + CFTypeRef pacFileURL = CFDictionaryGetValue(proxyInfo, kCFProxyAutoConfigurationURLKey); + if (pacFileURL && CFGetTypeID(pacFileURL) == CFURLGetTypeID()) { + executePACFileURL(static_cast<CFURLRef>(pacFileURL)); + return; + } + } + } + } + + CFDictionaryRef chosenProxy = 0; + for (CFIndex i = 0; i < proxyArrayCount; ++i) { + CFDictionaryRef proxyInfo = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(proxyArray, i)); + CFTypeRef proxyType = CFDictionaryGetValue(proxyInfo, kCFProxyTypeKey); + if (proxyType && CFGetTypeID(proxyType) == CFStringGetTypeID()) { + if (CFEqual(proxyType, kCFProxyTypeSOCKS)) { + m_connectionType = SOCKSProxy; + chosenProxy = proxyInfo; + break; + } + if (CFEqual(proxyType, kCFProxyTypeHTTPS)) { + m_connectionType = CONNECTProxy; + chosenProxy = proxyInfo; + // Keep looking for proxies, as a SOCKS one is preferable. + } + } + } + + if (chosenProxy) { + ASSERT(m_connectionType != Unknown); + ASSERT(m_connectionType != Direct); + + CFTypeRef proxyHost = CFDictionaryGetValue(chosenProxy, kCFProxyHostNameKey); + CFTypeRef proxyPort = CFDictionaryGetValue(chosenProxy, kCFProxyPortNumberKey); + + if (proxyHost && CFGetTypeID(proxyHost) == CFStringGetTypeID() && proxyPort && CFGetTypeID(proxyPort) == CFNumberGetTypeID()) { + m_proxyHost = static_cast<CFStringRef>(proxyHost); + m_proxyPort = static_cast<CFNumberRef>(proxyPort); + return; + } + } + + m_connectionType = Direct; +} + +#else // BUILDING_ON_TIGER + +void SocketStreamHandle::chooseProxy() +{ + // We don't need proxy information often, so there is no need to set up a permanent dynamic store session. + RetainPtr<CFDictionaryRef> proxyDictionary(AdoptCF, SCDynamicStoreCopyProxies(0)); + + // SOCKS or HTTPS (AKA CONNECT) proxies are supported. + // WebSocket protocol relies on handshake being transferred unchanged, so we need a proxy that will not modify headers. + // Since HTTP proxies must add Via headers, they are highly unlikely to work. + // Many CONNECT proxies limit connectivity to port 443, so we prefer SOCKS, if configured. + + if (!proxyDictionary) { + m_connectionType = Direct; + return; + } + + // FIXME: check proxy bypass list and ExcludeSimpleHostnames. + // FIXME: Support PAC files. + + CFTypeRef socksEnableCF = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesSOCKSEnable); + int socksEnable; + if (socksEnableCF && CFGetTypeID(socksEnableCF) == CFNumberGetTypeID() && CFNumberGetValue(static_cast<CFNumberRef>(socksEnableCF), kCFNumberIntType, &socksEnable) && socksEnable) { + CFTypeRef proxyHost = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesSOCKSProxy); + CFTypeRef proxyPort = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesSOCKSPort); + if (proxyHost && CFGetTypeID(proxyHost) == CFStringGetTypeID() && proxyPort && CFGetTypeID(proxyPort) == CFNumberGetTypeID()) { + m_proxyHost = static_cast<CFStringRef>(proxyHost); + m_proxyPort = static_cast<CFNumberRef>(proxyPort); + m_connectionType = SOCKSProxy; + return; + } + } + + CFTypeRef httpsEnableCF = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesHTTPSEnable); + int httpsEnable; + if (httpsEnableCF && CFGetTypeID(httpsEnableCF) == CFNumberGetTypeID() && CFNumberGetValue(static_cast<CFNumberRef>(httpsEnableCF), kCFNumberIntType, &httpsEnable) && httpsEnable) { + CFTypeRef proxyHost = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesHTTPSProxy); + CFTypeRef proxyPort = CFDictionaryGetValue(proxyDictionary.get(), kSCPropNetProxiesHTTPSPort); + + if (proxyHost && CFGetTypeID(proxyHost) == CFStringGetTypeID() && proxyPort && CFGetTypeID(proxyPort) == CFNumberGetTypeID()) { + m_proxyHost = static_cast<CFStringRef>(proxyHost); + m_proxyPort = static_cast<CFNumberRef>(proxyPort); + m_connectionType = CONNECTProxy; + return; + } + } + + m_connectionType = Direct; +} +#endif // BUILDING_ON_TIGER + +void SocketStreamHandle::createStreams() +{ + if (m_connectionType == Unknown) + chooseProxy(); + + // If it's still unknown, then we're resolving a PAC file asynchronously. + if (m_connectionType == Unknown) + return; + + RetainPtr<CFStringRef> host(AdoptCF, m_url.host().createCFString()); + + // Creating streams to final destination, not to proxy. + CFReadStreamRef readStream = 0; + CFWriteStreamRef writeStream = 0; + CFStreamCreatePairWithSocketToHost(0, host.get(), m_url.port(), &readStream, &writeStream); + + m_readStream.adoptCF(readStream); + m_writeStream.adoptCF(writeStream); + + switch (m_connectionType) { + case Unknown: + ASSERT_NOT_REACHED(); + break; + case Direct: + break; + case SOCKSProxy: { + // FIXME: SOCKS5 doesn't do challenge-response, should we try to apply credentials from Keychain right away? + // But SOCKS5 credentials don't work at the time of this writing anyway, see <rdar://6776698>. + const void* proxyKeys[] = { kCFStreamPropertySOCKSProxyHost, kCFStreamPropertySOCKSProxyPort }; + const void* proxyValues[] = { m_proxyHost.get(), m_proxyPort.get() }; + RetainPtr<CFDictionaryRef> connectDictionary(AdoptCF, CFDictionaryCreate(0, proxyKeys, proxyValues, sizeof(proxyKeys) / sizeof(*proxyKeys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); + CFReadStreamSetProperty(m_readStream.get(), kCFStreamPropertySOCKSProxy, connectDictionary.get()); + break; + } + case CONNECTProxy: + wkSetCONNECTProxyForStream(m_readStream.get(), m_proxyHost.get(), m_proxyPort.get()); + break; + } + + if (shouldUseSSL()) { + const void* keys[] = { kCFStreamSSLPeerName, kCFStreamSSLLevel }; + const void* values[] = { host.get(), kCFStreamSocketSecurityLevelNegotiatedSSL }; + RetainPtr<CFDictionaryRef> settings(AdoptCF, CFDictionaryCreate(0, keys, values, sizeof(keys) / sizeof(*keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); + CFReadStreamSetProperty(m_readStream.get(), kCFStreamPropertySSLSettings, settings.get()); + CFWriteStreamSetProperty(m_writeStream.get(), kCFStreamPropertySSLSettings, settings.get()); + } +} + +static bool getStoredCONNECTProxyCredentials(const ProtectionSpace& protectionSpace, String& login, String& password) +{ + // Try system credential storage first, matching HTTP behavior (CFNetwork only asks the client for password if it couldn't find it in Keychain). + Credential storedCredential = CredentialStorage::getFromPersistentStorage(protectionSpace); + if (storedCredential.isEmpty()) + storedCredential = CredentialStorage::get(protectionSpace); + + if (storedCredential.isEmpty()) + return false; + + login = storedCredential.user(); + password = storedCredential.password(); + + return true; +} + +static ProtectionSpaceAuthenticationScheme authenticationSchemeFromAuthenticationMethod(CFStringRef method) +{ + if (CFEqual(method, kCFHTTPAuthenticationSchemeBasic)) + return ProtectionSpaceAuthenticationSchemeHTTPBasic; + if (CFEqual(method, kCFHTTPAuthenticationSchemeDigest)) + return ProtectionSpaceAuthenticationSchemeHTTPDigest; +#ifndef BUILDING_ON_TIGER + if (CFEqual(method, kCFHTTPAuthenticationSchemeNTLM)) + return ProtectionSpaceAuthenticationSchemeNTLM; + if (CFEqual(method, kCFHTTPAuthenticationSchemeNegotiate)) + return ProtectionSpaceAuthenticationSchemeNegotiate; +#endif + ASSERT_NOT_REACHED(); + return ProtectionSpaceAuthenticationSchemeDefault; +} + +void SocketStreamHandle::addCONNECTCredentials(CFHTTPMessageRef proxyResponse) +{ + RetainPtr<CFHTTPAuthenticationRef> authentication(AdoptCF, CFHTTPAuthenticationCreateFromResponse(0, proxyResponse)); + + if (!CFHTTPAuthenticationRequiresUserNameAndPassword(authentication.get())) { + // That's all we can offer... + m_client->didFail(this, SocketStreamError()); // FIXME: Provide a sensible error. + return; + } + + int port = 0; + CFNumberGetValue(m_proxyPort.get(), kCFNumberIntType, &port); + RetainPtr<CFStringRef> methodCF(AdoptCF, CFHTTPAuthenticationCopyMethod(authentication.get())); + RetainPtr<CFStringRef> realmCF(AdoptCF, CFHTTPAuthenticationCopyRealm(authentication.get())); + ProtectionSpace protectionSpace(String(m_proxyHost.get()), port, ProtectionSpaceProxyHTTPS, String(realmCF.get()), authenticationSchemeFromAuthenticationMethod(methodCF.get())); + String login; + String password; + if (!m_sentStoredCredentials && getStoredCONNECTProxyCredentials(protectionSpace, login, password)) { + // Try to apply stored credentials, if we haven't tried those already. + RetainPtr<CFStringRef> loginCF(AdoptCF, login.createCFString()); + RetainPtr<CFStringRef> passwordCF(AdoptCF, password.createCFString()); + // Creating a temporary request to make CFNetwork apply credentials to it. Unfortunately, this cannot work with NTLM authentication. + RetainPtr<CFHTTPMessageRef> dummyRequest(AdoptCF, CFHTTPMessageCreateRequest(0, CFSTR("GET"), m_httpsURL.get(), kCFHTTPVersion1_1)); + + Boolean appliedCredentials = CFHTTPMessageApplyCredentials(dummyRequest.get(), authentication.get(), loginCF.get(), passwordCF.get(), 0); + ASSERT_UNUSED(appliedCredentials, appliedCredentials); + + RetainPtr<CFStringRef> proxyAuthorizationString(AdoptCF, CFHTTPMessageCopyHeaderFieldValue(dummyRequest.get(), CFSTR("Proxy-Authorization"))); + + if (!proxyAuthorizationString) { + // Fails e.g. for NTLM auth. + m_client->didFail(this, SocketStreamError()); // FIXME: Provide a sensible error. + return; + } + + // Setting the authorization results in a new connection attempt. + wkSetCONNECTProxyAuthorizationForStream(m_readStream.get(), proxyAuthorizationString.get()); + m_sentStoredCredentials = true; + return; + } + + // FIXME: Ask the client if credentials could not be found. + + m_client->didFail(this, SocketStreamError()); // FIXME: Provide a sensible error. +} + +CFStringRef SocketStreamHandle::copyCFStreamDescription(void* info) +{ + SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(info); + return ("WebKit socket stream, " + handle->m_url.string()).createCFString(); +} + +struct MainThreadEventCallbackInfo { + MainThreadEventCallbackInfo(CFStreamEventType type, SocketStreamHandle* handle) : type(type), handle(handle) { } + CFStreamEventType type; + SocketStreamHandle* handle; +}; + +void SocketStreamHandle::readStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void* clientCallBackInfo) +{ + SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(clientCallBackInfo); + ASSERT_UNUSED(stream, stream == handle->m_readStream.get()); +#if PLATFORM(WIN) + MainThreadEventCallbackInfo info(type, handle); + callOnMainThreadAndWait(readStreamCallbackMainThread, &info); +#else + ASSERT(isMainThread()); + handle->readStreamCallback(type); +#endif +} + +void SocketStreamHandle::writeStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void* clientCallBackInfo) +{ + SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(clientCallBackInfo); + ASSERT_UNUSED(stream, stream == handle->m_writeStream.get()); +#if PLATFORM(WIN) + MainThreadEventCallbackInfo info(type, handle); + callOnMainThreadAndWait(writeStreamCallbackMainThread, &info); +#else + ASSERT(isMainThread()); + handle->writeStreamCallback(type); +#endif +} + +#if PLATFORM(WIN) +void SocketStreamHandle::readStreamCallbackMainThread(void* invocation) +{ + MainThreadEventCallbackInfo* info = static_cast<MainThreadEventCallbackInfo*>(invocation); + info->handle->readStreamCallback(info->type); +} + +void SocketStreamHandle::writeStreamCallbackMainThread(void* invocation) +{ + MainThreadEventCallbackInfo* info = static_cast<MainThreadEventCallbackInfo*>(invocation); + info->handle->writeStreamCallback(info->type); +} +#endif // PLATFORM(WIN) + +void SocketStreamHandle::readStreamCallback(CFStreamEventType type) +{ + switch(type) { + case kCFStreamEventNone: + break; + case kCFStreamEventOpenCompleted: + break; + case kCFStreamEventHasBytesAvailable: { + if (m_connectingSubstate == WaitingForConnect) { + if (m_connectionType == CONNECTProxy) { + RetainPtr<CFHTTPMessageRef> proxyResponse(AdoptCF, wkCopyCONNECTProxyResponse(m_readStream.get(), m_httpsURL.get())); + if (proxyResponse && (407 == CFHTTPMessageGetResponseStatusCode(proxyResponse.get()))) { + addCONNECTCredentials(proxyResponse.get()); + return; + } + } + } else if (m_connectingSubstate == WaitingForCredentials) + break; + + if (m_connectingSubstate == WaitingForConnect) { + m_connectingSubstate = Connected; + m_state = Open; + + RefPtr<SocketStreamHandle> protect(this); // The client can close the handle, potentially removing the last reference. + m_client->didOpen(this); + if (m_state == Closed) + break; + // Fall through. + } else if (m_state == Closed) + break; + + ASSERT(m_state == Open); + ASSERT(m_connectingSubstate == Connected); + + CFIndex length; + UInt8 localBuffer[1024]; // Used if CFReadStreamGetBuffer couldn't return anything. + const UInt8* ptr = CFReadStreamGetBuffer(m_readStream.get(), 0, &length); + if (!ptr) { + length = CFReadStreamRead(m_readStream.get(), localBuffer, sizeof(localBuffer)); + ptr = localBuffer; + } + + m_client->didReceiveData(this, reinterpret_cast<const char*>(ptr), length); + + break; + } + case kCFStreamEventCanAcceptBytes: + ASSERT_NOT_REACHED(); + break; + case kCFStreamEventErrorOccurred: { + CFStreamError error = CFReadStreamGetError(m_readStream.get()); + m_client->didFail(this, SocketStreamError(error.error)); // FIXME: Provide a sensible error. + break; + } + case kCFStreamEventEndEncountered: + platformClose(); + break; + } +} + +void SocketStreamHandle::writeStreamCallback(CFStreamEventType type) +{ + switch(type) { + case kCFStreamEventNone: + break; + case kCFStreamEventOpenCompleted: + break; + case kCFStreamEventHasBytesAvailable: + ASSERT_NOT_REACHED(); + break; + case kCFStreamEventCanAcceptBytes: { + // Possibly, a spurious event from CONNECT handshake. + if (!CFWriteStreamCanAcceptBytes(m_writeStream.get())) + return; + + if (m_connectingSubstate == WaitingForCredentials) + break; + + if (m_connectingSubstate == WaitingForConnect) { + m_connectingSubstate = Connected; + m_state = Open; + + RefPtr<SocketStreamHandle> protect(this); // The client can close the handle, potentially removing the last reference. + m_client->didOpen(this); + break; + } + + ASSERT(m_state = Open); + ASSERT(m_connectingSubstate == Connected); + + sendPendingData(); + break; + } + case kCFStreamEventErrorOccurred: { + CFStreamError error = CFWriteStreamGetError(m_writeStream.get()); + m_client->didFail(this, SocketStreamError(error.error)); // FIXME: Provide a sensible error. + break; + } + case kCFStreamEventEndEncountered: + // FIXME: Currently, we handle closing in read callback, but these can come independently (e.g. a server can stop listening, but keep sending data). + break; + } } SocketStreamHandle::~SocketStreamHandle() { - LOG(Network, "SocketStreamHandle %p delete", this); - setClient(0); - notImplemented(); + LOG(Network, "SocketStreamHandle %p dtor", this); + +#ifndef BUILDING_ON_TIGER + ASSERT(!m_pacRunLoopSource); +#endif } -int SocketStreamHandle::platformSend(const char*, int) +int SocketStreamHandle::platformSend(const char* data, int length) { - LOG(Network, "SocketStreamHandle %p platformSend", this); - notImplemented(); - return 0; + if (!CFWriteStreamCanAcceptBytes(m_writeStream.get())) + return 0; + + return CFWriteStreamWrite(m_writeStream.get(), reinterpret_cast<const UInt8*>(data), length); } void SocketStreamHandle::platformClose() { LOG(Network, "SocketStreamHandle %p platformClose", this); - notImplemented(); -} -void SocketStreamHandle::didReceiveAuthenticationChallenge(const AuthenticationChallenge&) -{ - notImplemented(); +#ifndef BUILDING_ON_TIGER + if (m_pacRunLoopSource) + removePACRunLoopSource(); +#endif + + ASSERT(!m_readStream == !m_writeStream); + if (!m_readStream) + return; + + CFReadStreamUnscheduleFromRunLoop(m_readStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes); + CFWriteStreamUnscheduleFromRunLoop(m_writeStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes); + + CFReadStreamClose(m_readStream.get()); + CFWriteStreamClose(m_writeStream.get()); + + m_readStream = 0; + m_writeStream = 0; + + m_client->didClose(this); } void SocketStreamHandle::receivedCredential(const AuthenticationChallenge&, const Credential&) { - notImplemented(); } void SocketStreamHandle::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge&) { - notImplemented(); } void SocketStreamHandle::receivedCancellation(const AuthenticationChallenge&) { - notImplemented(); } } // namespace WebCore |