blob: 0ed5f1e051c1a64cebf4be09b9f86fd66b126cfa [file] [log] [blame]
// Copyright 2013 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_log_uploader.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/memory/shared_memory.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "chrome/browser/media/webrtc_log_upload_list.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_version_info.h"
#include "chrome/common/partial_circular_buffer.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"
namespace {
const int kLogCountLimit = 5;
const uint32 kIntermediateCompressionBufferBytes = 256 * 1024; // 256 KB
const int kLogListLimitLines = 50;
const char kUploadURL[] = "https://clients2.google.com/cr/report";
const char kUploadContentType[] = "multipart/form-data";
const char kMultipartBoundary[] =
"----**--yradnuoBgoLtrapitluMklaTelgooG--**----";
} // namespace
WebRtcLogUploader::WebRtcLogUploader()
: log_count_(0),
post_data_(NULL) {
base::FilePath log_dir_path;
PathService::Get(chrome::DIR_USER_DATA, &log_dir_path);
upload_list_path_ =
log_dir_path.AppendASCII(WebRtcLogUploadList::kWebRtcLogListFilename);
}
WebRtcLogUploader::~WebRtcLogUploader() {}
void WebRtcLogUploader::OnURLFetchComplete(
const net::URLFetcher* source) {
int response_code = source->GetResponseCode();
std::string report_id;
if (response_code == 200 && source->GetResponseAsString(&report_id))
AddUploadedLogInfoToUploadListFile(report_id);
}
void WebRtcLogUploader::OnURLFetchUploadProgress(
const net::URLFetcher* source, int64 current, int64 total) {
}
bool WebRtcLogUploader::ApplyForStartLogging() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
if (log_count_ < kLogCountLimit) {
++log_count_;
return true;
}
return false;
}
void WebRtcLogUploader::UploadLog(net::URLRequestContextGetter* request_context,
scoped_ptr<base::SharedMemory> shared_memory,
uint32 length,
const std::string& app_session_id,
const std::string& app_url) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
DCHECK(shared_memory);
DCHECK(shared_memory->memory());
std::string post_data;
SetupMultipart(&post_data, reinterpret_cast<uint8*>(shared_memory->memory()),
length, app_session_id, app_url);
// If a test has set the test string pointer, write to it and skip uploading.
// This will be removed when the browser test for this feature is fully done
// according to the test plan. See http://crbug.com/257329.
if (post_data_) {
*post_data_ = post_data;
return;
}
std::string content_type = kUploadContentType;
content_type.append("; boundary=");
content_type.append(kMultipartBoundary);
net::URLFetcher* url_fetcher =
net::URLFetcher::Create(GURL(kUploadURL), net::URLFetcher::POST, this);
url_fetcher->SetRequestContext(request_context);
url_fetcher->SetUploadData(content_type, post_data);
url_fetcher->Start();
content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
base::Bind(&WebRtcLogUploader::DecreaseLogCount, base::Unretained(this)));
}
void WebRtcLogUploader::SetupMultipart(std::string* post_data,
uint8* log_buffer,
uint32 log_buffer_length,
const std::string& app_session_id,
const std::string& app_url) {
#if defined(OS_WIN)
const char product[] = "Chrome";
#elif defined(OS_MACOSX)
const char product[] = "Chrome_Mac";
#elif defined(OS_LINUX)
#if !defined(ADDRESS_SANITIZER)
const char product[] = "Chrome_Linux";
#else
const char product[] = "Chrome_Linux_ASan";
#endif
#elif defined(OS_ANDROID)
const char product[] = "Chrome_Android";
#elif defined(OS_CHROMEOS)
const char product[] = "Chrome_ChromeOS";
#else
// This file should not be compiled for other platforms.
COMPILE_ASSERT(false);
#endif
net::AddMultipartValueForUpload("prod", product, kMultipartBoundary,
"", post_data);
chrome::VersionInfo version_info;
net::AddMultipartValueForUpload("ver", version_info.Version(),
kMultipartBoundary, "", post_data);
net::AddMultipartValueForUpload("guid", "0", kMultipartBoundary,
"", post_data);
net::AddMultipartValueForUpload("type", "webrtc_log", kMultipartBoundary,
"", post_data);
net::AddMultipartValueForUpload("app_session_id", app_session_id,
kMultipartBoundary, "", post_data);
net::AddMultipartValueForUpload("url", app_url, kMultipartBoundary,
"", post_data);
AddLogData(post_data, log_buffer, log_buffer_length);
net::AddMultipartFinalDelimiterForUpload(kMultipartBoundary, post_data);
}
void WebRtcLogUploader::AddLogData(std::string* post_data,
uint8* log_buffer,
uint32 log_buffer_length) {
post_data->append("--");
post_data->append(kMultipartBoundary);
post_data->append("\r\n");
post_data->append("Content-Disposition: form-data; name=\"webrtc_log\"");
post_data->append("; filename=\"webrtc_log.gz\"\r\n");
post_data->append("Content-Type: application/gzip\r\n\r\n");
CompressLog(post_data, log_buffer, log_buffer_length);
post_data->append("\r\n");
}
void WebRtcLogUploader::CompressLog(std::string* post_data,
uint8* input,
uint32 input_size) {
PartialCircularBuffer read_pcb(input, input_size);
z_stream stream = {0};
int result = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
// windowBits = 15 is default, 16 is added to
// produce a gzip header + trailer.
15 + 16,
8, // memLevel = 8 is default.
Z_DEFAULT_STRATEGY);
DCHECK_EQ(Z_OK, result);
uint8 intermediate_buffer[kIntermediateCompressionBufferBytes] = {0};
ResizeForNextOutput(post_data, &stream);
uint32 read = 0;
do {
if (stream.avail_in == 0) {
read = read_pcb.Read(&intermediate_buffer[0],
kIntermediateCompressionBufferBytes);
stream.next_in = &intermediate_buffer[0];
stream.avail_in = read;
if (read != kIntermediateCompressionBufferBytes)
break;
}
result = deflate(&stream, Z_SYNC_FLUSH);
DCHECK_EQ(Z_OK, result);
if (stream.avail_out == 0)
ResizeForNextOutput(post_data, &stream);
} while (true);
// Ensure we have enough room in the output buffer. Easier to always just do a
// resize than looping around and resize if needed.
if (stream.avail_out < kIntermediateCompressionBufferBytes)
ResizeForNextOutput(post_data, &stream);
result = deflate(&stream, Z_FINISH);
DCHECK_EQ(Z_STREAM_END, result);
result = deflateEnd(&stream);
DCHECK_EQ(Z_OK, result);
post_data->resize(post_data->size() - stream.avail_out);
}
void WebRtcLogUploader::ResizeForNextOutput(std::string* post_data,
z_stream* stream) {
size_t old_size = post_data->size() - stream->avail_out;
post_data->resize(old_size + kIntermediateCompressionBufferBytes);
stream->next_out = reinterpret_cast<uint8*>(&(*post_data)[old_size]);
stream->avail_out = kIntermediateCompressionBufferBytes;
}
void WebRtcLogUploader::DecreaseLogCount() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
--log_count_;
}
void WebRtcLogUploader::AddUploadedLogInfoToUploadListFile(
const std::string& report_id) {
std::string contents;
if (base::PathExists(upload_list_path_)) {
bool read_ok = base::ReadFileToString(upload_list_path_, &contents);
DPCHECK(read_ok);
// Limit the number of log entries to |kLogListLimitLines| - 1, to make room
// for the new entry. Each line including the last ends with a '\n', so hit
// n will be before line n-1 (from the back).
int lf_count = 0;
int i = contents.size() - 1;
for (; i >= 0 && lf_count < kLogListLimitLines; --i) {
if (contents[i] == '\n')
++lf_count;
}
if (lf_count >= kLogListLimitLines) {
// + 1 to compensate for the for loop decrease before the conditional
// check and + 1 to get the length.
contents.erase(0, i + 2);
}
}
// Write the Unix time and report ID to the log list file.
base::Time time_now = base::Time::Now();
contents += base::DoubleToString(time_now.ToDoubleT()) +
"," + report_id + '\n';
int written = file_util::WriteFile(upload_list_path_, &contents[0],
contents.size());
DPCHECK(written == static_cast<int>(contents.size()));
}