blob: 36ede7275af379c7e7dee0162bd30a616f0e4665 [file] [log] [blame]
/*
* Copyright (C) 2014-2018 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "DownloadClient.h"
#import "AuthenticationChallengeDisposition.h"
#import "AuthenticationChallengeProxy.h"
#import "AuthenticationDecisionListener.h"
#import "CompletionHandlerCallChecker.h"
#import "DownloadProxy.h"
#import "Logging.h"
#import "SystemPreviewController.h"
#import "WKNSURLAuthenticationChallenge.h"
#import "WKNSURLExtras.h"
#import "WebCredential.h"
#import "WebPageProxy.h"
#import "WebProcessProxy.h"
#import "_WKDownloadDelegate.h"
#import "_WKDownloadInternal.h"
#import <WebCore/ResourceError.h>
#import <WebCore/ResourceResponse.h>
#import <wtf/BlockPtr.h>
#import <wtf/FileSystem.h>
namespace WebKit {
DownloadClient::DownloadClient(id <_WKDownloadDelegate> delegate)
: m_delegate(delegate)
{
m_delegateMethods.downloadDidStart = [delegate respondsToSelector:@selector(_downloadDidStart:)];
m_delegateMethods.downloadDidReceiveResponse = [delegate respondsToSelector:@selector(_download:didReceiveResponse:)];
m_delegateMethods.downloadDidReceiveData = [delegate respondsToSelector:@selector(_download:didReceiveData:)];
m_delegateMethods.downloadDecideDestinationWithSuggestedFilenameAllowOverwrite = [delegate respondsToSelector:@selector(_download:decideDestinationWithSuggestedFilename:allowOverwrite:)];
m_delegateMethods.downloadDecideDestinationWithSuggestedFilenameCompletionHandler = [delegate respondsToSelector:@selector(_download:decideDestinationWithSuggestedFilename:completionHandler:)];
m_delegateMethods.downloadDidFinish = [delegate respondsToSelector:@selector(_downloadDidFinish:)];
m_delegateMethods.downloadDidFail = [delegate respondsToSelector:@selector(_download:didFailWithError:)];
m_delegateMethods.downloadDidCancel = [delegate respondsToSelector:@selector(_downloadDidCancel:)];
m_delegateMethods.downloadDidReceiveServerRedirectToURL = [delegate respondsToSelector:@selector(_download:didReceiveServerRedirectToURL:)];
m_delegateMethods.downloadDidReceiveAuthenticationChallengeCompletionHandler = [delegate respondsToSelector:@selector(_download:didReceiveAuthenticationChallenge:completionHandler:)];
m_delegateMethods.downloadShouldDecodeSourceDataOfMIMEType = [delegate respondsToSelector:@selector(_download:shouldDecodeSourceDataOfMIMEType:)];
m_delegateMethods.downloadDidCreateDestination = [delegate respondsToSelector:@selector(_download:didCreateDestination:)];
m_delegateMethods.downloadProcessDidCrash = [delegate respondsToSelector:@selector(_downloadProcessDidCrash:)];
}
void DownloadClient::didStart(WebProcessPool&, DownloadProxy& downloadProxy)
{
#if USE(SYSTEM_PREVIEW)
if (downloadProxy.isSystemPreviewDownload()) {
if (auto* webPage = downloadProxy.originatingPage()) {
// FIXME: Update the MIME-type once it is known in the ResourceResponse.
webPage->systemPreviewController()->start(URL(URL(), webPage->currentURL()), "application/octet-stream"_s, downloadProxy.systemPreviewDownloadRect());
}
takeActivityToken(downloadProxy);
return;
}
#endif
if (m_delegateMethods.downloadDidStart)
[m_delegate _downloadDidStart:wrapper(downloadProxy)];
}
void DownloadClient::didReceiveResponse(WebProcessPool&, DownloadProxy& downloadProxy, const WebCore::ResourceResponse& response)
{
#if USE(SYSTEM_PREVIEW)
if (downloadProxy.isSystemPreviewDownload() && response.isSuccessful()) {
downloadProxy.setExpectedContentLength(response.expectedContentLength());
downloadProxy.setBytesLoaded(0);
if (auto* webPage = downloadProxy.originatingPage())
webPage->systemPreviewController()->updateProgress(0);
return;
}
#endif
if (m_delegateMethods.downloadDidReceiveResponse)
[m_delegate _download:wrapper(downloadProxy) didReceiveResponse:response.nsURLResponse()];
}
void DownloadClient::didReceiveData(WebProcessPool&, DownloadProxy& downloadProxy, uint64_t length)
{
#if USE(SYSTEM_PREVIEW)
if (downloadProxy.isSystemPreviewDownload()) {
downloadProxy.setBytesLoaded(downloadProxy.bytesLoaded() + length);
if (auto* webPage = downloadProxy.originatingPage())
webPage->systemPreviewController()->updateProgress(static_cast<float>(downloadProxy.bytesLoaded()) / downloadProxy.expectedContentLength());
return;
}
#endif
if (m_delegateMethods.downloadDidReceiveData)
[m_delegate _download:wrapper(downloadProxy) didReceiveData:length];
}
void DownloadClient::didReceiveAuthenticationChallenge(WebProcessPool&, DownloadProxy& downloadProxy, AuthenticationChallengeProxy& authenticationChallenge)
{
// FIXME: System Preview needs code here.
if (!m_delegateMethods.downloadDidReceiveAuthenticationChallengeCompletionHandler) {
authenticationChallenge.listener().completeChallenge(WebKit::AuthenticationChallengeDisposition::PerformDefaultHandling);
return;
}
[m_delegate _download:wrapper(downloadProxy) didReceiveAuthenticationChallenge:wrapper(authenticationChallenge) completionHandler:makeBlockPtr([authenticationChallenge = makeRef(authenticationChallenge), checker = CompletionHandlerCallChecker::create(m_delegate.get().get(), @selector(_download:didReceiveAuthenticationChallenge:completionHandler:))] (NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential) {
if (checker->completionHandlerHasBeenCalled())
return;
checker->didCallCompletionHandler();
switch (disposition) {
case NSURLSessionAuthChallengeUseCredential:
authenticationChallenge->listener().completeChallenge(AuthenticationChallengeDisposition::UseCredential, credential ? WebCore::Credential(credential) : WebCore::Credential());
break;
case NSURLSessionAuthChallengePerformDefaultHandling:
authenticationChallenge->listener().completeChallenge(AuthenticationChallengeDisposition::PerformDefaultHandling);
break;
case NSURLSessionAuthChallengeCancelAuthenticationChallenge:
authenticationChallenge->listener().completeChallenge(AuthenticationChallengeDisposition::Cancel);
break;
case NSURLSessionAuthChallengeRejectProtectionSpace:
authenticationChallenge->listener().completeChallenge(AuthenticationChallengeDisposition::RejectProtectionSpaceAndContinue);
break;
default:
[NSException raise:NSInvalidArgumentException format:@"Invalid NSURLSessionAuthChallengeDisposition (%ld)", (long)disposition];
}
}).get()];
}
void DownloadClient::didCreateDestination(WebProcessPool&, DownloadProxy& downloadProxy, const String& destination)
{
#if USE(SYSTEM_PREVIEW)
if (downloadProxy.isSystemPreviewDownload()) {
downloadProxy.setDestinationFilename(destination);
return;
}
#endif
if (m_delegateMethods.downloadDidCreateDestination)
[m_delegate _download:wrapper(downloadProxy) didCreateDestination:destination];
}
void DownloadClient::processDidCrash(WebProcessPool&, DownloadProxy& downloadProxy)
{
#if USE(SYSTEM_PREVIEW)
if (downloadProxy.isSystemPreviewDownload()) {
if (auto* webPage = downloadProxy.originatingPage())
webPage->systemPreviewController()->cancel();
releaseActivityTokenIfNecessary(downloadProxy);
return;
}
#endif
if (m_delegateMethods.downloadProcessDidCrash)
[m_delegate _downloadProcessDidCrash:wrapper(downloadProxy)];
}
void DownloadClient::decideDestinationWithSuggestedFilename(WebProcessPool&, DownloadProxy& downloadProxy, const String& filename, Function<void(AllowOverwrite, String)>&& completionHandler)
{
#if USE(SYSTEM_PREVIEW)
if (downloadProxy.isSystemPreviewDownload()) {
NSString *temporaryDirectory = FileSystem::createTemporaryDirectory(@"SystemPreviews");
NSString *destination = [temporaryDirectory stringByAppendingPathComponent:filename];
completionHandler(AllowOverwrite::Yes, destination);
return;
}
#endif
if (!m_delegateMethods.downloadDecideDestinationWithSuggestedFilenameAllowOverwrite && !m_delegateMethods.downloadDecideDestinationWithSuggestedFilenameCompletionHandler)
return completionHandler(AllowOverwrite::No, { });
if (m_delegateMethods.downloadDecideDestinationWithSuggestedFilenameAllowOverwrite) {
BOOL allowOverwrite = NO;
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
NSString *destination = [m_delegate _download:wrapper(downloadProxy) decideDestinationWithSuggestedFilename:filename allowOverwrite:&allowOverwrite];
ALLOW_DEPRECATED_DECLARATIONS_END
completionHandler(allowOverwrite ? AllowOverwrite::Yes : AllowOverwrite::No, destination);
} else {
[m_delegate _download:wrapper(downloadProxy) decideDestinationWithSuggestedFilename:filename completionHandler:makeBlockPtr([checker = CompletionHandlerCallChecker::create(m_delegate.get().get(), @selector(_download:decideDestinationWithSuggestedFilename:completionHandler:)), completionHandler = WTFMove(completionHandler)] (BOOL allowOverwrite, NSString *destination) {
if (checker->completionHandlerHasBeenCalled())
return;
checker->didCallCompletionHandler();
completionHandler(allowOverwrite ? AllowOverwrite::Yes : AllowOverwrite::No, destination);
}).get()];
}
}
void DownloadClient::didFinish(WebProcessPool&, DownloadProxy& downloadProxy)
{
#if USE(SYSTEM_PREVIEW)
if (downloadProxy.isSystemPreviewDownload()) {
if (auto* webPage = downloadProxy.originatingPage()) {
WTF::URL destinationURL = WTF::URL::fileURLWithFileSystemPath(downloadProxy.destinationFilename());
if (!destinationURL.fragmentIdentifier().length())
destinationURL.setFragmentIdentifier(downloadProxy.request().url().fragmentIdentifier());
webPage->systemPreviewController()->finish(destinationURL);
}
releaseActivityTokenIfNecessary(downloadProxy);
return;
}
#endif
if (m_delegateMethods.downloadDidFinish)
[m_delegate _downloadDidFinish:wrapper(downloadProxy)];
}
void DownloadClient::didFail(WebProcessPool&, DownloadProxy& downloadProxy, const WebCore::ResourceError& error)
{
#if USE(SYSTEM_PREVIEW)
if (downloadProxy.isSystemPreviewDownload()) {
if (auto* webPage = downloadProxy.originatingPage())
webPage->systemPreviewController()->fail(error);
releaseActivityTokenIfNecessary(downloadProxy);
return;
}
#endif
if (m_delegateMethods.downloadDidFail)
[m_delegate _download:wrapper(downloadProxy) didFailWithError:error.nsError()];
}
void DownloadClient::didCancel(WebProcessPool&, DownloadProxy& downloadProxy)
{
#if USE(SYSTEM_PREVIEW)
if (downloadProxy.isSystemPreviewDownload()) {
if (auto* webPage = downloadProxy.originatingPage())
webPage->systemPreviewController()->cancel();
releaseActivityTokenIfNecessary(downloadProxy);
return;
}
#endif
if (m_delegateMethods.downloadDidCancel)
[m_delegate _downloadDidCancel:wrapper(downloadProxy)];
}
void DownloadClient::willSendRequest(WebProcessPool&, DownloadProxy& downloadProxy, WebCore::ResourceRequest&& request, const WebCore::ResourceResponse&, CompletionHandler<void(WebCore::ResourceRequest&&)>&& completionHandler)
{
if (m_delegateMethods.downloadDidReceiveServerRedirectToURL)
[m_delegate _download:wrapper(downloadProxy) didReceiveServerRedirectToURL:[NSURL _web_URLWithWTFString:request.url().string()]];
completionHandler(WTFMove(request));
}
#if USE(SYSTEM_PREVIEW)
void DownloadClient::takeActivityToken(DownloadProxy& downloadProxy)
{
#if PLATFORM(IOS_FAMILY)
if (auto* webPage = downloadProxy.originatingPage()) {
RELEASE_LOG_IF(webPage->isAlwaysOnLoggingAllowed(), ProcessSuspension, "%p - UIProcess is taking a background assertion because it is downloading a system preview", this);
ASSERT(!m_activityToken);
m_activityToken = webPage->process().throttler().backgroundActivityToken();
}
#else
UNUSED_PARAM(downloadProxy);
#endif
}
void DownloadClient::releaseActivityTokenIfNecessary(DownloadProxy& downloadProxy)
{
#if PLATFORM(IOS_FAMILY)
if (m_activityToken) {
RELEASE_LOG_IF(downloadProxy.originatingPage()->isAlwaysOnLoggingAllowed(), ProcessSuspension, "%p UIProcess is releasing a background assertion because a system preview download completed", this);
m_activityToken = nullptr;
}
#else
UNUSED_PARAM(downloadProxy);
#endif
}
#endif
} // namespace WebKit