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
26namespace {
27
28const int kLogCountLimit = 5;
29const uint32 kIntermediateCompressionBufferBytes = 256 * 1024;  // 256 KB
30const int kLogListLimitLines = 50;
31
32const char kUploadURL[] = "https://clients2.google.com/cr/report";
33const char kUploadContentType[] = "multipart/form-data";
34const char kMultipartBoundary[] =
35    "----**--yradnuoBgoLtrapitluMklaTelgooG--**----";
36
37const int kHttpResponseOk = 200;
38
39// Adds the header section for a gzip file to the multipart |post_data|.
40void 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|.
52void 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|.
61void 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
71WebRtcLogUploadDoneData::WebRtcLogUploadDoneData() {}
72
73WebRtcLogUploadDoneData::~WebRtcLogUploadDoneData() {}
74
75WebRtcLogUploader::WebRtcLogUploader()
76    : log_count_(0),
77      post_data_(NULL),
78      shutting_down_(false) {
79  file_thread_checker_.DetachFromThread();
80}
81
82WebRtcLogUploader::~WebRtcLogUploader() {
83  DCHECK(create_thread_checker_.CalledOnValidThread());
84  DCHECK(upload_done_data_.empty());
85  DCHECK(shutting_down_);
86}
87
88void 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
121void WebRtcLogUploader::OnURLFetchUploadProgress(
122    const net::URLFetcher* source, int64 current, int64 total) {
123}
124
125bool 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
134void WebRtcLogUploader::LoggingStoppedDontUpload() {
135  content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
136      base::Bind(&WebRtcLogUploader::DecreaseLogCount, base::Unretained(this)));
137}
138
139void 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
202void 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
216void 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
273void 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
319void 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
327void 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
347void WebRtcLogUploader::DecreaseLogCount() {
348  DCHECK(create_thread_checker_.CalledOnValidThread());
349  --log_count_;
350}
351
352void 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
362void 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
405void 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
444void 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