blob: b7dff32ea5f649ade971fdad071541b3eebf632a [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 "chrome/browser/tracing/crash_service_uploader.h"
#include <utility>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/format_macros.h"
#include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/task_scheduler/post_task.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/data_use_measurement/core/data_use_user_data.h"
#include "components/tracing/common/tracing_switches.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_switches.h"
#include "net/base/load_flags.h"
#include "net/base/mime_util.h"
#include "net/base/network_delegate.h"
#include "net/http/http_status_code.h"
#include "net/proxy_resolution/proxy_config.h"
#include "net/proxy_resolution/proxy_config_service.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_builder.h"
#include "net/url_request/url_request_context_getter.h"
#include "third_party/zlib/zlib.h"
#include "url/gurl.h"
using std::string;
namespace {
const char kUploadURL[] = "https://clients2.google.com/cr/report";
const char kCrashUploadContentType[] = "multipart/form-data";
const char kCrashMultipartBoundary[] =
"----**--yradnuoBgoLtrapitluMklaTelgooG--**----";
// Allow up to 10MB for trace upload
const size_t kMaxUploadBytes = 10000000;
} // namespace
TraceCrashServiceUploader::TraceCrashServiceUploader(
net::URLRequestContextGetter* request_context)
: request_context_(request_context), max_upload_bytes_(kMaxUploadBytes) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
std::string upload_url = kUploadURL;
if (command_line.HasSwitch(switches::kTraceUploadURL)) {
upload_url = command_line.GetSwitchValueASCII(switches::kTraceUploadURL);
}
SetUploadURL(upload_url);
}
TraceCrashServiceUploader::~TraceCrashServiceUploader() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
void TraceCrashServiceUploader::SetUploadURL(const std::string& url) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
upload_url_ = url;
if (!GURL(upload_url_).is_valid())
upload_url_.clear();
}
void TraceCrashServiceUploader::SetMaxUploadBytes(size_t max_upload_bytes) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
max_upload_bytes_ = max_upload_bytes;
}
void TraceCrashServiceUploader::OnURLFetchComplete(
const net::URLFetcher* source) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(source, url_fetcher_.get());
int response_code = source->GetResponseCode();
string feedback;
bool success = (response_code == net::HTTP_OK);
if (success) {
source->GetResponseAsString(&feedback);
} else {
feedback =
"Uploading failed, response code: " + base::IntToString(response_code);
}
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::BindOnce(std::move(done_callback_), success, feedback));
url_fetcher_.reset();
}
void TraceCrashServiceUploader::OnURLFetchUploadProgress(
const net::URLFetcher* source,
int64_t current,
int64_t total) {
DCHECK(url_fetcher_.get());
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
LOG(WARNING) << "Upload progress: " << current << " of " << total;
if (progress_callback_.is_null())
return;
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(progress_callback_, current, total));
}
void TraceCrashServiceUploader::DoUpload(
const std::string& file_contents,
UploadMode upload_mode,
std::unique_ptr<const base::DictionaryValue> metadata,
const UploadProgressCallback& progress_callback,
UploadDoneCallback done_callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
progress_callback_ = progress_callback;
done_callback_ = std::move(done_callback);
base::PostTaskWithTraits(
FROM_HERE, {base::TaskPriority::BEST_EFFORT},
base::Bind(&TraceCrashServiceUploader::DoCompressOnBackgroundThread,
base::Unretained(this), file_contents, upload_mode,
upload_url_, base::Passed(std::move(metadata))));
}
void TraceCrashServiceUploader::DoCompressOnBackgroundThread(
const std::string& file_contents,
UploadMode upload_mode,
const std::string& upload_url,
std::unique_ptr<const base::DictionaryValue> metadata) {
DCHECK(!url_fetcher_.get());
if (upload_url.empty()) {
OnUploadError("Upload URL empty or invalid");
return;
}
#if defined(OS_WIN)
const char product[] = "Chrome";
#elif defined(OS_MACOSX)
const char product[] = "Chrome_Mac";
#elif defined(OS_CHROMEOS)
// On ChromeOS, defined(OS_LINUX) also evalutes to true, so the
// defined(OS_CHROMEOS) block must come first.
const char product[] = "Chrome_ChromeOS";
#elif defined(OS_LINUX)
const char product[] = "Chrome_Linux";
#elif defined(OS_ANDROID)
const char product[] = "Chrome_Android";
#else
#error Platform not supported.
#endif
// version_info::GetProductNameAndVersionForUserAgent() returns a string like
// "Chrome/aa.bb.cc.dd", split out the part before the "/".
std::vector<std::string> product_components = base::SplitString(
version_info::GetProductNameAndVersionForUserAgent(), "/",
base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
DCHECK_EQ(2U, product_components.size());
std::string version;
if (product_components.size() == 2U) {
version = product_components[1];
} else {
version = "unknown";
}
if (url_fetcher_.get()) {
OnUploadError("Already uploading.");
return;
}
std::string compressed_contents;
if (upload_mode == COMPRESSED_UPLOAD) {
std::unique_ptr<char[]> compressed_buffer(new char[max_upload_bytes_]);
int compressed_bytes;
if (!Compress(file_contents, max_upload_bytes_, compressed_buffer.get(),
&compressed_bytes)) {
OnUploadError("Compressing file failed.");
return;
}
compressed_contents =
std::string(compressed_buffer.get(), compressed_bytes);
} else {
compressed_contents = file_contents;
}
if (compressed_contents.size() > max_upload_bytes_) {
OnUploadError("File is too large to upload.");
return;
}
std::string post_data;
SetupMultipart(product, version, std::move(metadata), "trace.json.gz",
compressed_contents, &post_data);
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&TraceCrashServiceUploader::CreateAndStartURLFetcher,
base::Unretained(this), upload_url, post_data));
}
void TraceCrashServiceUploader::OnUploadError(
const std::string& error_message) {
LOG(ERROR) << error_message;
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::BindOnce(std::move(done_callback_), false, error_message));
}
void TraceCrashServiceUploader::SetupMultipart(
const std::string& product,
const std::string& version,
std::unique_ptr<const base::DictionaryValue> metadata,
const std::string& trace_filename,
const std::string& trace_contents,
std::string* post_data) {
net::AddMultipartValueForUpload("prod", product, kCrashMultipartBoundary, "",
post_data);
net::AddMultipartValueForUpload("ver", version + "-trace",
kCrashMultipartBoundary, "", post_data);
net::AddMultipartValueForUpload("guid", "0", kCrashMultipartBoundary, "",
post_data);
net::AddMultipartValueForUpload("type", "trace", kCrashMultipartBoundary, "",
post_data);
// No minidump means no need for crash to process the report.
net::AddMultipartValueForUpload("should_process", "false",
kCrashMultipartBoundary, "", post_data);
if (metadata) {
for (base::DictionaryValue::Iterator it(*metadata); !it.IsAtEnd();
it.Advance()) {
std::string value;
if (!it.value().GetAsString(&value)) {
if (!base::JSONWriter::Write(it.value(), &value))
continue;
}
net::AddMultipartValueForUpload(it.key(), value, kCrashMultipartBoundary,
"", post_data);
}
}
AddTraceFile(trace_filename, trace_contents, post_data);
net::AddMultipartFinalDelimiterForUpload(kCrashMultipartBoundary, post_data);
}
void TraceCrashServiceUploader::AddTraceFile(const std::string& trace_filename,
const std::string& trace_contents,
std::string* post_data) {
post_data->append("--");
post_data->append(kCrashMultipartBoundary);
post_data->append("\r\n");
post_data->append("Content-Disposition: form-data; name=\"trace\"");
post_data->append("; filename=\"");
post_data->append(trace_filename);
post_data->append("\"\r\n");
post_data->append("Content-Type: application/gzip\r\n\r\n");
post_data->append(trace_contents);
post_data->append("\r\n");
}
bool TraceCrashServiceUploader::Compress(std::string input,
int max_compressed_bytes,
char* compressed,
int* compressed_bytes) {
DCHECK(compressed);
DCHECK(compressed_bytes);
z_stream stream = {0};
int result = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
// 16 is added to produce a gzip header + trailer.
MAX_WBITS + 16,
8, // memLevel = 8 is default.
Z_DEFAULT_STRATEGY);
DCHECK_EQ(Z_OK, result);
stream.next_in = reinterpret_cast<uint8_t*>(&input[0]);
stream.avail_in = input.size();
stream.next_out = reinterpret_cast<uint8_t*>(compressed);
stream.avail_out = max_compressed_bytes;
// Do a one-shot compression. This will return Z_STREAM_END only if |output|
// is large enough to hold all compressed data.
result = deflate(&stream, Z_FINISH);
bool success = (result == Z_STREAM_END);
result = deflateEnd(&stream);
DCHECK(result == Z_OK || result == Z_DATA_ERROR);
if (success)
*compressed_bytes = max_compressed_bytes - stream.avail_out;
LOG(WARNING) << "input size: " << input.size()
<< ", output size: " << *compressed_bytes;
return success;
}
void TraceCrashServiceUploader::CreateAndStartURLFetcher(
const std::string& upload_url,
const std::string& post_data) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!url_fetcher_.get());
std::string content_type = kCrashUploadContentType;
content_type.append("; boundary=");
content_type.append(kCrashMultipartBoundary);
// Create traffic annotation tag.
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("background_performance_tracer", R"(
semantics {
sender: "Background Performance Traces"
description:
"Under certain conditions, Chromium will send anonymized "
"performance timeline data to Google for the purposes of improving "
"Chromium performance. We can set up a percentage of the "
"population to send back trace reports when a certain UMA "
"histogram bucket is incremented, for example, 'For 1% of the Beta "
"population, send us a trace if it ever takes more than 1 seconds "
"for the Omnibox to respond to a typed character'. The possible "
"types of triggers right now are UMA histograms, and manually "
"triggered events from code (think of them like asserts, that'll "
"cause a report to be sent if enabled for that population)."
trigger:
"Google-controlled triggering conditions, usually when a bad "
"performance situation occurs."
data: "An anonymized Chromium trace (see about://tracing)."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"You can enable or disable this feature via 'Automatically send "
"usage statistics and crash reports to Google' in Chromium's "
"settings under Advanced, Privacy. This feature is enabled by "
"default."
chrome_policy {
MetricsReportingEnabled {
policy_options {mode: MANDATORY}
MetricsReportingEnabled: false
}
}
})");
url_fetcher_ = net::URLFetcher::Create(
GURL(upload_url), net::URLFetcher::POST, this, traffic_annotation);
data_use_measurement::DataUseUserData::AttachToFetcher(
url_fetcher_.get(),
data_use_measurement::DataUseUserData::TRACING_UPLOADER);
url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
net::LOAD_DO_NOT_SEND_COOKIES);
url_fetcher_->SetRequestContext(request_context_);
url_fetcher_->SetUploadData(content_type, post_data);
url_fetcher_->Start();
}