SocketStreamHandleCFNet.cpp [plain text]
#include "config.h"
#include "SocketStreamHandle.h"
#include "Credential.h"
#include "CredentialStorage.h"
#include "Logging.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);
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) return;
scheduleStreams();
}
void SocketStreamHandle::scheduleStreams()
{
ASSERT(m_readStream);
ASSERT(m_writeStream);
CFStreamClientContext clientContext = { 0, this, 0, 0, copyCFStreamDescription };
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);
callOnMainThreadAndWait(pacExecutionCallbackMainThread, &info);
}
void SocketStreamHandle::pacExecutionCallbackMainThread(void* invocation)
{
MainThreadPACCallbackInfo* info = static_cast<MainThreadPACCallbackInfo*>(invocation);
ASSERT(info->handle->m_connectingSubstate == ExecutingPACFile);
info->handle->chooseProxyFromArray(info->proxyList);
info->handle->createStreams();
info->handle->scheduleStreams();
}
void SocketStreamHandle::executePACFileURL(CFURLRef pacFileURL)
{
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
RetainPtr<CFDictionaryRef> proxyDictionary(AdoptCF, SCDynamicStoreCopyProxies(0));
#endif
if (!proxyDictionary) {
m_connectionType = Direct;
return;
}
RetainPtr<CFArrayRef> proxyArray(AdoptCF, CFNetworkCopyProxiesForURL(m_httpsURL.get(), proxyDictionary.get()));
chooseProxyFromArray(proxyArray.get());
}
void SocketStreamHandle::chooseProxyFromArray(CFArrayRef proxyArray)
{
if (!proxyArray) {
m_connectionType = Direct;
return;
}
CFIndex proxyArrayCount = CFArrayGetCount(proxyArray);
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;
}
}
}
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()
{
RetainPtr<CFDictionaryRef> proxyDictionary(AdoptCF, SCDynamicStoreCopyProxies(0));
if (!proxyDictionary) {
m_connectionType = Direct;
return;
}
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 (m_connectionType == Unknown)
return;
RetainPtr<CFStringRef> host(AdoptCF, m_url.host().createCFString());
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: {
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)
{
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 ProtectionSpaceAuthenticationSchemeUnknown;
}
void SocketStreamHandle::addCONNECTCredentials(CFHTTPMessageRef proxyResponse)
{
RetainPtr<CFHTTPAuthenticationRef> authentication(AdoptCF, CFHTTPAuthenticationCreateFromResponse(0, proxyResponse));
if (!CFHTTPAuthenticationRequiresUserNameAndPassword(authentication.get())) {
m_client->didFail(this, SocketStreamError()); 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)) {
RetainPtr<CFStringRef> loginCF(AdoptCF, login.createCFString());
RetainPtr<CFStringRef> passwordCF(AdoptCF, password.createCFString());
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) {
m_client->didFail(this, SocketStreamError()); return;
}
wkSetCONNECTProxyAuthorizationForStream(m_readStream.get(), proxyAuthorizationString.get());
m_sentStoredCredentials = true;
return;
}
m_client->didFail(this, SocketStreamError()); }
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); m_client->didOpen(this);
if (m_state == Closed)
break;
} else if (m_state == Closed)
break;
ASSERT(m_state == Open);
ASSERT(m_connectingSubstate == Connected);
CFIndex length;
UInt8 localBuffer[1024]; 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)); 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: {
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); 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)); break;
}
case kCFStreamEventEndEncountered:
break;
}
}
SocketStreamHandle::~SocketStreamHandle()
{
LOG(Network, "SocketStreamHandle %p dtor", this);
#ifndef BUILDING_ON_TIGER
ASSERT(!m_pacRunLoopSource);
#endif
}
int SocketStreamHandle::platformSend(const char* data, int length)
{
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);
#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&)
{
}
void SocketStreamHandle::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge&)
{
}
void SocketStreamHandle::receivedCancellation(const AuthenticationChallenge&)
{
}
}