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