blob: d34249efe32ec92c0a7dbeee0b47f98217cee409 [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 "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/strings/stringprintf.h"
#include "base/time/time.h"
#include "components/tracing/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/mime_util.h"
#include "net/base/network_delegate.h"
#include "net/proxy/proxy_config.h"
#include "net/proxy/proxy_config_service.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 kUploadContentType[] = "multipart/form-data";
const char kMultipartBoundary[] =
"----**--yradnuoBgoLtrapitluMklaTelgooG--**----";
const int kHttpResponseOk = 200;
// Allow up to 10MB for trace upload
const int kMaxUploadBytes = 10000000;
} // namespace
TraceCrashServiceUploader::TraceCrashServiceUploader(
net::URLRequestContextGetter* request_context)
: request_context_(request_context) {
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::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 == kHttpResponseOk);
if (success) {
source->GetResponseAsString(&feedback);
} else {
feedback =
"Uploading failed, response code: " + base::IntToString(response_code);
}
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(done_callback_, success, feedback));
url_fetcher_.reset();
}
void TraceCrashServiceUploader::OnURLFetchUploadProgress(
const net::URLFetcher* source,
int64 current,
int64 total) {
DCHECK(url_fetcher_.get());
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,
scoped_ptr<const base::DictionaryValue> metadata,
const UploadProgressCallback& progress_callback,
const UploadDoneCallback& done_callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::BrowserThread::PostTask(
content::BrowserThread::FILE, FROM_HERE,
base::Bind(&TraceCrashServiceUploader::DoUploadOnFileThread,
base::Unretained(this), file_contents, upload_mode,
upload_url_, base::Passed(metadata.Pass()), progress_callback,
done_callback));
}
void TraceCrashServiceUploader::DoUploadOnFileThread(
const std::string& file_contents,
UploadMode upload_mode,
const std::string& upload_url,
scoped_ptr<const base::DictionaryValue> metadata,
const UploadProgressCallback& progress_callback,
const UploadDoneCallback& done_callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
DCHECK(!url_fetcher_.get());
progress_callback_ = progress_callback;
done_callback_ = done_callback;
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_LINUX)
const char product[] = "Chrome_Linux";
#elif defined(OS_ANDROID)
const char product[] = "Chrome_Android";
#elif defined(OS_CHROMEOS)
const char product[] = "Chrome_ChromeOS";
#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) {
scoped_ptr<char[]> compressed_buffer(new char[kMaxUploadBytes]);
int compressed_bytes;
if (!Compress(file_contents, kMaxUploadBytes, compressed_buffer.get(),
&compressed_bytes)) {
OnUploadError("Compressing file failed.");
return;
}
compressed_contents =
std::string(compressed_buffer.get(), compressed_bytes);
} else {
if (file_contents.size() >= kMaxUploadBytes) {
OnUploadError("File is too large to upload.");
return;
}
compressed_contents = file_contents;
}
std::string post_data;
SetupMultipart(product, version, metadata.Pass(), "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::Bind(done_callback_, false, error_message));
}
void TraceCrashServiceUploader::SetupMultipart(
const std::string& product,
const std::string& version,
scoped_ptr<const base::DictionaryValue> metadata,
const std::string& trace_filename,
const std::string& trace_contents,
std::string* post_data) {
net::AddMultipartValueForUpload("prod", product, kMultipartBoundary, "",
post_data);
net::AddMultipartValueForUpload("ver", version + "-trace", kMultipartBoundary,
"", post_data);
net::AddMultipartValueForUpload("guid", "0", kMultipartBoundary, "",
post_data);
net::AddMultipartValueForUpload("type", "trace", kMultipartBoundary, "",
post_data);
// No minidump means no need for crash to process the report.
net::AddMultipartValueForUpload("should_process", "false", kMultipartBoundary,
"", 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, kMultipartBoundary, "",
post_data);
}
}
AddTraceFile(trace_filename, trace_contents, post_data);
net::AddMultipartFinalDelimiterForUpload(kMultipartBoundary, 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(kMultipartBoundary);
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*>(&input[0]);
stream.avail_in = input.size();
stream.next_out = reinterpret_cast<uint8*>(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 = kUploadContentType;
content_type.append("; boundary=");
content_type.append(kMultipartBoundary);
url_fetcher_ =
net::URLFetcher::Create(GURL(upload_url), net::URLFetcher::POST, this);
url_fetcher_->SetRequestContext(request_context_);
url_fetcher_->SetUploadData(content_type, post_data);
url_fetcher_->Start();
}