Enable HTTP and HTTPS proxies on iOS and make it a property of the NSURLSession
[WebKit-https.git] / Source / WebKit / NetworkProcess / cocoa / NetworkSessionCocoa.mm
1 /*
2  * Copyright (C) 2015-2018 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "NetworkSessionCocoa.h"
28
29 #import "AuthenticationChallengeDisposition.h"
30 #import "AuthenticationManager.h"
31 #import "DataReference.h"
32 #import "Download.h"
33 #import "LegacyCustomProtocolManager.h"
34 #import "Logging.h"
35 #import "NetworkLoad.h"
36 #import "NetworkProcess.h"
37 #import "NetworkSessionCreationParameters.h"
38 #import "SessionTracker.h"
39 #import <Foundation/NSURLSession.h>
40 #import <WebCore/Credential.h>
41 #import <WebCore/FormDataStreamMac.h>
42 #import <WebCore/FrameLoaderTypes.h>
43 #import <WebCore/NetworkStorageSession.h>
44 #import <WebCore/NotImplemented.h>
45 #import <WebCore/ResourceError.h>
46 #import <WebCore/ResourceRequest.h>
47 #import <WebCore/ResourceResponse.h>
48 #import <WebCore/SharedBuffer.h>
49 #import <WebCore/WebCoreURLResponse.h>
50 #import <pal/spi/cf/CFNetworkSPI.h>
51 #import <wtf/MainThread.h>
52 #import <wtf/NeverDestroyed.h>
53 #import <wtf/ProcessPrivilege.h>
54 #import <wtf/URL.h>
55
56 using namespace WebKit;
57
58 CFStringRef const WebKit2HTTPProxyDefaultsKey = static_cast<CFStringRef>(@"WebKit2HTTPProxy");
59 CFStringRef const WebKit2HTTPSProxyDefaultsKey = static_cast<CFStringRef>(@"WebKit2HTTPSProxy");
60
61 static NSURLSessionResponseDisposition toNSURLSessionResponseDisposition(WebCore::PolicyAction disposition)
62 {
63     switch (disposition) {
64     case WebCore::PolicyAction::Suspend:
65         LOG_ERROR("PolicyAction::Suspend encountered - Treating as PolicyAction::Ignore for now");
66         FALLTHROUGH;
67     case WebCore::PolicyAction::Ignore:
68         return NSURLSessionResponseCancel;
69     case WebCore::PolicyAction::Use:
70         return NSURLSessionResponseAllow;
71     case WebCore::PolicyAction::Download:
72         return NSURLSessionResponseBecomeDownload;
73     }
74 }
75
76 static NSURLSessionAuthChallengeDisposition toNSURLSessionAuthChallengeDisposition(WebKit::AuthenticationChallengeDisposition disposition)
77 {
78     switch (disposition) {
79     case WebKit::AuthenticationChallengeDisposition::UseCredential:
80         return NSURLSessionAuthChallengeUseCredential;
81     case WebKit::AuthenticationChallengeDisposition::PerformDefaultHandling:
82         return NSURLSessionAuthChallengePerformDefaultHandling;
83     case WebKit::AuthenticationChallengeDisposition::Cancel:
84         return NSURLSessionAuthChallengeCancelAuthenticationChallenge;
85     case WebKit::AuthenticationChallengeDisposition::RejectProtectionSpaceAndContinue:
86         return NSURLSessionAuthChallengeRejectProtectionSpace;
87     }
88 }
89
90 static WebCore::NetworkLoadPriority toNetworkLoadPriority(float priority)
91 {
92     if (priority <= NSURLSessionTaskPriorityLow)
93         return WebCore::NetworkLoadPriority::Low;
94     if (priority >= NSURLSessionTaskPriorityHigh)
95         return WebCore::NetworkLoadPriority::High;
96     return WebCore::NetworkLoadPriority::Medium;
97 }
98
99 @interface WKNetworkSessionDelegate : NSObject <NSURLSessionDataDelegate> {
100     RefPtr<WebKit::NetworkSessionCocoa> _session;
101     bool _withCredentials;
102 }
103
104 - (id)initWithNetworkSession:(WebKit::NetworkSessionCocoa&)session withCredentials:(bool)withCredentials;
105 - (void)sessionInvalidated;
106
107 @end
108
109 @implementation WKNetworkSessionDelegate
110
111 - (id)initWithNetworkSession:(WebKit::NetworkSessionCocoa&)session withCredentials:(bool)withCredentials
112 {
113     self = [super init];
114     if (!self)
115         return nil;
116
117     _session = &session;
118     _withCredentials = withCredentials;
119
120     return self;
121 }
122
123 - (void)sessionInvalidated
124 {
125     _session = nullptr;
126 }
127
128 - (NetworkDataTaskCocoa*)existingTask:(NSURLSessionTask *)task
129 {
130     if (!_session)
131         return nullptr;
132
133     if (!task)
134         return nullptr;
135
136     auto storedCredentialsPolicy = _withCredentials ? WebCore::StoredCredentialsPolicy::Use : WebCore::StoredCredentialsPolicy::DoNotUse;
137     return _session->dataTaskForIdentifier(task.taskIdentifier, storedCredentialsPolicy);
138 }
139
140 - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
141 {
142     ASSERT(!_session);
143 }
144
145 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
146 {
147     if (auto* networkDataTask = [self existingTask:task])
148         networkDataTask->didSendData(totalBytesSent, totalBytesExpectedToSend);
149 }
150
151 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler
152 {
153     auto* networkDataTask = [self existingTask:task];
154     if (!networkDataTask) {
155         completionHandler(nil);
156         return;
157     }
158
159     auto* body = networkDataTask->firstRequest().httpBody();
160     if (!body) {
161         completionHandler(nil);
162         return;
163     }
164
165     completionHandler(WebCore::createHTTPBodyNSInputStream(*body).get());
166 }
167
168 #if HAVE(CFNETWORK_WITH_IGNORE_HSTS)
169 static NSURLRequest* downgradeRequest(NSURLRequest *request)
170 {
171     NSMutableURLRequest *nsMutableRequest = [[request mutableCopy] autorelease];
172     if ([nsMutableRequest.URL.scheme isEqualToString:@"https"]) {
173         NSURLComponents *components = [NSURLComponents componentsWithURL:nsMutableRequest.URL resolvingAgainstBaseURL:NO];
174         components.scheme = @"http";
175         [nsMutableRequest setURL:components.URL];
176         ASSERT([nsMutableRequest.URL.scheme isEqualToString:@"http"]);
177         return nsMutableRequest;
178     }
179
180     ASSERT_NOT_REACHED();
181     return request;
182 }
183
184 static bool schemeWasUpgradedDueToDynamicHSTS(NSURLRequest *request)
185 {
186     return [request respondsToSelector:@selector(_schemeWasUpgradedDueToDynamicHSTS)]
187         && [request _schemeWasUpgradedDueToDynamicHSTS];
188 }
189
190 static void setIgnoreHSTS(NSMutableURLRequest *request, bool ignoreHSTS)
191 {
192     if ([request respondsToSelector:@selector(_setIgnoreHSTS:)])
193         [request _setIgnoreHSTS:ignoreHSTS];
194 }
195
196 static bool ignoreHSTS(NSURLRequest *request)
197 {
198     return [request respondsToSelector:@selector(_ignoreHSTS)]
199         && [request _ignoreHSTS];
200 }
201 #endif
202
203 static NSURLRequest* updateIgnoreStrictTransportSecuritySettingIfNecessary(NSURLRequest *request, bool shouldIgnoreHSTS)
204 {
205 #if HAVE(CFNETWORK_WITH_IGNORE_HSTS)
206     if ([request.URL.scheme isEqualToString:@"https"] && shouldIgnoreHSTS && ignoreHSTS(request)) {
207         // The request was upgraded for some other reason than HSTS.
208         // Don't ignore HSTS to avoid the risk of another downgrade.
209         NSMutableURLRequest *nsMutableRequest = [[request mutableCopy] autorelease];
210         setIgnoreHSTS(nsMutableRequest, false);
211         return nsMutableRequest;
212     }
213     
214     if ([request.URL.scheme isEqualToString:@"http"] && ignoreHSTS(request) != shouldIgnoreHSTS) {
215         NSMutableURLRequest *nsMutableRequest = [[request mutableCopy] autorelease];
216         setIgnoreHSTS(nsMutableRequest, shouldIgnoreHSTS);
217         return nsMutableRequest;
218     }
219 #else
220     UNUSED_PARAM(shouldIgnoreHSTS);
221 #endif
222
223     return request;
224 }
225
226 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler
227 {
228     auto taskIdentifier = task.taskIdentifier;
229     LOG(NetworkSession, "%llu willPerformHTTPRedirection from %s to %s", taskIdentifier, response.URL.absoluteString.UTF8String, request.URL.absoluteString.UTF8String);
230
231     if (auto* networkDataTask = [self existingTask:task]) {
232         auto completionHandlerCopy = Block_copy(completionHandler);
233
234         bool shouldIgnoreHSTS = false;
235 #if HAVE(CFNETWORK_WITH_IGNORE_HSTS)
236         shouldIgnoreHSTS = schemeWasUpgradedDueToDynamicHSTS(request) && WebCore::NetworkStorageSession::storageSession(_session->sessionID())->shouldBlockCookies(request, networkDataTask->frameID(), networkDataTask->pageID());
237         if (shouldIgnoreHSTS) {
238             request = downgradeRequest(request);
239             ASSERT([request.URL.scheme isEqualToString:@"http"]);
240             LOG(NetworkSession, "%llu Downgraded %s from https to http", taskIdentifier, request.URL.absoluteString.UTF8String);
241         }
242 #endif
243
244         networkDataTask->willPerformHTTPRedirection(response, request, [completionHandlerCopy, taskIdentifier, shouldIgnoreHSTS](auto&& request) {
245 #if !LOG_DISABLED
246             LOG(NetworkSession, "%llu willPerformHTTPRedirection completionHandler (%s)", taskIdentifier, request.url().string().utf8().data());
247 #else
248             UNUSED_PARAM(taskIdentifier);
249 #endif
250             auto nsRequest = request.nsURLRequest(WebCore::HTTPBodyUpdatePolicy::UpdateHTTPBody);
251             nsRequest = updateIgnoreStrictTransportSecuritySettingIfNecessary(nsRequest, shouldIgnoreHSTS);
252             completionHandlerCopy(nsRequest);
253             Block_release(completionHandlerCopy);
254         });
255     } else {
256         LOG(NetworkSession, "%llu willPerformHTTPRedirection completionHandler (nil)", taskIdentifier);
257         completionHandler(nil);
258     }
259 }
260
261 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask*)task _schemeUpgraded:(NSURLRequest*)request completionHandler:(void (^)(NSURLRequest*))completionHandler
262 {
263     auto taskIdentifier = task.taskIdentifier;
264     LOG(NetworkSession, "%llu _schemeUpgraded %s", taskIdentifier, request.URL.absoluteString.UTF8String);
265
266     if (auto* networkDataTask = [self existingTask:task]) {
267         bool shouldIgnoreHSTS = false;
268 #if HAVE(CFNETWORK_WITH_IGNORE_HSTS)
269         shouldIgnoreHSTS = schemeWasUpgradedDueToDynamicHSTS(request) && WebCore::NetworkStorageSession::storageSession(_session->sessionID())->shouldBlockCookies(request, networkDataTask->frameID(), networkDataTask->pageID());
270         if (shouldIgnoreHSTS) {
271             request = downgradeRequest(request);
272             ASSERT([request.URL.scheme isEqualToString:@"http"]);
273             LOG(NetworkSession, "%llu Downgraded %s from https to http", taskIdentifier, request.URL.absoluteString.UTF8String);
274         }
275 #endif
276
277         auto completionHandlerCopy = Block_copy(completionHandler);
278         networkDataTask->willPerformHTTPRedirection(WebCore::synthesizeRedirectResponseIfNecessary([task currentRequest], request, nil), request, [completionHandlerCopy, taskIdentifier, shouldIgnoreHSTS](auto&& request) {
279 #if !LOG_DISABLED
280             LOG(NetworkSession, "%llu _schemeUpgraded completionHandler (%s)", taskIdentifier, request.url().string().utf8().data());
281 #else
282             UNUSED_PARAM(taskIdentifier);
283 #endif
284             auto nsRequest = request.nsURLRequest(WebCore::HTTPBodyUpdatePolicy::UpdateHTTPBody);
285             nsRequest = updateIgnoreStrictTransportSecuritySettingIfNecessary(nsRequest, shouldIgnoreHSTS);
286             completionHandlerCopy(nsRequest);
287             Block_release(completionHandlerCopy);
288         });
289     } else {
290         LOG(NetworkSession, "%llu _schemeUpgraded completionHandler (nil)", taskIdentifier);
291         completionHandler(nil);
292     }
293 }
294
295 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler
296 {
297     if (!_session) {
298         completionHandler(nil);
299         return;
300     }
301
302     // FIXME: remove if <rdar://problem/20001985> is ever resolved.
303     if ([proposedResponse.response respondsToSelector:@selector(allHeaderFields)]
304         && [[(id)proposedResponse.response allHeaderFields] objectForKey:@"Content-Range"])
305         completionHandler(nil);
306     else
307         completionHandler(proposedResponse);
308 }
309
310 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
311 {
312     if (!_session) {
313         completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
314         return;
315     }
316
317     auto taskIdentifier = task.taskIdentifier;
318     LOG(NetworkSession, "%llu didReceiveChallenge", taskIdentifier);
319     
320     // Proxy authentication is handled by CFNetwork internally. We can get here if the user cancels
321     // CFNetwork authentication dialog, and we shouldn't ask the client to display another one in that case.
322     if (challenge.protectionSpace.isProxy) {
323         completionHandler(NSURLSessionAuthChallengeUseCredential, nil);
324         return;
325     }
326
327     if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
328         if (NetworkSessionCocoa::allowsSpecificHTTPSCertificateForHost(challenge))
329             return completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
330
331         // Handle server trust evaluation at platform-level if requested, for performance reasons and to use ATS defaults.
332         if (!NetworkProcess::singleton().canHandleHTTPSServerTrustEvaluation())
333             return completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
334     }
335
336     if (auto* networkDataTask = [self existingTask:task]) {
337         WebCore::AuthenticationChallenge authenticationChallenge(challenge);
338         auto completionHandlerCopy = Block_copy(completionHandler);
339         auto sessionID = _session->sessionID();
340         auto challengeCompletionHandler = [completionHandlerCopy, sessionID, authenticationChallenge, taskIdentifier, partition = networkDataTask->partition()](WebKit::AuthenticationChallengeDisposition disposition, const WebCore::Credential& credential)
341         {
342 #if !LOG_DISABLED
343             LOG(NetworkSession, "%llu didReceiveChallenge completionHandler %d", taskIdentifier, disposition);
344 #else
345             UNUSED_PARAM(taskIdentifier);
346 #endif
347 #if !USE(CREDENTIAL_STORAGE_WITH_NETWORK_SESSION)
348             UNUSED_PARAM(sessionID);
349             UNUSED_PARAM(authenticationChallenge);
350 #else
351             if (credential.persistence() == WebCore::CredentialPersistenceForSession && authenticationChallenge.protectionSpace().isPasswordBased()) {
352
353                 WebCore::Credential nonPersistentCredential(credential.user(), credential.password(), WebCore::CredentialPersistenceNone);
354                 URL urlToStore;
355                 if (authenticationChallenge.failureResponse().httpStatusCode() == 401)
356                     urlToStore = authenticationChallenge.failureResponse().url();
357                 if (auto storageSession = WebCore::NetworkStorageSession::storageSession(sessionID))
358                     storageSession->credentialStorage().set(partition, nonPersistentCredential, authenticationChallenge.protectionSpace(), urlToStore);
359                 else
360                     ASSERT_NOT_REACHED();
361
362                 completionHandlerCopy(toNSURLSessionAuthChallengeDisposition(disposition), nonPersistentCredential.nsCredential());
363             } else
364 #endif
365                 completionHandlerCopy(toNSURLSessionAuthChallengeDisposition(disposition), credential.nsCredential());
366             Block_release(completionHandlerCopy);
367         };
368         networkDataTask->didReceiveChallenge(challenge, WTFMove(challengeCompletionHandler));
369     } else {
370         auto downloadID = _session->downloadID(taskIdentifier);
371         if (downloadID.downloadID()) {
372             if (auto* download = WebKit::NetworkProcess::singleton().downloadManager().download(downloadID)) {
373                 // Received an authentication challenge for a download being resumed.
374                 WebCore::AuthenticationChallenge authenticationChallenge { challenge };
375                 auto completionHandlerCopy = Block_copy(completionHandler);
376                 auto challengeCompletionHandler = [completionHandlerCopy, authenticationChallenge](WebKit::AuthenticationChallengeDisposition disposition, const WebCore::Credential& credential) {
377                     completionHandlerCopy(toNSURLSessionAuthChallengeDisposition(disposition), credential.nsCredential());
378                     Block_release(completionHandlerCopy);
379                 };
380                 download->didReceiveChallenge(challenge, WTFMove(challengeCompletionHandler));
381                 return;
382             }
383         }
384         LOG(NetworkSession, "%llu didReceiveChallenge completionHandler (cancel)", taskIdentifier);
385         completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
386     }
387 }
388
389 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
390 {
391     if (!_session)
392         return;
393
394     LOG(NetworkSession, "%llu didCompleteWithError %@", task.taskIdentifier, error);
395     if (auto* networkDataTask = [self existingTask:task])
396         networkDataTask->didCompleteWithError(error, networkDataTask->networkLoadMetrics());
397     else if (error) {
398         auto downloadID = _session->takeDownloadID(task.taskIdentifier);
399         if (downloadID.downloadID()) {
400             if (auto* download = WebKit::NetworkProcess::singleton().downloadManager().download(downloadID)) {
401                 NSData *resumeData = nil;
402                 if (id userInfo = error.userInfo) {
403                     if ([userInfo isKindOfClass:[NSDictionary class]])
404                         resumeData = userInfo[@"NSURLSessionDownloadTaskResumeData"];
405                 }
406                 
407                 if (resumeData && [resumeData isKindOfClass:[NSData class]])
408                     download->didFail(error, { static_cast<const uint8_t*>(resumeData.bytes), resumeData.length });
409                 else
410                     download->didFail(error, { });
411             }
412         }
413     }
414 }
415
416 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
417 {
418     LOG(NetworkSession, "%llu didFinishCollectingMetrics", task.taskIdentifier);
419     if (auto* networkDataTask = [self existingTask:task]) {
420         NSURLSessionTaskTransactionMetrics *m = metrics.transactionMetrics.lastObject;
421         NSDate *fetchStartDate = m.fetchStartDate;
422         NSTimeInterval domainLookupStartInterval = m.domainLookupStartDate ? [m.domainLookupStartDate timeIntervalSinceDate:fetchStartDate] : -1;
423         NSTimeInterval domainLookupEndInterval = m.domainLookupEndDate ? [m.domainLookupEndDate timeIntervalSinceDate:fetchStartDate] : -1;
424         NSTimeInterval connectStartInterval = m.connectStartDate ? [m.connectStartDate timeIntervalSinceDate:fetchStartDate] : -1;
425         NSTimeInterval secureConnectionStartInterval = m.secureConnectionStartDate ? [m.secureConnectionStartDate timeIntervalSinceDate:fetchStartDate] : -1;
426         NSTimeInterval connectEndInterval = m.connectEndDate ? [m.connectEndDate timeIntervalSinceDate:fetchStartDate] : -1;
427         NSTimeInterval requestStartInterval = [m.requestStartDate timeIntervalSinceDate:fetchStartDate];
428         NSTimeInterval responseStartInterval = [m.responseStartDate timeIntervalSinceDate:fetchStartDate];
429         NSTimeInterval responseEndInterval = [m.responseEndDate timeIntervalSinceDate:fetchStartDate];
430
431         auto& networkLoadMetrics = networkDataTask->networkLoadMetrics();
432         networkLoadMetrics.domainLookupStart = Seconds(domainLookupStartInterval);
433         networkLoadMetrics.domainLookupEnd = Seconds(domainLookupEndInterval);
434         networkLoadMetrics.connectStart = Seconds(connectStartInterval);
435         networkLoadMetrics.secureConnectionStart = Seconds(secureConnectionStartInterval);
436         networkLoadMetrics.connectEnd = Seconds(connectEndInterval);
437         networkLoadMetrics.requestStart = Seconds(requestStartInterval);
438         networkLoadMetrics.responseStart = Seconds(responseStartInterval);
439         networkLoadMetrics.responseEnd = Seconds(responseEndInterval);
440         networkLoadMetrics.markComplete();
441         networkLoadMetrics.protocol = String(m.networkProtocolName);
442
443         if (networkDataTask->shouldCaptureExtraNetworkLoadMetrics()) {
444             networkLoadMetrics.priority = toNetworkLoadPriority(task.priority);
445
446 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300) || (PLATFORM(IOS_FAMILY) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
447             networkLoadMetrics.remoteAddress = String(m._remoteAddressAndPort);
448             networkLoadMetrics.connectionIdentifier = String([m._connectionIdentifier UUIDString]);
449 #endif
450
451             __block WebCore::HTTPHeaderMap requestHeaders;
452             [m.request.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(NSString *name, NSString *value, BOOL *) {
453                 requestHeaders.set(String(name), String(value));
454             }];
455             networkLoadMetrics.requestHeaders = WTFMove(requestHeaders);
456
457 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300) || (PLATFORM(IOS_FAMILY) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
458             uint64_t requestHeaderBytesSent = 0;
459             uint64_t responseHeaderBytesReceived = 0;
460             uint64_t responseBodyBytesReceived = 0;
461             uint64_t responseBodyDecodedSize = 0;
462
463             for (NSURLSessionTaskTransactionMetrics *transactionMetrics in metrics.transactionMetrics) {
464                 requestHeaderBytesSent += transactionMetrics._requestHeaderBytesSent;
465                 responseHeaderBytesReceived += transactionMetrics._responseHeaderBytesReceived;
466                 responseBodyBytesReceived += transactionMetrics._responseBodyBytesReceived;
467                 responseBodyDecodedSize += transactionMetrics._responseBodyBytesDecoded ? transactionMetrics._responseBodyBytesDecoded : transactionMetrics._responseBodyBytesReceived;
468             }
469
470             networkLoadMetrics.requestHeaderBytesSent = requestHeaderBytesSent;
471             networkLoadMetrics.requestBodyBytesSent = task.countOfBytesSent;
472             networkLoadMetrics.responseHeaderBytesReceived = responseHeaderBytesReceived;
473             networkLoadMetrics.responseBodyBytesReceived = responseBodyBytesReceived;
474             networkLoadMetrics.responseBodyDecodedSize = responseBodyDecodedSize;
475 #endif
476         }
477     }
478 }
479
480 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
481 {
482     auto taskIdentifier = dataTask.taskIdentifier;
483     LOG(NetworkSession, "%llu didReceiveResponse", taskIdentifier);
484     if (auto* networkDataTask = [self existingTask:dataTask]) {
485         ASSERT(RunLoop::isMain());
486         
487         // Avoid MIME type sniffing if the response comes back as 304 Not Modified.
488         int statusCode = [response respondsToSelector:@selector(statusCode)] ? [(id)response statusCode] : 0;
489         if (statusCode != 304) {
490             bool isMainResourceLoad = networkDataTask->firstRequest().requester() == WebCore::ResourceRequest::Requester::Main;
491             WebCore::adjustMIMETypeIfNecessary(response._CFURLResponse, isMainResourceLoad);
492         }
493
494         WebCore::ResourceResponse resourceResponse(response);
495         // Lazy initialization is not helpful in the WebKit2 case because we always end up initializing
496         // all the fields when sending the response to the WebContent process over IPC.
497         resourceResponse.disableLazyInitialization();
498
499         // FIXME: This cannot be eliminated until other code no longer relies on ResourceResponse's
500         // NetworkLoadMetrics. For example, PerformanceTiming.
501         copyTimingData([dataTask _timingData], resourceResponse.deprecatedNetworkLoadMetrics());
502
503         auto completionHandlerCopy = Block_copy(completionHandler);
504         networkDataTask->didReceiveResponse(WTFMove(resourceResponse), [completionHandlerCopy, taskIdentifier](WebCore::PolicyAction policyAction) {
505 #if !LOG_DISABLED
506             LOG(NetworkSession, "%llu didReceiveResponse completionHandler (%d)", taskIdentifier, policyAction);
507 #else
508             UNUSED_PARAM(taskIdentifier);
509 #endif
510             completionHandlerCopy(toNSURLSessionResponseDisposition(policyAction));
511             Block_release(completionHandlerCopy);
512         });
513     } else {
514         LOG(NetworkSession, "%llu didReceiveResponse completionHandler (cancel)", taskIdentifier);
515         completionHandler(NSURLSessionResponseCancel);
516     }
517 }
518
519 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
520 {
521     if (auto* networkDataTask = [self existingTask:dataTask])
522         networkDataTask->didReceiveData(WebCore::SharedBuffer::create(data));
523 }
524
525 - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
526 {
527     if (!_session)
528         return;
529
530     auto downloadID = _session->takeDownloadID([downloadTask taskIdentifier]);
531     if (auto* download = WebKit::NetworkProcess::singleton().downloadManager().download(downloadID))
532         download->didFinish();
533 }
534
535 - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
536 {
537     if (!_session)
538         return;
539
540     ASSERT_WITH_MESSAGE(![self existingTask:downloadTask], "The NetworkDataTask should be destroyed immediately after didBecomeDownloadTask returns");
541
542     auto downloadID = _session->downloadID([downloadTask taskIdentifier]);
543     if (auto* download = WebKit::NetworkProcess::singleton().downloadManager().download(downloadID))
544         download->didReceiveData(bytesWritten);
545 }
546
547 - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
548 {
549     if (!_session)
550         return;
551
552     notImplemented();
553 }
554
555 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
556 {
557     if (auto* networkDataTask = [self existingTask:dataTask]) {
558         Ref<NetworkDataTaskCocoa> protectedNetworkDataTask(*networkDataTask);
559         auto downloadID = networkDataTask->pendingDownloadID();
560         auto& downloadManager = WebKit::NetworkProcess::singleton().downloadManager();
561         auto download = std::make_unique<WebKit::Download>(downloadManager, downloadID, downloadTask, _session->sessionID(), networkDataTask->suggestedFilename());
562         networkDataTask->transferSandboxExtensionToDownload(*download);
563         ASSERT(WebCore::FileSystem::fileExists(networkDataTask->pendingDownloadLocation()));
564         download->didCreateDestination(networkDataTask->pendingDownloadLocation());
565         downloadManager.dataTaskBecameDownloadTask(downloadID, WTFMove(download));
566
567         _session->addDownloadID([downloadTask taskIdentifier], downloadID);
568     }
569 }
570
571 @end
572
573 namespace WebKit {
574
575 #if !ASSERT_DISABLED
576 static bool sessionsCreated = false;
577 #endif
578
579 static NSURLSessionConfiguration *configurationForSessionID(const PAL::SessionID& session)
580 {
581     if (session.isEphemeral()) {
582         NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
583         configuration._shouldSkipPreferredClientCertificateLookup = YES;
584         return configuration;
585     }
586     return [NSURLSessionConfiguration defaultSessionConfiguration];
587 }
588
589 #if PLATFORM(IOS_FAMILY)
590 static String& globalCTDataConnectionServiceType()
591 {
592     static NeverDestroyed<String> ctDataConnectionServiceType;
593     return ctDataConnectionServiceType.get();
594 }
595 #endif
596
597 #if PLATFORM(IOS_FAMILY)
598 void NetworkSessionCocoa::setCTDataConnectionServiceType(const String& type)
599 {
600     ASSERT(!sessionsCreated);
601     globalCTDataConnectionServiceType() = type;
602 }
603 #endif
604
605 Ref<NetworkSession> NetworkSessionCocoa::create(NetworkSessionCreationParameters&& parameters)
606 {
607     return adoptRef(*new NetworkSessionCocoa(WTFMove(parameters)));
608 }
609
610 static NSDictionary *proxyDictionary(const URL& httpProxy, const URL& httpsProxy)
611 {
612     if (!httpProxy.isValid() && !httpsProxy.isValid())
613         return nil;
614
615     ALLOW_DEPRECATED_DECLARATIONS_BEGIN
616
617     NSMutableDictionary *dictionary = [[[NSMutableDictionary alloc] init] autorelease];
618     if (httpProxy.isValid()) {
619         [dictionary setObject:httpProxy.host().toString() forKey:(NSString *)kCFStreamPropertyHTTPProxyHost];
620         if (auto port = httpProxy.port())
621             [dictionary setObject:@(*port) forKey:(NSString *)kCFStreamPropertyHTTPProxyHost];
622     }
623     if (httpsProxy.isValid()) {
624         [dictionary setObject:httpsProxy.host().toString() forKey:(NSString *)kCFStreamPropertyHTTPSProxyHost];
625         if (auto port = httpsProxy.port())
626             [dictionary setObject:@(*port) forKey:(NSString *)kCFStreamPropertyHTTPSProxyPort];
627     }
628     return dictionary;
629
630     ALLOW_DEPRECATED_DECLARATIONS_END
631 }
632
633 NetworkSessionCocoa::NetworkSessionCocoa(NetworkSessionCreationParameters&& parameters)
634     : NetworkSession(parameters.sessionID)
635     , m_boundInterfaceIdentifier(parameters.boundInterfaceIdentifier)
636     , m_proxyConfiguration(parameters.proxyConfiguration)
637     , m_shouldLogCookieInformation(parameters.shouldLogCookieInformation)
638     , m_loadThrottleLatency(parameters.loadThrottleLatency)
639 {
640     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
641
642     relaxAdoptionRequirement();
643
644 #if !ASSERT_DISABLED
645     sessionsCreated = true;
646 #endif
647
648     NSURLSessionConfiguration *configuration = configurationForSessionID(m_sessionID);
649
650 #if USE(CFNETWORK_AUTO_ADDED_HTTP_HEADER_SUPPRESSION)
651     // Without this, CFNetwork would sometimes add a Content-Type header to our requests (rdar://problem/34748470).
652     configuration._suppressedAutoAddedHTTPHeaders = [NSSet setWithObject:@"Content-Type"];
653 #endif
654
655     if (parameters.allowsCellularAccess == AllowsCellularAccess::No)
656         configuration.allowsCellularAccess = NO;
657
658     // The WebKit network cache was already queried.
659     configuration.URLCache = nil;
660
661     if (auto data = NetworkProcess::singleton().sourceApplicationAuditData())
662         configuration._sourceApplicationAuditTokenData = (__bridge NSData *)data.get();
663
664     if (!parameters.sourceApplicationBundleIdentifier.isEmpty()) {
665         configuration._sourceApplicationBundleIdentifier = parameters.sourceApplicationBundleIdentifier;
666         configuration._sourceApplicationAuditTokenData = nil;
667     }
668
669     if (!parameters.sourceApplicationSecondaryIdentifier.isEmpty())
670         configuration._sourceApplicationSecondaryIdentifier = parameters.sourceApplicationSecondaryIdentifier;
671
672     configuration.connectionProxyDictionary = proxyDictionary(parameters.httpProxy, parameters.httpsProxy);
673
674 #if PLATFORM(IOS_FAMILY)
675     auto& ctDataConnectionServiceType = globalCTDataConnectionServiceType();
676     if (!ctDataConnectionServiceType.isEmpty())
677         configuration._CTDataConnectionServiceType = ctDataConnectionServiceType;
678 #endif
679
680 #if ENABLE(LEGACY_CUSTOM_PROTOCOL_MANAGER)
681     NetworkProcess::singleton().supplement<LegacyCustomProtocolManager>()->registerProtocolClass(configuration);
682 #endif
683
684 #if HAVE(TIMINGDATAOPTIONS)
685     configuration._timingDataOptions = _TimingDataOptionsEnableW3CNavigationTiming;
686 #else
687     setCollectsTimingData();
688 #endif
689
690 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) || (PLATFORM(IOS_FAMILY) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000)
691     // FIXME: Replace @"kCFStreamPropertyAutoErrorOnSystemChange" with a constant from the SDK once rdar://problem/40650244 is in a build.
692     if (NetworkProcess::singleton().suppressesConnectionTerminationOnSystemChange())
693         configuration._socketStreamProperties = @{ @"kCFStreamPropertyAutoErrorOnSystemChange" : @(NO) };
694 #endif
695
696 #if PLATFORM(WATCHOS) && __WATCH_OS_VERSION_MIN_REQUIRED >= 60000
697     configuration._companionProxyPreference = NSURLSessionCompanionProxyPreferencePreferDirectToCloud;
698 #endif
699
700     auto* storageSession = WebCore::NetworkStorageSession::storageSession(parameters.sessionID);
701     RELEASE_ASSERT(storageSession);
702     if (CFHTTPCookieStorageRef storage = storageSession->cookieStorage().get())
703         configuration.HTTPCookieStorage = [[[NSHTTPCookieStorage alloc] _initWithCFHTTPCookieStorage:storage] autorelease];
704
705     m_sessionWithCredentialStorageDelegate = adoptNS([[WKNetworkSessionDelegate alloc] initWithNetworkSession:*this withCredentials:true]);
706     m_sessionWithCredentialStorage = [NSURLSession sessionWithConfiguration:configuration delegate:static_cast<id>(m_sessionWithCredentialStorageDelegate.get()) delegateQueue:[NSOperationQueue mainQueue]];
707     LOG(NetworkSession, "Created NetworkSession with cookieAcceptPolicy %lu", configuration.HTTPCookieStorage.cookieAcceptPolicy);
708
709     configuration.URLCredentialStorage = nil;
710     configuration._shouldSkipPreferredClientCertificateLookup = YES;
711     // FIXME: https://bugs.webkit.org/show_bug.cgi?id=177394
712     // configuration.HTTPCookieStorage = nil;
713     // configuration.HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyNever;
714
715     m_statelessSessionDelegate = adoptNS([[WKNetworkSessionDelegate alloc] initWithNetworkSession:*this withCredentials:false]);
716     m_statelessSession = [NSURLSession sessionWithConfiguration:configuration delegate:static_cast<id>(m_statelessSessionDelegate.get()) delegateQueue:[NSOperationQueue mainQueue]];
717 }
718
719 NetworkSessionCocoa::~NetworkSessionCocoa()
720 {
721 }
722
723 void NetworkSessionCocoa::invalidateAndCancel()
724 {
725     NetworkSession::invalidateAndCancel();
726
727     [m_sessionWithCredentialStorage invalidateAndCancel];
728     [m_statelessSession invalidateAndCancel];
729     [m_sessionWithCredentialStorageDelegate sessionInvalidated];
730     [m_statelessSessionDelegate sessionInvalidated];
731 }
732
733 void NetworkSessionCocoa::clearCredentials()
734 {
735 #if !USE(CREDENTIAL_STORAGE_WITH_NETWORK_SESSION)
736     ASSERT(m_dataTaskMapWithCredentials.isEmpty());
737     ASSERT(m_dataTaskMapWithoutState.isEmpty());
738     ASSERT(m_downloadMap.isEmpty());
739     // FIXME: Use resetWithCompletionHandler instead.
740     m_sessionWithCredentialStorage = [NSURLSession sessionWithConfiguration:m_sessionWithCredentialStorage.get().configuration delegate:static_cast<id>(m_sessionWithCredentialStorageDelegate.get()) delegateQueue:[NSOperationQueue mainQueue]];
741     m_statelessSession = [NSURLSession sessionWithConfiguration:m_statelessSession.get().configuration delegate:static_cast<id>(m_statelessSessionDelegate.get()) delegateQueue:[NSOperationQueue mainQueue]];
742 #endif
743 }
744
745 NetworkDataTaskCocoa* NetworkSessionCocoa::dataTaskForIdentifier(NetworkDataTaskCocoa::TaskIdentifier taskIdentifier, WebCore::StoredCredentialsPolicy storedCredentialsPolicy)
746 {
747     ASSERT(RunLoop::isMain());
748     if (storedCredentialsPolicy == WebCore::StoredCredentialsPolicy::Use)
749         return m_dataTaskMapWithCredentials.get(taskIdentifier);
750     return m_dataTaskMapWithoutState.get(taskIdentifier);
751 }
752
753 NSURLSessionDownloadTask* NetworkSessionCocoa::downloadTaskWithResumeData(NSData* resumeData)
754 {
755     return [m_sessionWithCredentialStorage downloadTaskWithResumeData:resumeData];
756 }
757
758 void NetworkSessionCocoa::addDownloadID(NetworkDataTaskCocoa::TaskIdentifier taskIdentifier, DownloadID downloadID)
759 {
760 #ifndef NDEBUG
761     ASSERT(!m_downloadMap.contains(taskIdentifier));
762     for (auto idInMap : m_downloadMap.values())
763         ASSERT(idInMap != downloadID);
764 #endif
765     m_downloadMap.add(taskIdentifier, downloadID);
766 }
767
768 DownloadID NetworkSessionCocoa::downloadID(NetworkDataTaskCocoa::TaskIdentifier taskIdentifier)
769 {
770     ASSERT(m_downloadMap.get(taskIdentifier).downloadID());
771     return m_downloadMap.get(taskIdentifier);
772 }
773
774 DownloadID NetworkSessionCocoa::takeDownloadID(NetworkDataTaskCocoa::TaskIdentifier taskIdentifier)
775 {
776     auto downloadID = m_downloadMap.take(taskIdentifier);
777     return downloadID;
778 }
779
780 static bool certificatesMatch(SecTrustRef trust1, SecTrustRef trust2)
781 {
782     if (!trust1 || !trust2)
783         return false;
784
785     CFIndex count1 = SecTrustGetCertificateCount(trust1);
786     CFIndex count2 = SecTrustGetCertificateCount(trust2);
787     if (count1 != count2)
788         return false;
789
790     for (CFIndex i = 0; i < count1; i++) {
791         auto cert1 = SecTrustGetCertificateAtIndex(trust1, i);
792         auto cert2 = SecTrustGetCertificateAtIndex(trust2, i);
793         RELEASE_ASSERT(cert1);
794         RELEASE_ASSERT(cert2);
795         if (!CFEqual(cert1, cert2))
796             return false;
797     }
798
799     return true;
800 }
801
802 bool NetworkSessionCocoa::allowsSpecificHTTPSCertificateForHost(const WebCore::AuthenticationChallenge& challenge)
803 {
804     const String& host = challenge.protectionSpace().host();
805     NSArray *certificates = [NSURLRequest allowsSpecificHTTPSCertificateForHost:host];
806     if (!certificates)
807         return false;
808
809     bool requireServerCertificates = challenge.protectionSpace().authenticationScheme() == WebCore::ProtectionSpaceAuthenticationScheme::ProtectionSpaceAuthenticationSchemeServerTrustEvaluationRequested;
810     RetainPtr<SecPolicyRef> policy = adoptCF(SecPolicyCreateSSL(requireServerCertificates, host.createCFString().get()));
811
812     SecTrustRef trustRef = nullptr;
813     if (SecTrustCreateWithCertificates((CFArrayRef)certificates, policy.get(), &trustRef) != noErr)
814         return false;
815     RetainPtr<SecTrustRef> trust = adoptCF(trustRef);
816
817     return certificatesMatch(trust.get(), challenge.nsURLAuthenticationChallenge().protectionSpace.serverTrust);
818 }
819
820 }