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