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