blob: fb4290fde2fb859e6cd7e3d9daade6b05ec11146 [file] [log] [blame]
// Copyright (c) 2018 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 "chrome/browser/media/webrtc/webrtc_event_log_uploader.h"
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "build/build_config.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/load_flags.h"
#include "net/base/mime_util.h"
#include "net/http/http_status_code.h"
#include "net/url_request/url_request_context_getter.h"
namespace {
// TODO(crbug.com/817495): Eliminate the duplication with other uploaders.
const char kUploadContentType[] = "multipart/form-data";
const char kBoundary[] = "----**--yradnuoBgoLtrapitluMklaTelgooG--**----";
const char kLogFilename[] = "webrtc_event_log";
const char kLogExtension[] = "log";
constexpr size_t kExpectedMimeOverheadBytes = 1000; // Intentional overshot.
// TODO(crbug.com/817495): Eliminate the duplication with other uploaders.
#if defined(OS_WIN)
const char kProduct[] = "Chrome";
#elif defined(OS_MACOSX)
const char kProduct[] = "Chrome_Mac";
#elif defined(OS_LINUX)
const char kProduct[] = "Chrome_Linux";
#elif defined(OS_ANDROID)
const char kProduct[] = "Chrome_Android";
#elif defined(OS_CHROMEOS)
const char kProduct[] = "Chrome_ChromeOS";
#else
#error Platform not supported.
#endif
// TODO(crbug.com/775415): Update comment to reflect new policy when discarding
// the command line flag.
constexpr net::NetworkTrafficAnnotationTag
kWebrtcEventLogUploaderTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("webrtc_event_log_uploader", R"(
semantics {
sender: "WebRTC Event Log uploader module"
description:
"Uploads a WebRTC event log to a server called Crash. These logs "
"will not contain private information. They will be used to "
"improve WebRTC (fix bugs, tune performance, etc.)."
trigger:
"A privileged JS application (Hangouts/Meet) has requested a peer "
"connection to be logged, and the resulting event log to be "
"uploaded at a time deemed to cause the least interference to the "
"user (i.e., when the user is not busy making other VoIP calls)."
data:
"WebRTC events such as the timing of audio playout (but not the "
"content), timing and size of RTP packets sent/received, etc."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting: "This feature is only enabled if the user launches Chrome "
"with a specific command line flag: "
"--enable-features=WebRtcRemoteEventLog"
policy_exception_justification:
"Not applicable."
})");
void AddFileContents(const std::string& file_contents,
const std::string& content_type,
std::string* post_data) {
// net::AddMultipartValueForUpload does almost what we want to do here, except
// that it does not add the "filename" attribute. We hack it to force it to.
std::string mime_value_name = base::StringPrintf(
"%s\"; filename=\"%s.%s\"", kLogFilename, kLogFilename, kLogExtension);
net::AddMultipartValueForUpload(mime_value_name, file_contents, kBoundary,
content_type, post_data);
}
std::string MimeContentType() {
const char kBoundaryKeywordAndMisc[] = "; boundary=";
std::string content_type;
content_type.reserve(sizeof(content_type) + sizeof(kBoundaryKeywordAndMisc) +
sizeof(kBoundary));
content_type.append(kUploadContentType);
content_type.append(kBoundaryKeywordAndMisc);
content_type.append(kBoundary);
return content_type;
}
} // namespace
const char WebRtcEventLogUploaderImpl::kUploadURL[] =
"https://clients2.google.com/cr/report";
WebRtcEventLogUploaderImpl::Factory::Factory(
net::URLRequestContextGetter* request_context_getter)
: request_context_getter_(request_context_getter) {}
std::unique_ptr<WebRtcEventLogUploader>
WebRtcEventLogUploaderImpl::Factory::Create(
const WebRtcLogFileInfo& log_file,
WebRtcEventLogUploaderObserver* observer) {
DCHECK(observer);
return std::make_unique<WebRtcEventLogUploaderImpl>(
request_context_getter_, log_file, observer, kMaxRemoteLogFileSizeBytes);
}
std::unique_ptr<WebRtcEventLogUploader>
WebRtcEventLogUploaderImpl::Factory::CreateWithCustomMaxSizeForTesting(
const WebRtcLogFileInfo& log_file,
WebRtcEventLogUploaderObserver* observer,
size_t max_log_file_size_bytes) {
DCHECK(observer);
return std::make_unique<WebRtcEventLogUploaderImpl>(
request_context_getter_, log_file, observer, max_log_file_size_bytes);
}
WebRtcEventLogUploaderImpl::Delegate::Delegate(
WebRtcEventLogUploaderImpl* owner)
: owner_(owner) {}
#if DCHECK_IS_ON()
void WebRtcEventLogUploaderImpl::Delegate::OnURLFetchUploadProgress(
const net::URLFetcher* source,
int64_t current,
int64_t total) {
std::string unit;
if (total <= 1000) {
unit = "bytes";
} else if (total <= 1000 * 1000) {
unit = "KBs";
current /= 1000;
total /= 1000;
} else {
unit = "MBs";
current /= 1000 * 1000;
total /= 1000 * 1000;
}
VLOG(1) << "WebRTC event log upload progress: " << current << " / " << total
<< " " << unit << ".";
}
#endif
void WebRtcEventLogUploaderImpl::Delegate::OnURLFetchComplete(
const net::URLFetcher* source) {
owner_->OnURLFetchComplete(source);
}
WebRtcEventLogUploaderImpl::WebRtcEventLogUploaderImpl(
net::URLRequestContextGetter* request_context_getter,
const WebRtcLogFileInfo& log_file,
WebRtcEventLogUploaderObserver* observer,
size_t max_log_file_size_bytes)
: delegate_(this),
request_context_getter_(request_context_getter),
log_file_(log_file),
observer_(observer),
max_log_file_size_bytes_(max_log_file_size_bytes),
io_task_runner_(base::SequencedTaskRunnerHandle::Get()) {
DCHECK(observer);
std::string upload_data;
if (!PrepareUploadData(&upload_data)) {
ReportResult(false);
return;
}
StartUpload(upload_data);
}
WebRtcEventLogUploaderImpl::~WebRtcEventLogUploaderImpl() {
// WebRtcEventLogUploaderImpl objects' deletion scenarios:
// 1. Upload started and finished - |url_fetcher_| should have been reset
// so that we would be able to DCHECK and demonstrate that the determinant
// is maintained.
// 2. Upload started and cancelled - behave similarly to a finished upload.
// 3. The upload was never started, due to an early failure (e.g. file not
// found). In that case, |url_fetcher_| will not have been set.
// 4. Chrome shutdown.
if (io_task_runner_->RunsTasksInCurrentSequence()) { // Scenarios 1-3.
DCHECK(!url_fetcher_);
} else { // # Scenario #4 - Chrome shutdown.
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
bool will_delete =
io_task_runner_->DeleteSoon(FROM_HERE, url_fetcher_.release());
DCHECK(!will_delete)
<< "Task runners must have been stopped by this stage of shutdown.";
}
}
const WebRtcLogFileInfo& WebRtcEventLogUploaderImpl::GetWebRtcLogFileInfo()
const {
DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
return log_file_;
}
bool WebRtcEventLogUploaderImpl::Cancel() {
DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
if (!url_fetcher_) {
// The upload either already completed, or was never properly started (due
// to a file read failure, etc.).
return false;
}
// Note that in this case, it might still be that the last bytes hit the
// wire right as we attempt to cancel the upload. OnURLFetchComplete, however,
// would not be called.
url_fetcher_.reset();
DeleteLogFile();
return true;
}
bool WebRtcEventLogUploaderImpl::PrepareUploadData(std::string* upload_data) {
DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
std::string log_file_contents;
if (!base::ReadFileToStringWithMaxSize(log_file_.path, &log_file_contents,
max_log_file_size_bytes_)) {
LOG(WARNING) << "Couldn't read event log file, or max file size exceeded.";
return false;
}
DCHECK(upload_data->empty());
upload_data->reserve(log_file_contents.size() + kExpectedMimeOverheadBytes);
net::AddMultipartValueForUpload("prod", kProduct, kBoundary, "", upload_data);
net::AddMultipartValueForUpload("ver",
version_info::GetVersionNumber() + "-webrtc",
kBoundary, "", upload_data);
net::AddMultipartValueForUpload("guid", "0", kBoundary, "", upload_data);
net::AddMultipartValueForUpload("type", kLogFilename, kBoundary, "",
upload_data);
AddFileContents(log_file_contents, "application/log", upload_data);
net::AddMultipartFinalDelimiterForUpload(kBoundary, upload_data);
return true;
}
void WebRtcEventLogUploaderImpl::StartUpload(const std::string& upload_data) {
DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
url_fetcher_ = net::URLFetcher::Create(
GURL(kUploadURL), net::URLFetcher::POST, &delegate_,
kWebrtcEventLogUploaderTrafficAnnotation);
url_fetcher_->SetRequestContext(request_context_getter_);
url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
net::LOAD_DO_NOT_SEND_COOKIES);
url_fetcher_->SetUploadData(MimeContentType(), upload_data);
url_fetcher_->Start(); // Delegat::OnURLFetchComplete called when finished.
}
void WebRtcEventLogUploaderImpl::OnURLFetchComplete(
const net::URLFetcher* source) {
DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
DCHECK(url_fetcher_);
DCHECK_EQ(source, url_fetcher_.get());
const bool upload_successful =
(source->GetStatus().status() == net::URLRequestStatus::SUCCESS &&
source->GetResponseCode() == net::HTTP_OK);
if (upload_successful) {
// TODO(crbug.com/775415): Update chrome://webrtc-logs.
std::string report_id;
if (!url_fetcher_->GetResponseAsString(&report_id)) {
LOG(WARNING) << "WebRTC event log completed, but report ID unknown.";
} else {
// TODO(crbug.com/775415): Remove this when chrome://webrtc-logs updated.
VLOG(1) << "WebRTC event log successfully uploaded: " << report_id;
}
} else {
LOG(WARNING) << "WebRTC event log upload failed.";
}
url_fetcher_.reset(); // Explicitly maintain determinant.
ReportResult(upload_successful);
}
void WebRtcEventLogUploaderImpl::ReportResult(bool result) {
DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
// * If the upload was successful, the file is no longer needed.
// * If the upload failed, we don't want to retry, because we run the risk of
// uploading significant amounts of data once again, only for the upload to
// fail again after (as an example) wasting 50MBs of upload bandwidth.
// * If the file was not found, this will simply have no effect (other than
// to LOG() an error).
// TODO(crbug.com/775415): Provide refined retrial behavior.
DeleteLogFile();
observer_->OnWebRtcEventLogUploadComplete(log_file_.path, result);
}
void WebRtcEventLogUploaderImpl::DeleteLogFile() {
DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
const bool deletion_successful =
base::DeleteFile(log_file_.path, /*recursive=*/false);
if (!deletion_successful) {
// This is a somewhat serious (though unlikely) error, because now we'll
// try to upload this file again next time Chrome launches.
LOG(ERROR) << "Could not delete pending WebRTC event log file.";
}
}