Move URL from WebCore to WTF
[WebKit-https.git] / Source / WebKit / UIProcess / Cocoa / DownloadClient.mm
1 /*
2  * Copyright (C) 2014-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 "DownloadClient.h"
28
29 #if WK_API_ENABLED
30
31 #import "AuthenticationChallengeDisposition.h"
32 #import "AuthenticationChallengeProxy.h"
33 #import "AuthenticationDecisionListener.h"
34 #import "CompletionHandlerCallChecker.h"
35 #import "DownloadProxy.h"
36 #import "Logging.h"
37 #import "SystemPreviewController.h"
38 #import "WKNSURLAuthenticationChallenge.h"
39 #import "WKNSURLExtras.h"
40 #import "WebCredential.h"
41 #import "WebPageProxy.h"
42 #import "WebProcessProxy.h"
43 #import "_WKDownloadDelegate.h"
44 #import "_WKDownloadInternal.h"
45 #import <WebCore/FileSystem.h>
46 #import <WebCore/ResourceError.h>
47 #import <WebCore/ResourceResponse.h>
48 #import <wtf/BlockPtr.h>
49
50 namespace WebKit {
51
52 DownloadClient::DownloadClient(id <_WKDownloadDelegate> delegate)
53     : m_delegate(delegate)
54 {
55     m_delegateMethods.downloadDidStart = [delegate respondsToSelector:@selector(_downloadDidStart:)];
56     m_delegateMethods.downloadDidReceiveResponse = [delegate respondsToSelector:@selector(_download:didReceiveResponse:)];
57     m_delegateMethods.downloadDidReceiveData = [delegate respondsToSelector:@selector(_download:didReceiveData:)];
58     m_delegateMethods.downloadDecideDestinationWithSuggestedFilenameAllowOverwrite = [delegate respondsToSelector:@selector(_download:decideDestinationWithSuggestedFilename:allowOverwrite:)];
59     m_delegateMethods.downloadDecideDestinationWithSuggestedFilenameCompletionHandler = [delegate respondsToSelector:@selector(_download:decideDestinationWithSuggestedFilename:completionHandler:)];
60     m_delegateMethods.downloadDidFinish = [delegate respondsToSelector:@selector(_downloadDidFinish:)];
61     m_delegateMethods.downloadDidFail = [delegate respondsToSelector:@selector(_download:didFailWithError:)];
62     m_delegateMethods.downloadDidCancel = [delegate respondsToSelector:@selector(_downloadDidCancel:)];
63     m_delegateMethods.downloadDidReceiveServerRedirectToURL = [delegate respondsToSelector:@selector(_download:didReceiveServerRedirectToURL:)];
64     m_delegateMethods.downloadDidReceiveAuthenticationChallengeCompletionHandler = [delegate respondsToSelector:@selector(_download:didReceiveAuthenticationChallenge:completionHandler:)];
65     m_delegateMethods.downloadShouldDecodeSourceDataOfMIMEType = [delegate respondsToSelector:@selector(_download:shouldDecodeSourceDataOfMIMEType:)];
66     m_delegateMethods.downloadDidCreateDestination = [delegate respondsToSelector:@selector(_download:didCreateDestination:)];
67     m_delegateMethods.downloadProcessDidCrash = [delegate respondsToSelector:@selector(_downloadProcessDidCrash:)];
68 }
69
70 void DownloadClient::didStart(WebProcessPool&, DownloadProxy& downloadProxy)
71 {
72 #if USE(SYSTEM_PREVIEW)
73     if (downloadProxy.isSystemPreviewDownload()) {
74         if (auto* webPage = downloadProxy.originatingPage()) {
75             // FIXME: Update the MIME-type once it is known in the ResourceResponse.
76             webPage->systemPreviewController()->start("application/octet-stream"_s, downloadProxy.systemPreviewDownloadRect());
77         }
78         takeActivityToken(downloadProxy);
79         return;
80     }
81 #endif
82
83     if (m_delegateMethods.downloadDidStart)
84         [m_delegate _downloadDidStart:wrapper(downloadProxy)];
85 }
86
87 void DownloadClient::didReceiveResponse(WebProcessPool&, DownloadProxy& downloadProxy, const WebCore::ResourceResponse& response)
88 {
89 #if USE(SYSTEM_PREVIEW)
90     if (downloadProxy.isSystemPreviewDownload() && response.isSuccessful()) {
91         downloadProxy.setExpectedContentLength(response.expectedContentLength());
92         downloadProxy.setBytesLoaded(0);
93         if (auto* webPage = downloadProxy.originatingPage())
94             webPage->systemPreviewController()->updateProgress(0);
95         return;
96     }
97 #endif
98
99     if (m_delegateMethods.downloadDidReceiveResponse)
100         [m_delegate _download:wrapper(downloadProxy) didReceiveResponse:response.nsURLResponse()];
101 }
102
103 void DownloadClient::didReceiveData(WebProcessPool&, DownloadProxy& downloadProxy, uint64_t length)
104 {
105 #if USE(SYSTEM_PREVIEW)
106     if (downloadProxy.isSystemPreviewDownload()) {
107         downloadProxy.setBytesLoaded(downloadProxy.bytesLoaded() + length);
108         if (auto* webPage = downloadProxy.originatingPage())
109             webPage->systemPreviewController()->updateProgress(static_cast<float>(downloadProxy.bytesLoaded()) / downloadProxy.expectedContentLength());
110         return;
111     }
112 #endif
113
114     if (m_delegateMethods.downloadDidReceiveData)
115         [m_delegate _download:wrapper(downloadProxy) didReceiveData:length];
116 }
117
118 void DownloadClient::didReceiveAuthenticationChallenge(WebProcessPool&, DownloadProxy& downloadProxy, AuthenticationChallengeProxy& authenticationChallenge)
119 {
120     // FIXME: System Preview needs code here.
121     if (!m_delegateMethods.downloadDidReceiveAuthenticationChallengeCompletionHandler) {
122         authenticationChallenge.listener().completeChallenge(WebKit::AuthenticationChallengeDisposition::PerformDefaultHandling);
123         return;
124     }
125
126     [m_delegate _download:wrapper(downloadProxy) didReceiveAuthenticationChallenge:wrapper(authenticationChallenge) completionHandler:BlockPtr<void(NSURLSessionAuthChallengeDisposition, NSURLCredential *)>::fromCallable([authenticationChallenge = makeRef(authenticationChallenge), checker = CompletionHandlerCallChecker::create(m_delegate.get().get(), @selector(_download:didReceiveAuthenticationChallenge:completionHandler:))] (NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential) {
127         if (checker->completionHandlerHasBeenCalled())
128             return;
129         checker->didCallCompletionHandler();
130         switch (disposition) {
131         case NSURLSessionAuthChallengeUseCredential:
132             authenticationChallenge->listener().completeChallenge(AuthenticationChallengeDisposition::UseCredential, credential ? WebCore::Credential(credential) : WebCore::Credential());
133             break;
134         case NSURLSessionAuthChallengePerformDefaultHandling:
135             authenticationChallenge->listener().completeChallenge(AuthenticationChallengeDisposition::PerformDefaultHandling);
136             break;
137             
138         case NSURLSessionAuthChallengeCancelAuthenticationChallenge:
139             authenticationChallenge->listener().completeChallenge(AuthenticationChallengeDisposition::Cancel);
140             break;
141             
142         case NSURLSessionAuthChallengeRejectProtectionSpace:
143             authenticationChallenge->listener().completeChallenge(AuthenticationChallengeDisposition::RejectProtectionSpaceAndContinue);
144             break;
145             
146         default:
147             [NSException raise:NSInvalidArgumentException format:@"Invalid NSURLSessionAuthChallengeDisposition (%ld)", (long)disposition];
148         }
149     }).get()];
150 }
151
152 void DownloadClient::didCreateDestination(WebProcessPool&, DownloadProxy& downloadProxy, const String& destination)
153 {
154 #if USE(SYSTEM_PREVIEW)
155     if (downloadProxy.isSystemPreviewDownload()) {
156         downloadProxy.setDestinationFilename(destination);
157         return;
158     }
159 #endif
160
161     if (m_delegateMethods.downloadDidCreateDestination)
162         [m_delegate _download:wrapper(downloadProxy) didCreateDestination:destination];
163 }
164
165 void DownloadClient::processDidCrash(WebProcessPool&, DownloadProxy& downloadProxy)
166 {
167 #if USE(SYSTEM_PREVIEW)
168     if (downloadProxy.isSystemPreviewDownload()) {
169         if (auto* webPage = downloadProxy.originatingPage())
170             webPage->systemPreviewController()->cancel();
171         releaseActivityTokenIfNecessary(downloadProxy);
172         return;
173     }
174 #endif
175
176     if (m_delegateMethods.downloadProcessDidCrash)
177         [m_delegate _downloadProcessDidCrash:wrapper(downloadProxy)];
178 }
179
180 void DownloadClient::decideDestinationWithSuggestedFilename(WebProcessPool&, DownloadProxy& downloadProxy, const String& filename, Function<void(AllowOverwrite, String)>&& completionHandler)
181 {
182 #if USE(SYSTEM_PREVIEW)
183     if (downloadProxy.isSystemPreviewDownload()) {
184         NSString *temporaryDirectory = WebCore::FileSystem::createTemporaryDirectory(@"SystemPreviews");
185         NSString *destination = [temporaryDirectory stringByAppendingPathComponent:filename];
186         completionHandler(AllowOverwrite::Yes, destination);
187         return;
188     }
189 #endif
190
191     if (!m_delegateMethods.downloadDecideDestinationWithSuggestedFilenameAllowOverwrite && !m_delegateMethods.downloadDecideDestinationWithSuggestedFilenameCompletionHandler)
192         return completionHandler(AllowOverwrite::No, { });
193
194     if (m_delegateMethods.downloadDecideDestinationWithSuggestedFilenameAllowOverwrite) {
195         BOOL allowOverwrite = NO;
196         ALLOW_DEPRECATED_DECLARATIONS_BEGIN
197         NSString *destination = [m_delegate _download:wrapper(downloadProxy) decideDestinationWithSuggestedFilename:filename allowOverwrite:&allowOverwrite];
198         ALLOW_DEPRECATED_DECLARATIONS_END
199         completionHandler(allowOverwrite ? AllowOverwrite::Yes : AllowOverwrite::No, destination);
200     } else {
201         [m_delegate _download:wrapper(downloadProxy) decideDestinationWithSuggestedFilename:filename completionHandler:BlockPtr<void(BOOL, NSString *)>::fromCallable([checker = CompletionHandlerCallChecker::create(m_delegate.get().get(), @selector(_download:decideDestinationWithSuggestedFilename:completionHandler:)), completionHandler = WTFMove(completionHandler)] (BOOL allowOverwrite, NSString *destination) {
202             if (checker->completionHandlerHasBeenCalled())
203                 return;
204             checker->didCallCompletionHandler();
205             completionHandler(allowOverwrite ? AllowOverwrite::Yes : AllowOverwrite::No, destination);
206         }).get()];
207     }
208 }
209
210 void DownloadClient::didFinish(WebProcessPool&, DownloadProxy& downloadProxy)
211 {
212 #if USE(SYSTEM_PREVIEW)
213     if (downloadProxy.isSystemPreviewDownload()) {
214         if (auto* webPage = downloadProxy.originatingPage()) {
215             WTF::URL destinationURL = WTF::URL::fileURLWithFileSystemPath(downloadProxy.destinationFilename());
216             if (!destinationURL.fragmentIdentifier().length())
217                 destinationURL.setFragmentIdentifier(downloadProxy.request().url().fragmentIdentifier());
218             webPage->systemPreviewController()->finish(destinationURL);
219         }
220         releaseActivityTokenIfNecessary(downloadProxy);
221         return;
222     }
223 #endif
224
225     if (m_delegateMethods.downloadDidFinish)
226         [m_delegate _downloadDidFinish:wrapper(downloadProxy)];
227 }
228
229 void DownloadClient::didFail(WebProcessPool&, DownloadProxy& downloadProxy, const WebCore::ResourceError& error)
230 {
231 #if USE(SYSTEM_PREVIEW)
232     if (downloadProxy.isSystemPreviewDownload()) {
233         if (auto* webPage = downloadProxy.originatingPage())
234             webPage->systemPreviewController()->fail(error);
235         releaseActivityTokenIfNecessary(downloadProxy);
236         return;
237     }
238 #endif
239
240     if (m_delegateMethods.downloadDidFail)
241         [m_delegate _download:wrapper(downloadProxy) didFailWithError:error.nsError()];
242 }
243
244 void DownloadClient::didCancel(WebProcessPool&, DownloadProxy& downloadProxy)
245 {
246 #if USE(SYSTEM_PREVIEW)
247     if (downloadProxy.isSystemPreviewDownload()) {
248         if (auto* webPage = downloadProxy.originatingPage())
249             webPage->systemPreviewController()->cancel();
250         releaseActivityTokenIfNecessary(downloadProxy);
251         return;
252     }
253 #endif
254
255     if (m_delegateMethods.downloadDidCancel)
256         [m_delegate _downloadDidCancel:wrapper(downloadProxy)];
257 }
258
259 void DownloadClient::willSendRequest(WebProcessPool&, DownloadProxy& downloadProxy, WebCore::ResourceRequest&& request, const WebCore::ResourceResponse&, CompletionHandler<void(WebCore::ResourceRequest&&)>&& completionHandler)
260 {
261     if (m_delegateMethods.downloadDidReceiveServerRedirectToURL)
262         [m_delegate _download:wrapper(downloadProxy) didReceiveServerRedirectToURL:[NSURL _web_URLWithWTFString:request.url().string()]];
263
264     completionHandler(WTFMove(request));
265 }
266
267 #if USE(SYSTEM_PREVIEW)
268 void DownloadClient::takeActivityToken(DownloadProxy& downloadProxy)
269 {
270 #if PLATFORM(IOS_FAMILY)
271     if (auto* webPage = downloadProxy.originatingPage()) {
272         RELEASE_LOG_IF(webPage->isAlwaysOnLoggingAllowed(), ProcessSuspension, "%p - UIProcess is taking a background assertion because it is downloading a system preview", this);
273         ASSERT(!m_activityToken);
274         m_activityToken = webPage->process().throttler().backgroundActivityToken();
275     }
276 #else
277     UNUSED_PARAM(downloadProxy);
278 #endif
279 }
280
281 void DownloadClient::releaseActivityTokenIfNecessary(DownloadProxy& downloadProxy)
282 {
283 #if PLATFORM(IOS_FAMILY)
284     if (m_activityToken) {
285         RELEASE_LOG_IF(downloadProxy.originatingPage()->isAlwaysOnLoggingAllowed(), ProcessSuspension, "%p UIProcess is releasing a background assertion because a system preview download completed", this);
286         m_activityToken = nullptr;
287     }
288 #else
289     UNUSED_PARAM(downloadProxy);
290 #endif
291 }
292 #endif
293
294 } // namespace WebKit
295
296 #endif // WK_API_ENABLED