blob: e7072498342ede347682ae225f0bfa45101eea8e [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 "content/browser/tracing/trace_uploader.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/memory/shared_memory.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "content/public/browser/browser_thread.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"
namespace content {
namespace {
const char kUploadContentType[] = "multipart/form-data";
const char kMultipartBoundary[] =
"----**--yradnuoBgoLtrapitluMklaTelgooG--**----";
const int kHttpResponseOk = 200;
} // namespace
TraceUploader::TraceUploader(const std::string& product,
const std::string& version,
const std::string& upload_url,
net::URLRequestContextGetter* request_context)
: product_(product),
version_(version),
upload_url_(upload_url),
request_context_(request_context) {
DCHECK(!product_.empty());
DCHECK(!version_.empty());
DCHECK(!upload_url_.empty());
}
TraceUploader::~TraceUploader() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
void TraceUploader::OnURLFetchComplete(const net::URLFetcher* source) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(source, url_fetcher_.get());
int response_code = source->GetResponseCode();
string report_id;
string error_message;
bool success = (response_code == kHttpResponseOk);
if (success) {
source->GetResponseAsString(&report_id);
} else {
error_message = "Uploading failed, response code: " +
base::IntToString(response_code);
}
BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(done_callback_, success, report_id, error_message));
url_fetcher_.reset();
}
void TraceUploader::OnURLFetchUploadProgress(
const net::URLFetcher* source, int64 current, int64 total) {
DCHECK(url_fetcher_.get());
LOG(WARNING) << "Upload progress: " << current << " of " << total;
BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(progress_callback_, current, total));
}
void TraceUploader::DoUpload(
const std::string& file_contents,
UploadProgressCallback progress_callback,
UploadDoneCallback done_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
DCHECK(!url_fetcher_.get());
progress_callback_ = progress_callback;
done_callback_ = done_callback;
if (url_fetcher_.get()) {
OnUploadError("Already uploading.");
}
scoped_ptr<char[]> compressed_contents(new char[kMaxUploadBytes]);
int compressed_bytes;
if (!Compress(file_contents, kMaxUploadBytes, compressed_contents.get(),
&compressed_bytes)) {
OnUploadError("Compressing file failed.");
return;
}
std::string post_data;
SetupMultipart("trace.json.gz",
std::string(compressed_contents.get(), compressed_bytes),
&post_data);
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&TraceUploader::CreateAndStartURLFetcher,
base::Unretained(this),
post_data));
}
void TraceUploader::OnUploadError(std::string error_message) {
LOG(ERROR) << error_message;
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(done_callback_, false, "", error_message));
}
void TraceUploader::SetupMultipart(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);
AddTraceFile(trace_filename, trace_contents, post_data);
net::AddMultipartFinalDelimiterForUpload(kMultipartBoundary, post_data);
}
void TraceUploader::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 TraceUploader::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 TraceUploader::CreateAndStartURLFetcher(const std::string& post_data) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!url_fetcher_.get());
std::string content_type = kUploadContentType;
content_type.append("; boundary=");
content_type.append(kMultipartBoundary);
url_fetcher_.reset(
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();
}
} // namespace content