chromium/ios/web/download/crw_web_view_download.mm

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/web/download/crw_web_view_download.h"

#import <WebKit/WebKit.h>

#import "base/files/file_path.h"
#import "base/files/file_util.h"
#import "base/functional/callback_helpers.h"
#import "base/ios/block_types.h"
#import "base/strings/sys_string_conversions.h"
#import "base/task/thread_pool.h"

@interface CRWWebViewDownload () <WKDownloadDelegate>

// Request URL called for the download.
@property(nonatomic, strong) NSURLRequest* request;

// Download object of a web resource.
@property(nonatomic, strong) WKDownload* download;

@end

@implementation CRWWebViewDownload {
  BOOL _isLocalDownload;
}

- (instancetype)initWithPath:(NSString*)destination
                     request:(NSURLRequest*)request
                     webview:(WKWebView*)webview
                    delegate:(id<CRWWebViewDownloadDelegate>)delegate {
  self = [super init];
  if (self) {
    self.destinationPath = destination;
    self.request = request;
    self.webView = webview;
    self.delegate = delegate;
  }
  return self;
}

- (void)startDownload {
  if ([self.request.URL isFileURL]) {
    [self startLocalDownload];
    return;
  }
  [self.webView startDownloadUsingRequest:self.request
                        completionHandler:^(WKDownload* download) {
                          download.delegate = self;
                          self.download = download;
                        }];
}

- (void)cancelDownload:(ProceduralBlock)completion {
  if (_isLocalDownload) {
    if (completion) {
      completion();
    }
    return;
  }
  [self.download cancel:^(NSData* resumeData) {
    if (completion) {
      completion();
    }
  }];
}

#pragma mark - WKDownloadDelegate

- (void)download:(WKDownload*)download
    decideDestinationUsingResponse:(NSURLResponse*)response
                 suggestedFilename:(NSString*)suggestedFilename
                 completionHandler:
                     (void (^)(NSURL* destination))completionHandler {
  NSURL* destinationURL = [NSURL fileURLWithPath:self.destinationPath];
  completionHandler(destinationURL);
}

- (void)downloadDidFinish:(WKDownload*)download {
  [self.delegate downloadDidFinish];
}

- (void)download:(WKDownload*)download
    didFailWithError:(NSError*)error
          resumeData:(NSData*)resumeData {
  [self.delegate downloadDidFailWithError:error];
}

#pragma mark - Private

// Start a `local download` which is really a copy from the request URL to the
// destination path.
- (void)startLocalDownload {
  _isLocalDownload = YES;
  __weak __typeof(self) weakSelf = self;
  base::FilePath sourcePath(base::SysNSStringToUTF8(self.request.URL.path));
  base::FilePath destPath(base::SysNSStringToUTF8(self.destinationPath));

  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE,
      {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
      base::BindOnce(&base::CopyFile, sourcePath, destPath),
      base::BindOnce(^(bool result) {
        if (result) {
          [weakSelf.delegate downloadDidFinish];
        } else {
          NSError* error = [NSError errorWithDomain:NSCocoaErrorDomain
                                               code:NSFileReadUnknownError
                                           userInfo:nil];
          [weakSelf.delegate downloadDidFailWithError:error];
        }
      }));
}

@end