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