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. 4 5 #include "chrome/browser/media/webrtc_log_uploader.h" 6 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" 25 26 namespace { 27 28 const int kLogCountLimit = 5; 29 const uint32 kIntermediateCompressionBufferBytes = 256 * 1024; // 256 KB 30 const int kLogListLimitLines = 50; 31 32 const char kUploadURL[] = "https://clients2.google.com/cr/report"; 33 const char kUploadContentType[] = "multipart/form-data"; 34 const char kMultipartBoundary[] = 35 "----**--yradnuoBgoLtrapitluMklaTelgooG--**----"; 36 37 const int kHttpResponseOk = 200; 38 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"); 49 } 50 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"); 58 } 59 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"); 67 } 68 69 } // namespace 70 71 WebRtcLogUploadDoneData::WebRtcLogUploadDoneData() {} 72 73 WebRtcLogUploadDoneData::~WebRtcLogUploadDoneData() {} 74 75 WebRtcLogUploader::WebRtcLogUploader() 76 : log_count_(0), 77 post_data_(NULL), 78 shutting_down_(false) { 79 file_thread_checker_.DetachFromThread(); 80 } 81 82 WebRtcLogUploader::~WebRtcLogUploader() { 83 DCHECK(create_thread_checker_.CalledOnValidThread()); 84 DCHECK(upload_done_data_.empty()); 85 DCHECK(shutting_down_); 86 } 87 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, 107 FROM_HERE, 108 base::Bind(&WebRtcLogUploader::AddUploadedLogInfoToUploadListFile, 109 base::Unretained(this), 110 log_list_path, 111 it->second.local_log_id, 112 report_id)); 113 } 114 NotifyUploadDone(response_code, report_id, it->second); 115 upload_done_data_.erase(it); 116 } 117 118 delete source; 119 } 120 121 void WebRtcLogUploader::OnURLFetchUploadProgress( 122 const net::URLFetcher* source, int64 current, int64 total) { 123 } 124 125 bool WebRtcLogUploader::ApplyForStartLogging() { 126 DCHECK(create_thread_checker_.CalledOnValidThread()); 127 if (log_count_ < kLogCountLimit && !shutting_down_) { 128 ++log_count_; 129 return true; 130 } 131 return false; 132 } 133 134 void WebRtcLogUploader::LoggingStoppedDontUpload() { 135 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, 136 base::Bind(&WebRtcLogUploader::DecreaseLogCount, base::Unretained(this))); 137 } 138 139 void WebRtcLogUploader::LoggingStoppedDoUpload( 140 scoped_ptr<unsigned char[]> log_buffer, 141 uint32 length, 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()); 147 148 std::vector<uint8> compressed_log; 149 CompressLog( 150 &compressed_log, reinterpret_cast<uint8*>(&log_buffer[0]), length); 151 152 std::string local_log_id; 153 154 if (base::PathExists(upload_done_data.log_path)) { 155 WebRtcLogUtil::DeleteOldWebRtcLogFiles(upload_done_data.log_path); 156 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); 162 163 base::FilePath log_list_path = 164 WebRtcLogList::GetWebRtcLogListFileForDirectory( 165 upload_done_data.log_path); 166 AddLocallyStoredLogInfoToUploadListFile(log_list_path, local_log_id); 167 } 168 169 WebRtcLogUploadDoneData upload_done_data_with_log_id = upload_done_data; 170 upload_done_data_with_log_id.local_log_id = local_log_id; 171 172 scoped_ptr<std::string> post_data(new std::string()); 173 SetupMultipart(post_data.get(), 174 compressed_log, 175 upload_done_data.incoming_rtp_dump, 176 upload_done_data.outgoing_rtp_dump, 177 meta_data); 178 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. 184 if (post_data_) { 185 *post_data_ = *post_data; 186 NotifyUploadDone(kHttpResponseOk, "", upload_done_data_with_log_id); 187 return; 188 } 189 190 content::BrowserThread::PostTask( 191 content::BrowserThread::UI, 192 FROM_HERE, 193 base::Bind(&WebRtcLogUploader::CreateAndStartURLFetcher, 194 base::Unretained(this), 195 upload_done_data_with_log_id, 196 Passed(&post_data))); 197 198 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, 199 base::Bind(&WebRtcLogUploader::DecreaseLogCount, base::Unretained(this))); 200 } 201 202 void WebRtcLogUploader::StartShutdown() { 203 DCHECK(create_thread_checker_.CalledOnValidThread()); 204 DCHECK(!shutting_down_); 205 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(); 209 ++it) { 210 delete it->first; 211 } 212 upload_done_data_.clear(); 213 shutting_down_ = true; 214 } 215 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) { 222 #if defined(OS_WIN) 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"; 229 #else 230 const char product[] = "Chrome_Linux_ASan"; 231 #endif 232 #elif defined(OS_ANDROID) 233 const char product[] = "Chrome_Android"; 234 #elif defined(OS_CHROMEOS) 235 const char product[] = "Chrome_ChromeOS"; 236 #else 237 #error Platform not supported. 238 #endif 239 net::AddMultipartValueForUpload("prod", product, kMultipartBoundary, 240 "", post_data); 241 chrome::VersionInfo version_info; 242 net::AddMultipartValueForUpload("ver", version_info.Version() + "-webrtc", 243 kMultipartBoundary, "", post_data); 244 net::AddMultipartValueForUpload("guid", "0", kMultipartBoundary, 245 "", post_data); 246 net::AddMultipartValueForUpload("type", "webrtc_log", kMultipartBoundary, 247 "", post_data); 248 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, 253 "", post_data); 254 } 255 256 AddLogData(post_data, compressed_log); 257 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"}; 261 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); 267 } 268 } 269 270 net::AddMultipartFinalDelimiterForUpload(kMultipartBoundary, post_data); 271 } 272 273 void WebRtcLogUploader::CompressLog(std::vector<uint8>* compressed_log, 274 uint8* input, 275 uint32 input_size) { 276 PartialCircularBuffer read_pcb(input, input_size); 277 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. 282 15 + 16, 283 8, // memLevel = 8 is default. 284 Z_DEFAULT_STRATEGY); 285 DCHECK_EQ(Z_OK, result); 286 287 uint8 intermediate_buffer[kIntermediateCompressionBufferBytes] = {0}; 288 ResizeForNextOutput(compressed_log, &stream); 289 uint32 read = 0; 290 291 do { 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) 298 break; 299 } 300 result = deflate(&stream, Z_SYNC_FLUSH); 301 DCHECK_EQ(Z_OK, result); 302 if (stream.avail_out == 0) 303 ResizeForNextOutput(compressed_log, &stream); 304 } while (true); 305 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); 310 311 result = deflate(&stream, Z_FINISH); 312 DCHECK_EQ(Z_STREAM_END, result); 313 result = deflateEnd(&stream); 314 DCHECK_EQ(Z_OK, result); 315 316 compressed_log->resize(compressed_log->size() - stream.avail_out); 317 } 318 319 void WebRtcLogUploader::ResizeForNextOutput(std::vector<uint8>* compressed_log, 320 z_stream* stream) { 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; 325 } 326 327 void WebRtcLogUploader::CreateAndStartURLFetcher( 328 const WebRtcLogUploadDoneData& upload_done_data, 329 scoped_ptr<std::string> post_data) { 330 DCHECK(create_thread_checker_.CalledOnValidThread()); 331 332 if (shutting_down_) 333 return; 334 335 std::string content_type = kUploadContentType; 336 content_type.append("; boundary="); 337 content_type.append(kMultipartBoundary); 338 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; 345 } 346 347 void WebRtcLogUploader::DecreaseLogCount() { 348 DCHECK(create_thread_checker_.CalledOnValidThread()); 349 --log_count_; 350 } 351 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()); 360 } 361 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()); 368 369 std::string contents; 370 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."; 374 return; 375 } 376 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). 380 int lf_count = 0; 381 int i = contents.size() - 1; 382 for (; i >= 0 && lf_count < kLogListLimitLines; --i) { 383 if (contents[i] == '\n') 384 ++lf_count; 385 } 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); 390 } 391 } 392 393 // Write the log ID to the log list file. Leave the upload time and report ID 394 // empty. 395 contents += ",," + local_log_id + '\n'; 396 397 int written = 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: " 401 << written; 402 } 403 } 404 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()); 413 414 std::string contents; 415 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."; 419 return; 420 } 421 } 422 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); 432 } else { 433 contents += time_now_str + "," + report_id + ",\n"; 434 } 435 436 int written = 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: " 440 << written; 441 } 442 } 443 444 void WebRtcLogUploader::NotifyUploadDone( 445 int response_code, 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; 454 if (!success) { 455 error_message = "Uploading failed, response code: " + 456 base::IntToString(response_code); 457 } 458 content::BrowserThread::PostTask( 459 content::BrowserThread::UI, FROM_HERE, 460 base::Bind(upload_done_data.callback, success, report_id, 461 error_message)); 462 } 463 } 464