// 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 #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 "build/build_config.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_t current, int64_t 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 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(std::move(metadata)), progress_callback, done_callback)); } void TraceCrashServiceUploader::DoUploadOnFileThread( const std::string& file_contents, UploadMode upload_mode, const std::string& upload_url, scoped_ptr 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 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 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, 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::Bind(done_callback_, false, error_message)); } void TraceCrashServiceUploader::SetupMultipart( const std::string& product, const std::string& version, scoped_ptr 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(&input[0]); stream.avail_in = input.size(); stream.next_out = reinterpret_cast(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(); }