// 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--**----"; const int kHttpResponseOk = 200; } // namespace WebRtcLogUploader::WebRtcLogUploader() : log_count_(0), post_data_(NULL) { file_thread_checker_.DetachFromThread(); } WebRtcLogUploader::~WebRtcLogUploader() { DCHECK(create_thread_checker_.CalledOnValidThread()); // Delete all pending URLFetcher and release all references to // WebRtcLoggingHandlerHost. for (UploadDoneDataMap::iterator it = upload_done_data_.begin(); it != upload_done_data_.end(); ++it) { delete it->first; } upload_done_data_.clear(); } void WebRtcLogUploader::OnURLFetchComplete( const net::URLFetcher* source) { DCHECK(create_thread_checker_.CalledOnValidThread()); DCHECK(upload_done_data_.find(source) != upload_done_data_.end()); int response_code = source->GetResponseCode(); std::string report_id; if (response_code == kHttpResponseOk && source->GetResponseAsString(&report_id)) { content::BrowserThread::PostTask( content::BrowserThread::FILE, FROM_HERE, base::Bind(&WebRtcLogUploader::AddUploadedLogInfoToUploadListFile, base::Unretained(this), upload_done_data_[source].upload_list_path, report_id)); } NotifyUploadDone(response_code, report_id, upload_done_data_[source]); upload_done_data_.erase(source); delete source; } void WebRtcLogUploader::OnURLFetchUploadProgress( const net::URLFetcher* source, int64 current, int64 total) { } bool WebRtcLogUploader::ApplyForStartLogging() { DCHECK(create_thread_checker_.CalledOnValidThread()); if (log_count_ < kLogCountLimit) { ++log_count_; return true; } return false; } void WebRtcLogUploader::LoggingStoppedDontUpload() { content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, base::Bind(&WebRtcLogUploader::DecreaseLogCount, base::Unretained(this))); } void WebRtcLogUploader::LoggingStoppedDoUpload( net::URLRequestContextGetter* request_context, scoped_ptr<unsigned char[]> log_buffer, uint32 length, const std::map<std::string, std::string>& meta_data, const WebRtcLogUploadDoneData& upload_done_data) { DCHECK(file_thread_checker_.CalledOnValidThread()); DCHECK(log_buffer.get()); DCHECK(!upload_done_data.upload_list_path.empty()); scoped_ptr<std::string> post_data; post_data.reset(new std::string); SetupMultipart(post_data.get(), reinterpret_cast<uint8*>(log_buffer.get()), length, meta_data); // If a test has set the test string pointer, write to it and skip uploading. // Still fire the upload callback so that we can run an extension API test // using the test framework for that without hanging. // TODO(grunell): Remove this when the api test for this feature is fully // implemented according to the test plan. http://crbug.com/257329. if (post_data_) { *post_data_ = *post_data; NotifyUploadDone(kHttpResponseOk, "", upload_done_data); return; } content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, base::Bind(&WebRtcLogUploader::CreateAndStartURLFetcher, base::Unretained(this), make_scoped_refptr(request_context), upload_done_data, Passed(&post_data))); 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::map<std::string, std::string>& meta_data) { #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); // Add custom meta data. std::map<std::string, std::string>::const_iterator it = meta_data.begin(); for (; it != meta_data.end(); ++it) { net::AddMultipartValueForUpload(it->first, it->second, 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::CreateAndStartURLFetcher( scoped_refptr<net::URLRequestContextGetter> request_context, const WebRtcLogUploadDoneData& upload_done_data, scoped_ptr<std::string> post_data) { DCHECK(create_thread_checker_.CalledOnValidThread()); 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(); upload_done_data_[url_fetcher] = upload_done_data; } void WebRtcLogUploader::DecreaseLogCount() { DCHECK(create_thread_checker_.CalledOnValidThread()); --log_count_; } void WebRtcLogUploader::AddUploadedLogInfoToUploadListFile( const base::FilePath& upload_list_path, const std::string& report_id) { DCHECK(file_thread_checker_.CalledOnValidThread()); 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())); } void WebRtcLogUploader::NotifyUploadDone( int response_code, const std::string& report_id, const WebRtcLogUploadDoneData& upload_done_data) { content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE, base::Bind(&WebRtcLoggingHandlerHost::UploadLogDone, upload_done_data.host)); if (!upload_done_data.callback.is_null()) { bool success = response_code == kHttpResponseOk; std::string error_message; if (!success) { error_message = "Uploading failed, response code: " + base::IntToString(response_code); } content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, base::Bind(upload_done_data.callback, success, report_id, error_message)); } }