blob: b674567c54919d370b88538cb6dc5fe13d465770 [file] [log] [blame]
// Copyright 2014 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/domain_reliability/uploader.h"
#include <utility>
#include "base/callback.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/logging.h"
#include "base/supports_user_data.h"
#include "components/domain_reliability/util.h"
#include "net/base/elements_upload_data_stream.h"
#include "net/base/isolation_info.h"
#include "net/base/net_errors.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
namespace domain_reliability {
namespace {
const char kJsonMimeType[] = "application/json; charset=utf-8";
// Each DR upload is tagged with an instance of this class, which identifies the
// depth of the upload. This is to prevent infinite loops of DR uploads about DR
// uploads, leading to an unbounded number of requests.
// Deeper requests will still generate a report beacon, but they won't trigger a
// separate upload. See DomainReliabilityContext::kMaxUploadDepthToSchedule.
struct UploadDepthData : public base::SupportsUserData::Data {
explicit UploadDepthData(int depth) : depth(depth) {}
// Key that identifies this data within SupportsUserData's map of data.
static const void* const kUserDataKey;
// This is 0 if the report being uploaded does not contain a beacon about a
// DR upload request. Otherwise, it is 1 + the depth of the deepest DR upload
// described in the report.
int depth;
};
const void* const UploadDepthData::kUserDataKey =
&UploadDepthData::kUserDataKey;
} // namespace
class DomainReliabilityUploaderImpl : public DomainReliabilityUploader,
public net::URLRequest::Delegate {
public:
DomainReliabilityUploaderImpl(MockableTime* time,
net::URLRequestContext* url_request_context)
: time_(time),
url_request_context_(url_request_context),
discard_uploads_(true),
shutdown_(false),
discarded_upload_count_(0u) {
DCHECK(url_request_context_);
}
~DomainReliabilityUploaderImpl() override {
DCHECK(shutdown_);
}
// DomainReliabilityUploader implementation:
void UploadReport(
const std::string& report_json,
int max_upload_depth,
const GURL& upload_url,
const net::NetworkIsolationKey& network_isolation_key,
DomainReliabilityUploader::UploadCallback callback) override {
DVLOG(1) << "Uploading report to " << upload_url;
DVLOG(2) << "Report JSON: " << report_json;
if (discard_uploads_)
discarded_upload_count_++;
if (discard_uploads_ || shutdown_) {
DVLOG(1) << "Discarding report instead of uploading.";
UploadResult result;
result.status = UploadResult::SUCCESS;
std::move(callback).Run(result);
return;
}
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("domain_reliability_report_upload",
R"(
semantics {
sender: "Domain Reliability"
description:
"If Chromium has trouble reaching certain Google sites or "
"services, Domain Reliability may report the problems back to "
"Google."
trigger: "Failure to load certain Google sites or services."
data:
"Details of the failed request, including the URL, any IP "
"addresses the browser tried to connect to, error(s) "
"encountered loading the resource, and other connection details."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"Users can enable or disable Domain Reliability on desktop, via "
"toggling 'Automatically send usage statistics and crash reports "
"to Google' in Chromium's settings under Privacy. On ChromeOS, "
"the setting is named 'Automatically send diagnostic and usage "
"data to Google'."
policy_exception_justification: "Not implemented."
})");
std::unique_ptr<net::URLRequest> request =
url_request_context_->CreateRequest(
upload_url, net::RequestPriority::IDLE, this /* delegate */,
traffic_annotation);
request->set_method("POST");
request->set_allow_credentials(false);
request->SetExtraRequestHeaderByName(net::HttpRequestHeaders::kContentType,
kJsonMimeType, true /* overwrite */);
request->set_isolation_info(net::IsolationInfo::CreatePartial(
net::IsolationInfo::RequestType::kOther, network_isolation_key));
std::vector<char> report_data(report_json.begin(), report_json.end());
auto upload_reader =
std::make_unique<net::UploadOwnedBytesElementReader>(&report_data);
request->set_upload(net::ElementsUploadDataStream::CreateWithReader(
std::move(upload_reader), 0 /* identifier */));
request->SetUserData(
UploadDepthData::kUserDataKey,
std::make_unique<UploadDepthData>(max_upload_depth + 1));
UploadMap::iterator it;
bool inserted;
std::tie(it, inserted) = uploads_.insert(
std::make_pair(std::move(request), std::move(callback)));
DCHECK(inserted);
it->first->Start();
}
void SetDiscardUploads(bool discard_uploads) override {
discard_uploads_ = discard_uploads;
DVLOG(1) << "Setting discard_uploads to " << discard_uploads;
}
void Shutdown() override {
DCHECK(!shutdown_);
shutdown_ = true;
uploads_.clear();
}
int GetDiscardedUploadCount() const override {
return discarded_upload_count_;
}
// net::URLRequest::Delegate implementation:
void OnResponseStarted(net::URLRequest* request, int net_error) override {
DCHECK(!shutdown_);
auto request_it = uploads_.find(request);
DCHECK(request_it != uploads_.end());
int http_response_code = -1;
base::TimeDelta retry_after;
if (net_error == net::OK) {
http_response_code = request->GetResponseCode();
std::string retry_after_string;
if (request->response_headers() &&
request->response_headers()->EnumerateHeader(nullptr, "Retry-After",
&retry_after_string)) {
net::HttpUtil::ParseRetryAfterHeader(retry_after_string, time_->Now(),
&retry_after);
}
}
DVLOG(1) << "Upload finished with net error " << net_error
<< ", response code " << http_response_code << ", retry after "
<< retry_after;
std::move(request_it->second)
.Run(GetUploadResultFromResponseDetails(net_error, http_response_code,
retry_after));
uploads_.erase(request_it);
}
// Requests are cancelled in OnResponseStarted() once response headers are
// read, without reading the body, so this is not needed.
void OnReadCompleted(net::URLRequest* request, int bytes_read) override {
NOTREACHED();
}
private:
MockableTime* time_;
net::URLRequestContext* url_request_context_;
// Stores each in-flight upload request with the callback to notify its
// initiating DRContext of its completion.
using UploadMap = std::map<std::unique_ptr<net::URLRequest>,
UploadCallback,
base::UniquePtrComparator>;
UploadMap uploads_;
bool discard_uploads_;
bool shutdown_;
int discarded_upload_count_;
};
DomainReliabilityUploader::DomainReliabilityUploader() {}
DomainReliabilityUploader::~DomainReliabilityUploader() {}
// static
std::unique_ptr<DomainReliabilityUploader> DomainReliabilityUploader::Create(
MockableTime* time,
net::URLRequestContext* url_request_context) {
return std::make_unique<DomainReliabilityUploaderImpl>(time,
url_request_context);
}
// static
int DomainReliabilityUploader::GetURLRequestUploadDepth(
const net::URLRequest& request) {
UploadDepthData* data = static_cast<UploadDepthData*>(
request.GetUserData(UploadDepthData::kUserDataKey));
return data ? data->depth : 0;
}
} // namespace domain_reliability