Ignore HSTS for partitioned, cross-origin subresource requests
[WebKit-https.git] / Source / WebKit / NetworkProcess / cocoa / NetworkSessionCocoa.mm
1 /*
2  * Copyright (C) 2015-2017 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 #if USE(NETWORK_SESSION)
30
31 #import "AuthenticationManager.h"
32 #import "DataReference.h"
33 #import "Download.h"
34 #import "LegacyCustomProtocolManager.h"
35 #import "Logging.h"
36 #import "NetworkLoad.h"
37 #import "NetworkProcess.h"
38 #import "NetworkSessionCreationParameters.h"
39 #import "SessionTracker.h"
40 #import <Foundation/NSURLSession.h>
41 #import <WebCore/Credential.h>
42 #import <WebCore/FormDataStreamMac.h>
43 #import <WebCore/FrameLoaderTypes.h>
44 #import <WebCore/NetworkStorageSession.h>
45 #import <WebCore/NotImplemented.h>
46 #import <WebCore/ResourceError.h>
47 #import <WebCore/ResourceRequest.h>
48 #import <WebCore/ResourceResponse.h>
49 #import <WebCore/SharedBuffer.h>
50 #import <WebCore/URL.h>
51 #import <WebCore/WebCoreURLResponse.h>
52 #import <pal/spi/cf/CFNetworkSPI.h>
53 #import <wtf/MainThread.h>
54 #import <wtf/NeverDestroyed.h>
55
56 using namespace WebKit;
57
58 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300) || (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
59 @interface NSURLSessionConfiguration (WKStaging)
60 @property (nullable, copy) NSSet *_suppressedAutoAddedHTTPHeaders;
61 @end
62 #endif
63
64 static NSURLSessionResponseDisposition toNSURLSessionResponseDisposition(WebCore::PolicyAction disposition)
65 {
66     switch (disposition) {
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::RejectProtectionSpace:
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:(nullable 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_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] autorelease];
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 NSURLRequest* updateIgnoreStrictTransportSecuritySettingIfNecessary(NSURLRequest *request, bool shouldIgnoreHSTS)
185 {
186     if ([request.URL.scheme isEqualToString:@"https"] && shouldIgnoreHSTS && [request _ignoreHSTS]) {
187         // The request was upgraded for some other reason than HSTS.
188         // Don't ignore HSTS to avoid the risk of another downgrade.
189         NSMutableURLRequest *nsMutableRequest = [[request mutableCopy] autorelease];
190         [nsMutableRequest _setIgnoreHSTS:NO];
191         return nsMutableRequest;
192     }
193     
194     if ([request.URL.scheme isEqualToString:@"http"] && [request _ignoreHSTS] != shouldIgnoreHSTS) {
195         NSMutableURLRequest *nsMutableRequest = [[request mutableCopy] autorelease];
196         [nsMutableRequest _setIgnoreHSTS:shouldIgnoreHSTS];
197         return nsMutableRequest;
198     }
199     
200     return request;
201 }
202 #endif
203
204 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler
205 {
206     auto taskIdentifier = task.taskIdentifier;
207     LOG(NetworkSession, "%llu willPerformHTTPRedirection from %s to %s", taskIdentifier, response.URL.absoluteString.UTF8String, request.URL.absoluteString.UTF8String);
208
209     if (auto* networkDataTask = [self existingTask:task]) {
210         auto completionHandlerCopy = Block_copy(completionHandler);
211         
212         bool shouldIgnoreHSTS = false;
213 #if HAVE(CFNETWORK_IGNORE_HSTS)
214         shouldIgnoreHSTS = [request _schemeWasUpgradedDueToDynamicHSTS] && !(WebCore::NetworkStorageSession::storageSession(_session->sessionID())->cookieStoragePartition(request)).isEmpty();
215         if (shouldIgnoreHSTS) {
216             request = downgradeRequest(request);
217             ASSERT([request.URL.scheme isEqualToString:@"http"]);
218             LOG(NetworkSession, "%llu Downgraded %s from https to http", taskIdentifier, request.URL.absoluteString.UTF8String);
219         }
220 #endif
221
222         networkDataTask->willPerformHTTPRedirection(response, request, [completionHandlerCopy, taskIdentifier, shouldIgnoreHSTS](auto& request) {
223 #if !LOG_DISABLED
224             LOG(NetworkSession, "%llu willPerformHTTPRedirection completionHandler (%s)", taskIdentifier, request.url().string().utf8().data());
225 #else
226             UNUSED_PARAM(taskIdentifier);
227 #endif
228             auto nsRequest = request.nsURLRequest(WebCore::UpdateHTTPBody);
229 #if HAVE(CFNETWORK_IGNORE_HSTS)
230             nsRequest = updateIgnoreStrictTransportSecuritySettingIfNecessary(nsRequest, shouldIgnoreHSTS);
231 #else
232             UNUSED_PARAM(shouldIgnoreHSTS);
233 #endif
234             completionHandlerCopy(nsRequest);
235             Block_release(completionHandlerCopy);
236         });
237     } else {
238         LOG(NetworkSession, "%llu willPerformHTTPRedirection completionHandler (nil)", taskIdentifier);
239         completionHandler(nil);
240     }
241 }
242
243 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask*)task _schemeUpgraded:(NSURLRequest*)request completionHandler:(void (^)(NSURLRequest*))completionHandler
244 {
245     auto taskIdentifier = task.taskIdentifier;
246     LOG(NetworkSession, "%llu _schemeUpgraded %s", taskIdentifier, request.URL.absoluteString.UTF8String);
247
248     bool shouldIgnoreHSTS = false;
249 #if HAVE(CFNETWORK_IGNORE_HSTS)
250     shouldIgnoreHSTS = [request _schemeWasUpgradedDueToDynamicHSTS] && !(WebCore::NetworkStorageSession::storageSession(_session->sessionID())->cookieStoragePartition(request)).isEmpty();
251     if (shouldIgnoreHSTS) {
252         request = downgradeRequest(request);
253         ASSERT([request.URL.scheme isEqualToString:@"http"]);
254         LOG(NetworkSession, "%llu Downgraded %s from https to http", taskIdentifier, request.URL.absoluteString.UTF8String);
255     }
256 #endif
257
258     if (auto* networkDataTask = [self existingTask:task]) {
259         auto completionHandlerCopy = Block_copy(completionHandler);
260         networkDataTask->willPerformHTTPRedirection(WebCore::synthesizeRedirectResponseIfNecessary([task currentRequest], request, nil), request, [completionHandlerCopy, taskIdentifier, shouldIgnoreHSTS](auto& request) {
261 #if !LOG_DISABLED
262             LOG(NetworkSession, "%llu _schemeUpgraded completionHandler (%s)", taskIdentifier, request.url().string().utf8().data());
263 #else
264             UNUSED_PARAM(taskIdentifier);
265 #endif
266             auto nsRequest = request.nsURLRequest(WebCore::UpdateHTTPBody);
267 #if HAVE(CFNETWORK_IGNORE_HSTS)
268             nsRequest = updateIgnoreStrictTransportSecuritySettingIfNecessary(nsRequest, shouldIgnoreHSTS);
269 #else
270             UNUSED_PARAM(shouldIgnoreHSTS);
271 #endif
272             completionHandlerCopy(nsRequest);
273             Block_release(completionHandlerCopy);
274         });
275     } else {
276         LOG(NetworkSession, "%llu _schemeUpgraded completionHandler (nil)", taskIdentifier);
277         completionHandler(nil);
278     }
279 }
280
281 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse * _Nullable cachedResponse))completionHandler
282 {
283     if (!_session) {
284         completionHandler(nil);
285         return;
286     }
287
288     // FIXME: remove if <rdar://problem/20001985> is ever resolved.
289     if ([proposedResponse.response respondsToSelector:@selector(allHeaderFields)]
290         && [[(id)proposedResponse.response allHeaderFields] objectForKey:@"Content-Range"])
291         completionHandler(nil);
292     else
293         completionHandler(proposedResponse);
294 }
295
296 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
297 {
298     auto taskIdentifier = task.taskIdentifier;
299     LOG(NetworkSession, "%llu didReceiveChallenge", taskIdentifier);
300     
301     // Proxy authentication is handled by CFNetwork internally. We can get here if the user cancels
302     // CFNetwork authentication dialog, and we shouldn't ask the client to display another one in that case.
303     if (challenge.protectionSpace.isProxy) {
304         completionHandler(NSURLSessionAuthChallengeUseCredential, nil);
305         return;
306     }
307
308     // Handle server trust evaluation at platform-level if requested, for performance reasons.
309     if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust] && !NetworkProcess::singleton().canHandleHTTPSServerTrustEvaluation()) {
310         if (NetworkSessionCocoa::allowsSpecificHTTPSCertificateForHost(challenge))
311             completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
312         else
313             completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
314         return;
315     }
316
317     if (auto* networkDataTask = [self existingTask:task]) {
318         WebCore::AuthenticationChallenge authenticationChallenge(challenge);
319         auto completionHandlerCopy = Block_copy(completionHandler);
320         auto sessionID = _session->sessionID();
321         auto challengeCompletionHandler = [completionHandlerCopy, sessionID, authenticationChallenge, taskIdentifier, partition = networkDataTask->partition()](WebKit::AuthenticationChallengeDisposition disposition, const WebCore::Credential& credential)
322         {
323 #if !LOG_DISABLED
324             LOG(NetworkSession, "%llu didReceiveChallenge completionHandler %d", taskIdentifier, disposition);
325 #else
326             UNUSED_PARAM(taskIdentifier);
327 #endif
328 #if !USE(CREDENTIAL_STORAGE_WITH_NETWORK_SESSION)
329             UNUSED_PARAM(sessionID);
330             UNUSED_PARAM(authenticationChallenge);
331 #else
332             if (credential.persistence() == WebCore::CredentialPersistenceForSession && authenticationChallenge.protectionSpace().isPasswordBased()) {
333
334                 WebCore::Credential nonPersistentCredential(credential.user(), credential.password(), WebCore::CredentialPersistenceNone);
335                 WebCore::URL urlToStore;
336                 if (authenticationChallenge.failureResponse().httpStatusCode() == 401)
337                     urlToStore = authenticationChallenge.failureResponse().url();
338                 if (auto storageSession = WebCore::NetworkStorageSession::storageSession(sessionID))
339                     storageSession->credentialStorage().set(partition, nonPersistentCredential, authenticationChallenge.protectionSpace(), urlToStore);
340                 else
341                     ASSERT_NOT_REACHED();
342
343                 completionHandlerCopy(toNSURLSessionAuthChallengeDisposition(disposition), nonPersistentCredential.nsCredential());
344             } else
345 #endif
346                 completionHandlerCopy(toNSURLSessionAuthChallengeDisposition(disposition), credential.nsCredential());
347             Block_release(completionHandlerCopy);
348         };
349         networkDataTask->didReceiveChallenge(challenge, WTFMove(challengeCompletionHandler));
350     } else {
351         auto downloadID = _session->downloadID(task.taskIdentifier);
352         if (downloadID.downloadID()) {
353             if (auto* download = WebKit::NetworkProcess::singleton().downloadManager().download(downloadID)) {
354                 // Received an authentication challenge for a download being resumed.
355                 WebCore::AuthenticationChallenge authenticationChallenge { challenge };
356                 auto completionHandlerCopy = Block_copy(completionHandler);
357                 auto challengeCompletionHandler = [completionHandlerCopy, authenticationChallenge](WebKit::AuthenticationChallengeDisposition disposition, const WebCore::Credential& credential) {
358                     completionHandlerCopy(toNSURLSessionAuthChallengeDisposition(disposition), credential.nsCredential());
359                     Block_release(completionHandlerCopy);
360                 };
361                 download->didReceiveChallenge(challenge, WTFMove(challengeCompletionHandler));
362                 return;
363             }
364         }
365         LOG(NetworkSession, "%llu didReceiveChallenge completionHandler (cancel)", taskIdentifier);
366         completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
367     }
368 }
369
370 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
371 {
372     if (!_session)
373         return;
374
375     LOG(NetworkSession, "%llu didCompleteWithError %@", task.taskIdentifier, error);
376     if (auto* networkDataTask = [self existingTask:task])
377         networkDataTask->didCompleteWithError(error, networkDataTask->networkLoadMetrics());
378     else if (error) {
379         auto downloadID = _session->takeDownloadID(task.taskIdentifier);
380         if (downloadID.downloadID()) {
381             if (auto* download = WebKit::NetworkProcess::singleton().downloadManager().download(downloadID)) {
382                 NSData *resumeData = nil;
383                 if (id userInfo = error.userInfo) {
384                     if ([userInfo isKindOfClass:[NSDictionary class]])
385                         resumeData = userInfo[@"NSURLSessionDownloadTaskResumeData"];
386                 }
387                 
388                 if (resumeData && [resumeData isKindOfClass:[NSData class]])
389                     download->didFail(error, { static_cast<const uint8_t*>(resumeData.bytes), resumeData.length });
390                 else
391                     download->didFail(error, { });
392             }
393         }
394     }
395 }
396
397 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
398 {
399     LOG(NetworkSession, "%llu didFinishCollectingMetrics", task.taskIdentifier);
400     if (auto* networkDataTask = [self existingTask:task]) {
401         NSURLSessionTaskTransactionMetrics *m = metrics.transactionMetrics.lastObject;
402         NSDate *fetchStartDate = m.fetchStartDate;
403         NSTimeInterval domainLookupStartInterval = m.domainLookupStartDate ? [m.domainLookupStartDate timeIntervalSinceDate:fetchStartDate] : -1;
404         NSTimeInterval domainLookupEndInterval = m.domainLookupEndDate ? [m.domainLookupEndDate timeIntervalSinceDate:fetchStartDate] : -1;
405         NSTimeInterval connectStartInterval = m.connectStartDate ? [m.connectStartDate timeIntervalSinceDate:fetchStartDate] : -1;
406         NSTimeInterval secureConnectionStartInterval = m.secureConnectionStartDate ? [m.secureConnectionStartDate timeIntervalSinceDate:fetchStartDate] : -1;
407         NSTimeInterval connectEndInterval = m.connectEndDate ? [m.connectEndDate timeIntervalSinceDate:fetchStartDate] : -1;
408         NSTimeInterval requestStartInterval = [m.requestStartDate timeIntervalSinceDate:fetchStartDate];
409         NSTimeInterval responseStartInterval = [m.responseStartDate timeIntervalSinceDate:fetchStartDate];
410         NSTimeInterval responseEndInterval = [m.responseEndDate timeIntervalSinceDate:fetchStartDate];
411
412         auto& networkLoadMetrics = networkDataTask->networkLoadMetrics();
413         networkLoadMetrics.domainLookupStart = Seconds(domainLookupStartInterval);
414         networkLoadMetrics.domainLookupEnd = Seconds(domainLookupEndInterval);
415         networkLoadMetrics.connectStart = Seconds(connectStartInterval);
416         networkLoadMetrics.secureConnectionStart = Seconds(secureConnectionStartInterval);
417         networkLoadMetrics.connectEnd = Seconds(connectEndInterval);
418         networkLoadMetrics.requestStart = Seconds(requestStartInterval);
419         networkLoadMetrics.responseStart = Seconds(responseStartInterval);
420         networkLoadMetrics.responseEnd = Seconds(responseEndInterval);
421         networkLoadMetrics.markComplete();
422         networkLoadMetrics.protocol = String(m.networkProtocolName);
423
424         if (networkDataTask->shouldCaptureExtraNetworkLoadMetrics()) {
425             networkLoadMetrics.priority = toNetworkLoadPriority(task.priority);
426
427 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300) || (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
428             networkLoadMetrics.remoteAddress = String(m._remoteAddressAndPort);
429             networkLoadMetrics.connectionIdentifier = String([m._connectionIdentifier UUIDString]);
430 #endif
431
432             __block WebCore::HTTPHeaderMap requestHeaders;
433             [m.request.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(NSString *name, NSString *value, BOOL *) {
434                 requestHeaders.set(String(name), String(value));
435             }];
436             networkLoadMetrics.requestHeaders = WTFMove(requestHeaders);
437
438 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300) || (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
439             uint64_t requestHeaderBytesSent = 0;
440             uint64_t responseHeaderBytesReceived = 0;
441             uint64_t responseBodyBytesReceived = 0;
442             uint64_t responseBodyDecodedSize = 0;
443
444             for (NSURLSessionTaskTransactionMetrics *transactionMetrics in metrics.transactionMetrics) {
445                 requestHeaderBytesSent += transactionMetrics._requestHeaderBytesSent;
446                 responseHeaderBytesReceived += transactionMetrics._responseHeaderBytesReceived;
447                 responseBodyBytesReceived += transactionMetrics._responseBodyBytesReceived;
448                 responseBodyDecodedSize += transactionMetrics._responseBodyBytesDecoded ? transactionMetrics._responseBodyBytesDecoded : transactionMetrics._responseBodyBytesReceived;
449             }
450
451             networkLoadMetrics.requestHeaderBytesSent = requestHeaderBytesSent;
452             networkLoadMetrics.requestBodyBytesSent = task.countOfBytesSent;
453             networkLoadMetrics.responseHeaderBytesReceived = responseHeaderBytesReceived;
454             networkLoadMetrics.responseBodyBytesReceived = responseBodyBytesReceived;
455             networkLoadMetrics.responseBodyDecodedSize = responseBodyDecodedSize;
456 #endif
457         }
458     }
459 }
460
461 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
462 {
463     auto taskIdentifier = dataTask.taskIdentifier;
464     LOG(NetworkSession, "%llu didReceiveResponse", taskIdentifier);
465     if (auto* networkDataTask = [self existingTask:dataTask]) {
466         ASSERT(RunLoop::isMain());
467         
468         // Avoid MIME type sniffing if the response comes back as 304 Not Modified.
469         int statusCode = [response respondsToSelector:@selector(statusCode)] ? [(id)response statusCode] : 0;
470         if (statusCode != 304) {
471             bool isMainResourceLoad = networkDataTask->firstRequest().requester() == WebCore::ResourceRequest::Requester::Main;
472             WebCore::adjustMIMETypeIfNecessary(response._CFURLResponse, isMainResourceLoad);
473         }
474
475         WebCore::ResourceResponse resourceResponse(response);
476         // Lazy initialization is not helpful in the WebKit2 case because we always end up initializing
477         // all the fields when sending the response to the WebContent process over IPC.
478         resourceResponse.disableLazyInitialization();
479
480         // FIXME: This cannot be eliminated until other code no longer relies on ResourceResponse's
481         // NetworkLoadMetrics. For example, PerformanceTiming.
482         copyTimingData([dataTask _timingData], resourceResponse.deprecatedNetworkLoadMetrics());
483
484         auto completionHandlerCopy = Block_copy(completionHandler);
485         networkDataTask->didReceiveResponse(WTFMove(resourceResponse), [completionHandlerCopy, taskIdentifier](WebCore::PolicyAction policyAction) {
486 #if !LOG_DISABLED
487             LOG(NetworkSession, "%llu didReceiveResponse completionHandler (%d)", taskIdentifier, policyAction);
488 #else
489             UNUSED_PARAM(taskIdentifier);
490 #endif
491             completionHandlerCopy(toNSURLSessionResponseDisposition(policyAction));
492             Block_release(completionHandlerCopy);
493         });
494     } else {
495         LOG(NetworkSession, "%llu didReceiveResponse completionHandler (cancel)", taskIdentifier);
496         completionHandler(NSURLSessionResponseCancel);
497     }
498 }
499
500 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
501 {
502     if (auto* networkDataTask = [self existingTask:dataTask])
503         networkDataTask->didReceiveData(WebCore::SharedBuffer::create(data));
504 }
505
506 - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
507 {
508     if (!_session)
509         return;
510
511     auto downloadID = _session->takeDownloadID([downloadTask taskIdentifier]);
512     if (auto* download = WebKit::NetworkProcess::singleton().downloadManager().download(downloadID))
513         download->didFinish();
514 }
515
516 - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
517 {
518     if (!_session)
519         return;
520
521     ASSERT_WITH_MESSAGE(![self existingTask:downloadTask], "The NetworkDataTask should be destroyed immediately after didBecomeDownloadTask returns");
522
523     auto downloadID = _session->downloadID([downloadTask taskIdentifier]);
524     if (auto* download = WebKit::NetworkProcess::singleton().downloadManager().download(downloadID))
525         download->didReceiveData(bytesWritten);
526 }
527
528 - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
529 {
530     if (!_session)
531         return;
532
533     notImplemented();
534 }
535
536 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
537 {
538     if (auto* networkDataTask = [self existingTask:dataTask]) {
539         Ref<NetworkDataTaskCocoa> protectedNetworkDataTask(*networkDataTask);
540         auto downloadID = networkDataTask->pendingDownloadID();
541         auto& downloadManager = WebKit::NetworkProcess::singleton().downloadManager();
542         auto download = std::make_unique<WebKit::Download>(downloadManager, downloadID, downloadTask, _session->sessionID(), networkDataTask->suggestedFilename());
543         networkDataTask->transferSandboxExtensionToDownload(*download);
544         ASSERT(WebCore::fileExists(networkDataTask->pendingDownloadLocation()));
545         download->didCreateDestination(networkDataTask->pendingDownloadLocation());
546         downloadManager.dataTaskBecameDownloadTask(downloadID, WTFMove(download));
547
548         _session->addDownloadID([downloadTask taskIdentifier], downloadID);
549     }
550 }
551
552 @end
553
554 namespace WebKit {
555     
556 static bool usesNetworkCache { false };
557
558 #if !ASSERT_DISABLED
559 static bool sessionsCreated = false;
560 #endif
561
562 static NSURLSessionConfiguration *configurationForSessionID(const PAL::SessionID& session)
563 {
564     if (session.isEphemeral()) {
565         NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
566         configuration._shouldSkipPreferredClientCertificateLookup = YES;
567         return configuration;
568     }
569     return [NSURLSessionConfiguration defaultSessionConfiguration];
570 }
571
572 static RetainPtr<CFDataRef>& globalSourceApplicationAuditTokenData()
573 {
574     static NeverDestroyed<RetainPtr<CFDataRef>> sourceApplicationAuditTokenData;
575     return sourceApplicationAuditTokenData.get();
576 }
577
578 static String& globalSourceApplicationBundleIdentifier()
579 {
580     static NeverDestroyed<String> sourceApplicationBundleIdentifier;
581     return sourceApplicationBundleIdentifier.get();
582 }
583
584 static String& globalSourceApplicationSecondaryIdentifier()
585 {
586     static NeverDestroyed<String> sourceApplicationSecondaryIdentifier;
587     return sourceApplicationSecondaryIdentifier.get();
588 }
589
590 #if PLATFORM(IOS)
591 static String& globalCTDataConnectionServiceType()
592 {
593     static NeverDestroyed<String> ctDataConnectionServiceType;
594     return ctDataConnectionServiceType.get();
595 }
596 #endif
597     
598 void NetworkSessionCocoa::setSourceApplicationAuditTokenData(RetainPtr<CFDataRef>&& data)
599 {
600     ASSERT(!sessionsCreated);
601     globalSourceApplicationAuditTokenData() = data;
602 }
603
604 void NetworkSessionCocoa::setSourceApplicationBundleIdentifier(const String& identifier)
605 {
606     ASSERT(!sessionsCreated);
607     globalSourceApplicationBundleIdentifier() = identifier;
608 }
609
610 void NetworkSessionCocoa::setSourceApplicationSecondaryIdentifier(const String& identifier)
611 {
612     ASSERT(!sessionsCreated);
613     globalSourceApplicationSecondaryIdentifier() = identifier;
614 }
615
616 void NetworkSessionCocoa::setUsesNetworkCache(bool value)
617 {
618     usesNetworkCache = value;
619 }
620
621 #if PLATFORM(IOS)
622 void NetworkSessionCocoa::setCTDataConnectionServiceType(const String& type)
623 {
624     ASSERT(!sessionsCreated);
625     globalCTDataConnectionServiceType() = type;
626 }
627 #endif
628
629 Ref<NetworkSession> NetworkSessionCocoa::create(NetworkSessionCreationParameters&& parameters)
630 {
631     return adoptRef(*new NetworkSessionCocoa(WTFMove(parameters)));
632 }
633
634 NetworkSessionCocoa::NetworkSessionCocoa(NetworkSessionCreationParameters&& parameters)
635     : NetworkSession(parameters.sessionID)
636     , m_boundInterfaceIdentifier(parameters.boundInterfaceIdentifier)
637 {
638     relaxAdoptionRequirement();
639
640 #if !ASSERT_DISABLED
641     sessionsCreated = true;
642 #endif
643
644     NSURLSessionConfiguration *configuration = configurationForSessionID(m_sessionID);
645
646 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300) || (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
647     // Without this, CFNetwork would sometimes add a Content-Type header to our requests (rdar://problem/34748470).
648     if ([configuration respondsToSelector:@selector(_suppressedAutoAddedHTTPHeaders)])
649         configuration._suppressedAutoAddedHTTPHeaders = [NSSet setWithObject:@"Content-Type"];
650 #endif
651
652     if (parameters.allowsCellularAccess == AllowsCellularAccess::No)
653         configuration.allowsCellularAccess = NO;
654
655     if (usesNetworkCache)
656         configuration.URLCache = nil;
657
658     if (auto& data = globalSourceApplicationAuditTokenData())
659         configuration._sourceApplicationAuditTokenData = (NSData *)data.get();
660
661     auto& sourceApplicationBundleIdentifier = globalSourceApplicationBundleIdentifier();
662     if (!sourceApplicationBundleIdentifier.isEmpty()) {
663         configuration._sourceApplicationBundleIdentifier = sourceApplicationBundleIdentifier;
664         configuration._sourceApplicationAuditTokenData = nil;
665     }
666
667     auto& sourceApplicationSecondaryIdentifier = globalSourceApplicationSecondaryIdentifier();
668     if (!sourceApplicationSecondaryIdentifier.isEmpty())
669         configuration._sourceApplicationSecondaryIdentifier = sourceApplicationSecondaryIdentifier;
670
671 #if PLATFORM(IOS)
672     auto& ctDataConnectionServiceType = globalCTDataConnectionServiceType();
673     if (!ctDataConnectionServiceType.isEmpty())
674         configuration._CTDataConnectionServiceType = ctDataConnectionServiceType;
675 #endif
676
677     if (parameters.legacyCustomProtocolManager)
678         parameters.legacyCustomProtocolManager->registerProtocolClass(configuration);
679     
680 #if HAVE(TIMINGDATAOPTIONS)
681     configuration._timingDataOptions = _TimingDataOptionsEnableW3CNavigationTiming;
682 #else
683     setCollectsTimingData();
684 #endif
685
686     auto* storageSession = WebCore::NetworkStorageSession::storageSession(parameters.sessionID);
687     RELEASE_ASSERT(storageSession);
688     if (CFHTTPCookieStorageRef storage = storageSession->cookieStorage().get())
689         configuration.HTTPCookieStorage = [[[NSHTTPCookieStorage alloc] _initWithCFHTTPCookieStorage:storage] autorelease];
690
691     m_sessionWithCredentialStorageDelegate = adoptNS([[WKNetworkSessionDelegate alloc] initWithNetworkSession:*this withCredentials:true]);
692     m_sessionWithCredentialStorage = [NSURLSession sessionWithConfiguration:configuration delegate:static_cast<id>(m_sessionWithCredentialStorageDelegate.get()) delegateQueue:[NSOperationQueue mainQueue]];
693     LOG(NetworkSession, "Created NetworkSession with cookieAcceptPolicy %lu", configuration.HTTPCookieStorage.cookieAcceptPolicy);
694
695     configuration.URLCredentialStorage = nil;
696     configuration._shouldSkipPreferredClientCertificateLookup = YES;
697     // FIXME: https://bugs.webkit.org/show_bug.cgi?id=177394
698     // configuration.HTTPCookieStorage = nil;
699     // configuration.HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyNever;
700
701     m_statelessSessionDelegate = adoptNS([[WKNetworkSessionDelegate alloc] initWithNetworkSession:*this withCredentials:false]);
702     m_statelessSession = [NSURLSession sessionWithConfiguration:configuration delegate:static_cast<id>(m_statelessSessionDelegate.get()) delegateQueue:[NSOperationQueue mainQueue]];
703 }
704
705 NetworkSessionCocoa::~NetworkSessionCocoa()
706 {
707 }
708
709 void NetworkSessionCocoa::invalidateAndCancel()
710 {
711     NetworkSession::invalidateAndCancel();
712
713     [m_sessionWithCredentialStorage invalidateAndCancel];
714     [m_statelessSession invalidateAndCancel];
715     [m_sessionWithCredentialStorageDelegate sessionInvalidated];
716     [m_statelessSessionDelegate sessionInvalidated];
717 }
718
719 void NetworkSessionCocoa::clearCredentials()
720 {
721 #if !USE(CREDENTIAL_STORAGE_WITH_NETWORK_SESSION)
722     ASSERT(m_dataTaskMapWithCredentials.isEmpty());
723     ASSERT(m_dataTaskMapWithoutState.isEmpty());
724     ASSERT(m_downloadMap.isEmpty());
725     // FIXME: Use resetWithCompletionHandler instead.
726     m_sessionWithCredentialStorage = [NSURLSession sessionWithConfiguration:m_sessionWithCredentialStorage.get().configuration delegate:static_cast<id>(m_sessionWithCredentialStorageDelegate.get()) delegateQueue:[NSOperationQueue mainQueue]];
727     m_statelessSession = [NSURLSession sessionWithConfiguration:m_statelessSession.get().configuration delegate:static_cast<id>(m_statelessSessionDelegate.get()) delegateQueue:[NSOperationQueue mainQueue]];
728 #endif
729 }
730
731 NetworkDataTaskCocoa* NetworkSessionCocoa::dataTaskForIdentifier(NetworkDataTaskCocoa::TaskIdentifier taskIdentifier, WebCore::StoredCredentialsPolicy storedCredentialsPolicy)
732 {
733     ASSERT(RunLoop::isMain());
734     if (storedCredentialsPolicy == WebCore::StoredCredentialsPolicy::Use)
735         return m_dataTaskMapWithCredentials.get(taskIdentifier);
736     return m_dataTaskMapWithoutState.get(taskIdentifier);
737 }
738
739 NSURLSessionDownloadTask* NetworkSessionCocoa::downloadTaskWithResumeData(NSData* resumeData)
740 {
741     return [m_sessionWithCredentialStorage downloadTaskWithResumeData:resumeData];
742 }
743
744 void NetworkSessionCocoa::addDownloadID(NetworkDataTaskCocoa::TaskIdentifier taskIdentifier, DownloadID downloadID)
745 {
746 #ifndef NDEBUG
747     ASSERT(!m_downloadMap.contains(taskIdentifier));
748     for (auto idInMap : m_downloadMap.values())
749         ASSERT(idInMap != downloadID);
750 #endif
751     m_downloadMap.add(taskIdentifier, downloadID);
752 }
753
754 DownloadID NetworkSessionCocoa::downloadID(NetworkDataTaskCocoa::TaskIdentifier taskIdentifier)
755 {
756     ASSERT(m_downloadMap.get(taskIdentifier).downloadID());
757     return m_downloadMap.get(taskIdentifier);
758 }
759
760 DownloadID NetworkSessionCocoa::takeDownloadID(NetworkDataTaskCocoa::TaskIdentifier taskIdentifier)
761 {
762     auto downloadID = m_downloadMap.take(taskIdentifier);
763     return downloadID;
764 }
765
766 static bool certificatesMatch(SecTrustRef trust1, SecTrustRef trust2)
767 {
768     if (!trust1 || !trust2)
769         return false;
770
771     CFIndex count1 = SecTrustGetCertificateCount(trust1);
772     CFIndex count2 = SecTrustGetCertificateCount(trust2);
773     if (count1 != count2)
774         return false;
775
776     for (CFIndex i = 0; i < count1; i++) {
777         auto cert1 = SecTrustGetCertificateAtIndex(trust1, i);
778         auto cert2 = SecTrustGetCertificateAtIndex(trust2, i);
779         RELEASE_ASSERT(cert1);
780         RELEASE_ASSERT(cert2);
781         if (!CFEqual(cert1, cert2))
782             return false;
783     }
784
785     return true;
786 }
787
788 bool NetworkSessionCocoa::allowsSpecificHTTPSCertificateForHost(const WebCore::AuthenticationChallenge& challenge)
789 {
790     const String& host = challenge.protectionSpace().host();
791     NSArray *certificates = [NSURLRequest allowsSpecificHTTPSCertificateForHost:host];
792     if (!certificates)
793         return false;
794
795     bool requireServerCertificates = challenge.protectionSpace().authenticationScheme() == WebCore::ProtectionSpaceAuthenticationScheme::ProtectionSpaceAuthenticationSchemeServerTrustEvaluationRequested;
796     RetainPtr<SecPolicyRef> policy = adoptCF(SecPolicyCreateSSL(requireServerCertificates, host.createCFString().get()));
797
798     SecTrustRef trustRef = nullptr;
799     if (SecTrustCreateWithCertificates((CFArrayRef)certificates, policy.get(), &trustRef) != noErr)
800         return false;
801     RetainPtr<SecTrustRef> trust = adoptCF(trustRef);
802
803     return certificatesMatch(trust.get(), challenge.nsURLAuthenticationChallenge().protectionSpace.serverTrust);
804 }
805
806 }
807
808 #endif