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