1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/media/webrtc_log_uploader.h"
7 #include "base/files/file_enumerator.h"
8 #include "base/files/file_path.h"
9 #include "base/files/file_util.h"
10 #include "base/logging.h"
11 #include "base/path_service.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_split.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/time/time.h"
16 #include "chrome/browser/browser_process.h"
17 #include "chrome/browser/media/webrtc_log_list.h"
18 #include "chrome/browser/media/webrtc_log_util.h"
19 #include "chrome/common/chrome_version_info.h"
20 #include "chrome/common/partial_circular_buffer.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "net/base/mime_util.h"
23 #include "net/url_request/url_fetcher.h"
24 #include "third_party/zlib/zlib.h"
28 const int kLogCountLimit = 5;
29 const uint32 kIntermediateCompressionBufferBytes = 256 * 1024; // 256 KB
30 const int kLogListLimitLines = 50;
32 const char kUploadURL[] = "https://clients2.google.com/cr/report";
33 const char kUploadContentType[] = "multipart/form-data";
34 const char kMultipartBoundary[] =
35 "----**--yradnuoBgoLtrapitluMklaTelgooG--**----";
37 const int kHttpResponseOk = 200;
39 // Adds the header section for a gzip file to the multipart |post_data|.
40 void AddMultipartFileContentHeader(std::string* post_data,
41 const std::string& content_name) {
42 post_data->append("--");
43 post_data->append(kMultipartBoundary);
44 post_data->append("\r\nContent-Disposition: form-data; name=\"");
45 post_data->append(content_name);
46 post_data->append("\"; filename=\"");
47 post_data->append(content_name + ".gz");
48 post_data->append("\"\r\nContent-Type: application/gzip\r\n\r\n");
51 // Adds |compressed_log| to |post_data|.
52 void AddLogData(std::string* post_data,
53 const std::vector<uint8>& compressed_log) {
54 AddMultipartFileContentHeader(post_data, "webrtc_log");
55 post_data->append(reinterpret_cast<const char*>(&compressed_log[0]),
56 compressed_log.size());
57 post_data->append("\r\n");
60 // Adds the RTP dump data to |post_data|.
61 void AddRtpDumpData(std::string* post_data,
62 const std::string& name,
63 const std::string& dump_data) {
64 AddMultipartFileContentHeader(post_data, name);
65 post_data->append(dump_data.data(), dump_data.size());
66 post_data->append("\r\n");
71 WebRtcLogUploadDoneData::WebRtcLogUploadDoneData() {}
73 WebRtcLogUploadDoneData::~WebRtcLogUploadDoneData() {}
75 WebRtcLogUploader::WebRtcLogUploader()
78 shutting_down_(false) {
79 file_thread_checker_.DetachFromThread();
82 WebRtcLogUploader::~WebRtcLogUploader() {
83 DCHECK(create_thread_checker_.CalledOnValidThread());
84 DCHECK(upload_done_data_.empty());
85 DCHECK(shutting_down_);
88 void WebRtcLogUploader::OnURLFetchComplete(
89 const net::URLFetcher* source) {
90 DCHECK(create_thread_checker_.CalledOnValidThread());
91 DCHECK(upload_done_data_.find(source) != upload_done_data_.end());
92 DCHECK(!shutting_down_);
93 int response_code = source->GetResponseCode();
94 UploadDoneDataMap::iterator it = upload_done_data_.find(source);
95 if (it != upload_done_data_.end()) {
96 // The log path can be empty here if we failed getting it before. We still
97 // upload the log if that's the case.
98 std::string report_id;
99 if (response_code == kHttpResponseOk &&
100 source->GetResponseAsString(&report_id) &&
101 !it->second.log_path.empty()) {
102 // TODO(jiayl): Add the RTP dump records to chrome://webrtc-logs.
103 base::FilePath log_list_path =
104 WebRtcLogList::GetWebRtcLogListFileForDirectory(it->second.log_path);
105 content::BrowserThread::PostTask(
106 content::BrowserThread::FILE,
108 base::Bind(&WebRtcLogUploader::AddUploadedLogInfoToUploadListFile,
109 base::Unretained(this),
111 it->second.local_log_id,
114 NotifyUploadDone(response_code, report_id, it->second);
115 upload_done_data_.erase(it);
121 void WebRtcLogUploader::OnURLFetchUploadProgress(
122 const net::URLFetcher* source, int64 current, int64 total) {
125 bool WebRtcLogUploader::ApplyForStartLogging() {
126 DCHECK(create_thread_checker_.CalledOnValidThread());
127 if (log_count_ < kLogCountLimit && !shutting_down_) {
134 void WebRtcLogUploader::LoggingStoppedDontUpload() {
135 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
136 base::Bind(&WebRtcLogUploader::DecreaseLogCount, base::Unretained(this)));
139 void WebRtcLogUploader::LoggingStoppedDoUpload(
140 scoped_ptr<unsigned char[]> log_buffer,
142 const std::map<std::string, std::string>& meta_data,
143 const WebRtcLogUploadDoneData& upload_done_data) {
144 DCHECK(file_thread_checker_.CalledOnValidThread());
145 DCHECK(log_buffer.get());
146 DCHECK(!upload_done_data.log_path.empty());
148 std::vector<uint8> compressed_log;
150 &compressed_log, reinterpret_cast<uint8*>(&log_buffer[0]), length);
152 std::string local_log_id;
154 if (base::PathExists(upload_done_data.log_path)) {
155 WebRtcLogUtil::DeleteOldWebRtcLogFiles(upload_done_data.log_path);
157 local_log_id = base::DoubleToString(base::Time::Now().ToDoubleT());
158 base::FilePath log_file_path =
159 upload_done_data.log_path.AppendASCII(local_log_id)
160 .AddExtension(FILE_PATH_LITERAL(".gz"));
161 WriteCompressedLogToFile(compressed_log, log_file_path);
163 base::FilePath log_list_path =
164 WebRtcLogList::GetWebRtcLogListFileForDirectory(
165 upload_done_data.log_path);
166 AddLocallyStoredLogInfoToUploadListFile(log_list_path, local_log_id);
169 WebRtcLogUploadDoneData upload_done_data_with_log_id = upload_done_data;
170 upload_done_data_with_log_id.local_log_id = local_log_id;
172 scoped_ptr<std::string> post_data(new std::string());
173 SetupMultipart(post_data.get(),
175 upload_done_data.incoming_rtp_dump,
176 upload_done_data.outgoing_rtp_dump,
179 // If a test has set the test string pointer, write to it and skip uploading.
180 // Still fire the upload callback so that we can run an extension API test
181 // using the test framework for that without hanging.
182 // TODO(grunell): Remove this when the api test for this feature is fully
183 // implemented according to the test plan. http://crbug.com/257329.
185 *post_data_ = *post_data;
186 NotifyUploadDone(kHttpResponseOk, "", upload_done_data_with_log_id);
190 content::BrowserThread::PostTask(
191 content::BrowserThread::UI,
193 base::Bind(&WebRtcLogUploader::CreateAndStartURLFetcher,
194 base::Unretained(this),
195 upload_done_data_with_log_id,
196 Passed(&post_data)));
198 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
199 base::Bind(&WebRtcLogUploader::DecreaseLogCount, base::Unretained(this)));
202 void WebRtcLogUploader::StartShutdown() {
203 DCHECK(create_thread_checker_.CalledOnValidThread());
204 DCHECK(!shutting_down_);
206 // Delete all URLFetchers first and clear the upload done map.
207 for (UploadDoneDataMap::iterator it = upload_done_data_.begin();
208 it != upload_done_data_.end();
212 upload_done_data_.clear();
213 shutting_down_ = true;
216 void WebRtcLogUploader::SetupMultipart(
217 std::string* post_data,
218 const std::vector<uint8>& compressed_log,
219 const base::FilePath& incoming_rtp_dump,
220 const base::FilePath& outgoing_rtp_dump,
221 const std::map<std::string, std::string>& meta_data) {
223 const char product[] = "Chrome";
224 #elif defined(OS_MACOSX)
225 const char product[] = "Chrome_Mac";
226 #elif defined(OS_LINUX)
227 #if !defined(ADDRESS_SANITIZER)
228 const char product[] = "Chrome_Linux";
230 const char product[] = "Chrome_Linux_ASan";
232 #elif defined(OS_ANDROID)
233 const char product[] = "Chrome_Android";
234 #elif defined(OS_CHROMEOS)
235 const char product[] = "Chrome_ChromeOS";
237 #error Platform not supported.
239 net::AddMultipartValueForUpload("prod", product, kMultipartBoundary,
241 chrome::VersionInfo version_info;
242 net::AddMultipartValueForUpload("ver", version_info.Version() + "-webrtc",
243 kMultipartBoundary, "", post_data);
244 net::AddMultipartValueForUpload("guid", "0", kMultipartBoundary,
246 net::AddMultipartValueForUpload("type", "webrtc_log", kMultipartBoundary,
249 // Add custom meta data.
250 std::map<std::string, std::string>::const_iterator it = meta_data.begin();
251 for (; it != meta_data.end(); ++it) {
252 net::AddMultipartValueForUpload(it->first, it->second, kMultipartBoundary,
256 AddLogData(post_data, compressed_log);
258 // Add the rtp dumps if they exist.
259 base::FilePath rtp_dumps[2] = {incoming_rtp_dump, outgoing_rtp_dump};
260 static const char* kRtpDumpNames[2] = {"rtpdump_recv", "rtpdump_send"};
262 for (size_t i = 0; i < 2; ++i) {
263 if (!rtp_dumps[i].empty() && base::PathExists(rtp_dumps[i])) {
264 std::string dump_data;
265 if (base::ReadFileToString(rtp_dumps[i], &dump_data))
266 AddRtpDumpData(post_data, kRtpDumpNames[i], dump_data);
270 net::AddMultipartFinalDelimiterForUpload(kMultipartBoundary, post_data);
273 void WebRtcLogUploader::CompressLog(std::vector<uint8>* compressed_log,
276 PartialCircularBuffer read_pcb(input, input_size);
278 z_stream stream = {0};
279 int result = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
280 // windowBits = 15 is default, 16 is added to
281 // produce a gzip header + trailer.
283 8, // memLevel = 8 is default.
285 DCHECK_EQ(Z_OK, result);
287 uint8 intermediate_buffer[kIntermediateCompressionBufferBytes] = {0};
288 ResizeForNextOutput(compressed_log, &stream);
292 if (stream.avail_in == 0) {
293 read = read_pcb.Read(&intermediate_buffer[0],
294 kIntermediateCompressionBufferBytes);
295 stream.next_in = &intermediate_buffer[0];
296 stream.avail_in = read;
297 if (read != kIntermediateCompressionBufferBytes)
300 result = deflate(&stream, Z_SYNC_FLUSH);
301 DCHECK_EQ(Z_OK, result);
302 if (stream.avail_out == 0)
303 ResizeForNextOutput(compressed_log, &stream);
306 // Ensure we have enough room in the output buffer. Easier to always just do a
307 // resize than looping around and resize if needed.
308 if (stream.avail_out < kIntermediateCompressionBufferBytes)
309 ResizeForNextOutput(compressed_log, &stream);
311 result = deflate(&stream, Z_FINISH);
312 DCHECK_EQ(Z_STREAM_END, result);
313 result = deflateEnd(&stream);
314 DCHECK_EQ(Z_OK, result);
316 compressed_log->resize(compressed_log->size() - stream.avail_out);
319 void WebRtcLogUploader::ResizeForNextOutput(std::vector<uint8>* compressed_log,
321 size_t old_size = compressed_log->size() - stream->avail_out;
322 compressed_log->resize(old_size + kIntermediateCompressionBufferBytes);
323 stream->next_out = &(*compressed_log)[old_size];
324 stream->avail_out = kIntermediateCompressionBufferBytes;
327 void WebRtcLogUploader::CreateAndStartURLFetcher(
328 const WebRtcLogUploadDoneData& upload_done_data,
329 scoped_ptr<std::string> post_data) {
330 DCHECK(create_thread_checker_.CalledOnValidThread());
335 std::string content_type = kUploadContentType;
336 content_type.append("; boundary=");
337 content_type.append(kMultipartBoundary);
339 net::URLFetcher* url_fetcher =
340 net::URLFetcher::Create(GURL(kUploadURL), net::URLFetcher::POST, this);
341 url_fetcher->SetRequestContext(g_browser_process->system_request_context());
342 url_fetcher->SetUploadData(content_type, *post_data);
343 url_fetcher->Start();
344 upload_done_data_[url_fetcher] = upload_done_data;
347 void WebRtcLogUploader::DecreaseLogCount() {
348 DCHECK(create_thread_checker_.CalledOnValidThread());
352 void WebRtcLogUploader::WriteCompressedLogToFile(
353 const std::vector<uint8>& compressed_log,
354 const base::FilePath& log_file_path) {
355 DCHECK(file_thread_checker_.CalledOnValidThread());
356 DCHECK(!compressed_log.empty());
357 base::WriteFile(log_file_path,
358 reinterpret_cast<const char*>(&compressed_log[0]),
359 compressed_log.size());
362 void WebRtcLogUploader::AddLocallyStoredLogInfoToUploadListFile(
363 const base::FilePath& upload_list_path,
364 const std::string& local_log_id) {
365 DCHECK(file_thread_checker_.CalledOnValidThread());
366 DCHECK(!upload_list_path.empty());
367 DCHECK(!local_log_id.empty());
369 std::string contents;
371 if (base::PathExists(upload_list_path)) {
372 if (!base::ReadFileToString(upload_list_path, &contents)) {
373 DPLOG(WARNING) << "Could not read WebRTC log list file.";
377 // Limit the number of log entries to |kLogListLimitLines| - 1, to make room
378 // for the new entry. Each line including the last ends with a '\n', so hit
379 // n will be before line n-1 (from the back).
381 int i = contents.size() - 1;
382 for (; i >= 0 && lf_count < kLogListLimitLines; --i) {
383 if (contents[i] == '\n')
386 if (lf_count >= kLogListLimitLines) {
387 // + 1 to compensate for the for loop decrease before the conditional
388 // check and + 1 to get the length.
389 contents.erase(0, i + 2);
393 // Write the log ID to the log list file. Leave the upload time and report ID
395 contents += ",," + local_log_id + '\n';
398 base::WriteFile(upload_list_path, &contents[0], contents.size());
399 if (written != static_cast<int>(contents.size())) {
400 DPLOG(WARNING) << "Could not write all data to WebRTC log list file: "
405 void WebRtcLogUploader::AddUploadedLogInfoToUploadListFile(
406 const base::FilePath& upload_list_path,
407 const std::string& local_log_id,
408 const std::string& report_id) {
409 DCHECK(file_thread_checker_.CalledOnValidThread());
410 DCHECK(!upload_list_path.empty());
411 DCHECK(!local_log_id.empty());
412 DCHECK(!report_id.empty());
414 std::string contents;
416 if (base::PathExists(upload_list_path)) {
417 if (!base::ReadFileToString(upload_list_path, &contents)) {
418 DPLOG(WARNING) << "Could not read WebRTC log list file.";
423 // Write the Unix time and report ID to the log list file. We should be able
424 // to find the local log ID, in that case insert the data into the existing
425 // line. Otherwise add it in the end.
426 base::Time time_now = base::Time::Now();
427 std::string time_now_str = base::DoubleToString(time_now.ToDoubleT());
428 size_t pos = contents.find(",," + local_log_id);
429 if (pos != std::string::npos) {
430 contents.insert(pos, time_now_str);
431 contents.insert(pos + time_now_str.length() + 1, report_id);
433 contents += time_now_str + "," + report_id + ",\n";
437 base::WriteFile(upload_list_path, &contents[0], contents.size());
438 if (written != static_cast<int>(contents.size())) {
439 DPLOG(WARNING) << "Could not write all data to WebRTC log list file: "
444 void WebRtcLogUploader::NotifyUploadDone(
446 const std::string& report_id,
447 const WebRtcLogUploadDoneData& upload_done_data) {
448 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
449 base::Bind(&WebRtcLoggingHandlerHost::UploadLogDone,
450 upload_done_data.host));
451 if (!upload_done_data.callback.is_null()) {
452 bool success = response_code == kHttpResponseOk;
453 std::string error_message;
455 error_message = "Uploading failed, response code: " +
456 base::IntToString(response_code);
458 content::BrowserThread::PostTask(
459 content::BrowserThread::UI, FROM_HERE,
460 base::Bind(upload_done_data.callback, success, report_id,