Use move semantics for SandboxExtension::Handle
[WebKit-https.git] / Source / WebKit / NetworkProcess / cocoa / NetworkDataTaskCocoa.mm
1 /*
2  * Copyright (C) 2016-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 "NetworkDataTaskCocoa.h"
28
29 #if USE(NETWORK_SESSION)
30
31 #import "AuthenticationManager.h"
32 #import "Download.h"
33 #import "DownloadProxyMessages.h"
34 #import "Logging.h"
35 #import "NetworkProcess.h"
36 #import "NetworkSessionCocoa.h"
37 #import "SessionTracker.h"
38 #import "WebCoreArgumentCoders.h"
39 #import <WebCore/AuthenticationChallenge.h>
40 #import <WebCore/FileSystem.h>
41 #import <WebCore/NetworkStorageSession.h>
42 #import <WebCore/NotImplemented.h>
43 #import <WebCore/ResourceRequest.h>
44 #import <pal/spi/cf/CFNetworkSPI.h>
45 #import <wtf/MainThread.h>
46 #import <wtf/text/Base64.h>
47
48 namespace WebKit {
49
50 #if USE(CREDENTIAL_STORAGE_WITH_NETWORK_SESSION)
51 static void applyBasicAuthorizationHeader(WebCore::ResourceRequest& request, const WebCore::Credential& credential)
52 {
53     String authenticationHeader = "Basic " + base64Encode(String(credential.user() + ":" + credential.password()).utf8());
54     request.setHTTPHeaderField(WebCore::HTTPHeaderName::Authorization, authenticationHeader);
55 }
56 #endif
57
58 static float toNSURLSessionTaskPriority(WebCore::ResourceLoadPriority priority)
59 {
60     switch (priority) {
61     case WebCore::ResourceLoadPriority::VeryLow:
62         return 0;
63     case WebCore::ResourceLoadPriority::Low:
64         return 0.25;
65     case WebCore::ResourceLoadPriority::Medium:
66         return 0.5;
67     case WebCore::ResourceLoadPriority::High:
68         return 0.75;
69     case WebCore::ResourceLoadPriority::VeryHigh:
70         return 1;
71     }
72
73     ASSERT_NOT_REACHED();
74     return NSURLSessionTaskPriorityDefault;
75 }
76
77 void NetworkDataTaskCocoa::applySniffingPoliciesAndBindRequestToInferfaceIfNeeded(NSURLRequest*& nsRequest, bool shouldContentSniff, bool shouldContentEncodingSniff)
78 {
79 #if !PLATFORM(MAC)
80     UNUSED_PARAM(shouldContentEncodingSniff);
81 #elif __MAC_OS_X_VERSION_MIN_REQUIRED < 101302
82     shouldContentEncodingSniff = true;
83 #endif
84     auto& cocoaSession = static_cast<NetworkSessionCocoa&>(m_session.get());
85     if (shouldContentSniff && shouldContentEncodingSniff && cocoaSession.m_boundInterfaceIdentifier.isNull())
86         return;
87
88     auto mutableRequest = adoptNS([nsRequest mutableCopy]);
89
90 #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101302
91     if (!shouldContentEncodingSniff)
92         [mutableRequest _setProperty:@(YES) forKey:(NSString *)kCFURLRequestContentDecoderSkipURLCheck];
93 #endif
94
95     if (!shouldContentSniff)
96         [mutableRequest _setProperty:@(NO) forKey:(NSString *)_kCFURLConnectionPropertyShouldSniff];
97
98     if (!cocoaSession.m_boundInterfaceIdentifier.isNull())
99         [mutableRequest setBoundInterfaceIdentifier:cocoaSession.m_boundInterfaceIdentifier];
100
101     nsRequest = mutableRequest.autorelease();
102 }
103
104 NetworkDataTaskCocoa::NetworkDataTaskCocoa(NetworkSession& session, NetworkDataTaskClient& client, const WebCore::ResourceRequest& requestWithCredentials, uint64_t frameID, uint64_t pageID, WebCore::StoredCredentialsPolicy storedCredentialsPolicy, WebCore::ContentSniffingPolicy shouldContentSniff, WebCore::ContentEncodingSniffingPolicy shouldContentEncodingSniff, bool shouldClearReferrerOnHTTPSToHTTPRedirect, PreconnectOnly shouldPreconnectOnly)
105     : NetworkDataTask(session, client, requestWithCredentials, storedCredentialsPolicy, shouldClearReferrerOnHTTPSToHTTPRedirect)
106     , m_frameID(frameID)
107     , m_pageID(pageID)
108 {
109     if (m_scheduledFailureType != NoFailure)
110         return;
111
112     auto request = requestWithCredentials;
113     auto url = request.url();
114     if (storedCredentialsPolicy == WebCore::StoredCredentialsPolicy::Use && url.protocolIsInHTTPFamily()) {
115         m_user = url.user();
116         m_password = url.pass();
117         request.removeCredentials();
118         url = request.url();
119     
120 #if USE(CREDENTIAL_STORAGE_WITH_NETWORK_SESSION)
121         if (m_user.isEmpty() && m_password.isEmpty())
122             m_initialCredential = m_session->networkStorageSession().credentialStorage().get(m_partition, url);
123         else
124             m_session->networkStorageSession().credentialStorage().set(m_partition, WebCore::Credential(m_user, m_password, WebCore::CredentialPersistenceNone), url);
125 #endif
126     }
127
128 #if USE(CREDENTIAL_STORAGE_WITH_NETWORK_SESSION)
129     if (!m_initialCredential.isEmpty()) {
130         // FIXME: Support Digest authentication, and Proxy-Authorization.
131         applyBasicAuthorizationHeader(request, m_initialCredential);
132     }
133 #endif
134     
135     NSURLRequest *nsRequest = request.nsURLRequest(WebCore::UpdateHTTPBody);
136     applySniffingPoliciesAndBindRequestToInferfaceIfNeeded(nsRequest, shouldContentSniff == WebCore::SniffContent && !url.isLocalFile(), shouldContentEncodingSniff == WebCore::ContentEncodingSniffingPolicy::Sniff);
137
138     if (session.networkStorageSession().shouldBlockCookies(request)) {
139         storedCredentialsPolicy = WebCore::StoredCredentialsPolicy::DoNotUse;
140         m_storedCredentialsPolicy = WebCore::StoredCredentialsPolicy::DoNotUse;
141     }
142
143     auto& cocoaSession = static_cast<NetworkSessionCocoa&>(m_session.get());
144     if (storedCredentialsPolicy == WebCore::StoredCredentialsPolicy::Use) {
145         m_task = [cocoaSession.m_sessionWithCredentialStorage dataTaskWithRequest:nsRequest];
146         ASSERT(!cocoaSession.m_dataTaskMapWithCredentials.contains([m_task taskIdentifier]));
147         cocoaSession.m_dataTaskMapWithCredentials.add([m_task taskIdentifier], this);
148         LOG(NetworkSession, "%llu Creating stateless NetworkDataTask with URL %s", [m_task taskIdentifier], nsRequest.URL.absoluteString.UTF8String);
149     } else {
150         m_task = [cocoaSession.m_statelessSession dataTaskWithRequest:nsRequest];
151         ASSERT(!cocoaSession.m_dataTaskMapWithoutState.contains([m_task taskIdentifier]));
152         cocoaSession.m_dataTaskMapWithoutState.add([m_task taskIdentifier], this);
153         LOG(NetworkSession, "%llu Creating NetworkDataTask with URL %s", [m_task taskIdentifier], nsRequest.URL.absoluteString.UTF8String);
154     }
155
156     if (shouldPreconnectOnly == PreconnectOnly::Yes) {
157 #if ENABLE(SERVER_PRECONNECT)
158         m_task.get()._preconnect = true;
159 #else
160         ASSERT_NOT_REACHED();
161 #endif
162     }
163
164 #if HAVE(CFNETWORK_STORAGE_PARTITIONING)
165     if (storedCredentialsPolicy == WebCore::StoredCredentialsPolicy::Use) {
166         String storagePartition = session.networkStorageSession().cookieStoragePartition(request, m_frameID, m_pageID);
167         if (!storagePartition.isEmpty()) {
168             LOG(NetworkSession, "%llu Partitioning cookies for URL %s", [m_task taskIdentifier], nsRequest.URL.absoluteString.UTF8String);
169             m_task.get()._storagePartitionIdentifier = storagePartition;
170         }
171     }
172 #endif
173
174     if (WebCore::ResourceRequest::resourcePrioritiesEnabled())
175         m_task.get().priority = toNSURLSessionTaskPriority(request.priority());
176 }
177
178 NetworkDataTaskCocoa::~NetworkDataTaskCocoa()
179 {
180     if (!m_task)
181         return;
182
183     auto& cocoaSession = static_cast<NetworkSessionCocoa&>(m_session.get());
184     if (m_storedCredentialsPolicy == WebCore::StoredCredentialsPolicy::Use) {
185         ASSERT(cocoaSession.m_dataTaskMapWithCredentials.get([m_task taskIdentifier]) == this);
186         cocoaSession.m_dataTaskMapWithCredentials.remove([m_task taskIdentifier]);
187     } else {
188         ASSERT(cocoaSession.m_dataTaskMapWithoutState.get([m_task taskIdentifier]) == this);
189         cocoaSession.m_dataTaskMapWithoutState.remove([m_task taskIdentifier]);
190     }
191 }
192
193 void NetworkDataTaskCocoa::didSendData(uint64_t totalBytesSent, uint64_t totalBytesExpectedToSend)
194 {
195     if (m_client)
196         m_client->didSendData(totalBytesSent, totalBytesExpectedToSend);
197 }
198
199 void NetworkDataTaskCocoa::didReceiveChallenge(const WebCore::AuthenticationChallenge& challenge, ChallengeCompletionHandler&& completionHandler)
200 {
201     if (tryPasswordBasedAuthentication(challenge, completionHandler))
202         return;
203
204     if (m_client)
205         m_client->didReceiveChallenge(challenge, WTFMove(completionHandler));
206     else {
207         ASSERT_NOT_REACHED();
208         completionHandler(AuthenticationChallengeDisposition::PerformDefaultHandling, { });
209     }
210 }
211
212 void NetworkDataTaskCocoa::didCompleteWithError(const WebCore::ResourceError& error, const WebCore::NetworkLoadMetrics& networkLoadMetrics)
213 {
214     if (m_client)
215         m_client->didCompleteWithError(error, networkLoadMetrics);
216 }
217
218 void NetworkDataTaskCocoa::didReceiveData(Ref<WebCore::SharedBuffer>&& data)
219 {
220     if (m_client)
221         m_client->didReceiveData(WTFMove(data));
222 }
223
224 static bool shouldChangePartition(const String& requiredStoragePartition, const String& currentStoragePartition)
225 {
226     // The need for a partion change is according to the following:
227     //      currentStoragePartition:  null  ""    abc
228     // requiredStoragePartition: ""   false false true
229     //                           abc  true  true  false
230     //                           xyz  true  true  true
231     return !((requiredStoragePartition.isEmpty() && currentStoragePartition.isEmpty()) || currentStoragePartition == requiredStoragePartition);
232 }
233
234 void NetworkDataTaskCocoa::willPerformHTTPRedirection(WebCore::ResourceResponse&& redirectResponse, WebCore::ResourceRequest&& request, RedirectCompletionHandler&& completionHandler)
235 {
236     if (redirectResponse.httpStatusCode() == 307 || redirectResponse.httpStatusCode() == 308) {
237         ASSERT(m_lastHTTPMethod == request.httpMethod());
238         WebCore::FormData* body = m_firstRequest.httpBody();
239         if (body && !body->isEmpty() && !equalLettersIgnoringASCIICase(m_lastHTTPMethod, "get"))
240             request.setHTTPBody(body);
241         
242         String originalContentType = m_firstRequest.httpContentType();
243         if (!originalContentType.isEmpty())
244             request.setHTTPHeaderField(WebCore::HTTPHeaderName::ContentType, originalContentType);
245     }
246     
247     // Should not set Referer after a redirect from a secure resource to non-secure one.
248     if (m_shouldClearReferrerOnHTTPSToHTTPRedirect && !request.url().protocolIs("https") && WebCore::protocolIs(request.httpReferrer(), "https"))
249         request.clearHTTPReferrer();
250     
251     const auto& url = request.url();
252     m_user = url.user();
253     m_password = url.pass();
254     m_lastHTTPMethod = request.httpMethod();
255     request.removeCredentials();
256     
257     if (!protocolHostAndPortAreEqual(request.url(), redirectResponse.url())) {
258         // The network layer might carry over some headers from the original request that
259         // we want to strip here because the redirect is cross-origin.
260         request.clearHTTPAuthorization();
261         request.clearHTTPOrigin();
262 #if USE(CREDENTIAL_STORAGE_WITH_NETWORK_SESSION)
263     } else {
264         // Only consider applying authentication credentials if this is actually a redirect and the redirect
265         // URL didn't include credentials of its own.
266         if (m_user.isEmpty() && m_password.isEmpty() && !redirectResponse.isNull()) {
267             auto credential = m_session->networkStorageSession().credentialStorage().get(m_partition, request.url());
268             if (!credential.isEmpty()) {
269                 m_initialCredential = credential;
270
271                 // FIXME: Support Digest authentication, and Proxy-Authorization.
272                 applyBasicAuthorizationHeader(request, m_initialCredential);
273             }
274         }
275 #endif
276     }
277     
278 #if HAVE(CFNETWORK_STORAGE_PARTITIONING)
279     if (m_storedCredentialsPolicy == WebCore::StoredCredentialsPolicy::Use) {
280         String requiredStoragePartition = m_session->networkStorageSession().cookieStoragePartition(request, m_frameID, m_pageID);
281         if (shouldChangePartition(requiredStoragePartition, m_task.get()._storagePartitionIdentifier)) {
282             LOG(NetworkSession, "%llu %s cookies for redirected URL %s", [m_task taskIdentifier], (requiredStoragePartition.isEmpty() ? "Not partitioning" : "Partitioning"), request.url().string().utf8().data());
283             m_task.get()._storagePartitionIdentifier = requiredStoragePartition;
284         }
285     }
286 #endif
287
288     if (m_client)
289         m_client->willPerformHTTPRedirection(WTFMove(redirectResponse), WTFMove(request), WTFMove(completionHandler));
290     else {
291         ASSERT_NOT_REACHED();
292         completionHandler({ });
293     }
294 }
295
296 void NetworkDataTaskCocoa::setPendingDownloadLocation(const WTF::String& filename, SandboxExtension::Handle&& sandboxExtensionHandle, bool allowOverwrite)
297 {
298     NetworkDataTask::setPendingDownloadLocation(filename, { }, allowOverwrite);
299
300     ASSERT(!m_sandboxExtension);
301     m_sandboxExtension = SandboxExtension::create(WTFMove(sandboxExtensionHandle));
302     if (m_sandboxExtension)
303         m_sandboxExtension->consume();
304
305     m_task.get()._pathToDownloadTaskFile = m_pendingDownloadLocation;
306
307     if (allowOverwrite && WebCore::FileSystem::fileExists(m_pendingDownloadLocation))
308         WebCore::FileSystem::deleteFile(filename);
309 }
310
311 bool NetworkDataTaskCocoa::tryPasswordBasedAuthentication(const WebCore::AuthenticationChallenge& challenge, ChallengeCompletionHandler& completionHandler)
312 {
313     if (!challenge.protectionSpace().isPasswordBased())
314         return false;
315     
316     if (!m_user.isNull() && !m_password.isNull()) {
317         auto persistence = m_storedCredentialsPolicy == WebCore::StoredCredentialsPolicy::Use ? WebCore::CredentialPersistenceForSession : WebCore::CredentialPersistenceNone;
318         completionHandler(AuthenticationChallengeDisposition::UseCredential, WebCore::Credential(m_user, m_password, persistence));
319         m_user = String();
320         m_password = String();
321         return true;
322     }
323
324 #if USE(CREDENTIAL_STORAGE_WITH_NETWORK_SESSION)
325     if (m_storedCredentialsPolicy == WebCore::StoredCredentialsPolicy::Use) {
326         if (!m_initialCredential.isEmpty() || challenge.previousFailureCount()) {
327             // The stored credential wasn't accepted, stop using it.
328             // There is a race condition here, since a different credential might have already been stored by another ResourceHandle,
329             // but the observable effect should be very minor, if any.
330             m_session->networkStorageSession().credentialStorage().remove(m_partition, challenge.protectionSpace());
331         }
332
333         if (!challenge.previousFailureCount()) {
334             auto credential = m_session->networkStorageSession().credentialStorage().get(m_partition, challenge.protectionSpace());
335             if (!credential.isEmpty() && credential != m_initialCredential) {
336                 ASSERT(credential.persistence() == WebCore::CredentialPersistenceNone);
337                 if (challenge.failureResponse().httpStatusCode() == 401) {
338                     // Store the credential back, possibly adding it as a default for this directory.
339                     m_session->networkStorageSession().credentialStorage().set(m_partition, credential, challenge.protectionSpace(), challenge.failureResponse().url());
340                 }
341                 completionHandler(AuthenticationChallengeDisposition::UseCredential, credential);
342                 return true;
343             }
344         }
345     }
346 #endif
347
348     if (!challenge.proposedCredential().isEmpty() && !challenge.previousFailureCount()) {
349         completionHandler(AuthenticationChallengeDisposition::UseCredential, challenge.proposedCredential());
350         return true;
351     }
352     
353     return false;
354 }
355
356 void NetworkDataTaskCocoa::transferSandboxExtensionToDownload(Download& download)
357 {
358     download.setSandboxExtension(WTFMove(m_sandboxExtension));
359 }
360
361 String NetworkDataTaskCocoa::suggestedFilename() const
362 {
363     if (!m_suggestedFilename.isEmpty())
364         return m_suggestedFilename;
365     return m_task.get().response.suggestedFilename;
366 }
367
368 void NetworkDataTaskCocoa::cancel()
369 {
370     [m_task cancel];
371 }
372
373 void NetworkDataTaskCocoa::resume()
374 {
375     if (m_scheduledFailureType != NoFailure)
376         m_failureTimer.startOneShot(0_s);
377     [m_task resume];
378 }
379
380 void NetworkDataTaskCocoa::suspend()
381 {
382     if (m_failureTimer.isActive())
383         m_failureTimer.stop();
384     [m_task suspend];
385 }
386
387 NetworkDataTask::State NetworkDataTaskCocoa::state() const
388 {
389     switch ([m_task state]) {
390     case NSURLSessionTaskStateRunning:
391         return State::Running;
392     case NSURLSessionTaskStateSuspended:
393         return State::Suspended;
394     case NSURLSessionTaskStateCanceling:
395         return State::Canceling;
396     case NSURLSessionTaskStateCompleted:
397         return State::Completed;
398     }
399
400     ASSERT_NOT_REACHED();
401     return State::Completed;
402 }
403
404 WebCore::Credential serverTrustCredential(const WebCore::AuthenticationChallenge& challenge)
405 {
406     return WebCore::Credential([NSURLCredential credentialForTrust:challenge.nsURLAuthenticationChallenge().protectionSpace.serverTrust]);
407 }
408
409 }
410
411 #endif