Home | History | Annotate | Download | only in media
      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