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