text: // Copyright 2017 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include <utility>
  
  #include "base/bind.h"
  #include "base/bind_helpers.h"
  #include "content/browser/background_fetch/background_fetch_cross_origin_filter.h"
  #include "content/browser/background_fetch/background_fetch_data_manager.h"
  #include "content/browser/background_fetch/background_fetch_job_controller.h"
  #include "content/browser/background_fetch/background_fetch_request_match_params.h"
  #include "content/public/browser/browser_thread.h"
  #include "content/public/common/origin_util.h"
  #include "services/network/public/cpp/cors/cors.h"
  #include "third_party/blink/public/mojom/background_fetch/background_fetch.mojom.h"
  
  namespace content {
  
  namespace {
  
  // Performs mixed content checks on the |request| for Background Fetch.
  // Background Fetch depends on Service Workers, which are restricted for use
  // on secure origins. We can therefore assume that the registration's origin
  // is secure. This test ensures that the origin for the url of every
  // request is also secure.
  bool IsMixedContent(const BackgroundFetchRequestInfo& request) {
    // Empty request is valid, it shouldn't fail the mixed content check.
    if (request.fetch_request()->url.is_empty())
      return false;
  
    return !IsOriginSecure(request.fetch_request()->url);
  }
  
  // Whether the |request| needs CORS preflight.
  // Requests that require CORS preflights are temporarily blocked, because the
  // browser side of Background Fetch doesn't yet support performing CORS
  // checks. TODO(crbug.com/711354): Remove this temporary block.
  bool RequiresCorsPreflight(const BackgroundFetchRequestInfo& request,
                             const url::Origin& origin) {
    const blink::mojom::FetchAPIRequestPtr& fetch_request =
        request.fetch_request();
  
    // Same origin requests don't require a CORS preflight.
    // https://fetch.spec.whatwg.org/#main-fetch
    // TODO(crbug.com/711354): Make sure that cross-origin redirects are disabled.
    if (url::IsSameOriginWith(origin.GetURL(), fetch_request->url))
      return false;
  
    // Requests that are more involved than what is possible with HTML's form
    // element require a CORS-preflight request.
    // https://fetch.spec.whatwg.org/#main-fetch
    if (!fetch_request->method.empty() &&
        !network::cors::IsCorsSafelistedMethod(fetch_request->method)) {
      return true;
    }
  
    net::HttpRequestHeaders::HeaderVector headers;
    for (const auto& header : fetch_request->headers)
      headers.emplace_back(header.first, header.second);
  
    return !network::cors::CorsUnsafeRequestHeaderNames(headers).empty();
  }
  
  }  // namespace
  
  using blink::mojom::BackgroundFetchError;
  using blink::mojom::BackgroundFetchFailureReason;
  
  BackgroundFetchJobController::BackgroundFetchJobController(
      BackgroundFetchDataManager* data_manager,
      BackgroundFetchDelegateProxy* delegate_proxy,
      const BackgroundFetchRegistrationId& registration_id,
      blink::mojom::BackgroundFetchOptionsPtr options,
      const SkBitmap& icon,
      uint64_t bytes_downloaded,
      uint64_t bytes_uploaded,
      uint64_t upload_total,
      ProgressCallback progress_callback,
      FinishedCallback finished_callback)
      : data_manager_(data_manager),
        delegate_proxy_(delegate_proxy),
        registration_id_(registration_id),
        options_(std::move(options)),
        icon_(icon),
        complete_requests_downloaded_bytes_cache_(bytes_downloaded),
        complete_requests_uploaded_bytes_cache_(bytes_uploaded),
        upload_total_(upload_total),
        progress_callback_(std::move(progress_callback)),
        finished_callback_(std::move(finished_callback)) {
    DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  }
  
  void BackgroundFetchJobController::InitializeRequestStatus(
      int completed_downloads,
      int total_downloads,
      std::vector<scoped_refptr<BackgroundFetchRequestInfo>>
          active_fetch_requests,
      bool start_paused) {
    DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  
    // Don't allow double initialization.
    DCHECK_GT(total_downloads, 0);
    DCHECK_EQ(total_downloads_, 0);
  
    completed_downloads_ = completed_downloads;
    total_downloads_ = total_downloads;
    pending_downloads_ = active_fetch_requests.size();
  
    std::vector<std::string> active_guids;
    active_guids.reserve(active_fetch_requests.size());
    for (const auto& request_info : active_fetch_requests)
      active_guids.push_back(request_info->download_guid());
  
    auto fetch_description = std::make_unique<BackgroundFetchDescription>(
        registration_id().unique_id(), registration_id().origin(),
        options_->title, icon_, completed_downloads_, total_downloads_,
        complete_requests_downloaded_bytes_cache_,
        complete_requests_uploaded_bytes_cache_, options_->download_total,
        upload_total_, std::move(active_guids), start_paused);
  
    for (auto& active_request : active_fetch_requests)
      active_request_map_[active_request->download_guid()] = active_request;
  
    delegate_proxy_->CreateDownloadJob(weak_ptr_factory_.GetWeakPtr(),
                                       std::move(fetch_description));
  }
  
  BackgroundFetchJobController::~BackgroundFetchJobController() {
    DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  }
  
  bool BackgroundFetchJobController::HasMoreRequests() {
    return completed_downloads_ + pending_downloads_ < total_downloads_;
  }
  
  void BackgroundFetchJobController::StartRequest(
      scoped_refptr<BackgroundFetchRequestInfo> request,
      RequestFinishedCallback request_finished_callback) {
    DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
    DCHECK_LT(completed_downloads_, total_downloads_);
    DCHECK(request_finished_callback);
    DCHECK(request);
  
    active_request_finished_callbacks_.emplace(
        request->download_guid(), std::move(request_finished_callback));
  
    if (IsMixedContent(*request.get()) ||
        RequiresCorsPreflight(*request.get(), registration_id_.origin())) {
      request->SetEmptyResultWithFailureReason(
          BackgroundFetchResult::FailureReason::FETCH_ERROR);
  
      NotifyDownloadComplete(std::move(request));
      return;
    }
  
    active_request_map_[request->download_guid()] = request;
    delegate_proxy_->StartRequest(registration_id().unique_id(),
                                  registration_id().origin(), request.get());
  }
  
  void BackgroundFetchJobController::DidStartRequest(
      const std::string& guid,
      std::unique_ptr<BackgroundFetchResponse> response) {
    DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  
    DCHECK(active_request_map_.count(guid));
    const auto& request = active_request_map_[guid];
    DCHECK(request);
  
    request->PopulateWithResponse(std::move(response));
  
    // TODO(crbug.com/884672): Stop the fetch if the cross origin filter fails.
    BackgroundFetchCrossOriginFilter filter(registration_id_.origin(), *request);
    request->set_can_populate_body(filter.CanPopulateBody());
  }
  
  void BackgroundFetchJobController::DidUpdateRequest(const std::string& guid,
                                                      uint64_t bytes_uploaded,
                                                      uint64_t bytes_downloaded) {
    DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  
    DCHECK(active_request_map_.count(guid));
    const auto& request = active_request_map_[guid];
    DCHECK(request);
    InProgressRequestBytes& in_progress_bytes = active_bytes_map_[guid];
  
    // Don't send download updates so the size is not leaked.
    // Upload updates are fine since that information is already available.
    if (!request->can_populate_body() && bytes_downloaded > 0u)
      return;
  
    if (in_progress_bytes.downloaded == bytes_downloaded &&
        in_progress_bytes.uploaded == bytes_uploaded) {
      return;
    }
  
    in_progress_bytes.downloaded = bytes_downloaded;
    in_progress_bytes.uploaded = bytes_uploaded;
  
    auto registration_data = NewRegistrationData();
    registration_data->downloaded += GetInProgressDownloadedBytes();
    registration_data->uploaded += GetInProgressUploadedBytes();
    progress_callback_.Run(registration_id_.unique_id(), *registration_data);
  }
  
  void BackgroundFetchJobController::DidCompleteRequest(
      const std::string& guid,
      std::unique_ptr<BackgroundFetchResult> result) {
    DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  
    DCHECK(active_request_map_.count(guid));
    const auto& request = active_request_map_[guid];
    DCHECK(request);
  
    request->SetResult(std::move(result));
  
    if (request->can_populate_body())
      complete_requests_downloaded_bytes_cache_ += request->GetResponseSize();
    complete_requests_uploaded_bytes_cache_ += request->request_body_size();
  
    NotifyDownloadComplete(request);
    active_bytes_map_.erase(guid);
    active_request_map_.erase(guid);
  }
  
  blink::mojom::BackgroundFetchRegistrationDataPtr
  BackgroundFetchJobController::NewRegistrationData() const {
    return blink::mojom::BackgroundFetchRegistrationData::New(
        registration_id().developer_id(), upload_total_,
        complete_requests_uploaded_bytes_cache_, options_->download_total,
        complete_requests_downloaded_bytes_cache_,
        blink::mojom::BackgroundFetchResult::UNSET, failure_reason_);
  }
  
  uint64_t BackgroundFetchJobController::GetInProgressDownloadedBytes() {
    uint64_t bytes = 0u;
    for (const std::pair<const std::string, InProgressRequestBytes>&
             in_progress_bytes : active_bytes_map_) {
      bytes += in_progress_bytes.second.downloaded;
    }
    return bytes;
  }
  
  uint64_t BackgroundFetchJobController::GetInProgressUploadedBytes() {
    uint64_t bytes = 0u;
    for (const std::pair<const std::string, InProgressRequestBytes>&
             in_progress_bytes : active_bytes_map_) {
      bytes += in_progress_bytes.second.uploaded;
    }
    return bytes;
  }
  
  void BackgroundFetchJobController::AbortFromDelegate(
      BackgroundFetchFailureReason failure_reason) {
    failure_reason_ = failure_reason;
  
    Finish(failure_reason_, base::DoNothing());
  }
  
  void BackgroundFetchJobController::Abort(
      BackgroundFetchFailureReason failure_reason,
      ErrorCallback callback) {
    failure_reason_ = failure_reason;
  
    // Cancel any in-flight downloads and UI through the BGFetchDelegate.
    delegate_proxy_->Abort(registration_id().unique_id());
  
    Finish(failure_reason_, std::move(callback));
  }
  
  void BackgroundFetchJobController::Finish(
      BackgroundFetchFailureReason reason_to_abort,
      ErrorCallback callback) {
    DCHECK(reason_to_abort != BackgroundFetchFailureReason::NONE ||
           !HasMoreRequests());
  
    // Race conditions make it possible for a controller to finish twice. This
    // should be removed when the scheduler starts owning the controllers.
    if (!finished_callback_) {
      std::move(callback).Run(BackgroundFetchError::INVALID_ID);
      return;
    }
  
    std::move(finished_callback_)
        .Run(registration_id_, reason_to_abort, std::move(callback));
  }
  
  void BackgroundFetchJobController::PopNextRequest(
      RequestStartedCallback request_started_callback,
      RequestFinishedCallback request_finished_callback) {
    DCHECK(HasMoreRequests());
  
    ++pending_downloads_;
    data_manager_->PopNextRequest(
        registration_id(),
        base::BindOnce(&BackgroundFetchJobController::DidPopNextRequest,
                       weak_ptr_factory_.GetWeakPtr(),
                       std::move(request_started_callback),
                       std::move(request_finished_callback)));
  }
  
  void BackgroundFetchJobController::DidPopNextRequest(
      RequestStartedCallback request_started_callback,
      RequestFinishedCallback request_finished_callback,
      BackgroundFetchError error,
      scoped_refptr<BackgroundFetchRequestInfo> request_info) {
    if (error != BackgroundFetchError::NONE) {
      Abort(BackgroundFetchFailureReason::SERVICE_WORKER_UNAVAILABLE,
            base::DoNothing());
      return;
    }
  
    std::move(request_started_callback)
        .Run(registration_id(), request_info.get());
    StartRequest(std::move(request_info), std::move(request_finished_callback));
  }
  
  void BackgroundFetchJobController::MarkRequestAsComplete(
      scoped_refptr<BackgroundFetchRequestInfo> request_info) {
    data_manager_->MarkRequestAsComplete(
        registration_id(), std::move(request_info),
        base::BindOnce(&BackgroundFetchJobController::DidMarkRequestAsComplete,
                       weak_ptr_factory_.GetWeakPtr()));
  }
  
  void BackgroundFetchJobController::DidMarkRequestAsComplete(
      BackgroundFetchError error) {
    switch (error) {
      case BackgroundFetchError::NONE:
        break;
      case BackgroundFetchError::STORAGE_ERROR:
        Abort(BackgroundFetchFailureReason::SERVICE_WORKER_UNAVAILABLE,
              base::DoNothing());
        return;
      case BackgroundFetchError::QUOTA_EXCEEDED:
        Abort(BackgroundFetchFailureReason::QUOTA_EXCEEDED, base::DoNothing());
        return;
      default:
        NOTREACHED();
    }
  
    if (completed_downloads_ == total_downloads_) {
      Finish(BackgroundFetchFailureReason::NONE, base::DoNothing());
      return;
    }
  }
  
  void BackgroundFetchJobController::NotifyDownloadComplete(
      scoped_refptr<BackgroundFetchRequestInfo> request) {
    --pending_downloads_;
    ++completed_downloads_;
    auto it = active_request_finished_callbacks_.find(request->download_guid());
    DCHECK(it != active_request_finished_callbacks_.end());
    std::move(it->second).Run(registration_id(), std::move(request));
    active_request_finished_callbacks_.erase(it);
  }
  
  void BackgroundFetchJobController::GetUploadData(
      const std::string& guid,
      BackgroundFetchDelegate::GetUploadDataCallback callback) {
    DCHECK(active_request_map_.count(guid));
    const auto& request = active_request_map_[guid];
    DCHECK(request);
  
    data_manager_->GetRequestBlob(
        registration_id(), request,
        base::BindOnce(&BackgroundFetchJobController::DidGetUploadData,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }
  
  void BackgroundFetchJobController::DidGetUploadData(
      BackgroundFetchDelegate::GetUploadDataCallback callback,
      BackgroundFetchError error,
      blink::mojom::SerializedBlobPtr blob) {
    if (error != BackgroundFetchError::NONE) {
      Abort(BackgroundFetchFailureReason::SERVICE_WORKER_UNAVAILABLE,
            base::DoNothing());
      std::move(callback).Run(nullptr);
    }
  
    DCHECK(blob);
    std::move(callback).Run(std::move(blob));
  }
  
  }  // namespace content
  