blob: e81833f867306b72a618f92d69ed2c8aba6bdc12 [file] [log] [blame]
// 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<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<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