Move NetworkStorageSession ownership to NetworkProcess
[WebKit-https.git] / Source / WebCore / platform / network / cf / SocketStreamHandleImplCFNet.cpp
1 /*
2  * Copyright (C) 2009-2016 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 "SocketStreamHandleImpl.h"
34
35 #include "Credential.h"
36 #include "CredentialStorage.h"
37 #include "DeprecatedGlobalSettings.h"
38 #include "Logging.h"
39 #include "NetworkStorageSession.h"
40 #include "ProtectionSpace.h"
41 #include "SocketStreamError.h"
42 #include "SocketStreamHandleClient.h"
43 #include "StorageSessionProvider.h"
44 #include <CFNetwork/CFNetwork.h>
45 #include <wtf/Condition.h>
46 #include <wtf/Lock.h>
47 #include <wtf/MainThread.h>
48 #include <wtf/SoftLinking.h>
49 #include <wtf/cf/TypeCastsCF.h>
50 #include <wtf/text/WTFString.h>
51
52 #if PLATFORM(WIN)
53 #include "LoaderRunLoopCF.h"
54 #include <pal/spi/cf/CFNetworkSPI.h>
55 #endif
56
57 #if PLATFORM(IOS_FAMILY)
58 #include "WebCoreThreadInternal.h"
59 #endif
60
61 #if PLATFORM(IOS_FAMILY) || PLATFORM(MAC)
62 extern "C" const CFStringRef kCFStreamPropertySourceApplication;
63 extern "C" const CFStringRef _kCFStreamSocketSetNoDelay;
64 #endif
65
66 #if PLATFORM(COCOA)
67 #import <pal/spi/cf/CFNetworkSPI.h>
68 #endif
69
70 #if PLATFORM(WIN)
71 SOFT_LINK_LIBRARY(CFNetwork);
72 SOFT_LINK_OPTIONAL(CFNetwork, _CFHTTPMessageSetResponseProxyURL, void, __cdecl, (CFHTTPMessageRef, CFURLRef));
73 #endif
74
75 WTF_DECLARE_CF_TYPE_TRAIT(CFHTTPMessage);
76
77 namespace WebCore {
78
79 static inline CFRunLoopRef callbacksRunLoop()
80 {
81 #if PLATFORM(WIN)
82     return loaderRunLoop();
83 #elif PLATFORM(IOS_FAMILY)
84     return WebThreadRunLoop();
85 #else
86     return CFRunLoopGetMain();
87 #endif
88 }
89
90 static inline auto callbacksRunLoopMode()
91 {
92 #if PLATFORM(WIN)
93     return kCFRunLoopDefaultMode;
94 #else
95     return kCFRunLoopCommonModes;
96 #endif
97 }
98
99 SocketStreamHandleImpl::SocketStreamHandleImpl(const URL& url, SocketStreamHandleClient& client, PAL::SessionID sessionID, const String& credentialPartition, SourceApplicationAuditToken&& auditData, const StorageSessionProvider* provider)
100     : SocketStreamHandle(url, client)
101     , m_connectingSubstate(New)
102     , m_connectionType(Unknown)
103     , m_sentStoredCredentials(false)
104     , m_sessionID(sessionID)
105     , m_credentialPartition(credentialPartition)
106     , m_auditData(WTFMove(auditData))
107     , m_storageSessionProvider(provider)
108 {
109     LOG(Network, "SocketStreamHandle %p new client %p", this, &m_client);
110
111     ASSERT(url.protocolIs("ws") || url.protocolIs("wss"));
112
113     URL httpsURL(URL(), "https://" + m_url.host());
114     m_httpsURL = httpsURL.createCFURL();
115
116 #if PLATFORM(COCOA)
117     // Don't check for HSTS violation for ephemeral sessions since
118     // HSTS state should not transfer between regular and private browsing.
119     if (url.protocolIs("ws")
120         && !sessionID.isEphemeral()
121         && _CFNetworkIsKnownHSTSHostWithSession(m_httpsURL.get(), nullptr)) {
122         // Call this asynchronously because the socket stream is not fully constructed at this point.
123         callOnMainThread([this, protectedThis = makeRef(*this)] {
124             m_client.didFailSocketStream(*this, SocketStreamError(0, m_url.string(), "WebSocket connection failed because it violates HTTP Strict Transport Security."));
125         });
126         return;
127     }
128 #endif
129
130     createStreams();
131     ASSERT(!m_readStream == !m_writeStream);
132     if (!m_readStream) // Doing asynchronous PAC file processing, streams will be created later.
133         return;
134
135     scheduleStreams();
136 }
137
138 void SocketStreamHandleImpl::scheduleStreams()
139 {
140     ASSERT(m_readStream);
141     ASSERT(m_writeStream);
142
143     CFStreamClientContext clientContext = { 0, this, retainSocketStreamHandle, releaseSocketStreamHandle, copyCFStreamDescription };
144     // FIXME: Pass specific events we're interested in instead of -1.
145     CFReadStreamSetClient(m_readStream.get(), static_cast<CFOptionFlags>(-1), readStreamCallback, &clientContext);
146     CFWriteStreamSetClient(m_writeStream.get(), static_cast<CFOptionFlags>(-1), writeStreamCallback, &clientContext);
147
148     CFReadStreamScheduleWithRunLoop(m_readStream.get(), callbacksRunLoop(), callbacksRunLoopMode());
149     CFWriteStreamScheduleWithRunLoop(m_writeStream.get(), callbacksRunLoop(), callbacksRunLoopMode());
150
151     CFReadStreamOpen(m_readStream.get());
152     CFWriteStreamOpen(m_writeStream.get());
153
154     if (m_pacRunLoopSource)
155         removePACRunLoopSource();
156
157     m_connectingSubstate = WaitingForConnect;
158 }
159
160 void* SocketStreamHandleImpl::retainSocketStreamHandle(void* info)
161 {
162     SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(info);
163     handle->ref();
164     return handle;
165 }
166
167 void SocketStreamHandleImpl::releaseSocketStreamHandle(void* info)
168 {
169     SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(info);
170     handle->deref();
171 }
172
173 CFStringRef SocketStreamHandleImpl::copyPACExecutionDescription(void*)
174 {
175     return CFSTR("WebSocket proxy PAC file execution");
176 }
177
178 struct MainThreadPACCallbackInfo {
179     MainThreadPACCallbackInfo(SocketStreamHandle* handle, CFArrayRef proxyList)
180         : handle(handle), proxyList(proxyList)
181     { }
182     RefPtr<SocketStreamHandle> handle;
183     CFArrayRef proxyList;
184 };
185
186 void SocketStreamHandleImpl::pacExecutionCallback(void* client, CFArrayRef proxyList, CFErrorRef)
187 {
188     SocketStreamHandleImpl* handle = static_cast<SocketStreamHandleImpl*>(client);
189
190     RefPtr<SocketStreamHandle> protector(handle);
191     callOnMainThreadAndWait([&] {
192         ASSERT(handle->m_connectingSubstate == ExecutingPACFile);
193         // This time, the array won't have PAC as a first entry.
194         if (handle->m_state != Connecting)
195             return;
196         handle->chooseProxyFromArray(proxyList);
197         handle->createStreams();
198         handle->scheduleStreams();
199     });
200 }
201
202 void SocketStreamHandleImpl::executePACFileURL(CFURLRef pacFileURL)
203 {
204     // CFNetwork returns an empty proxy array for WebSocket schemes, so use m_httpsURL.
205     CFStreamClientContext clientContext = { 0, this, retainSocketStreamHandle, releaseSocketStreamHandle, copyPACExecutionDescription };
206     m_pacRunLoopSource = adoptCF(CFNetworkExecuteProxyAutoConfigurationURL(pacFileURL, m_httpsURL.get(), pacExecutionCallback, &clientContext));
207     CFRunLoopAddSource(callbacksRunLoop(), m_pacRunLoopSource.get(), callbacksRunLoopMode());
208     m_connectingSubstate = ExecutingPACFile;
209 }
210
211 void SocketStreamHandleImpl::removePACRunLoopSource()
212 {
213     ASSERT(m_pacRunLoopSource);
214
215     CFRunLoopSourceInvalidate(m_pacRunLoopSource.get());
216     CFRunLoopRemoveSource(callbacksRunLoop(), m_pacRunLoopSource.get(), callbacksRunLoopMode());
217     m_pacRunLoopSource = 0;
218 }
219
220 void SocketStreamHandleImpl::chooseProxy()
221 {
222     RetainPtr<CFDictionaryRef> proxyDictionary = adoptCF(CFNetworkCopySystemProxySettings());
223
224     // SOCKS or HTTPS (AKA CONNECT) proxies are supported.
225     // WebSocket protocol relies on handshake being transferred unchanged, so we need a proxy that will not modify headers.
226     // Since HTTP proxies must add Via headers, they are highly unlikely to work.
227     // Many CONNECT proxies limit connectivity to port 443, so we prefer SOCKS, if configured.
228
229     if (!proxyDictionary) {
230         m_connectionType = Direct;
231         return;
232     }
233
234     // CFNetworkCopyProxiesForURL doesn't know about WebSocket schemes, so pretend to use http.
235     // 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.
236     RetainPtr<CFArrayRef> proxyArray = adoptCF(CFNetworkCopyProxiesForURL(m_httpsURL.get(), proxyDictionary.get()));
237
238     chooseProxyFromArray(proxyArray.get());
239 }
240
241 void SocketStreamHandleImpl::chooseProxyFromArray(CFArrayRef proxyArray)
242 {
243     if (!proxyArray) {
244         m_connectionType = Direct;
245         return;
246     }
247
248     CFIndex proxyArrayCount = CFArrayGetCount(proxyArray);
249
250     // PAC is always the first entry, if present.
251     if (proxyArrayCount) {
252         CFDictionaryRef proxyInfo = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(proxyArray, 0));
253         CFTypeRef proxyType = CFDictionaryGetValue(proxyInfo, kCFProxyTypeKey);
254         if (proxyType && CFGetTypeID(proxyType) == CFStringGetTypeID()) {
255             if (CFEqual(proxyType, kCFProxyTypeAutoConfigurationURL)) {
256                 CFTypeRef pacFileURL = CFDictionaryGetValue(proxyInfo, kCFProxyAutoConfigurationURLKey);
257                 if (pacFileURL && CFGetTypeID(pacFileURL) == CFURLGetTypeID()) {
258                     executePACFileURL(static_cast<CFURLRef>(pacFileURL));
259                     return;
260                 }
261             }
262         }
263     }
264
265     CFDictionaryRef chosenProxy = 0;
266     for (CFIndex i = 0; i < proxyArrayCount; ++i) {
267         CFDictionaryRef proxyInfo = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(proxyArray, i));
268         CFTypeRef proxyType = CFDictionaryGetValue(proxyInfo, kCFProxyTypeKey);
269         if (proxyType && CFGetTypeID(proxyType) == CFStringGetTypeID()) {
270             if (CFEqual(proxyType, kCFProxyTypeSOCKS)) {
271                 m_connectionType = SOCKSProxy;
272                 chosenProxy = proxyInfo;
273                 break;
274             }
275             if (CFEqual(proxyType, kCFProxyTypeHTTPS)) {
276                 m_connectionType = CONNECTProxy;
277                 chosenProxy = proxyInfo;
278                 // Keep looking for proxies, as a SOCKS one is preferable.
279             }
280         }
281     }
282
283     if (chosenProxy) {
284         ASSERT(m_connectionType != Unknown);
285         ASSERT(m_connectionType != Direct);
286
287         CFTypeRef proxyHost = CFDictionaryGetValue(chosenProxy, kCFProxyHostNameKey);
288         CFTypeRef proxyPort = CFDictionaryGetValue(chosenProxy, kCFProxyPortNumberKey);
289
290         if (proxyHost && CFGetTypeID(proxyHost) == CFStringGetTypeID() && proxyPort && CFGetTypeID(proxyPort) == CFNumberGetTypeID()) {
291             m_proxyHost = static_cast<CFStringRef>(proxyHost);
292             m_proxyPort = static_cast<CFNumberRef>(proxyPort);
293             return;
294         }
295     }
296
297     m_connectionType = Direct;
298 }
299
300 static void setCONNECTProxyForStream(CFReadStreamRef stream, CFStringRef proxyHost, CFNumberRef proxyPort)
301 {
302     const void* proxyKeys[] = { kCFStreamPropertyCONNECTProxyHost, kCFStreamPropertyCONNECTProxyPort };
303     const void* proxyValues[] = { proxyHost, proxyPort };
304     auto connectDictionary = adoptCF(CFDictionaryCreate(kCFAllocatorDefault, proxyKeys, proxyValues, sizeof(proxyKeys) / sizeof(*proxyKeys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
305     CFReadStreamSetProperty(stream, kCFStreamPropertyCONNECTProxy, connectDictionary.get());
306 }
307
308 void SocketStreamHandleImpl::createStreams()
309 {
310     if (m_connectionType == Unknown)
311         chooseProxy();
312
313     // If it's still unknown, then we're resolving a PAC file asynchronously.
314     if (m_connectionType == Unknown)
315         return;
316
317     RetainPtr<CFStringRef> host = m_url.host().createCFString();
318
319     // Creating streams to final destination, not to proxy.
320     CFReadStreamRef readStream = 0;
321     CFWriteStreamRef writeStream = 0;
322     CFStreamCreatePairWithSocketToHost(0, host.get(), port(), &readStream, &writeStream);
323 #if PLATFORM(COCOA)
324     // <rdar://problem/12855587> _kCFStreamSocketSetNoDelay is not exported on Windows
325     CFWriteStreamSetProperty(writeStream, _kCFStreamSocketSetNoDelay, kCFBooleanTrue);
326     if (m_auditData.sourceApplicationAuditData && m_auditData.sourceApplicationAuditData.get()) {
327         CFReadStreamSetProperty(readStream, kCFStreamPropertySourceApplication, m_auditData.sourceApplicationAuditData.get());
328         CFWriteStreamSetProperty(writeStream, kCFStreamPropertySourceApplication, m_auditData.sourceApplicationAuditData.get());
329     }
330     
331 #endif
332
333     m_readStream = adoptCF(readStream);
334     m_writeStream = adoptCF(writeStream);
335
336     switch (m_connectionType) {
337     case Unknown:
338         ASSERT_NOT_REACHED();
339         break;
340     case Direct:
341         break;
342     case SOCKSProxy: {
343         // FIXME: SOCKS5 doesn't do challenge-response, should we try to apply credentials from Keychain right away?
344         // But SOCKS5 credentials don't work at the time of this writing anyway, see <rdar://6776698>.
345         const void* proxyKeys[] = { kCFStreamPropertySOCKSProxyHost, kCFStreamPropertySOCKSProxyPort };
346         const void* proxyValues[] = { m_proxyHost.get(), m_proxyPort.get() };
347         RetainPtr<CFDictionaryRef> connectDictionary = adoptCF(CFDictionaryCreate(0, proxyKeys, proxyValues, WTF_ARRAY_LENGTH(proxyKeys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
348         CFReadStreamSetProperty(m_readStream.get(), kCFStreamPropertySOCKSProxy, connectDictionary.get());
349         break;
350         }
351     case CONNECTProxy:
352         setCONNECTProxyForStream(m_readStream.get(), m_proxyHost.get(), m_proxyPort.get());
353         break;
354     }
355
356     if (shouldUseSSL()) {
357         CFBooleanRef validateCertificateChain = DeprecatedGlobalSettings::allowsAnySSLCertificate() ? kCFBooleanFalse : kCFBooleanTrue;
358         const void* keys[] = { kCFStreamSSLPeerName, kCFStreamSSLLevel, kCFStreamSSLValidatesCertificateChain };
359         const void* values[] = { host.get(), kCFStreamSocketSecurityLevelNegotiatedSSL, validateCertificateChain };
360         RetainPtr<CFDictionaryRef> settings = adoptCF(CFDictionaryCreate(0, keys, values, WTF_ARRAY_LENGTH(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
361         CFReadStreamSetProperty(m_readStream.get(), kCFStreamPropertySSLSettings, settings.get());
362         CFWriteStreamSetProperty(m_writeStream.get(), kCFStreamPropertySSLSettings, settings.get());
363     }
364 }
365
366 bool SocketStreamHandleImpl::getStoredCONNECTProxyCredentials(const ProtectionSpace& protectionSpace, String& login, String& password)
367 {
368     // FIXME (<rdar://problem/10416495>): Proxy credentials should be retrieved from AuthBrokerAgent.
369
370     // Try system credential storage first, matching HTTP behavior (CFNetwork only asks the client for password if it couldn't find it in Keychain).
371     Credential storedCredential;
372     if (auto* storageSession = m_storageSessionProvider ? m_storageSessionProvider->storageSession() : nullptr) {
373         storedCredential = CredentialStorage::getFromPersistentStorage(protectionSpace);
374         if (storedCredential.isEmpty())
375             storedCredential = storageSession->credentialStorage().get(m_credentialPartition, protectionSpace);
376     }
377
378     if (storedCredential.isEmpty())
379         return false;
380
381     login = storedCredential.user();
382     password = storedCredential.password();
383
384     return true;
385 }
386
387 static ProtectionSpaceAuthenticationScheme authenticationSchemeFromAuthenticationMethod(CFStringRef method)
388 {
389     if (CFEqual(method, kCFHTTPAuthenticationSchemeBasic))
390         return ProtectionSpaceAuthenticationSchemeHTTPBasic;
391     if (CFEqual(method, kCFHTTPAuthenticationSchemeDigest))
392         return ProtectionSpaceAuthenticationSchemeHTTPDigest;
393     if (CFEqual(method, kCFHTTPAuthenticationSchemeNTLM))
394         return ProtectionSpaceAuthenticationSchemeNTLM;
395     if (CFEqual(method, kCFHTTPAuthenticationSchemeNegotiate))
396         return ProtectionSpaceAuthenticationSchemeNegotiate;
397     ASSERT_NOT_REACHED();
398     return ProtectionSpaceAuthenticationSchemeUnknown;
399 }
400     
401 static void setCONNECTProxyAuthorizationForStream(CFReadStreamRef stream, CFStringRef proxyAuthorizationString)
402 {
403     auto originalCONNECTDictionary = adoptCF((CFDictionaryRef)CFReadStreamCopyProperty(stream, kCFStreamPropertyCONNECTProxy));
404     auto connectDictionary = adoptCF(CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, originalCONNECTDictionary.get()));
405
406     const void* headerFieldNames[] = { CFSTR("Proxy-Authorization") };
407     const void* headerFieldValues[] = { proxyAuthorizationString };
408     auto additionalHeaderFields = adoptCF(CFDictionaryCreate(kCFAllocatorDefault, headerFieldNames, headerFieldValues, sizeof(headerFieldNames) / sizeof(*headerFieldValues), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
409
410     CFDictionarySetValue(connectDictionary.get(), kCFStreamPropertyCONNECTAdditionalHeaders, additionalHeaderFields.get());
411     CFReadStreamSetProperty(stream, kCFStreamPropertyCONNECTProxy, connectDictionary.get());
412 }
413
414 void SocketStreamHandleImpl::addCONNECTCredentials(CFHTTPMessageRef proxyResponse)
415 {
416     RetainPtr<CFHTTPAuthenticationRef> authentication = adoptCF(CFHTTPAuthenticationCreateFromResponse(0, proxyResponse));
417
418     if (!CFHTTPAuthenticationRequiresUserNameAndPassword(authentication.get())) {
419         // That's all we can offer...
420         m_client.didFailSocketStream(*this, SocketStreamError(0, m_url.string(), "Proxy authentication scheme is not supported for WebSockets"));
421         return;
422     }
423
424     int port = 0;
425     CFNumberGetValue(m_proxyPort.get(), kCFNumberIntType, &port);
426     RetainPtr<CFStringRef> methodCF = adoptCF(CFHTTPAuthenticationCopyMethod(authentication.get()));
427     RetainPtr<CFStringRef> realmCF = adoptCF(CFHTTPAuthenticationCopyRealm(authentication.get()));
428
429     if (!methodCF || !realmCF) {
430         // This shouldn't happen, but on some OS versions we get incomplete authentication data, see <rdar://problem/10416316>.
431         m_client.didFailSocketStream(*this, SocketStreamError(0, m_url.string(), "WebSocket proxy authentication couldn't be handled"));
432         return;
433     }
434
435     ProtectionSpace protectionSpace(String(m_proxyHost.get()), port, ProtectionSpaceProxyHTTPS, String(realmCF.get()), authenticationSchemeFromAuthenticationMethod(methodCF.get()));
436     String login;
437     String password;
438     if (!m_sentStoredCredentials && getStoredCONNECTProxyCredentials(protectionSpace, login, password)) {
439         // Try to apply stored credentials, if we haven't tried those already.
440         // Create a temporary request to make CFNetwork apply credentials to it. Unfortunately, this cannot work with NTLM authentication.
441         RetainPtr<CFHTTPMessageRef> dummyRequest = adoptCF(CFHTTPMessageCreateRequest(0, CFSTR("GET"), m_httpsURL.get(), kCFHTTPVersion1_1));
442
443         Boolean appliedCredentials = CFHTTPMessageApplyCredentials(dummyRequest.get(), authentication.get(), login.createCFString().get(), password.createCFString().get(), 0);
444         ASSERT_UNUSED(appliedCredentials, appliedCredentials);
445
446         RetainPtr<CFStringRef> proxyAuthorizationString = adoptCF(CFHTTPMessageCopyHeaderFieldValue(dummyRequest.get(), CFSTR("Proxy-Authorization")));
447
448         if (!proxyAuthorizationString) {
449             // Fails e.g. for NTLM auth.
450             m_client.didFailSocketStream(*this, SocketStreamError(0, m_url.string(), "Proxy authentication scheme is not supported for WebSockets"));
451             return;
452         }
453
454         // Setting the authorization results in a new connection attempt.
455         setCONNECTProxyAuthorizationForStream(m_readStream.get(), proxyAuthorizationString.get());
456         m_sentStoredCredentials = true;
457         return;
458     }
459
460     // FIXME: On platforms where AuthBrokerAgent is not available, ask the client if credentials could not be found.
461
462     m_client.didFailSocketStream(*this, SocketStreamError(0, m_url.string(), "Proxy credentials are not available"));
463 }
464
465 CFStringRef SocketStreamHandleImpl::copyCFStreamDescription(void* info)
466 {
467     SocketStreamHandleImpl* handle = static_cast<SocketStreamHandleImpl*>(info);
468     return String("WebKit socket stream, " + handle->m_url.string()).createCFString().leakRef();
469 }
470
471 void SocketStreamHandleImpl::readStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void* clientCallBackInfo)
472 {
473     SocketStreamHandleImpl* handle = static_cast<SocketStreamHandleImpl*>(clientCallBackInfo);
474     ASSERT_UNUSED(stream, stream == handle->m_readStream.get());
475     // Workaround for <rdar://problem/17727073>. Keeping this below the assertion as we'd like better steps to reproduce this.
476     if (!handle->m_readStream)
477         return;
478
479     RefPtr<SocketStreamHandle> protector(handle);
480     callOnMainThreadAndWait([&] {
481         if (handle->m_readStream)
482             handle->readStreamCallback(type);
483     });
484 }
485
486 void SocketStreamHandleImpl::writeStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void* clientCallBackInfo)
487 {
488     SocketStreamHandleImpl* handle = static_cast<SocketStreamHandleImpl*>(clientCallBackInfo);
489     ASSERT_UNUSED(stream, stream == handle->m_writeStream.get());
490     // This wasn't seen happening in practice, yet it seems like it could, due to symmetry with read stream callback.
491     if (!handle->m_writeStream)
492         return;
493
494     RefPtr<SocketStreamHandle> protector(handle);
495     callOnMainThreadAndWait([&] {
496         if (handle->m_writeStream)
497             handle->writeStreamCallback(type);
498     });
499 }
500
501 #if !PLATFORM(IOS_FAMILY)
502 static void setResponseProxyURL(CFHTTPMessageRef message, CFURLRef proxyURL)
503 {
504 #if PLATFORM(WIN)
505     if (_CFHTTPMessageSetResponseProxyURLPtr())
506         _CFHTTPMessageSetResponseProxyURLPtr()(message, proxyURL);
507 #else
508     _CFHTTPMessageSetResponseProxyURL(message, proxyURL);
509 #endif
510 }
511 #endif
512
513 static RetainPtr<CFHTTPMessageRef> copyCONNECTProxyResponse(CFReadStreamRef stream, CFURLRef responseURL, CFStringRef proxyHost, CFNumberRef proxyPort)
514 {
515     auto message = adoptCF(checked_cf_cast<CFHTTPMessageRef>(CFReadStreamCopyProperty(stream, kCFStreamPropertyCONNECTResponse)));
516     // CFNetwork needs URL to be set on response in order to handle authentication - even though it doesn't seem to make sense to provide ultimate target URL when authenticating to a proxy.
517     // This is set by CFNetwork internally for normal HTTP responses, but not for proxies.
518     _CFHTTPMessageSetResponseURL(message.get(), responseURL);
519
520 #if !PLATFORM(IOS_FAMILY)
521     // Ditto for proxy URL.
522     auto proxyURLString = adoptCF(CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("https://%@:%@"), proxyHost, proxyPort));
523     auto proxyURL = adoptCF(CFURLCreateWithString(kCFAllocatorDefault, proxyURLString.get(), nullptr));
524     setResponseProxyURL(message.get(), proxyURL.get());
525 #else
526     UNUSED_PARAM(proxyHost);
527     UNUSED_PARAM(proxyPort);
528 #endif
529
530     return message;
531 }
532
533 void SocketStreamHandleImpl::readStreamCallback(CFStreamEventType type)
534 {
535     switch (type) {
536     case kCFStreamEventNone:
537         return;
538     case kCFStreamEventOpenCompleted:
539         return;
540     case kCFStreamEventHasBytesAvailable: {
541         if (m_connectingSubstate == WaitingForCredentials)
542             return;
543
544         if (m_connectingSubstate == WaitingForConnect) {
545             if (m_connectionType == CONNECTProxy) {
546                 RetainPtr<CFHTTPMessageRef> proxyResponse = copyCONNECTProxyResponse(m_readStream.get(), m_httpsURL.get(), m_proxyHost.get(), m_proxyPort.get());
547                 if (!proxyResponse)
548                     return;
549
550                 CFIndex proxyResponseCode = CFHTTPMessageGetResponseStatusCode(proxyResponse.get());
551                 switch (proxyResponseCode) {
552                 case 200:
553                     // Successful connection.
554                     break;
555                 case 407:
556                     addCONNECTCredentials(proxyResponse.get());
557                     return;
558                 default:
559                     m_client.didFailSocketStream(*this, SocketStreamError(static_cast<int>(proxyResponseCode), m_url.string(), "Proxy connection could not be established, unexpected response code"));
560                     platformClose();
561                     return;
562                 }
563             }
564             m_connectingSubstate = Connected;
565             m_state = Open;
566             m_client.didOpenSocketStream(*this);
567         }
568
569         // Not an "else if", we could have made a client call above, and it could close the connection.
570         if (m_state == Closed)
571             return;
572
573         ASSERT(m_state == Open);
574         ASSERT(m_connectingSubstate == Connected);
575
576         CFIndex length;
577         UInt8 localBuffer[1024]; // Used if CFReadStreamGetBuffer couldn't return anything.
578         const UInt8* ptr = CFReadStreamGetBuffer(m_readStream.get(), 0, &length);
579         if (!ptr) {
580             length = CFReadStreamRead(m_readStream.get(), localBuffer, sizeof(localBuffer));
581             ptr = localBuffer;
582         }
583
584         if (!length)
585             return;
586
587         if (length == -1)
588             m_client.didFailToReceiveSocketStreamData(*this);
589         else
590             m_client.didReceiveSocketStreamData(*this, reinterpret_cast<const char*>(ptr), length);
591
592         return;
593     }
594     case kCFStreamEventCanAcceptBytes:
595         ASSERT_NOT_REACHED();
596         return;
597     case kCFStreamEventErrorOccurred: {
598         RetainPtr<CFErrorRef> error = adoptCF(CFReadStreamCopyError(m_readStream.get()));
599         reportErrorToClient(error.get());
600         return;
601     }
602     case kCFStreamEventEndEncountered:
603         platformClose();
604         return;
605     }
606 }
607
608 void SocketStreamHandleImpl::writeStreamCallback(CFStreamEventType type)
609 {
610     switch (type) {
611     case kCFStreamEventNone:
612         return;
613     case kCFStreamEventOpenCompleted:
614         return;
615     case kCFStreamEventHasBytesAvailable:
616         ASSERT_NOT_REACHED();
617         return;
618     case kCFStreamEventCanAcceptBytes: {
619         // Can be false if read stream callback just decided to retry a CONNECT with credentials.
620         if (!CFWriteStreamCanAcceptBytes(m_writeStream.get()))
621             return;
622
623         if (m_connectingSubstate == WaitingForCredentials)
624             return;
625
626         if (m_connectingSubstate == WaitingForConnect) {
627             if (m_connectionType == CONNECTProxy) {
628                 RetainPtr<CFHTTPMessageRef> proxyResponse = copyCONNECTProxyResponse(m_readStream.get(), m_httpsURL.get(), m_proxyHost.get(), m_proxyPort.get());
629                 if (!proxyResponse)
630                     return;
631
632                 // Don't write anything until read stream callback has dealt with CONNECT credentials.
633                 // The order of callbacks is not defined, so this can be called before readStreamCallback's kCFStreamEventHasBytesAvailable.
634                 CFIndex proxyResponseCode = CFHTTPMessageGetResponseStatusCode(proxyResponse.get());
635                 if (proxyResponseCode != 200)
636                     return;
637             }
638             m_connectingSubstate = Connected;
639             m_state = Open;
640             m_client.didOpenSocketStream(*this);
641         }
642
643         // Not an "else if", we could have made a client call above, and it could close the connection.
644         if (m_state == Closed)
645             return;
646
647         ASSERT(m_state == Open);
648         ASSERT(m_connectingSubstate == Connected);
649
650         sendPendingData();
651         return;
652     }
653     case kCFStreamEventErrorOccurred: {
654         RetainPtr<CFErrorRef> error = adoptCF(CFWriteStreamCopyError(m_writeStream.get()));
655         reportErrorToClient(error.get());
656         return;
657     }
658     case kCFStreamEventEndEncountered:
659         // FIXME: Currently, we handle closing in read callback, but these can come independently (e.g. a server can stop listening, but keep sending data).
660         return;
661     }
662 }
663
664 void SocketStreamHandleImpl::reportErrorToClient(CFErrorRef error)
665 {
666     CFIndex errorCode = CFErrorGetCode(error);
667     String description;
668
669 #if PLATFORM(MAC)
670
671     ALLOW_DEPRECATED_DECLARATIONS_BEGIN
672
673     if (CFEqual(CFErrorGetDomain(error), kCFErrorDomainOSStatus)) {
674         const char* descriptionOSStatus = GetMacOSStatusCommentString(static_cast<OSStatus>(errorCode));
675         if (descriptionOSStatus && descriptionOSStatus[0] != '\0')
676             description = "OSStatus Error " + String::number(errorCode) + ": " + descriptionOSStatus;
677     }
678
679     ALLOW_DEPRECATED_DECLARATIONS_END
680
681 #endif
682
683     if (description.isNull()) {
684         RetainPtr<CFStringRef> descriptionCF = adoptCF(CFErrorCopyDescription(error));
685         description = String(descriptionCF.get());
686     }
687
688     m_client.didFailSocketStream(*this, SocketStreamError(static_cast<int>(errorCode), m_url.string(), description));
689 }
690
691 SocketStreamHandleImpl::~SocketStreamHandleImpl()
692 {
693     LOG(Network, "SocketStreamHandle %p dtor", this);
694
695     ASSERT(!m_pacRunLoopSource);
696 }
697
698 Optional<size_t> SocketStreamHandleImpl::platformSendInternal(const uint8_t* data, size_t length)
699 {
700     if (!m_writeStream)
701         return 0;
702
703     if (!CFWriteStreamCanAcceptBytes(m_writeStream.get()))
704         return 0;
705
706     CFIndex result = CFWriteStreamWrite(m_writeStream.get(), reinterpret_cast<const UInt8*>(data), length);
707     if (result == -1)
708         return WTF::nullopt;
709
710     ASSERT(result >= 0);
711     return static_cast<size_t>(result);
712 }
713
714 void SocketStreamHandleImpl::platformClose()
715 {
716     LOG(Network, "SocketStreamHandle %p platformClose", this);
717
718     if (m_pacRunLoopSource) 
719         removePACRunLoopSource();
720
721     ASSERT(!m_readStream == !m_writeStream);
722     if (!m_readStream) {
723         if (m_connectingSubstate == New || m_connectingSubstate == ExecutingPACFile)
724             m_client.didCloseSocketStream(*this);
725         return;
726     }
727
728     CFReadStreamUnscheduleFromRunLoop(m_readStream.get(), callbacksRunLoop(), callbacksRunLoopMode());
729     CFWriteStreamUnscheduleFromRunLoop(m_writeStream.get(), callbacksRunLoop(), callbacksRunLoopMode());
730
731     CFReadStreamClose(m_readStream.get());
732     CFWriteStreamClose(m_writeStream.get());
733     
734     m_readStream = nullptr;
735     m_writeStream = nullptr;
736
737     m_client.didCloseSocketStream(*this);
738 }
739
740 unsigned short SocketStreamHandleImpl::port() const
741 {
742     if (auto urlPort = m_url.port())
743         return urlPort.value();
744     if (shouldUseSSL())
745         return 443;
746     return 80;
747 }
748
749 } // namespace WebCore