blob: 1d5e3942653609cab11554233ff79adebe13d714 [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/public/common/download_response_handler.h"
#include <memory>
#include "base/metrics/histogram_functions.h"
#include "components/download/public/common/download_stats.h"
#include "components/download/public/common/download_url_parameters.h"
#include "components/download/public/common/download_utils.h"
#include "net/http/http_status_code.h"
namespace download {
namespace {
mojom::NetworkRequestStatus ConvertInterruptReasonToMojoNetworkRequestStatus(
DownloadInterruptReason reason) {
switch (reason) {
case DOWNLOAD_INTERRUPT_REASON_NONE:
return mojom::NetworkRequestStatus::OK;
case DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT:
return mojom::NetworkRequestStatus::NETWORK_TIMEOUT;
case DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED:
return mojom::NetworkRequestStatus::NETWORK_DISCONNECTED;
case DOWNLOAD_INTERRUPT_REASON_NETWORK_SERVER_DOWN:
return mojom::NetworkRequestStatus::NETWORK_SERVER_DOWN;
case DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE:
return mojom::NetworkRequestStatus::SERVER_NO_RANGE;
case DOWNLOAD_INTERRUPT_REASON_SERVER_CONTENT_LENGTH_MISMATCH:
return mojom::NetworkRequestStatus::SERVER_CONTENT_LENGTH_MISMATCH;
case DOWNLOAD_INTERRUPT_REASON_SERVER_UNREACHABLE:
return mojom::NetworkRequestStatus::SERVER_UNREACHABLE;
case DOWNLOAD_INTERRUPT_REASON_SERVER_CERT_PROBLEM:
return mojom::NetworkRequestStatus::SERVER_CERT_PROBLEM;
case DOWNLOAD_INTERRUPT_REASON_USER_CANCELED:
return mojom::NetworkRequestStatus::USER_CANCELED;
case DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED:
return mojom::NetworkRequestStatus::NETWORK_FAILED;
default:
NOTREACHED();
return mojom::NetworkRequestStatus::NETWORK_FAILED;
}
}
} // namespace
DownloadResponseHandler::DownloadResponseHandler(
network::ResourceRequest* resource_request,
Delegate* delegate,
std::unique_ptr<DownloadSaveInfo> save_info,
bool is_parallel_request,
bool is_transient,
bool fetch_error_body,
bool follow_cross_origin_redirects,
const DownloadUrlParameters::RequestHeadersType& request_headers,
const std::string& request_origin,
DownloadSource download_source,
bool ignore_content_length_mismatch,
std::vector<GURL> url_chain,
bool is_background_mode)
: delegate_(delegate),
started_(false),
save_info_(std::move(save_info)),
url_chain_(std::move(url_chain)),
method_(resource_request->method),
referrer_(resource_request->referrer),
referrer_policy_(resource_request->referrer_policy),
is_transient_(is_transient),
fetch_error_body_(fetch_error_body),
follow_cross_origin_redirects_(follow_cross_origin_redirects),
first_origin_(url::Origin::Create(resource_request->url)),
request_headers_(request_headers),
request_origin_(request_origin),
download_source_(download_source),
ignore_content_length_mismatch_(ignore_content_length_mismatch),
is_partial_request_(save_info_->offset > 0),
completed_(false),
abort_reason_(DOWNLOAD_INTERRUPT_REASON_NONE),
is_background_mode_(is_background_mode) {
if (!is_parallel_request) {
RecordDownloadCountWithSource(UNTHROTTLED_COUNT, download_source);
}
if (resource_request->request_initiator.has_value())
request_initiator_ = resource_request->request_initiator;
}
DownloadResponseHandler::~DownloadResponseHandler() = default;
void DownloadResponseHandler::OnReceiveResponse(
const network::ResourceResponseHead& head) {
create_info_ = CreateDownloadCreateInfo(head);
cert_status_ = head.cert_status;
// TODO(xingliu): Do not use http cache.
// Sets page transition type correctly and call
// |RecordDownloadSourcePageTransitionType| here.
if (head.headers) {
// ERR_CONTENT_LENGTH_MISMATCH can be caused by 1 of the following reasons:
// 1. Server or proxy closes the connection too early.
// 2. The content-length header is wrong.
// If the download has strong validators, we can interrupt the download
// and let it resume automatically. Otherwise, resuming the download will
// cause it to restart and the download may never complete if the error was
// caused by reason 2. As a result, downloads without strong validators are
// treated as completed here.
ignore_content_length_mismatch_ |= !head.headers->HasStrongValidators();
RecordDownloadHttpResponseCode(head.headers->response_code(),
is_background_mode_);
RecordDownloadContentDisposition(create_info_->content_disposition);
}
// Blink verifies that the requester of this download is allowed to set a
// suggested name for the security origin of the downlaod URL. However, this
// assumption doesn't hold if there were cross origin redirects. Therefore,
// clear the suggested_name for such requests.
if (request_initiator_.has_value() &&
!create_info_->url_chain.back().SchemeIsBlob() &&
!create_info_->url_chain.back().SchemeIs(url::kAboutScheme) &&
!create_info_->url_chain.back().SchemeIs(url::kDataScheme) &&
request_initiator_.value() !=
url::Origin::Create(create_info_->url_chain.back())) {
create_info_->save_info->suggested_name.clear();
}
if (create_info_->result != DOWNLOAD_INTERRUPT_REASON_NONE)
OnResponseStarted(mojom::DownloadStreamHandlePtr());
}
std::unique_ptr<DownloadCreateInfo>
DownloadResponseHandler::CreateDownloadCreateInfo(
const network::ResourceResponseHead& head) {
auto create_info = std::make_unique<DownloadCreateInfo>(
base::Time::Now(), std::move(save_info_));
DownloadInterruptReason result =
head.headers
? HandleSuccessfulServerResponse(
*head.headers, create_info->save_info.get(), fetch_error_body_)
: DOWNLOAD_INTERRUPT_REASON_NONE;
create_info->total_bytes = head.content_length > 0 ? head.content_length : 0;
create_info->result = result;
if (result == DOWNLOAD_INTERRUPT_REASON_NONE)
create_info->remote_address = head.remote_endpoint.ToStringWithoutPort();
create_info->method = method_;
create_info->connection_info = head.connection_info;
create_info->url_chain = url_chain_;
create_info->referrer_url = referrer_;
create_info->referrer_policy = referrer_policy_;
create_info->transient = is_transient_;
create_info->response_headers = head.headers;
create_info->offset = create_info->save_info->offset;
create_info->mime_type = head.mime_type;
create_info->fetch_error_body = fetch_error_body_;
create_info->request_headers = request_headers_;
create_info->request_origin = request_origin_;
create_info->download_source = download_source_;
create_info->request_initiator = request_initiator_;
HandleResponseHeaders(head.headers.get(), create_info.get());
return create_info;
}
void DownloadResponseHandler::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& head) {
if (!follow_cross_origin_redirects_ &&
!first_origin_.IsSameOriginWith(
url::Origin::Create(redirect_info.new_url))) {
abort_reason_ = DOWNLOAD_INTERRUPT_REASON_SERVER_CROSS_ORIGIN_REDIRECT;
url_chain_.push_back(redirect_info.new_url);
method_ = redirect_info.new_method;
referrer_ = GURL(redirect_info.new_referrer);
referrer_policy_ = redirect_info.new_referrer_policy;
OnComplete(network::URLLoaderCompletionStatus(net::OK));
return;
}
if (is_partial_request_) {
// A redirect while attempting a partial resumption indicates a potential
// middle box. Trigger another interruption so that the
// DownloadItem can retry.
abort_reason_ = DOWNLOAD_INTERRUPT_REASON_SERVER_UNREACHABLE;
OnComplete(network::URLLoaderCompletionStatus(net::OK));
return;
}
// Check if redirect URL is web safe.
if (delegate_ && !delegate_->CanRequestURL(redirect_info.new_url)) {
abort_reason_ = DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST;
OnComplete(network::URLLoaderCompletionStatus(net::OK));
return;
}
url_chain_.push_back(redirect_info.new_url);
method_ = redirect_info.new_method;
referrer_ = GURL(redirect_info.new_referrer);
referrer_policy_ = redirect_info.new_referrer_policy;
delegate_->OnReceiveRedirect();
}
void DownloadResponseHandler::OnUploadProgress(
int64_t current_position,
int64_t total_size,
OnUploadProgressCallback callback) {
delegate_->OnUploadProgress(current_position);
std::move(callback).Run();
}
void DownloadResponseHandler::OnReceiveCachedMetadata(
mojo_base::BigBuffer data) {}
void DownloadResponseHandler::OnTransferSizeUpdated(
int32_t transfer_size_diff) {}
void DownloadResponseHandler::OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) {
if (started_)
return;
mojom::DownloadStreamHandlePtr stream_handle =
mojom::DownloadStreamHandle::New();
stream_handle->stream = std::move(body);
stream_handle->client_request = mojo::MakeRequest(&client_ptr_);
OnResponseStarted(std::move(stream_handle));
}
void DownloadResponseHandler::OnComplete(
const network::URLLoaderCompletionStatus& status) {
if (completed_)
return;
completed_ = true;
DownloadInterruptReason reason = HandleRequestCompletionStatus(
static_cast<net::Error>(status.error_code),
ignore_content_length_mismatch_, cert_status_, is_partial_request_,
abort_reason_);
if (client_ptr_) {
client_ptr_->OnStreamCompleted(
ConvertInterruptReasonToMojoNetworkRequestStatus(reason));
}
if (reason == DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED) {
base::UmaHistogramSparse("Download.MapErrorNetworkFailed.NetworkService",
std::abs(status.error_code));
if (is_background_mode_) {
base::UmaHistogramSparse(
"Download.MapErrorNetworkFailed.NetworkService.BackgroundDownload",
std::abs(status.error_code));
}
}
if (started_) {
delegate_->OnResponseCompleted();
return;
}
// OnComplete() called without OnResponseStarted(). This should only
// happen when the request was aborted.
if (!create_info_)
create_info_ = CreateDownloadCreateInfo(network::ResourceResponseHead());
create_info_->result = reason == DOWNLOAD_INTERRUPT_REASON_NONE
? DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED
: reason;
OnResponseStarted(mojom::DownloadStreamHandlePtr());
delegate_->OnResponseCompleted();
}
void DownloadResponseHandler::OnResponseStarted(
mojom::DownloadStreamHandlePtr stream_handle) {
started_ = true;
delegate_->OnResponseStarted(std::move(create_info_),
std::move(stream_handle));
}
} // namespace download