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