• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
60 namespace WebCore {
61 
SocketStreamHandle(const KURL & url,SocketStreamHandleClient * client)62 SocketStreamHandle::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 
scheduleStreams()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
copyPACExecutionDescription(void *)113 CFStringRef SocketStreamHandle::copyPACExecutionDescription(void*)
114 {
115     return CFSTR("WebSocket proxy PAC file execution");
116 }
117 
118 struct MainThreadPACCallbackInfo {
MainThreadPACCallbackInfoWebCore::MainThreadPACCallbackInfo119     MainThreadPACCallbackInfo(SocketStreamHandle* handle, CFArrayRef proxyList) : handle(handle), proxyList(proxyList) { }
120     RefPtr<SocketStreamHandle> handle;
121     CFArrayRef proxyList;
122 };
123 
pacExecutionCallback(void * client,CFArrayRef proxyList,CFErrorRef)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 
pacExecutionCallbackMainThread(void * invocation)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 
executePACFileURL(CFURLRef pacFileURL)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 
removePACRunLoopSource()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 
chooseProxy()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 
chooseProxyFromArray(CFArrayRef proxyArray)194 void 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 
chooseProxy()255 void 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 
createStreams()304 void 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 
getStoredCONNECTProxyCredentials(const ProtectionSpace & protectionSpace,String & login,String & password)352 static 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 
authenticationSchemeFromAuthenticationMethod(CFStringRef method)368 static 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 
addCONNECTCredentials(CFHTTPMessageRef proxyResponse)384 void 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 
copyCFStreamDescription(void * info)430 CFStringRef SocketStreamHandle::copyCFStreamDescription(void* info)
431 {
432     SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(info);
433     return ("WebKit socket stream, " + handle->m_url.string()).createCFString();
434 }
435 
436 struct MainThreadEventCallbackInfo {
MainThreadEventCallbackInfoWebCore::MainThreadEventCallbackInfo437     MainThreadEventCallbackInfo(CFStreamEventType type, SocketStreamHandle* handle) : type(type), handle(handle) { }
438     CFStreamEventType type;
439     RefPtr<SocketStreamHandle> handle;
440 };
441 
readStreamCallback(CFReadStreamRef stream,CFStreamEventType type,void * clientCallBackInfo)442 void 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 
writeStreamCallback(CFWriteStreamRef stream,CFStreamEventType type,void * clientCallBackInfo)455 void 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)
readStreamCallbackMainThread(void * invocation)469 void SocketStreamHandle::readStreamCallbackMainThread(void* invocation)
470 {
471     MainThreadEventCallbackInfo* info = static_cast<MainThreadEventCallbackInfo*>(invocation);
472     info->handle->readStreamCallback(info->type);
473 }
474 
writeStreamCallbackMainThread(void * invocation)475 void SocketStreamHandle::writeStreamCallbackMainThread(void* invocation)
476 {
477     MainThreadEventCallbackInfo* info = static_cast<MainThreadEventCallbackInfo*>(invocation);
478     info->handle->writeStreamCallback(info->type);
479 }
480 #endif // PLATFORM(WIN)
481 
readStreamCallback(CFStreamEventType type)482 void 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 
writeStreamCallback(CFStreamEventType type)547 void 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
reportErrorToClient(CFErrorRef error)597 void 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 
~SocketStreamHandle()619 SocketStreamHandle::~SocketStreamHandle()
620 {
621     LOG(Network, "SocketStreamHandle %p dtor", this);
622 
623 #ifndef BUILDING_ON_TIGER
624     ASSERT(!m_pacRunLoopSource);
625 #endif
626 }
627 
platformSend(const char * data,int length)628 int 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 
platformClose()636 void 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 
receivedCredential(const AuthenticationChallenge &,const Credential &)661 void SocketStreamHandle::receivedCredential(const AuthenticationChallenge&, const Credential&)
662 {
663 }
664 
receivedRequestToContinueWithoutCredential(const AuthenticationChallenge &)665 void SocketStreamHandle::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge&)
666 {
667 }
668 
receivedCancellation(const AuthenticationChallenge &)669 void SocketStreamHandle::receivedCancellation(const AuthenticationChallenge&)
670 {
671 }
672 
port() const673 unsigned 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