blob: 58653eedde1ae606686f55d5b7544f1d193b5889 [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 "components/download/content/internal/download_driver_impl.h"
#include <set>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/memory_usage_estimator.h"
#include "components/download/internal/background_service/driver_entry.h"
#include "components/download/public/common/download_interrupt_reasons.h"
#include "components/download/public/common/download_url_parameters.h"
#include "content/public/browser/browser_thread.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
namespace download {
namespace {
// Converts a DownloadItem::DownloadState to DriverEntry::State.
DriverEntry::State ToDriverEntryState(
DownloadItem::DownloadState state) {
switch (state) {
case DownloadItem::IN_PROGRESS:
return DriverEntry::State::IN_PROGRESS;
case DownloadItem::COMPLETE:
return DriverEntry::State::COMPLETE;
case DownloadItem::CANCELLED:
return DriverEntry::State::CANCELLED;
case DownloadItem::INTERRUPTED:
return DriverEntry::State::INTERRUPTED;
case DownloadItem::MAX_DOWNLOAD_STATE:
return DriverEntry::State::UNKNOWN;
default:
NOTREACHED();
return DriverEntry::State::UNKNOWN;
}
}
FailureType FailureTypeFromInterruptReason(DownloadInterruptReason reason) {
switch (reason) {
case DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED:
case DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE:
case DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG:
case DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE:
case DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED:
case DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED:
case DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED:
case DOWNLOAD_INTERRUPT_REASON_SERVER_UNAUTHORIZED:
case DOWNLOAD_INTERRUPT_REASON_SERVER_CERT_PROBLEM:
case DOWNLOAD_INTERRUPT_REASON_SERVER_FORBIDDEN:
case DOWNLOAD_INTERRUPT_REASON_USER_CANCELED:
return FailureType::NOT_RECOVERABLE;
default:
return FailureType::RECOVERABLE;
}
}
// Logs interrupt reason when download fails.
void LogDownloadInterruptReason(download::DownloadInterruptReason reason) {
base::UmaHistogramSparse("Download.Service.Driver.InterruptReason", reason);
}
} // namespace
// static
DriverEntry DownloadDriverImpl::CreateDriverEntry(
const DownloadItem* item) {
DCHECK(item);
DriverEntry entry;
entry.guid = item->GetGuid();
entry.state = ToDriverEntryState(item->GetState());
entry.paused = item->IsPaused();
entry.done = item->IsDone();
entry.bytes_downloaded = item->GetReceivedBytes();
entry.expected_total_size = item->GetTotalBytes();
entry.current_file_path =
item->GetState() == DownloadItem::DownloadState::COMPLETE
? item->GetTargetFilePath()
: item->GetFullPath();
entry.completion_time = item->GetEndTime();
entry.response_headers = item->GetResponseHeaders();
if (entry.response_headers) {
entry.can_resume =
entry.response_headers->HasHeaderValue("Accept-Ranges", "bytes") ||
(entry.response_headers->HasHeader("Content-Range") &&
entry.response_headers->response_code() == net::HTTP_PARTIAL_CONTENT);
entry.can_resume &= entry.response_headers->HasStrongValidators();
} else {
// If we haven't issued the request yet, treat this like a resume based on
// the etag and last modified time.
entry.can_resume =
!item->GetETag().empty() || !item->GetLastModifiedTime().empty();
}
entry.url_chain = item->GetUrlChain();
if (item->GetState() == DownloadItem::DownloadState::COMPLETE) {
std::string hash = item->GetHash();
if (!hash.empty())
entry.hash256 = base::HexEncode(hash.data(), hash.size());
}
return entry;
}
DownloadDriverImpl::DownloadDriverImpl(content::DownloadManager* manager)
: download_manager_(manager), client_(nullptr), weak_ptr_factory_(this) {
DCHECK(download_manager_);
}
DownloadDriverImpl::~DownloadDriverImpl() = default;
void DownloadDriverImpl::Initialize(DownloadDriver::Client* client) {
DCHECK(!client_);
client_ = client;
DCHECK(client_);
// |download_manager_| may be shut down. Informs the client.
if (!download_manager_) {
client_->OnDriverReady(false);
return;
}
notifier_ =
std::make_unique<AllDownloadItemNotifier>(download_manager_, this);
}
void DownloadDriverImpl::HardRecover() {
// TODO(dtrainor, xingliu): Implement recovery for the DownloadManager.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&DownloadDriverImpl::OnHardRecoverComplete,
weak_ptr_factory_.GetWeakPtr(), true));
}
bool DownloadDriverImpl::IsReady() const {
return client_ && download_manager_ &&
download_manager_->IsManagerInitialized();
}
void DownloadDriverImpl::Start(
const RequestParams& request_params,
const std::string& guid,
const base::FilePath& file_path,
scoped_refptr<network::ResourceRequestBody> post_body,
const net::NetworkTrafficAnnotationTag& traffic_annotation) {
DCHECK(!request_params.url.is_empty());
DCHECK(!guid.empty());
if (!download_manager_)
return;
std::unique_ptr<DownloadUrlParameters> download_url_params(
new DownloadUrlParameters(request_params.url,
traffic_annotation));
// TODO(xingliu): Make content::DownloadManager handle potential guid
// collision and return an error to fail the download cleanly.
for (net::HttpRequestHeaders::Iterator it(request_params.request_headers);
it.GetNext();) {
download_url_params->add_request_header(it.name(), it.value());
}
download_url_params->set_guid(guid);
download_url_params->set_transient(true);
download_url_params->set_method(request_params.method);
download_url_params->set_file_path(file_path);
if (request_params.fetch_error_body)
download_url_params->set_fetch_error_body(true);
download_url_params->set_download_source(
download::DownloadSource::INTERNAL_API);
download_url_params->set_post_body(post_body);
download_url_params->set_follow_cross_origin_redirects(true);
download_url_params->set_upload_progress_callback(
base::BindRepeating(&DownloadDriverImpl::OnUploadProgress,
weak_ptr_factory_.GetWeakPtr(), guid));
download_manager_->DownloadUrl(std::move(download_url_params),
nullptr /* blob_data_handle */,
nullptr /* blob_url_loader_factory */);
}
void DownloadDriverImpl::Remove(const std::string& guid, bool remove_file) {
guid_to_remove_.emplace(guid);
// DownloadItem::Remove will cause the item object removed from memory, post
// the remove task to avoid the object being accessed in the same call stack.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&DownloadDriverImpl::DoRemoveDownload,
weak_ptr_factory_.GetWeakPtr(), guid, remove_file));
}
void DownloadDriverImpl::DoRemoveDownload(const std::string& guid,
bool remove_file) {
if (!download_manager_)
return;
DownloadItem* item = download_manager_->GetDownloadByGuid(guid);
// Cancels the download and removes the persisted records in content layer.
if (item) {
// Remove the download file for completed download.
if (remove_file)
item->DeleteFile(base::DoNothing());
item->Remove();
}
}
void DownloadDriverImpl::Pause(const std::string& guid) {
if (!download_manager_)
return;
DownloadItem* item = download_manager_->GetDownloadByGuid(guid);
if (item)
item->Pause();
}
void DownloadDriverImpl::Resume(const std::string& guid) {
if (!download_manager_)
return;
DownloadItem* item = download_manager_->GetDownloadByGuid(guid);
if (item)
item->Resume(true);
}
base::Optional<DriverEntry> DownloadDriverImpl::Find(const std::string& guid) {
if (!download_manager_)
return base::nullopt;
DownloadItem* item = download_manager_->GetDownloadByGuid(guid);
if (item)
return CreateDriverEntry(item);
return base::nullopt;
}
std::set<std::string> DownloadDriverImpl::GetActiveDownloads() {
std::set<std::string> guids;
if (!download_manager_)
return guids;
std::vector<DownloadItem*> items;
download_manager_->GetAllDownloads(&items);
for (auto* item : items) {
DriverEntry::State state = ToDriverEntryState(item->GetState());
if (state == DriverEntry::State::IN_PROGRESS)
guids.insert(item->GetGuid());
}
return guids;
}
size_t DownloadDriverImpl::EstimateMemoryUsage() const {
return base::trace_event::EstimateMemoryUsage(guid_to_remove_) +
notifier_->EstimateMemoryUsage();
}
void DownloadDriverImpl::OnDownloadUpdated(content::DownloadManager* manager,
DownloadItem* item) {
DCHECK(client_);
// Blocks the observer call if we asked to remove the download.
if (guid_to_remove_.find(item->GetGuid()) != guid_to_remove_.end())
return;
using DownloadState = DownloadItem::DownloadState;
DownloadState state = item->GetState();
download::DownloadInterruptReason reason = item->GetLastReason();
DriverEntry entry = CreateDriverEntry(item);
if (state == DownloadState::COMPLETE) {
client_->OnDownloadSucceeded(entry);
} else if (state == DownloadState::IN_PROGRESS) {
client_->OnDownloadUpdated(entry);
} else if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
if (client_->IsTrackingDownload(item->GetGuid()))
LogDownloadInterruptReason(reason);
client_->OnDownloadFailed(entry, FailureTypeFromInterruptReason(reason));
}
}
void DownloadDriverImpl::OnDownloadRemoved(content::DownloadManager* manager,
DownloadItem* download) {
guid_to_remove_.erase(download->GetGuid());
// |download| is about to be deleted.
}
void DownloadDriverImpl::OnDownloadCreated(content::DownloadManager* manager,
DownloadItem* item) {
if (guid_to_remove_.find(item->GetGuid()) != guid_to_remove_.end()) {
// Client has removed the download before content persistence layer created
// the record, remove the download immediately.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&DownloadDriverImpl::DoRemoveDownload,
weak_ptr_factory_.GetWeakPtr(),
item->GetGuid(), false /* remove_file */));
return;
}
// Listens to all downloads.
DCHECK(client_);
DriverEntry entry = CreateDriverEntry(item);
// Only notifies the client about new downloads. Existing download data will
// be loaded before the driver is ready.
if (IsReady())
client_->OnDownloadCreated(entry);
}
void DownloadDriverImpl::OnUploadProgress(const std::string& guid,
uint64_t bytes_uploaded) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
client_->OnUploadProgress(guid, bytes_uploaded);
}
void DownloadDriverImpl::OnManagerInitialized(
content::DownloadManager* manager) {
DCHECK_EQ(download_manager_, manager);
DCHECK(client_);
DCHECK(download_manager_);
client_->OnDriverReady(true);
}
void DownloadDriverImpl::OnManagerGoingDown(content::DownloadManager* manager) {
DCHECK_EQ(download_manager_, manager);
download_manager_ = nullptr;
}
void DownloadDriverImpl::OnHardRecoverComplete(bool success) {
client_->OnDriverHardRecoverComplete(success);
}
} // namespace download